如何在 Pandas 中设置具有多个条件的一行的样式?

How can I style one row with multiple conditions in Pandas?

我有一个数据框,我想为其设置一些行的样式,以便索引出现在字典中。 用apply, applymap, subset with row/column/IndexSlice尝试了一堆东西后,我仍然没有找到

的解决方案
  1. 根据 多个 条件和 不同颜色 为特定行设置不同单元格的样式。 (由于行和列需要以相同的方式写入 excel,转置将不起作用(?!))
  2. 在 1 个函数中对特定行的样式条件和颜色进行分组/'cat'。
  3. 根据另一行但同一列中单元格的值有条件地设置一行中所有单元格的样式(参考所需结果)。

注意:有些行会是'styled',有些不会。

第一期: cat1 出现在字典中,所以我需要它有条件的样式。我需要通过条件传递行中的每个单元格以应用某种颜色。 经过大量尝试后,下面的代码 'closest' 成为一个潜在的解决方案,但我在保存到 excel 时遇到错误。 (当我只使用 'green' 的代码时,它确实完全有效)。

”DataFrame的真值不明确。使用a.empty、a.bool()、a.item()、a.any()或 a.all()."

d_functions_colors={'cat1': catone_color,'cat5': catfive_color, 'cat40':catforty_color}

        2020-12   2019-12   2018-12   2017-12    2016-12      ...
idx  
cat1        100       NaN        50        35          5      ...
cat2          5       NaN         7         3          2      ...
cat3       6.25       NaN      6.25      4.93       5.21      ...
avg_cat3      5         4         3         2          1      ...
max_cat3     10        20        10        30          1      ...  
cat4                                                          ...
cat5          5        20         8         9.5       25      ...
avg_cat5     10        15         8         9.5       20      ...
...

