如何用Pandas Styler基于列值为不同行组设置差异化样式
DataFrame分组样式定制方案
需求说明
现有如下DataFrame df:
import pandas as pd data = {'person': {0: 'a', 1: 'a', 2: 'a', 3: 'a', 4: 'a', 5: 'a', 6: 'b', 7: 'b', 8: 'b', 9: 'b', 10: 'b', 11: 'b', 12: 'c', 13: 'c', 14: 'c', 15: 'c', 16: 'c', 17: 'c'}, 'x': {0: 1, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1, 7: 1, 8: 1, 9: 1, 10: 1, 11: 1, 12: 1, 13: 1, 14: 1, 15: 1, 16: 1, 17: 1}, 'y': {0: 2, 1: 2, 2: 2, 3: 2, 4: 2, 5: 2, 6: 2, 7: 2, 8: 2, 9: 2, 10: 2, 11: 2, 12: 2, 13: 2, 14: 2, 15: 2, 16: 2, 17: 2}, 'z': {0: 'foo', 1: 'foo', 2: 'foo', 3: 'bar', 4: 'bar', 5: 'bar', 6: 'foo', 7: 'foo', 8: 'foo', 9: 'bar', 10: 'bar', 11: 'bar', 12: 'foo', 13: 'foo', 14: 'foo', 15: 'bar', 16: 'bar', 17: 'bar'}} df = pd.DataFrame.from_dict(data, orient='columns')
需要实现两个样式效果:
- 每个
person分组内,不同z值对应的行组使用交替背景色 - 每个
person的最后一行添加底部边框
期望效果
每个person分组内,z为foo的行组和z为bar的行组呈现不同的背景色(如浅灰和白色交替),且每个person的最后一行有明显的底部分隔边框。
已尝试方案及问题
- 嵌套循环拆分分组后拼接Styler对象:报错
TypeError: cannot concatenate object of type '<class 'pandas.io.formats.style.Styler'>'; only Series and DataFrame objs are valid,因为Styler对象不支持拼接。 - 使用嵌套
np.where的lambda函数:报错AttributeError: 'Styler' object has no attribute 'style',因为循环后变量变为Styler对象,无法再调用style属性。
解决方案
1. 实现分组内z值行组的交替背景色
通过生成分组内的样式标记,结合Styler.apply批量设置背景色:
def set_alternate_colors(df): # 为每个person内的z组生成唯一标记 df['color_group'] = df.groupby(['person', 'z']).ngroup() # 同一person内,z组的标记取模2得到交替标识 df['color_flag'] = df.groupby('person')['color_group'].transform(lambda x: x % 2) # 根据标识生成样式矩阵 styles = pd.DataFrame('', index=df.index, columns=df.columns) styles.loc[df['color_flag'] == 1, :] = 'background-color: #f0f0f0' return styles # 应用背景色样式 styled_df = df.style.apply(set_alternate_colors, axis=None)
2. 为每个person最后一行添加底部边框
通过groupby定位最后一行索引,生成边框样式矩阵:
def set_bottom_border(df): # 获取每个person分组的最后一行索引 last_rows = df.groupby('person').tail(1).index styles = pd.DataFrame('', index=df.index, columns=df.columns) styles.loc[last_rows, :] = 'border-bottom: 2px solid black' return styles # 叠加边框样式 styled_df = styled_df.apply(set_bottom_border, axis=None) # 显示最终样式效果 styled_df
完整代码
import pandas as pd data = {'person': {0: 'a', 1: 'a', 2: 'a', 3: 'a', 4: 'a', 5: 'a', 6: 'b', 7: 'b', 8: 'b', 9: 'b', 10: 'b', 11: 'b', 12: 'c', 13: 'c', 14: 'c', 15: 'c', 16: 'c', 17: 'c'}, 'x': {0: 1, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1, 7: 1, 8: 1, 9: 1, 10: 1, 11: 1, 12: 1, 13: 1, 14: 1, 15: 1, 16: 1, 17: 1}, 'y': {0: 2, 1: 2, 2: 2, 3: 2, 4: 2, 5: 2, 6: 2, 7: 2, 8: 2, 9: 2, 10: 2, 11: 2, 12: 2, 13: 2, 14: 2, 15: 2, 16: 2, 17: 2}, 'z': {0: 'foo', 1: 'foo', 2: 'foo', 3: 'bar', 4: 'bar', 5: 'bar', 6: 'foo', 7: 'foo', 8: 'foo', 9: 'bar', 10: 'bar', 11: 'bar', 12: 'foo', 13: 'foo', 14: 'foo', 15: 'bar', 16: 'bar', 17: 'bar'}} df = pd.DataFrame.from_dict(data, orient='columns') def set_alternate_colors(df): df['color_group'] = df.groupby(['person', 'z']).ngroup() df['color_flag'] = df.groupby('person')['color_group'].transform(lambda x: x % 2) styles = pd.DataFrame('', index=df.index, columns=df.columns) styles.loc[df['color_flag'] == 1, :] = 'background-color: #f0f0f0' return styles def set_bottom_border(df): last_rows = df.groupby('person').tail(1).index styles = pd.DataFrame('', index=df.index, columns=df.columns) styles.loc[last_rows, :] = 'border-bottom: 2px solid black' return styles styled_df = df.style.apply(set_alternate_colors, axis=None).apply(set_bottom_border, axis=None) styled_df
内容的提问来源于stack exchange,提问作者bismo
相关产品推荐
相关产品推荐

