更改 matplotlib 条形图中特定条形的颜色

Change color of specific bar in matplotlib barplot

如果满足特定条件,我想更改 matplotlib 分组条形图中条形的颜色。我正在为每个 species 绘制两个条 - 一个用于 today,一个用于 avg,其中 avg 包含显示第 10 个和第 90 个百分位数的 yerr 个误差条值。

现在,如果 todaylength 值 > 第 10 个百分位数,我希望 avg 条为绿色,如果 todaylength 条为红色] 值 < 第 10 个百分位数。

我尝试了这些帖子中的解决方案

但条形总是绿色的。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

z = pd.DataFrame(data={'length': [40,35,34,40,36,39,38,44,40,39,35,46],
                       'species': ['A','A','A','A','B','B','B','B','C','C','C','C'],
                       'type': ['today','avg','10pc','90pc','today','avg','10pc','90pc','today','avg','10pc','90pc']
                      },
                )
z['Date'] = pd.to_datetime('2021-09-20')
z.set_index('Date',inplace=True)

z0 = z.loc[(z.type=='today') | (z.type=='avg')] # average length and today's length
z1 = z.loc[(z.type=='10pc') | (z.type=='90pc')] # 10th and 90th percentile

z2 = []
for n in z.species.unique().tolist():
    dz = z.loc[(z.species==n) & (z.type=='today'),'length'].values[0] - z.loc[(z.species==n) & (z.type=='10pc'),'length'].values[0]
    if dw>0:
        z2.append(1)
    else:
        z2.append(0)

errors = z1.pivot_table(columns=[z1.index,'species'],index='type',values=['length']).values
avgs = z0.length[z0.type=='avg'].values
bars = np.stack((np.absolute(errors-avgs), np.zeros([2,z1.species.unique().size])), axis=0)

col = ['pink']
for k in z2:
    if k==1:
        col.append('g') # length within 10% bounds = green
    else:
        col.append('r') # length outside 10% bounds = red

fig, ax = plt.subplots()
z0.pivot(index='species', columns='type', values='length').plot(kind='bar', yerr=bars, ax=ax, color=col, capsize=0)
ax.set_title(z0.index[0].strftime('%d %b %Y'), fontsize=16)
ax.set_xlabel('species', fontsize=14)
ax.set_ylabel('length (cm)', fontsize=14)

plt.show()

一种方法是在创建绘图后覆盖颜色。首先,您需要将初始化 col 的行更改为

col = ['pink']*z['species'].nunique()

获取平均柱数,然后使用相同的 for 循环根据您的情况添加 g 或 r。最后,改变这个

fig, ax = plt.subplots()
z0.pivot(index='species', columns='type', values='length')\
  .plot(kind='bar', yerr=bars, ax=ax, 
        color=['pink','g'], capsize=0) # here use only pink and g
# here overwrite the colors 
for p, c in zip(ax.patches, col):
    p.set_color(c)
ax.set_title...

请注意,即使您有红色条,今天的图例也是绿色的,这可能会造成混淆。

这是完整的工作示例,感谢 this answer

在图例中添加了红色条目
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches # import this extra

z = pd.DataFrame(data={'length': [40,35,34,40,36,39,38,44,40,39,35,46],
                       'species': ['A','A','A','A','B','B','B','B','C','C','C','C'],
                       'type': ['today','avg','10pc','90pc','today','avg','10pc','90pc','today','avg','10pc','90pc']
                      },
                )
z['Date'] = pd.to_datetime('2021-09-20')
z.set_index('Date',inplace=True)

z0 = z.loc[(z.type=='today') | (z.type=='avg')] # average length and today's length
z1 = z.loc[(z.type=='10pc') | (z.type=='90pc')] # 10th and 90th percentile

z2 = []
for n in z.species.unique().tolist():
    dz = z.loc[(z.species==n) & (z.type=='today'),'length'].values[0] - z.loc[(z.species==n) & (z.type=='10pc'),'length'].values[0]
    if dz>0:
        z2.append(1)
    else:
        z2.append(0)

errors = z1.pivot_table(columns=[z1.index,'species'],index='type',values=['length']).values
avgs = z0.length[z0.type=='avg'].values
bars = np.stack((np.absolute(errors-avgs), np.zeros([2,z1.species.unique().size])), axis=0)

col = ['pink']*z['species'].nunique()
for k in z2:
    if k==1:
        col.append('g') # length within 10% bounds = green
    else:
        col.append('r') # length outside 10% bounds = red
print(col)
# ['pink', 'pink', 'pink', 'g', 'r', 'g']

fig, ax = plt.subplots()
z0.pivot(index='species', columns='type', values='length').plot(kind='bar', yerr=bars, ax=ax, color=['pink','g'], capsize=0)
for p, c in zip(ax.patches, col):
    p.set_color(c)
ax.set_title(z0.index[0].strftime('%d %b %Y'), fontsize=16)
ax.set_xlabel('species', fontsize=14)
ax.set_ylabel('length (cm)', fontsize=14)

handles = None
labels = None

if 0 in z2: ## add the entry on the legend only when there is red bar
    # where some data has already been plotted to ax
    handles, labels = ax.get_legend_handles_labels()
    # manually define a new patch 
    patch = mpatches.Patch(color='r', label='today')
    # handles is a list, so append manual patch
    handles.append(patch) 
    # plot the legend
    plt.legend(handles=handles)
else:
    # plot the legend when there isn't red bar
    plt.legend(handles=handles)

plt.show()

我得到了红条