水平堆积条形图并为每个部分添加标签

新手上路,请多包涵

我正在尝试在 matplotlib 中复制以下图像,似乎 barh 是我唯一的选择。虽然看起来你不能堆叠 barh 图表所以我不知道该怎么做

在此处输入图像描述

如果你知道更好的 python 库来绘制这种东西,请告诉我。

这就是我一开始所能想到的:

 import matplotlib.pyplot as plt; plt.rcdefaults()
import numpy as np
import matplotlib.pyplot as plt

people = ('A','B','C','D','E','F','G','H')
y_pos = np.arange(len(people))
bottomdata = 3 + 10 * np.random.rand(len(people))
topdata = 3 + 10 * np.random.rand(len(people))
fig = plt.figure(figsize=(10,8))
ax = fig.add_subplot(111)
ax.barh(y_pos, bottomdata,color='r',align='center')
ax.barh(y_pos, topdata,color='g',align='center')
ax.set_yticks(y_pos)
ax.set_yticklabels(people)
ax.set_xlabel('Distance')

plt.show()

然后我将不得不使用 ax.text 单独添加标签,这会很乏味。理想情况下,我只想指定要插入的部分的宽度,然后它会用我选择的字符串更新该部分的中心。外面的标签(例如 3800)我可以稍后添加自己,它主要是在条形部分本身上的标签,并以一种我遇到问题的很好的方式创建这个堆叠方法。你能以任何方式指定一个“距离”,即颜色跨度吗?

在此处输入图像描述

原文由 Griff 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 614
2 个回答

编辑 2:用于更多异构数据。 (我已经离开了上述方法,因为我发现每个系列使用相同数量的记录更常见)

回答问题的两个部分:

a) barh 返回它绘制的所有补丁的句柄容器。您可以使用补丁的坐标来帮助文本定位。

b) 按照我之前提到的问题的 两个 答案(请参阅 Matplotlib 中的水平堆叠条形图),您可以通过设置“左”输入来水平堆叠条形图。

另外 c) 处理形状不太统一的数据。

下面是处理形状不太统一的数据的一种方法,即简单地独立处理每个段。

 import numpy as np
import matplotlib.pyplot as plt

# some labels for each row
people = ('A','B','C','D','E','F','G','H')
r = len(people)

# how many data points overall (average of 3 per person)
n = r * 3

# which person does each segment belong to?
rows = np.random.randint(0, r, (n,))
# how wide is the segment?
widths = np.random.randint(3,12, n,)
# what label to put on the segment (xrange in py2.7, range for py3)
labels = range(n)
colors ='rgbwmc'

patch_handles = []

fig = plt.figure(figsize=(10,8))
ax = fig.add_subplot(111)

left = np.zeros(r,)
row_counts = np.zeros(r,)

for (r, w, l) in zip(rows, widths, labels):
    print r, w, l
    patch_handles.append(ax.barh(r, w, align='center', left=left[r],
        color=colors[int(row_counts[r]) % len(colors)]))
    left[r] += w
    row_counts[r] += 1
    # we know there is only one patch but could enumerate if expanded
    patch = patch_handles[-1][0]
    bl = patch.get_xy()
    x = 0.5*patch.get_width() + bl[0]
    y = 0.5*patch.get_height() + bl[1]
    ax.text(x, y, "%d%%" % (l), ha='center',va='center')

y_pos = np.arange(8)
ax.set_yticks(y_pos)
ax.set_yticklabels(people)
ax.set_xlabel('Distance')

plt.show()

产生这样的图形异构hbars,每个系列中存在不同数量的段。

请注意,这不是特别有效,因为每个段都使用对 ax.barh 的单独调用。可能有更有效的方法(例如,通过用零宽度段或 nan 值填充矩阵)但这可能是特定于问题的并且是一个不同的问题。


编辑:更新以回答问题的两个部分。

 import numpy as np
import matplotlib.pyplot as plt

people = ('A','B','C','D','E','F','G','H')
segments = 4

# generate some multi-dimensional data & arbitrary labels
data = 3 + 10* np.random.rand(segments, len(people))
percentages = (np.random.randint(5,20, (len(people), segments)))
y_pos = np.arange(len(people))

fig = plt.figure(figsize=(10,8))
ax = fig.add_subplot(111)

colors ='rgbwmc'
patch_handles = []
left = np.zeros(len(people)) # left alignment of data starts at zero
for i, d in enumerate(data):
    patch_handles.append(ax.barh(y_pos, d,
      color=colors[i%len(colors)], align='center',
      left=left))
    # accumulate the left-hand offsets
    left += d

# go through all of the bar segments and annotate
for j in range(len(patch_handles)):
    for i, patch in enumerate(patch_handles[j].get_children()):
        bl = patch.get_xy()
        x = 0.5*patch.get_width() + bl[0]
        y = 0.5*patch.get_height() + bl[1]
        ax.text(x,y, "%d%%" % (percentages[i,j]), ha='center')

ax.set_yticks(y_pos)
ax.set_yticklabels(people)
ax.set_xlabel('Distance')

plt.show()

您可以按照这些路线获得结果(注意:我使用的百分比与条形宽度无关,因为示例中的关系似乎不清楚):

示例输出

有关堆叠水平条形图的一些想法,请参阅 Matplotlib 中的水平堆叠条形图


原文由 Bonlenfum 发布,翻译遵循 CC BY-SA 4.0 许可协议

导入和测试 DataFrame

