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

2025-02-14 09:50:00
admin
原创
44
摘要:问题描述:我正在尝试在 matplotlib 中复制以下图像,这似乎barh是我唯一的选择。不过看起来你不能堆叠barh图表,所以我不知道该怎么做如果您知道更好的 Python 库来绘制这种东西,请告诉我。这是我所能想到的开始:import matplotlib.pyplot as plt; plt.rcde...

问题描述:

我正在尝试在 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)我可以稍后自己添加,主要是在条形部分本身上贴标签,并以很好的方式创建这种堆叠方法,我遇到了问题。您甚至可以以任何方式指定“距离”,即颜色跨度吗?

在此处输入图片描述


解决方案 1:

编辑 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()

这会生成像这样的图表异质材料,其中每个系列中存在不同数量的段。

请注意,由于每个段都使用了对的单独调用,因此这种方法并不是特别有效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 中的水平堆叠条形图。


解决方案 2:

导入并测试 DataFrame

  • 已在python 3.10, pandas 1.4.2, matplotlib 3.5.1,测试seaborn 0.11.2

  • 对于垂直堆叠条形图,请参阅带居中标签的堆叠条形图

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

  • 使用matplotlib.pyplot.bar_label

  • 请参阅如何在条形图上添加值标签以获取更多详细信息和示例.bar_label

  • labels = [f'{v.get_width():.2f}%' if v.get_width() > 0 else '' for v in c ]对于 python <3.8,没有赋值表达式 ( :=)。

pandas.DataFrame.plot使用绘制kind='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

  • sns.barplot但是,没有堆积条形图的选项,sns.histplot可以sns.displot用来创建水平堆积条形图。

  • seaborn 通常要求数据框采用长格式而不是宽格式,因此使用它pandas.DataFrame.melt来重塑数据框。

重塑数据框

# 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,使用:

      • pandas.DataFrame.plot.barh

        • label_text = f'{width}'用于注释

      • pandas.DataFrame.plot.bar

        • label_text = f'{height}'用于注释

        • SO:带居中标签的垂直堆积条形图

  • 这些方法返回其中的一个matplotlib.axes.Axes或一个。numpy.ndarray

  • 使用该.patches方法解包一个对象列表matplotlib.patches.Rectangle,堆叠条形图的每个部分对应一个对象。

    • 每种.Rectangle方法都有提取定义矩形的各种值的方法。

    • 每个.Rectangle都是从左到右、从下到上的顺序,因此.Rectangle在迭代时,每个级别的所有对象都按顺序出现.patches

  • 标签使用f 字符串,制作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

在此处输入图片描述

解决方案 3:

对于这种情况,上述答案非常有效。我遇到的问题是,我经常需要在多子图图中绘制堆叠条形图,其中包含许多值,而这些值的振幅往往非常不均匀,我在网上找不到即插即用的解决方案。

(注意:我通常使用 pandas 数据框和 matplotlib。我无法使 matplotlib 的 bar_label() 方法一直起作用。)

因此,我只是给出了一种临时但易于推广的解决方案。在此示例中,我使用的是单行数据框(用于每小时的电力交换监控目的),因此,我的数据框 (df) 只有一行。

(我提供了一个示例图来展示这在非常密集的图中如何有用)

''' 此实现生成一个堆叠的水平条形图。

df --> pandas 数据框。列用作迭代器,并且仅使用每列的第一个值。

瀑布-->布尔值:如果为 True,则除了堆栈方向之外,还会添加垂直偏移。

cyclic_offset_x --> 列表(任意长度)或无:循环遍历这些值以用作 x 偏移像素。

cyclic_offset_y --> 列表(任意长度)或无:循环遍历这些值以用作 y 偏移像素。

ax --> matplotlib Axes,或 None:如果为 None,则创建一个新的轴和图形。''​​'

    def magic_stacked_bar(df, waterfall=False, cyclic_offset_x=None, cyclic_offset_y=None, ax=None):



        if isinstance(cyclic_offset_x, type(None)):
            cyclic_offset_x = [0, 0]
        if isinstance(cyclic_offset_y, type(None)):
            cyclic_offset_y = [0, 0]

        ax0 = ax
        if isinstance(ax, type(None)):
            fig, ax = plt.subplots()
            fig.set_size_inches(19, 10)

        cycler = 0;
        prev = 0 # summation variable to make it stacked
        for c in df.columns:
            if waterfall:
                y = c ; label = "" # bidirectional stack
            else:
                y = 0; label = c # unidirectional stack
            ax.barh(y=y, width=df[c].values[0], height=1, left=prev, label = label)
            prev += df[c].values[0] # add to sum-stack

            offset_x = cyclic_offset_x[divmod(cycler, len(cyclic_offset_x))[1]]
            offset_y = cyclic_offset_y[divmod(cycler, len(cyclic_offset_y))[1]]

            ax.annotate(text="{}".format(int(df[c].values[0])), xy=(prev - df[c].values / 2, y),
                        xytext=(offset_x, offset_y), textcoords='offset pixels',
                        ha='center', va='top', fontsize=8,
                        arrowprops=dict(facecolor='black', shrink=0.01, width=0.3, headwidth=0.3),
                        bbox=dict(boxstyle='round', facecolor='grey', alpha=0.5))

            cycler += 1

        if not waterfall:
            ax.legend() # if waterfall, the index annotates the columns. If 
                        # waterfall ==False, the legend annotates the columns
        if isinstance(ax0, type(None)):
            ax.set_title("Voi la")
            ax.set_xlabel("UltraWatts")
            plt.show()
        else:
            return ax

'''(有时,它更加繁琐,需要一些自定义功能才能使标签看起来良好。

'''

A, B = 80,80
n_units = df.shape[1]
cyclic_offset_x = -A*np.cos(2*np.pi / (2*n_units)  *np.arange(n_units))
cyclic_offset_y = B*np.sin(2*np.pi / (2*n_units) * np.arange(n_units)) + B/2
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1590  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1361  
  信创产品在政府采购中的占比分析随着信息技术的飞速发展以及国家对信息安全重视程度的不断提高,信创产业应运而生并迅速崛起。信创,即信息技术应用创新,旨在实现信息技术领域的自主可控,减少对国外技术的依赖,保障国家信息安全。政府采购作为推动信创产业发展的重要力量,其对信创产品的采购占比情况备受关注。这不仅关系到信创产业的发展前...
信创和国产化的区别   18  
  信创,即信息技术应用创新产业,旨在实现信息技术领域的自主可控,摆脱对国外技术的依赖。近年来,国货国用信创发展势头迅猛,在诸多领域取得了显著成果。这一发展趋势对科技创新产生了深远的推动作用,不仅提升了我国在信息技术领域的自主创新能力,还为经济社会的数字化转型提供了坚实支撑。信创推动核心技术突破信创产业的发展促使企业和科研...
信创工作   18  
  信创技术,即信息技术应用创新产业,旨在实现信息技术领域的自主可控与安全可靠。近年来,信创技术发展迅猛,对中小企业产生了深远的影响,带来了诸多不可忽视的价值。在数字化转型的浪潮中,中小企业面临着激烈的市场竞争和复杂多变的环境,信创技术的出现为它们提供了新的发展机遇和支撑。信创技术对中小企业的影响技术架构变革信创技术促使中...
信创国产化   19  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用