将属性(不是函数)传递给 python `pandas.DataFrame.style`

Pass Attributes (not functions) to python `pandas.DataFrame.style`

我有一个 pandas.DataFrame 里面有值,比如说:

df = pd.DataFrame(np.random.randn(5, 3), columns=['a', 'b', 'c'])

In [160]: df
Out[160]:
          a         b         c
0 -0.316527 -0.721590  1.812285
1 -1.704653 -0.415888 -0.294740
2 -1.126637  0.032084 -1.344484
3  0.081789 -1.311954  1.941496
4  0.617405  0.114212 -0.763610

现在 我已经编写了自己的颜色渐变函数 这样我就得到了一个 pd.DataFrame 相同大小和形状,但每个单元格都有颜色十六进制代码, 说:

df_clrs = pd.DataFrame([
    ['#bc4700', '#dea380', '#bc4700'], 
    ['#384f69', '#dea380', '#bc4700'], 
    ['#dea380', '#bc4700', '#384f69'], 
    ['#384f69', '#384f69', '#dea380'],
    ['#dea380', '#bc4700', '#384f69']], 
    columns=['a', 'b', 'c']
)

In [164]: df_clrs
Out[164]:
         a        b        c
0  #bc4700  #dea380  #bc4700
1  #384f69  #dea380  #bc4700
2  #dea380  #bc4700  #384f69
3  #384f69  #384f69  #dea380
4  #dea380  #bc4700  #384f69

假设我也用文本颜色完成了此操作,所以:

 df_fnts = pd.DataFrame([
    ['#f1f1f1','#f1f1f1','#000000'],
    ['#000000','#f1f1f1','#f1f1f1'],
    ['#000000','#f1f1f1','#000000'],
    ['#f1f1f1','#000000','#f1f1f1'],
    ['#000000','#000000','#f1f1f1']],
    columns=['a', 'b' ,'c']
)

In [167]: df_fnts
Out[167]:
         a        b        c
0  #f1f1f1  #f1f1f1  #000000
1  #000000  #f1f1f1  #f1f1f1
2  #000000  #f1f1f1  #000000
3  #f1f1f1  #000000  #f1f1f1
4  #000000  #000000  #f1f1f1

我的目标是公开 DatFrame.style 功能,如 these tutorials 中所示。

然而,教程中演示的所有函数都专注于传递函数(使用 pd.DataFrame.style.applymap),但是,我已经创建了所有属性。

我尝试过的事情

因为在文档中看起来您需要为该值附加适当的 属性,我创建了一个这样的函数:

def _apply_prop(df, prop):
   return df.applymap(lambda x: prop + ':' + x)

# apply the color mapping
df.style.applymap(
    _apply_prop(
        df_clrs, 
       'background-color'
    )
).to_excel('~/Desktop/background-colors.xlsx')

但是我得到了 TypeError

TypeError: the first argument must be callable

我知道这不是您想要的,但它确实成功地将颜色应用到 df_clrs DataFrame

def apply_prop(val):
    return 'color: %s' % val
df_clrs.style.applymap(apply_prop)

applymap 只能将函数作为对象排除。所以您将无法向函数添加参数。

请记住,样式化的想法是根据正在设置样式的数据框中的数据来设置样式。不是另一个 DataFrame。

df (5x3) 和 df_clrs (4x3) 有不同的形状。假设您已更正该问题,请尝试以下操作:

def _apply_prop(_, style, prop):
    return style.applymap(lambda x: prop + ':' + x)

df.style.apply(_apply_prop, axis=None, style=df_clrs, prop='background-color')

输出:

一些注意事项:

  • 不要打电话给 style.applymap。它遍历 个单元格 。使用 apply(..., axis=...) 遍历列/行/table。无论您迭代什么,return 一个具有相同形状的对象。
  • 您没有在 apply / applymap 中执行样式功能。您提供函数的名称及其参数
  • 样式函数的第一个参数始终是要设置样式的数据框。 apply / applymap 将数据框隐式传递给样式函数。您可以通过关键字传递额外的参数。

我最终想出了一个不同的解决方案:

  1. 避免使用 applymap(我记得应用到所有元素是一个较慢的操作)
  2. 利用 apply 函数中的 "column name" 来引用样式矩阵的适当列。
def _apply_format(srs, df, prop):
    """
    Parameters
    ----------
    srs : :class:`Series`
        This is the column that will be passed automatically in the `apply` function
    df : :class:`DataFrame`
        The matrix of styling attributes with the same shape as the matrix to be styled
    prop : str
        The property to style, e.g. 'background-color'
    """
    nm  = srs.name
    row = df[nm]
    return (prop + ': ' + row).tolist()

现在我可以将背景颜色和字体颜色的样式功能链接在一起,如下所示:

(df.style.
         apply(_apply_format, axis=0, subset=subset, **{'df': df_clrs, 'prop': 'background-color'}).
         apply(_apply_format, axis=0, subset=subset, **{'df': df_fnts, 'prop': 'color'}).
         to_excel('~/Desktop/pretty-table.xlsx', engine='openpyxl')
     )