moys
moys

Reputation: 8033

How to apply conditional color stying to a column in a pandas dataframe

I'm trying to apply a styler function to a df but getting this error and I don't know how to fix it.

ValueError: Function <function highlight at 0x0000029D89935F30> resulted in the apply method collapsing to a Series.
Usually, this is the result of the function returning a single value, instead of list-like.

I have a dataframe like below

Value       Limit    Actual
Issues      < 33     0
Rating      > 4      4.2
Complaints  < 15     18
Time        30 - 45  41
Tip         --       -

I want to color the column Actual based on the Limit column.

Based on this SO question Link, I created a function that evaluates the logic.

import re

def highlight(row):
    numbers = re.findall('[0-9]+', row['Limit'])
    if row['Value'] in ('Issues','Rating','Complaints'):
        if '>' in row['Limit'] and row['Actual'] > numbers[0]:
            color = 'green'
        elif row['Actual'] < numbers[0]:
            color = 'green'
        else:
            color = 'red'
    else:
        if len(numbers) == 0:
            color = 'yellow'
        elif row['Actual'] > numbers[0] and row['Actual'] < numbers[1]:
            color = 'green'
        else:
            color = 'red'
    return f"background-color: {color}"

However, when I try df.style.apply(highlight, axis=1), I get the error message at the beginning of the question.

How do I properly implement my logic to get the coloring I want? Below is what I want when I export this df to excel.

screenshot of table with coloring

Upvotes: 1

Views: 2382

Answers (1)

wjandrea
wjandrea

Reputation: 32921

The error message identifies the problem: "Usually, this is the result of the function returning a single value, instead of list-like." You have multiple columns, but you're only returning the style for one, and df.style.apply() doesn't know which one. The docs explain:

func should take a Series if axis in [0,1] and return a list-like object of same length, or a Series, not necessarily of same length, with valid index labels considering subset.

...

The elements of the output of func should be CSS styles as strings, in the format 'attribute: value; attribute2: value2; ...' or, if nothing is to be applied to that element, an empty string or None.

So in this case, you could simply return a list with no styles specified for the plain columns:

return [None, None, f"background-color: {color}"]

Or you could return a Series with just the "Actual" column style specified. This would still work even if you rearranged the columns or added more.

return pd.Series({'Actual': f"background-color: {color}"})

(Sidenote: I'm surprised it doesn't allow a dict, i.e. return {'Actual': f"background-color: {color}"}.)

Upvotes: 1

Related Questions