最终我找到了一个技巧(编辑:请查看下文以使用 seaborn 和长格式数据框):
使用 pandas 和 matplotlib 的解决方案
以下是一个更完整的示例:
import pandas as pd
import matplotlib.cm as cm
import numpy as np
import matplotlib.pyplot as plt
def plot_clustered_stacked(dfall, labels=None, title="multiple stacked bar plot", H="/", **kwargs):
"""Given a list of dataframes, with identical columns and index, create a clustered stacked bar plot.
labels is a list of the names of the dataframe, used for the legend
title is a string for the title of the plot
H is the hatch used for identification of the different dataframe"""
n_df = len(dfall)
n_col = len(dfall[0].columns)
n_ind = len(dfall[0].index)
axe = plt.subplot(111)
for df in dfall :
axe = df.plot(kind="bar",
linewidth=0,
stacked=True,
ax=axe,
legend=False,
grid=False,
**kwargs)
h,l = axe.get_legend_handles_labels()
for i in range(0, n_df * n_col, n_col):
for j, pa in enumerate(h[i:i+n_col]):
for rect in pa.patches:
rect.set_x(rect.get_x() + 1 / float(n_df + 1) * i / float(n_col))
rect.set_hatch(H * int(i / n_col))
rect.set_width(1 / float(n_df + 1))
axe.set_xticks((np.arange(0, 2 * n_ind, 2) + 1 / float(n_df + 1)) / 2.)
axe.set_xticklabels(df.index, rotation = 0)
axe.set_title(title)
n=[]
for i in range(n_df):
n.append(axe.bar(0, 0, color="gray", hatch=H * i))
l1 = axe.legend(h[:n_col], l[:n_col], loc=[1.01, 0.5])
if labels is not None:
l2 = plt.legend(n, labels, loc=[1.01, 0.1])
axe.add_artist(l1)
return axe
df1 = pd.DataFrame(np.random.rand(4, 5),
index=["A", "B", "C", "D"],
columns=["I", "J", "K", "L", "M"])
df2 = pd.DataFrame(np.random.rand(4, 5),
index=["A", "B", "C", "D"],
columns=["I", "J", "K", "L", "M"])
df3 = pd.DataFrame(np.random.rand(4, 5),
index=["A", "B", "C", "D"],
columns=["I", "J", "K", "L", "M"])
plot_clustered_stacked([df1, df2, df3],["df1", "df2", "df3"])
它会得到这样的结果:
![多个堆叠条形图](https://istack.dev59.com/3ZdAH.webp)
您可以通过传递cmap参数来更改条形的颜色:
plot_clustered_stacked([df1, df2, df3],
["df1", "df2", "df3"],
cmap=plt.cm.viridis)
使用 seaborn 的解决方案:
对于给定的 df1、df2、df3 数据框,我将它们转换为长格式:
df1["Name"] = "df1"
df2["Name"] = "df2"
df3["Name"] = "df3"
dfall = pd.concat([pd.melt(i.reset_index(),
id_vars=["Name", "index"])
for i in [df1, df2, df3]],
ignore_index=True)
Seaborn的问题在于它本身不支持堆叠条形图,因此诀窍是在彼此之上绘制每个条的累计总和:
dfall.set_index(["Name", "index", "variable"], inplace=1)
dfall["vcs"] = dfall.groupby(level=["Name", "index"]).cumsum()
dfall.reset_index(inplace=True)
>>> dfall.head(6)
Name index variable value vcs
0 df1 A I 0.717286 0.717286
1 df1 B I 0.236867 0.236867
2 df1 C I 0.952557 0.952557
3 df1 D I 0.487995 0.487995
4 df1 A J 0.174489 0.891775
5 df1 B J 0.332001 0.568868
然后循环遍历每组 variable
并绘制累计和:
c = ["blue", "purple", "red", "green", "pink"]
for i, g in enumerate(dfall.groupby("variable")):
ax = sns.barplot(data=g[1],
x="index",
y="vcs",
hue="Name",
color=c[i],
zorder=-i,
edgecolor="k")
ax.legend_.remove()
![多个堆叠柱状图seaborn](https://istack.dev59.com/mVUc1.webp)
我认为它缺少可以轻松添加的图例。问题在于,我们有一个亮度梯度来区分数据帧,而不是方格线(可以很容易地添加),对于第一个数据帧来说有点太浅了,我不知道如何在不逐个更改每个矩形的情况下更改它(就像第一种解决方案一样)。
如果您不理解代码中的某些内容,请告诉我。
随意重复使用此处在CC0下开源的代码。
mpld3.display(plt.gcf())
或类似的方法来显示它。 - jrjc