import pandas as pd
import numpy as np

# create sample data as shown in the OP
np.random.seed(365)
people = ('A','B','C','D','E','F','G','H')
bottomdata = 3 + 10 * np.random.rand(len(people))
topdata = 3 + 10 * np.random.rand(len(people))

# create the dataframe
df = pd.DataFrame({'Female': bottomdata, 'Male': topdata}, index=people)

# display(df)
   Female   Male
A   12.41   7.42
B    9.42   4.10
C    9.85   7.38
D    8.89  10.53
E    8.44   5.92
F    6.68  11.86
G   10.67  12.97
H    6.05   7.87

更新为 matplotlib v3.4.2

使用 pandas.DataFrame.plotkind='barh' 绘制

ax = df.plot(kind='barh', stacked=True, figsize=(8, 6))

for c in ax.containers:

    # customize the label to account for cases when there might not be a bar section
    labels = [f'{w:.2f}%' if (w := v.get_width()) > 0 else '' for v in c ]

    # set the bar label
    ax.bar_label(c, labels=labels, label_type='center')

    # uncomment and use the next line if there are no nan or 0 length sections; just use fmt to add a % (the previous two lines of code are not needed, in this case)
#     ax.bar_label(c, fmt='%.2f%%', label_type='center')

# move the legend
ax.legend(bbox_to_anchor=(1.025, 1), loc='upper left', borderaxespad=0.)

# add labels
ax.set_ylabel("People", fontsize=18)
ax.set_xlabel("Percent", fontsize=18)
plt.show()

在此处输入图像描述

使用 seaborn

重塑数据框

# convert the dataframe to a long form
df = df.reset_index()
df = df.rename(columns={'index': 'People'})
dfm = df.melt(id_vars='People', var_name='Gender', value_name='Percent')

# display(dfm)
   People  Gender    Percent
0       A  Female  12.414557
1       B  Female   9.416027
2       C  Female   9.846105
3       D  Female   8.885621
4       E  Female   8.438872
5       F  Female   6.680709
6       G  Female  10.666258
7       H  Female   6.050124
8       A    Male   7.420860
9       B    Male   4.104433
10      C    Male   7.383738
11      D    Male  10.526158
12      E    Male   5.916262
13      F    Male  11.857227
14      G    Male  12.966913
15      H    Male   7.865684

sns.histplot :轴级图

fig, axe = plt.subplots(figsize=(8, 6))
sns.histplot(data=dfm, y='People', hue='Gender', discrete=True, weights='Percent', multiple='stack', ax=axe)

# iterate through each set of containers
for c in axe.containers:
    # add bar annotations
    axe.bar_label(c, fmt='%.2f%%', label_type='center')

axe.set_xlabel('Percent')
plt.show()

在此处输入图像描述

sns.displot : 图级图

g = sns.displot(data=dfm, y='People', hue='Gender', discrete=True, weights='Percent', multiple='stack', height=6)

# iterate through each facet / supbplot
for axe in g.axes.flat:
    # iteate through each set of containers
    for c in axe.containers:
        # add the bar annotations
        axe.bar_label(c, fmt='%.2f%%', label_type='center')
    axe.set_xlabel('Percent')

plt.show()

在此处输入图像描述

原始答案 - 之前 matplotlib v3.4.2

  • 绘制水平或垂直堆叠条形图的最简单方法是将数据加载到 pandas.DataFrame
    • 这将绘制并正确注释,即使所有类别( 'People' )都没有所有段(例如某些值为 0 或 NaN
  • 一旦数据在数据框中:
    1. 操作和分析更容易
    2. 它可以用 matplotlib 引擎绘制,使用:
  • 这些方法返回一个 matplotlib.axes.Axes 或一个 numpy.ndarray
  • 使用 .patches 方法解压缩 matplotlib.patches.Rectangle 对象列表,每个对象对应堆叠条形图的每个部分。
    • 每个 .Rectangle 都有提取定义矩形的各种值的方法。
    • 每个 .Rectangle 是从左到右,从下到上的顺序,所以所有 .Rectangle 对象,对于每个级别,在遍历 .patches 时按顺序出现 ---
  • 标签是使用 f-string label_text = f'{width:.2f}%' 的,因此可以根据需要添加任何其他文本。

绘图和注释

  • 绘制条形图,是 1 行,其余部分用于注释矩形
# plot the dataframe with 1 line
ax = df.plot.barh(stacked=True, figsize=(8, 6))

# .patches is everything inside of the chart
for rect in ax.patches:
    # Find where everything is located
    height = rect.get_height()
    width = rect.get_width()
    x = rect.get_x()
    y = rect.get_y()

    # The height of the bar is the data value and can be used as the label
    label_text = f'{width:.2f}%'  # f'{width:.2f}' to format decimal values

    # ax.text(x, y, text)
    label_x = x + width / 2
    label_y = y + height / 2

    # only plot labels greater than given width
    if width > 0:
        ax.text(label_x, label_y, label_text, ha='center', va='center', fontsize=8)

# move the legend
ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0.)

# add labels
ax.set_ylabel("People", fontsize=18)
ax.set_xlabel("Percent", fontsize=18)
plt.show()

在此处输入图像描述

缺少片段的示例

# set one of the dataframe values to 0
df.iloc[4, 1] = 0

  • 请注意,注释都位于 df 的正确位置。

在此处输入图像描述

原文由 Trenton McKinney 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
logo
Stack Overflow 翻译
子站问答
访问
宣传栏