I have two dataframes that have different styles but with the same columns.
This is a minimal example, with less data and simpler styles (I had some complicated highlighting instead of highlight_max
)
import pandas as pd
data = pd.DataFrame({'maturity': ['2022-03-11', '2022-04-21', '2022-04-20', '2022-03-11', '2022-04-21'],
'position': [-1500000, 2.3, -50, 10, -9],
'price': [12, 51, 62, 10, 90000]})
data_dict = {'pos_data': data[data.position > 0], 'neg_data': data[data.position < 0]}
styled_df = {}
first = True
for name, df in data_dict.items():
color = 'green' if 'pos' in name else 'red'
header = pd.DataFrame({'position': [name]}, columns=df.columns)
styled_df[name] = (
pd.concat([header, df]).reset_index(drop=True)
.style.highlight_max(pd.IndexSlice[1:, 'position'], color=color, axis=0)
.format(precision=2, na_rep='')
.set_table_styles([{'selector': 'tbody td', 'props': [
('border-style', 'solid'), ('border-width', 'thin'), ('border-color', 'gray'),
('border-collapse', 'collapse !important')]}], overwrite=False)
.set_table_styles([{'selector': 'th', 'props': [
('border-style', 'solid'), ('border-width', 'thin'),
('border-collapse', 'collapse !important')]}], overwrite=False)
.set_table_attributes(
'style="border-width: thin; border-collapse :collapse !important;'
' border-color:black; border-style: solid !important"')
.hide_index()
)
if first:
first = False
else:
styled_df[name] = styled_df[name].hide_columns()
email_body = f"<html><body> {''.join(s.to_html() for s in styled_df.values())} </body></html>"
# saving `email_body` to local and open gives:
Current Output:
How I can make the two resulting tables share the same column width, (as if they were a concatenated dataframe) ?
Desired Output:
Edit:
What I meant by
some complicated highlighting instead of
highlight_max
in my case is something like :
.style.apply(highlight_range, columns=cols_red, low=5,
high=10, color='red', color_light='light_red', axis=1)
.apply(highlight_range, columns=cols_blue, low=0,
high=5,color='blue', color_light='light_blue', axis=1))
instead of .style.highlight_max(pd.IndexSlice[1:, 'position'], color=color, axis=0)
above.
where highlight_range
is:
def highlight_range(row, columns, low: int, high: int, color: str, color_light: str):
is_between = pd.Series(data=False, index=row.index)
is_between[columns] = row.loc[columns].between(low, high, inclusive='left') # <= row <
if not is_between.any():
return [''] * len(is_between)
return [f'background-color: {color_light}' if e else f'background-color: {color}' for e in is_between]
The easiest way to have these two DataFrames appear as a single concatenated DataFrame would be to actually concatenate the two DataFrames then make a Styler. The following solution uses pandas 1.4.2 (the Styler can have significant variance between versions).
We can first compute the styles that we want to apply to the individual cells:
Here we use
np.where
to determine if the current value matches the maximal value by comparing the individual values to themax
. Anywhere this condition is true we fill with thestyle_str
everywhere else gets an empty string.The resulting DataFrames look like:
Notice that the styles correspond to each value.
Now we can just
concat
the DataFrames:Now it is trivial to use the style column of
df
to style the position column withapply
:All together the styling could look something like:
As of pandas 1.3.0, we can get complete HTML from Styler objects with
Styler.to_html
This generates the following HTML/CSS:
Setup and imports used.