颜色 pandas DataFrame 值如果大于 1.5*median(column)

Color pandas DataFrame value if larger than 1.5*median(column)

假设我有一个如下所示的 DataFrame:

df= pd.DataFrame({'A': [1,-2,0,-1,17],
                  'B': [11,-23,1,-3,132],
                  'C': [121,2029,-243,17,-45]}
                )

我使用 jupyter notebook 并且想用 df.style 为每列中的值着色,只有当它们超过值 X 时,其中 X=1.5*median(column)。所以,我想要这样的东西:

最好,我希望对值的颜色进行一些渐变 (df.style.background_gradient),例如在 A 列中,条目 171 更暗,因为 17 离列的中位数更远。但是渐变是可选的。

我该怎么做?

此答案使用 pandas 1.4.2,Styler 的功能可能因版本而异。

简单的案例相当简单。创建一个接受系列作为输入的函数,然后使用 np.where 有条件地构建样式:

import numpy as np
import pandas as pd

df = pd.DataFrame({
    'A': [1, -2, 0, -1, 17],
    'B': [11, -23, 1, -3, 132],
    'C': [121, 2029, -243, 17, -45]
})


def simple_median_style(
        s: pd.Series, true_css: str, false_css: str = ''
) -> np.ndarray:
    return np.where(s > 1.5 * s.median(), true_css, false_css)


df.style.apply(simple_median_style, true_css='background-color: green')

通过配置 true_colorfalse_color 值,简单支持值 > 1.5 * 中值和小于的单独样式。当然,可以根据具体需要添加更多功能。


渐变部分有点复杂。我们可以使用 get_cmap to get a Colormap from its name. Then we can create a CenteredNorm 围绕特定值形成渐变。在这种情况下,每列的中值 (1.5 * median)。将这两者结合使用,我们可以在整个列上创建渐变。

这里我使用了一个简单的列表理解来有条件地应用渐变或一些错误的样式('' 没有样式)。

from typing import List

import pandas as pd
from matplotlib.cm import get_cmap
from matplotlib.colors import Colormap, CenteredNorm, rgb2hex

df = pd.DataFrame({
    'A': [1, -2, 0, -1, 17],
    'B': [11, -23, 1, -3, 132],
    'C': [121, 2029, -243, 17, -45]
})


def centered_gradient(
        s: pd.Series, cmap: Colormap, false_css: str = ''
) -> List[str]:
    # Find center point
    center = 1.5 * s.median()
    # Create normaliser centered on median
    norm = CenteredNorm(vcenter=center)
    # s = s.where(s > center, center)
    return [
        # Conditionally apply gradient to values above center only
        f'background-color: {rgb2hex(rgba)}' if row > center else false_css
        for row, rgba in zip(s, cmap(norm(s)))
    ]


df.style.apply(centered_gradient, cmap=get_cmap('Greens'))

注意:此方法在归一化时会考虑所有值,因此梯度将受到列中所有值的影响。


如果需要更一般的情况,可以构建以中位数为中心的无条件梯度(其余与上面的完整示例相同):

def centered_gradient(
        s: pd.Series, cmap: Colormap, false_css: str = ''
) -> List[str]:
    # Find center point
    center = 1.5 * s.median()
    # Create normaliser centered on median
    norm = CenteredNorm(vcenter=center)
    # Convert rgba value arrays to hex
    return [
        f'background-color: {rgb2hex(rgba)}' for rgba in cmap(norm(s))
    ]

虽然@HenryEcker 解决方案得到了很好的解释和详细说明,但一种非常简单的方法是直接使用样式链接解决您的问题,例如:

styler = df.style
for col in df.columns:
    mask = (df[col] > df[col].median() * 1.5)
    styler.background_gradient(subset=(mask, col), cmap="Blues", vmin=-100)
styler