Style background color of dataframe cells based on rule

Question:

I’m trying to style a simple multiplication table. I have the following rule, and if a cell doesn’t satisfy it, I must colorize it in a different color.


Example 1: Given numbers 10 and 11, the rule is broken since -798010 != 110, so the color is red:

  • Combine (100-(100-10)-(100-11)) = -79 and (100-10)*(100-11) = 8010 as strings: -798010
  • Multiply 10*11: 110

Example 2: Given numbers 10 and 99, the rule is satisfied since 990 == 990, so the color is green.

  • Combine (100-(100-10)-(100-99)) = 9 and (100-10)*(100-99) = 90 as strings: 990
  • Multiply 10*90: 990

Currently I’m checking if numbers are in the rule or not. Either way I colorize cells, all I get is all cells in one color:

import pandas as pd

l = []
for i in range(10, 100):
     for j in range(10, 100):
        l.append(f'{i}*{j}')
        result = [l[idx:idx+90] for idx in range(0, len(l), 90)]
        df = pd.DataFrame(result)

def style_dataframe(s):
    for row_index, row in df.iterrows():
        for col_name, cell in row.items():
            if int(cell[:2])*int(cell[3:5]) == int(str(100 - ((100 - (int(cell[:2])))-(100-(int(cell[3:5])))))+str((100-(int(cell[:2])))*int(int(cell[3:5])))):
                return 'background-color: green'
            elif int(cell[:2])*int(cell[3:5]) != int(str(100 - ((100 - (int(cell[:2])))-(100-(int(cell[3:5])))))+str((100-(int(cell[:2])))*int(int(cell[3:5])))):
                return 'background-color: red'
  
s = df.style.applymap(style_dataframe)
Asked By: Irina

||

Answers:

  • df.style.applymap operates elementwise, so the styling function expects only 1 cell (not a dataframe)
  • 100-(100-a)-(100-b) can be simplified to just a+b-100

So the styling function should look something like this:

def rule(cell):
    a, b = map(int, cell.split('*'))                  # split 'a*b' into [a, b]
    cond1 = str(a+b-100) + str((100-a)*(100-b))       # string version
    cond2 = a * b                                     # multiplication version
    color = 'green' if int(cond1) == cond2 else 'red' # check rule
    return f'background-color: {color}'               # return css style

df.style.applymap(rule)


As a side note, it’s much simpler to create the original df with numpy broadcasting:

a = np.arange(10, 100).astype(str)
df = pd.DataFrame(np.char.add(np.char.add(a[:, None], '*'), a))

#         0      1      2  ...     87     88     89
# 0   10*10  10*11  10*12  ...  10*97  10*98  10*99
# 1   11*10  11*11  11*12  ...  11*97  11*98  11*99
# 2   12*10  12*11  12*12  ...  12*97  12*98  12*99
# ..    ...    ...    ...  ...    ...    ...    ...
# 87  97*10  97*11  97*12  ...  97*97  97*98  97*99
# 88  98*10  98*11  98*12  ...  98*97  98*98  98*99
# 89  99*10  99*11  99*12  ...  99*97  99*98  99*99
# 
# [90 rows x 90 columns]
Answered By: tdy