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) 

What I am trying to accomplish:
enter image description here

Asked By: NIC3301

||

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.

Answered By: sj95126

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.

Answered By: moken
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.