【问题标题】:stack bar plot in matplotlib and add label to each sectionmatplotlib 中的堆栈条图并为每个部分添加标签
【发布时间】:2014-02-19 06:38:28
【问题描述】:

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

【问题讨论】:

    标签: python matplotlib bar-chart


    【解决方案1】:

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

    回答问题的两个部分:

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

    b) 按照thesetwo 对我之前提到的问题的回答(参见Horizontal stacked bar chart in 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()
    

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

    有关堆叠水平条形图的一些想法,请参阅 Horizontal stacked bar chart in Matplotlib


    【讨论】:

      【解决方案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

      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()
      
      • 这些图与下图相同。

      注释资源 - 来自matplotlib v3.4.2

      原始答案 - 在matplotlib v3.4.2 之前

      • 绘制水平或垂直堆积条的最简单方法是将数据加载到pandas.DataFrame
        • 即使所有类别 ('People') 没有所有细分(例如,某些值为 0 或 NaN),这也会正确绘制和注释
      • 一旦数据在数据框中:
        1. 更易于操作和分析
        2. 可以使用matplotlib 引擎绘制它,使用:
      • 这些方法返回一个matplotlib.axes.Axes 或一个numpy.ndarray
      • 使用.patches 方法解包matplotlib.patches.Rectangle 对象列表,每个对象对应堆叠条形的每个部分。
        • 每个.Rectangle 都有用于提取定义矩形的各种值的方法。
        • 每个.Rectangle 的顺序是从左到右,从下到上,因此在遍历.patches 时,每个级别的所有.Rectangle 对象都会按顺序出现。
      • 标签是使用f-stringlabel_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 的正确位置。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-04
        • 1970-01-01
        • 2018-12-20
        相关资源
        最近更新 更多