将样式应用于 Pandas 多索引,使用所有索引级别作为上下文

问题描述 投票:0回答:1

我正在尝试根据通过索引提供的类别使用单元格颜色设置数据框的样式。索引级别 0 和 1 提供水果和部分。我希望每个细胞都根据水果部分的不同类别进行着色。 当尝试格式化整个数据框中的单元格值时,这工作正常,但在尝试设置索引样式时会产生问题。

下面是一个示例脚本,用于设置水果部分示例数据框的样式。

import pandas as pd

# Dynamically sets style based on index values
def style_row(row):
    color_map = {
        ('apple', 'skin'): 'red',
        ('apple', 'seed'): 'blue',
        ('banana', 'skin'): 'red',
        ('banana', 'seed'): 'blue',
    }
    fruit, part, _ = row.name
    color = color_map.get((fruit, part))
    return [f'background-color: {color}']*len(row)
    
# Sample data
data = [
    ('apple', 'skin', 'color', 'red', 2),
    ('apple', 'skin', 'surface_area', 15, 3),
    ('apple', 'skin', 'texture', 'smooth', 1),
    ('apple', 'seed', 'color', 'grey', 3),
    ('apple', 'seed', 'weight', 4, 2),
    ('banana', 'skin', 'color', 'yellow', 3),
    ('banana', 'skin', 'surface_area', 25, 21),
    ('banana', 'skin', 'texture', 'smooth', 5),
    ('banana', 'seed', 'color', 'grey', 5),
    ('banana', 'seed', 'weight', 6, 2)
]

# Create a DataFrame
df = pd.DataFrame(data, columns=['fruit', 'part', 'property', 'value', 'observances'])
df = df.set_index(['fruit', 'part', 'property'])

# Styled DataFrame
styled_df = df.style.apply(lambda row: style_row(row), axis=1)
styled_df

结果:
Semi-complete, without colored index.

根据需要生成一个包含红色和蓝色单元格的数据框。最后,我现在只需要根据索引级别 0 和 1 值设置索引级别 1 和 2 值的样式。这似乎是不可能的。

问题似乎源于

pandas.io.formats.style.Styler
API 的功能。诸如
Styler.map_index
Styler.format_index
Styler.apply_index
之类的方法均采用回调函数来设置每个索引值的样式。但是,对于多索引,将使用每个索引级别的值独立调用回调函数。 这一 API 决策将索引值解耦,并防止任何回调函数同时做出依赖于多个索引级别的决策。

有关 map_indexapply_indexformat_index 的文档。

尝试1绕过此问题:
下面的方法尝试使用其他级别的上下文来丰富多重索引的每个级别,然后应用样式,然后删除先前添加到索引每个级别的冗余。 理论上它应该工作得很好,但是(由于某种原因)

styled_df.index = new_multiindex
对传递给
styled_df.map_index(callback_func)
的值没有影响。就好像
styled_df.index = new_multiindex
什么也没做!

# Function to style indices
def style_indices(index_val):
    if isinstance(index_val, tuple):
        fruit, part, _ = index_val
        color_map = {
            ('apple', 'skin'): 'red',
            ('apple', 'seed'): 'blue',
            ('banana', 'skin'): 'red',
            ('banana', 'seed'): 'blue',
        }
        return f'background-color: {color_map.get((fruit, part), "white")}'


# Convert MultiIndex to flat index for modification
flat_index = styled_df.index.to_flat_index()

# Sets index values with more context for callback functions
new_tuples = [(a, flat_index[idx], flat_index[idx]) for idx, (a, b, _) in enumerate(styled_df.index)]
new_multiindex = pd.MultiIndex.from_tuples(new_tuples)
styled_df.index = new_multiindex # This does not do what you would expect!

# Apply styles to the index
styled_df = styled_df.map_index(lambda index_val: style_indices(index_val), axis=0)

# Revert the index back to its original form
simplified_index = pd.MultiIndex.from_tuples([(a, b[1], c[2]) for a, b, c in styled_df.index])
styled_df.index = simplified_index # This does not do what you would expect!

styled_df

备注:

  • 此数据框保持多索引很重要,以便
    df.to_excel()
    自动应用合并和中心样式。
  • 解决方案必须兼容
    .to_excel()
    功能,以便Excel文件输出保留样式。
python python-3.x excel pandas dataframe
1个回答
0
投票

您可以使用

set_table_styles
并为 0 级
not
的行着色:

st = (
    df.style.set_table_styles(
        [
            {
                "selector": ", ".join(map(".row{}:not(.level0)".format, idx)),
                "props": f"background-color: {color_map[pair]}",
            }
            for pair, idx in df.groupby(level=[0, 1], sort=False).indices.items()
        ]
    )
)

但是,如果您需要一种Excel 友好 的方法,一个选择是:

css = {
    idx: f"background-color:{color_map[pair]};"
    # you can add more CSS styles to the f-string, if needed..
    for pair, lst in df.groupby(level=[0, 1], sort=False).indices.items()
    for idx in lst
}

st = (
    df.style
    .apply_index(lambda _: pd.Series(css), level=[1, 2])
    .apply(lambda _: pd.Series(css).tolist())
)
© www.soinside.com 2019 - 2024. All rights reserved.