Issue
I am using Jupyter notebook. I have combined two DataFrames together, so in order to differentiate between the two I have created a new third column 'Color Code' where there are two values 'a' and 'b'. I have no problem iterating through the list of colors I have defined but I need to be able to color dark vs light depending on the unique columns and 'Color Code' column.
I have an MRE below. Apologies for the big example DataFrame, just need to be able to showcase what I am trying to accomplish.
df1 = pd.DataFrame({'x1':['a', 'a', 'a', 'a', 'a', 'a', 'b', 'b', 'b', 'b', 'b', 'b', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'd', 'd', 'd', 'd'],
'x2':['3','4','8','0','11','1','15','5','67','3','1','4','5','88','9','3','7','2','3','43','34','56','96','26'],
'Color Mode':['a','a','b','b','b','b','a','a','a','a','b','b','a','b','b','b','b','b','b','b','a','a','a','b']})
def Color_Unique(s):
df = s.copy()
color_map1 = {}
color_map2 = {}
x = pd.DataFrame({'Color Mode': ['a']})
y = pd.DataFrame({'Color Mode': ['b']})
Trade_Cusip_Combo_Key = df['x1'].unique()
if x.any(axis=None):
colors_to_use = ['background-color: #ADD8E6', 'background-color: #90ee90', 'background-color: #FFD580', 'background-color: #CBC3E3', 'background-color: #D3D3D3', 'background-color: #C4A484']
colors_cycle = cycle(colors_to_use)
for Trade_Cusip_Combo in Trade_Cusip_Combo_Key:
color_map1[Trade_Cusip_Combo] = next(colors_cycle)
for index, row in df.iterrows():
if row['x1'] in Trade_Cusip_Combo_Key:
Trade_Cusip_Combo = row['x1']
my_color = color_map1[Trade_Cusip_Combo]
df.loc[index,:] = my_color
else:
df.loc[index,:] = 'background-color: '
return df
elif y.any(axis=None):
colors_to_use = ['background-color: #ADD8E6', 'background-color: #90ee90', 'background-color: #FFD580', 'background-color: #CBC3E3', 'background-color: #D3D3D3', 'background-color: #C4A484']
colors_cycle = cycle(colors_to_use)
for Trade_Cusip_Combo in Trade_Cusip_Combo_Key:
color_map2[Trade_Cusip_Combo] = next(colors_cycle)
for index, row in df.iterrows():
if row['x1'] in Trade_Cusip_Combo_Key:
Trade_Cusip_Combo = row['x1']
my_color = color_map2[Trade_Cusip_Combo]
df.loc[index,:] = my_color
else:
df.loc[index,:] = 'background-color: '
return df
else:
print("boken")
df4 = df1.style.apply(Color_Unique, axis=None)
df4
This is what my code currently looks like when you run my above MRE.
This is what I need it to look like:
You will notice dark vs light colors alternate when column 'x1' values are the same but column 'Color Mode' values are either 'a' (dark color) or 'b' (lighter color). I need my function to be able to iterate through my defined dark and light colors based on column 'x1' and column ' Color Mode' as well as skip a line after each group is colored (blue to green to orange to purple).
Solution
Before turning to the "styling" part of the exercise, let's use a function to insert blank rows into your df. We can do this as follows:
import pandas as pd
df1 = pd.DataFrame({'x1':['a', 'a', 'a', 'a', 'a', 'a', 'b', 'b', 'b',
'b', 'b', 'b', 'c', 'c', 'c', 'c', 'c', 'c',
'c', 'c', 'd', 'd', 'd', 'd'],
'x2':['3','4','8','0','11','1','15','5','67','3',
'1','4','5','88','9','3','7','2','3','43',
'34','56','96','26'],
'Color Mode':['a','a','b','b','b','b','a','a','a',
'a','b','b','a','b','b','b','b','b',
'b','b','a','a','a','b']})
def blank_rows(df, col_name):
insert_idx = [v[-1]+1 for v in df.groupby(col_name).groups.values()]
for i, idx in enumerate(insert_idx[:-1]):
temp = pd.DataFrame({k: '' for k in df.columns}, index=[idx+i])
df = pd.concat([df.iloc[:idx+i], temp, df.iloc[idx+i:]])\
.reset_index(drop=True)
return df
# add `col_name` (e.g. `x1`) to specify which groups need to be split up
df = blank_rows(df1, 'x1')
# N.B. the groups inside column `col_name` need to be sorted,
# and the index needs to be "regular" (i.e. `0,1,2...`).
# E.g. `df = blank_rows(df1, 'Color Mode')` would get you a nonsensical result;
# Instead you would want to use:
# `df = blank_rows(df1.sort_values('Color Mode').reset_index(drop=True),
# 'Color Mode')`
df
will now contain empty rows like below:
df.iloc[[5,6,7,12,13,14],:]
Now, let's apply a function to take care of the styles. E.g.:
from itertools import cycle
def color_unique(df_input):
df = df_input.copy()
colors = ['#8EA9DB', '#D9E1F2', # blues
'#A9D08E', '#E2EFDA', # greens
'#F4B084', '#FCE4D6', # reds
'#9751CB', '#DEC8EE'] # purples
colors_cycle = cycle(colors)
groups = df.groupby(['x1','Color Mode']).groups
for k, v in groups.items():
if '' in k:
df.iloc[v] = f'background-color: white;'
else:
df.iloc[v] = f'background-color: {next(colors_cycle)};'
return df
df_styled = df.style.apply(color_unique, axis=None)
df_styled
Result:
Explanation
- Create a list with dark/light color pairs. Since we are using
cycle
fromitertools
, the list does not need to be exhaustive. (N.B.: the above function is expecting that each group inx1
is always split in 2 subgroups based onColor Mode
. E.g. if you expect groups that only havea
, notb
, the above function would need to be adjusted!) - Use
df.groupby
to isolate the different groups. Make sure to addsort=False
to preserve the original order of the groups. - The
Groupby.groups
property stores adict
with "group name" (e.g.('a', 'a')
) as keys and the associated index values as values (e.g. for('a', 'a')
the value isInt64Index([0, 1], dtype='int64')
. - We want to use the values per group to select slices consecutively from the df with
.iloc
and assign a color. However, we want to skip the group that has key(''),('')
, For that reason, we want to iterate overgroup.items()
and catch both the(k)ey
and(v)alue
. If the key (tuple) contains an empty string, we color the row "white", else we assign the next color from our list of colors.
Answered By - ouroboros1
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.