2

Consider this MultiIndex dataframe

i = pd.MultiIndex.from_tuples([(0, 0), (0, 1), (1, 0), (1, 1)], names=['level_0', 'level_1'])
df = pd.DataFrame(range(0, 4), index=i, columns=['foo'])

df

and this color dictionary

colors = {0: (0.6, 0.8, 0.8, 1), 1: (1, 0.9, 0.4, 1)}

where the first three items of the tuple are RGB values and the fourth item is the transparency alpha.

How can the df be colored based on the level_0 index?

This result would be nice to have:

df_level_0

But this one here I would consider fine art:

df_art

In the latter style, the lighter cells would have the same RGB settings but a transparency of 0.25.

2 Answers 2

2

You can use Styler.set_table_styles for set styles only first level of MultiIndex:

i = pd.MultiIndex.from_tuples([(0, 0), (0, 1), (1, 0), (1, 1)], names=['level_0', 'level_1'])
df = pd.DataFrame(range(0, 4), index=i, columns=['foo']) 
#print (df)

import matplotlib.colors as col
colors = {0: (0.6, 0.8, 0.8, 1), 1: (1, 0.9, 0.4, 1)}

c = {k:col.rgb2hex(v) for k, v in colors.items()}
idx = df.index.get_level_values(0)

css = [{'selector': f'.row{i}.level0','props': [('background-color', c[v])]}
             for i,v in enumerate(idx)]
print (css)

df.style.set_table_styles(css)

Second is also possible, but more complicated:

colors = {0: (0.6, 0.8, 0.8, 1), 1: (1, 0.9, 0.4, 1)}
#converts grba to integers
c1 = {k: (int(r * 255),int(g * 255),int(b * 255), a) for k, (r,g,b,a) in colors.items()}
c2 = {k: (int(r * 255),int(g * 255),int(b * 255), 0.25) for k, (r,g,b,a) in colors.items()}

#get values of first level of MulitIndex
idx = df.index.get_level_values(0)
#set css for first level
css = [{'selector': f'.row{i}.level0', 
        'props': [('background-color', f'rgba{c1[j]}')]} for i,j in enumerate(idx)]
#counter per first level for pair and unpair coloring
zipped = zip(df.groupby(idx).cumcount(), enumerate(idx))

css1 = [{'selector': f'.row{i}', 'props': [('background-color', f'rgba{c1[j]}')]} 
       if v % 2 == 0 
       else {'selector': f'.row{i}', 'props': [('background-color', f'rgba{c2[j]}')]} 
       for v,(i, j) in zipped]


df.style.set_table_styles(css1 + css)
Sign up to request clarification or add additional context in comments.

3 Comments

Excellent! Thanks, @jezrael, you've done the art. Note: To display, I only call df.style.set_table_styles(css1 + css).
@hyco - yes, it was really interesting question, but really hard, it was challenge ;)
see below that cssisn't even necessary.
1

@jezrael has done the art!

import pandas as pd

i = pd.MultiIndex.from_tuples([(0, 'zero'), (0, 'one'), (0, 'two'), (1, 'zero'), (1, 'one')], names=['level_0', 'level_1'])
df = pd.DataFrame(range(0, 5), index=i, columns=['foo']) 

colors = {0: (0.6, 0.8, 0.8, 1), 1: (1, 0.9, 0.4, 1)}
#convert rgba to integers
c1 = {k: (int(r * 255),int(g * 255),int(b * 255), a) for k, (r,g,b,a) in colors.items()}
c2 = {k: (int(r * 255),int(g * 255),int(b * 255), 0.25) for k, (r,g,b,a) in colors.items()}

#get values of first level of MulitIndex
idx = df.index.get_level_values(0)
#counter per first level for pair and unpair coloring
zipped = zip(df.groupby(idx).cumcount(), enumerate(idx))

css = [{'selector': f'.row{i}', 'props': [('background-color', f'rgba{c1[j]}')]} 
       if v % 2 == 0 
       else {'selector': f'.row{i}', 'props': [('background-color', f'rgba{c2[j]}')]} 
       for v,(i, j) in zipped]

df.style.set_table_styles(css)

Compared to @jezrael's solution, the above is even shorter.

I also like that the solution doesn't depend on index labels being ordered integers. Note that I'm using strings (at least for the second index level).

1 Comment

How would I extend this to a longer dataframe so that I can alternate between the two colours based on the level 0 index?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.