openpyxl / Python – Change text color to red if value in column D does not match value in column H
Question:
This is probably a pretty basic question but I am trying to change the cell text to red and bold for both columns D and H if they do not equal. Same scenario with Column E to Column I. I also have some Cell Background fill that I do not want modified.. just the text color.
I have tried searching online but have not been able to find anything to help with this.
This is the code that I have, but right now it is changing the font for the entire columns D-H
for row in worksheet['D':'H']:
if worksheet['D'] != worksheet['H']:
for cell in row:
cell.font = Font(color = colors.RED, bold=True)
Answers:
Part of the problem is that worksheet['D':'H']
is returning all columns between 'D'
and 'H'
. You’re getting back a tuple of tuples of cell objects, and trying to compare entire columns at once.
Instead, you can iterate through each row by zipping together the cells in just the ‘D’ and ‘H’ columns and compare their values, then set the font for each cell. It’s not necessary to create a new font object each time, so you can reuse it. All together, it looks like this:
red = Font(color=colors.RED, bold=True)
for d, h in zip(worksheet['D'], worksheet['H']):
if d.value != h.value:
d.font = red
h.font = red
Note that empty cells will have a value of None
. If you only want to set the font for cells that both have values, you can expand the if
statement:
if d.value is not None and h.value is not None and d.value != h.value:
To address the additional question of how to skip the header row, there’s two possible approaches using this zip
method (and possibly others, with some different form of iteration).
The first way is to create the zipped rows iterator separately and skip the first pair:
cells = zip(worksheet['D'], worksheet['H'])
next(cells)
for d, h in cells:
[....]
Second, more generally, check the .row
atttribute of each cell:
for d, h in zip(worksheet['D'], worksheet['H']):
if d.row == 1:
continue
[....]
This gives more flexibility to skip the first X
rows, or skip odd-numbered rows, or only compare rows that are prime numbers, etc.
Complementary to @sj95126 answer, if you want it to be dynamic as in if a value in Column ‘D’ and/or ‘H’ changes then the cell font colour changes immediately, you could implement ‘conditional formatting’ like this;
red_text = Font(color="FF0000")
for d, h in zip(worksheet['D'], worksheet['H']):
d_coord = d.coordinate
h_coord = h.coordinate
range_coord = ''
for x in range(0, len(d_coord+h_coord), 1):
range_coord += '$' + '$'.join((d_coord + h_coord)[x:x + 1])
if x == 1: range_coord += ','
worksheet.conditional_formatting.add(f'{d_coord} {h_coord}',
FormulaRule(formula=[f'NOT(EXACT({range_coord}))'],
font=red_text,
stopIfTrue=False))
So you don’t need to run the script again if you update the sheet.
This is probably a pretty basic question but I am trying to change the cell text to red and bold for both columns D and H if they do not equal. Same scenario with Column E to Column I. I also have some Cell Background fill that I do not want modified.. just the text color.
I have tried searching online but have not been able to find anything to help with this.
This is the code that I have, but right now it is changing the font for the entire columns D-H
for row in worksheet['D':'H']:
if worksheet['D'] != worksheet['H']:
for cell in row:
cell.font = Font(color = colors.RED, bold=True)
Part of the problem is that worksheet['D':'H']
is returning all columns between 'D'
and 'H'
. You’re getting back a tuple of tuples of cell objects, and trying to compare entire columns at once.
Instead, you can iterate through each row by zipping together the cells in just the ‘D’ and ‘H’ columns and compare their values, then set the font for each cell. It’s not necessary to create a new font object each time, so you can reuse it. All together, it looks like this:
red = Font(color=colors.RED, bold=True)
for d, h in zip(worksheet['D'], worksheet['H']):
if d.value != h.value:
d.font = red
h.font = red
Note that empty cells will have a value of None
. If you only want to set the font for cells that both have values, you can expand the if
statement:
if d.value is not None and h.value is not None and d.value != h.value:
To address the additional question of how to skip the header row, there’s two possible approaches using this zip
method (and possibly others, with some different form of iteration).
The first way is to create the zipped rows iterator separately and skip the first pair:
cells = zip(worksheet['D'], worksheet['H'])
next(cells)
for d, h in cells:
[....]
Second, more generally, check the .row
atttribute of each cell:
for d, h in zip(worksheet['D'], worksheet['H']):
if d.row == 1:
continue
[....]
This gives more flexibility to skip the first X
rows, or skip odd-numbered rows, or only compare rows that are prime numbers, etc.
Complementary to @sj95126 answer, if you want it to be dynamic as in if a value in Column ‘D’ and/or ‘H’ changes then the cell font colour changes immediately, you could implement ‘conditional formatting’ like this;
red_text = Font(color="FF0000")
for d, h in zip(worksheet['D'], worksheet['H']):
d_coord = d.coordinate
h_coord = h.coordinate
range_coord = ''
for x in range(0, len(d_coord+h_coord), 1):
range_coord += '$' + '$'.join((d_coord + h_coord)[x:x + 1])
if x == 1: range_coord += ','
worksheet.conditional_formatting.add(f'{d_coord} {h_coord}',
FormulaRule(formula=[f'NOT(EXACT({range_coord}))'],
font=red_text,
stopIfTrue=False))
So you don’t need to run the script again if you update the sheet.