4

I have a pandas.DataFrame with values in it, say:

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

Now I've written my own color gradient functions so that I get a pd.DataFrame of the same size and shape, but with color hex codes for each cell, say:

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

I let's say I've done this as well with text colors, so:

 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

My goal is to now expose the DatFrame.style functionality, as demonstrated in these tutorials.

However, all of the functions demonstrated in tutorial focus on passing a function (using the pd.DataFrame.style.applymap), however, I have all of the properties already created.

Things I've Tried

Because in the documentation it looks like you need to append the value with the appropriate property, I've created a function like this:

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')

But I get a TypeError

TypeError: the first argument must be callable
Henry Ecker
  • 34,399
  • 18
  • 41
  • 57
benjaminmgross
  • 2,052
  • 1
  • 24
  • 29

3 Answers3

3

df (5x3) and df_clrs (4x3) have different shapes. Assuming you got that corrected, try this:

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')

Output:

Styled data frame

Some notes:

  • Don't call style.applymap. It iterates over cells. Use apply(..., axis=...) to iterate over column / row / table. Whatever you iterate over, return an object with the same shape.
  • You do not execute the styling function in apply / applymap. You supply the function's name along with its parameters
  • The first parameter of the styling function is always the data frame being styled. apply / applymap implicitly passes the data frame to the styling function. You can pass additional arguments by keywords.
Code Different
  • 90,614
  • 16
  • 144
  • 163
  • I've changed the question so the matrices are the same, thanks for the catch! Your solution was great -- I ended up figuring out a version that I will detail below in a separate answer, but I accepted your answer as the correct answer to the question (I'll just add mine below for completeness). – benjaminmgross Jul 24 '19 at 13:33
0

I know this isn't exactly what you are wanting, but it does succesfully apply the colors to the df_clrs DataFrame

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

the applymap is only able to except a function as an object. So you aren't going to be able to add arguments to the function.

Keep in mind that the idea of the styling is to style based on the data in the dataframe that is being styled. Not another DataFrame.

braintho
  • 381
  • 1
  • 8
0

I ended up coming up with a different solution that:

  1. Avoids using applymap (as I recall applying to all elements is a slower operation)
  2. Leverages the "column name" in an apply function to reference the appropriate column of the styling matrix.
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()

Now I can just chain together the styling functionality for background colors and font colors, like so:

(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')
     )

enter image description here

benjaminmgross
  • 2,052
  • 1
  • 24
  • 29