(the results for avg, max are calculated and added for some 'cats' beforehand based on multiple (other) data sources)
    def highlight_row_green(x):
        c0 = ''
        c1 = 'background-color: green'
        c2 = 'background-color: yellow'
        c3 = 'background-color: red'
         
        df1 = pd.DataFrame('', index=x.index, columns=x.columns)
    
        m1 = x.index.astype(str).str.contains('^cat1')
    
        mask = (x[m1] >= 50).reindex(x.index, fill_value=False)
    
        df1 = df1.mask(mask, c1)
        return df1
    
    def highlight_row_yellow(x):
        c0 = ''
        c1 = 'background-color: green'
        c2 = 'background-color: yellow'
        c3 = 'background-color: red'
         
        df1 = pd.DataFrame('', index=x.index, columns=x.columns)
    
        m1 = x.index.astype(str).str.contains('^cat1$')
    
        mask = (20 <= x[m1] < 50)
    
        df1 = df1.mask(mask, c2)
        return df1
    
    
    def highlight_row_red(x):
        c0 = ''
        c1 = 'background-color: green'
        c2 = 'background-color: yellow'
        c3 = 'background-color: red'
         
        df1 = pd.DataFrame('', index=x.index, columns=x.columns)
    
        m1 = x.index.astype(str).str.contains('^cat1$')
    
        mask = ( x[m1] < 20)
    
        df1 = df1.mask(mask, c3)
        return df1

    def pandastoExcel(path,filename,sheetname,my_dataframe):
    outputfilepath=(path+'\'+filename)
    if len(sheetname)>=30:
        sheetname='Default'
    else:
        pass
    writer = pd.ExcelWriter(outputfilepath , engine='xlsxwriter')
    try:
        my_dataframe.to_excel(writer, sheet_name=sheetname,index=True)
    except Exception as e:
        print(str(e))
    writer.save()


df_output=df.style.\
    apply(highlight_row_green, axis=None).\
    apply(highlight_row_yellow, axis=None).\
    apply(highlight_row_red, axis=None)



第二期: 我试着把它们分组,但得到了同样的错误 "DataFrame的真值不明确。使用a.empty、a.bool()、a.item()、a.any()或a.all()."

def catone_color(x):
     c1 = 'background-color: green'
     c2 = 'background-color: yellow'
     c3 = 'background-color: red'
     c0 = '' 

     df1 = pd.DataFrame('', index=x.index, columns=x.columns)
     m1 = x.index.astype(str).str.contains('^cat1$')

     mask = (x[m1] >= 50).reindex(x.index, fill_value=False)
     masky = (20 <= x[m1] < 50)
     maskr = (x[m1] < 20)

     df1 = df1.mask(mask, c1)
     df1 = df1.mask(masky, c2)
     df1 = df1.mask(maskr, c3)
     return df1

df_output=df.style.apply(catone_color, axis=None)

第三期:(例如cat5),样式基于cat5_avg中的值。所以2020cat5_value的颜色是以2020cat5_avg为基础的,2019cat5_value的颜色是以2019cat5_value为基础的,等等

尝试将数据框中的 2 行(例如 row_values、row_avg)和其他一些东西组合起来,但远未实现任何目标。

            2020-12   2019-12   2018-12   2017-12    2016-12      ...
    idx  
    cat1        100       NaN        50        35          5      ...
    cat2          5       NaN         7         3          2      ...
    cat3       6.25       NaN      6.25      4.93       5.21      ...
    avg_cat3      5         4         3         2          1      ...
    max_cat3     10        20        10        30          1      ...  
    cat4                                                          ...
    cat5          5        20         8         9.5       25      ...
    avg_cat5     10        15         8         9.5       20      ...
    ...

#Idea of what I'm trying to accomplish:
for cat5_value in row_cat_5:
    If cat5_value > avg_cat5_same column:
          color = 'green'
    elif cat5_value == avg_cat5_same_column:
          color = 'yellow'
    elif cat5_value < avg_cat5_same_column:
          color = 'red'
    else:
          color=''
    return 'background-color: %s' % color

#(Background color row cat_5 would be red,green,yellow,yellow,green).  

#What I've got left from trying:
    g= 'green'
    y = 'yellow'
    r = 'red'

    m1 = val.iloc[0, :] < val.iloc[1, :]
    m2 = val.iloc[0, :] == val.iloc[1, :]
    m3 = val.iloc[0, :] > val.iloc[1, :]

    df1 = pd.DataFrame('background-color: ', index=val.index, columns=val.columns)

    df1.iloc[:,0] = np.where(m1, 'background-color: {}'.format(g), df1.iloc[0, :])
    df1.iloc[:,0] = np.where(m2, 'background-color: {}'.format(y), df1.iloc[0, :])
    df1.iloc[:,0] = np.where(m3, 'background-color: {}'.format(r), df1.iloc[0, :])
    return df1

Desired result

提前谢谢你,

PS:提前感谢所有贡献者,他们的答案是上述结果代码的基础。

用openpyxl解决

 from openpyxl import Workbook,load_workbook
 from openpyxl.styles import Color, PatternFill, Font, Border
 from openpyxl.styles import colors
 from openpyxl.cell import Cell

 d_functions_colors={'cat1': catone_color,
                      'cat5': catfive_color,'cat40':catforty_color}

 wb = load_workbook(outputfilepath)
 ws = wb.active

 for row in ws.iter_rows(min_row=ws.min_row, max_row=ws.max_row):
    if row[0] in d_functions_colors:
       row=d_functions_colors[row[0].value](row)

 def catone_color(row):
    for cell in row:            
        try:
          fl_cell=float(cell.value)
          if fl_cell>=50:
           #Green
           cell.fill = 
PatternFill(start_color='0000FF00',end_color='0000FF00',fill_type='solid')
          elif 50>fl_cell>=20:
           #yellow
           cell.fill = 
PatternFill(start_color='00FFFF00',end_color='00FFFF00',fill_type='solid')             
          elif 20 > fl_cell:
           #red
           cell.fill = 
PatternFill(start_color='FFFF0000',end_color='FFFF0000',fill_type='solid')
          else:
           pass
        except Exception as e:
           print(str(e))
    return row