Search code examples
pythonpandasdataframestyling

Python: simple mapping of dataframe styler


I am stuck on figuring out how to map a pre-existing df of styling options to a df of integers:

import numpy as np
import pandas as pd

# Create a 9 x 3 array of integers
random_state = np.random.RandomState(seed=44)
myArray = random_state.randint(0,9, (9,3))

Which gives the output:

[[4 3 1]
 [3 0 4]
 [3 8 7]
 [7 6 3]
 [7 3 3]
 [6 5 4]
 [5 1 8]
 [7 4 5]
 [3 0 4]]

Then create a random 2D list of 'color: option' to be mapped onto myArray with the styler:

styleList    = []
colorOptions = ['color: lime','color: red','color: yellow']

for i in arr:
  inner = []
  for j in i:
    inner.append(np.random.choice(colorOptions))
  styleList.append(inner)

for i in styleList: print(i)

Check the output:

['color: yellow', 'color: lime'  , 'color: red']
['color: red'   , 'color: lime'  , 'color: red']
['color: yellow', 'color: yellow', 'color: yellow']
['color: yellow', 'color: red'   , 'color: lime']
['color: yellow', 'color: red'   , 'color: yellow']
['color: red'   , 'color: lime'  , 'color: yellow']
['color: red'   , 'color: yellow', 'color: red']
['color: yellow', 'color: yellow', 'color: yellow']
['color: lime'  , 'color: red'   , 'color: red']

Convert both of these to dataframes:

df      = pd.DataFrame(data=myArray,   index=None, columns=['col1', 'col2', 'col3'])
dfStyle = pd.DataFrame(data=styleList, index=None, columns=['col1', 'col2', 'col3'])

Since I already have the dataframe dfStyle with the styling options, how can I simply map its values to the integer values in df (and without having to generate dfStyle within a separate function)?

I'll avoid cluttering this space with my various attempts using df.style.apply(), df.style.applymap() and even functions that attempt to simply return dfStyle, but this seemingly straightforward task has thrown me in circles.

Desired result is a styled df:

df[0][0] should be the number 4 displayed with the color yellow

df[0][1] should be the number 3 displayed with the color lime

df[0][2] should be the number 1 displayed with the color red

etc.

Thanks for any help.

••• UPDATE •••

I figured out how to properly map the style as I wanted by using:

def color(row):
  dfs = dfStyle.copy()
  x = [dfs.iloc[row.name][j] for i,j in zip(row, dfStyle)]
  return x

df.style.apply(color, axis=1)

which results in the desired:

enter image description here


Solution

  • After much prodding of my convoluted solution, I have come to the following (much cleaner) solutions for my original question.

    I wasn't able to find these elsewhere on SO, so to anyone else who is interested:

    Solution using apply(axis=0)

    def f1(col): return [ dfStyle[col.name][i] for i in range(len(dfStyle)) ]
    
    df.style.apply(f1, axis=0)
    

    Solution using applymap() via list comprehension

    styleIterator = iter([ row for col in dfStyle for row in dfStyle[col] ] )
    
    def f1(x): return next(styleIterator)
    
    df.style.applymap(f1)
    

    Solution using applymap() via built-in functions

    styleIterator = iter(dfStyle.values.flatten(order='F').tolist() )
    
    # OR
    
    styleIterator = iter(dfStyle.to_numpy().flatten(order='F').tolist() )
    
    def f1(x): return next(styleIterator)
    
    df.style.applymap(f1)
    

    and, as provided by a friend, Solution using apply(axis=1) with lambda

    color = lambda row: [dfStyle.iloc[row.name][col] for col in dfStyle]
    
    df.style.apply(color, axis=1)