【问题标题】:Adding value labels on a matplotlib bar chart在 matplotlib 条形图上添加值标签
【发布时间】:2015-05-09 23:20:50
【问题描述】:

我陷入了一些感觉应该相对容易的事情上。我在下面带来的代码是基于我正在处理的一个更大项目的示例。我认为没有理由发布所有详细信息,因此请按原样接受我带来的数据结构。

基本上,我正在创建一个条形图,我可以弄清楚如何在条形图上添加值标签(在条形图的中心,或正上方)。一直在网上查看示例,但没有成功实现我自己的代码。我相信解决方案是使用“文本”或“注释”,但我: a) 不知道使用哪个(一般来说,还没有弄清楚何时使用哪个)。 b) 看不到任何一个来呈现价值标签。 感谢您的帮助,我的代码如下。 提前致谢!

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
pd.set_option('display.mpl_style', 'default') 
%matplotlib inline

# Bring some raw data.
frequencies = [6, 16, 75, 160, 244, 260, 145, 73, 16, 4, 1]

# In my original code I create a series and run on that, 
# so for consistency I create a series from the list.
freq_series = pd.Series.from_array(frequencies)

x_labels = [108300.0, 110540.0, 112780.0, 115020.0, 117260.0, 119500.0, 
            121740.0, 123980.0, 126220.0, 128460.0, 130700.0]

# Plot the figure.
plt.figure(figsize=(12, 8))
fig = freq_series.plot(kind='bar')
fig.set_title('Amount Frequency')
fig.set_xlabel('Amount ($)')
fig.set_ylabel('Frequency')
fig.set_xticklabels(x_labels)

【问题讨论】:

标签: python pandas matplotlib data-visualization bar-chart


【解决方案1】:

首先freq_series.plot 返回一个轴不是一个数字,所以为了让我的答案更清楚一点,我已经更改了你给定的代码以将其称为ax 而不是fig与其他代码示例更加一致。

您可以从ax.patches 成员处获取绘图中生成的条形列表。然后您可以使用this matplotlib gallery example 中演示的技术,使用ax.text 方法添加标签。

import pandas as pd
import matplotlib.pyplot as plt

# Bring some raw data.
frequencies = [6, 16, 75, 160, 244, 260, 145, 73, 16, 4, 1]
# In my original code I create a series and run on that,
# so for consistency I create a series from the list.
freq_series = pd.Series(frequencies)

x_labels = [
    108300.0,
    110540.0,
    112780.0,
    115020.0,
    117260.0,
    119500.0,
    121740.0,
    123980.0,
    126220.0,
    128460.0,
    130700.0,
]

# Plot the figure.
plt.figure(figsize=(12, 8))
ax = freq_series.plot(kind="bar")
ax.set_title("Amount Frequency")
ax.set_xlabel("Amount ($)")
ax.set_ylabel("Frequency")
ax.set_xticklabels(x_labels)

rects = ax.patches

# Make some labels.
labels = [f"label{i}" for i in range(len(rects))]

for rect, label in zip(rects, labels):
    height = rect.get_height()
    ax.text(
        rect.get_x() + rect.get_width() / 2, height + 5, label, ha="center", va="bottom"
    )

plt.show()

这会生成一个标记图,如下所示:

【讨论】:

  • 图形是一个或多个轴的集合,例如在这个例子中,matplotlib.org/examples/statistics/… 是一个由 4 个不同轴组成的图形。
  • 不错的解决方案。我写了一篇基于此处解决方案的博客文章,并提供了一个更强大的版本,该版本根据轴的高度进行缩放,因此相同的代码适用于具有不同轴高度的不同绘图:composition.al/blog/2015/11/29/…
【解决方案2】:

基于this answer to another question 中提到的一个功能,我找到了一个非常普遍适用的解决方案,用于在条形图上放置标签。

不幸的是,其他解决方案在很多情况下都不起作用,因为标签和条形之间的间距要么是given in absolute units of the bars,要么是scaled by the height of the bar。前者仅适用于范围较窄的值,而后者在一个图中给出了不一致的间距。两者都不适用于对数轴。

我提出的解决方案独立于比例(即对于小数和大数)工作,甚至可以正确放置负值和对数比例的标签,因为它使用视觉单位 points 进行偏移。

我添加了一个负数来展示在这种情况下标签的正确位置。

每个条的高度值用作它的标签。其他标签可以很容易地与Simon's for rect, label in zip(rects, labels) snippet一起使用。

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

# Bring some raw data.
frequencies = [6, -16, 75, 160, 244, 260, 145, 73, 16, 4, 1]

# In my original code I create a series and run on that,
# so for consistency I create a series from the list.
freq_series = pd.Series.from_array(frequencies)

x_labels = [108300.0, 110540.0, 112780.0, 115020.0, 117260.0, 119500.0,
            121740.0, 123980.0, 126220.0, 128460.0, 130700.0]

# Plot the figure.
plt.figure(figsize=(12, 8))
ax = freq_series.plot(kind='bar')
ax.set_title('Amount Frequency')
ax.set_xlabel('Amount ($)')
ax.set_ylabel('Frequency')
ax.set_xticklabels(x_labels)


def add_value_labels(ax, spacing=5):
    """Add labels to the end of each bar in a bar chart.

    Arguments:
        ax (matplotlib.axes.Axes): The matplotlib object containing the axes
            of the plot to annotate.
        spacing (int): The distance between the labels and the bars.
    """

    # For each bar: Place a label
    for rect in ax.patches:
        # Get X and Y placement of label from rect.
        y_value = rect.get_height()
        x_value = rect.get_x() + rect.get_width() / 2

        # Number of points between bar and label. Change to your liking.
        space = spacing
        # Vertical alignment for positive values
        va = 'bottom'

        # If value of bar is negative: Place label below bar
        if y_value < 0:
            # Invert space to place label below
            space *= -1
            # Vertically align label at top
            va = 'top'

        # Use Y value as label and format number with one decimal place
        label = "{:.1f}".format(y_value)

        # Create annotation
        ax.annotate(
            label,                      # Use `label` as label
            (x_value, y_value),         # Place label at end of the bar
            xytext=(0, space),          # Vertically shift label by `space`
            textcoords="offset points", # Interpret `xytext` as offset in points
            ha='center',                # Horizontally center label
            va=va)                      # Vertically align label differently for
                                        # positive and negative values.


# Call the function above. All the magic happens there.
add_value_labels(ax)

plt.savefig("image.png")

编辑:我已经提取了函数中的相关功能,正如barnhillec 所建议的那样。

这会产生以下输出:

使用对数刻度(并对输入数据进行一些调整以展示对数刻度),结果如下:

【讨论】:

    【解决方案3】:

    在上述(很棒!)答案的基础上,我们还可以通过一些调整来制作水平条形图:

    # Bring some raw data.
    frequencies = [6, -16, 75, 160, 244, 260, 145, 73, 16, 4, 1]
    
    freq_series = pd.Series(frequencies)
    
    y_labels = [108300.0, 110540.0, 112780.0, 115020.0, 117260.0, 119500.0, 
                121740.0, 123980.0, 126220.0, 128460.0, 130700.0]
    
    # Plot the figure.
    plt.figure(figsize=(12, 8))
    ax = freq_series.plot(kind='barh')
    ax.set_title('Amount Frequency')
    ax.set_xlabel('Frequency')
    ax.set_ylabel('Amount ($)')
    ax.set_yticklabels(y_labels)
    ax.set_xlim(-40, 300) # expand xlim to make labels easier to read
    
    rects = ax.patches
    
    # For each bar: Place a label
    for rect in rects:
        # Get X and Y placement of label from rect.
        x_value = rect.get_width()
        y_value = rect.get_y() + rect.get_height() / 2
    
        # Number of points between bar and label. Change to your liking.
        space = 5
        # Vertical alignment for positive values
        ha = 'left'
    
        # If value of bar is negative: Place label left of bar
        if x_value < 0:
            # Invert space to place label to the left
            space *= -1
            # Horizontally align label at right
            ha = 'right'
    
        # Use X value as label and format number with one decimal place
        label = "{:.1f}".format(x_value)
    
        # Create annotation
        plt.annotate(
            label,                      # Use `label` as label
            (x_value, y_value),         # Place label at end of the bar
            xytext=(space, 0),          # Horizontally shift label by `space`
            textcoords="offset points", # Interpret `xytext` as offset in points
            va='center',                # Vertically center label
            ha=ha)                      # Horizontally align label differently for
                                        # positive and negative values.
    
    plt.savefig("image.png")
    

    【讨论】:

    • 非常感谢!我一直在尝试使最佳答案的代码与水平条一起工作,但最后这个工作了。
    【解决方案4】:

    如果您只想标记条形上方的数据点,可以使用 plt.annotate()

    我的代码:

    import numpy as np
    import matplotlib.pyplot as plt
    
    n = [1,2,3,4,5,]
    s = [i**2 for i in n]
    line = plt.bar(n,s)
    plt.xlabel('Number')
    plt.ylabel("Square")
    
    for i in range(len(s)):
        plt.annotate(str(s[i]), xy=(n[i],s[i]), ha='center', va='bottom')
    
    plt.show()
    

    通过分别指定'center''bottom' 的水平和垂直对齐方式,可以获得居中的注释。

    【讨论】:

      【解决方案5】:

      截至matplotlib v3.4.2

      import pandas as pd
      
      # dataframe using frequencies and x_labels from the OP
      df = pd.DataFrame({'Frequency': frequencies}, index=x_labels)
      
      # display(df)
                Frequency
      108300.0          6
      110540.0         16
      112780.0         75
      115020.0        160
      117260.0        244
      
      # plot
      ax = df.plot(kind='bar', figsize=(12, 8), title='Amount Frequency',
                   xlabel='Amount ($)', ylabel='Frequency', legend=False)
      
      # annotate
      ax.bar_label(ax.containers[0], label_type='edge')
      
      # pad the spacing between the number and the edge of the figure
      ax.margins(y=0.1)
      

      ax.bar_label(ax.containers[0], label_type='edge', color='red', rotation=90, fontsize=7, padding=3)
      

      bar_label 的示例

      【讨论】:

        【解决方案6】:

        我也需要条形标签,请注意,我的 y 轴是使用 y 轴限制的缩放视图。将标签放在条形顶部的默认计算仍然使用高度(示例中的 use_global_coordinate=False)。但我想表明,标签也可以使用matplotlib 3.0.2 中的全局坐标在缩放视图中放置在图表的底部。希望对某人有所帮助。

        def autolabel(rects,data):
        """
        Attach a text label above each bar displaying its height
        """
        c = 0
        initial = 0.091
        offset = 0.205
        use_global_coordinate = True
        
        if use_global_coordinate:
            for i in data:        
                ax.text(initial+offset*c, 0.05, str(i), horizontalalignment='center',
                        verticalalignment='center', transform=ax.transAxes,fontsize=8)
                c=c+1
        else:
            for rect,i in zip(rects,data):
                height = rect.get_height()
                ax.text(rect.get_x() + rect.get_width()/2., height,str(i),ha='center', va='bottom')
        

        【讨论】:

          【解决方案7】:

          如果您只想在条形上方添加数据点,您可以轻松地做到这一点:

           for i in range(len(frequencies)): # your number of bars
              plt.text(x = x_values[i]-0.25, #takes your x values as horizontal positioning argument 
              y = y_values[i]+1, #takes your y values as vertical positioning argument 
              s = data_labels[i], # the labels you want to add to the data
              size = 9) # font size of datalabels
          

          【讨论】:

            猜你喜欢
            • 2016-07-06
            • 2022-01-11
            • 1970-01-01
            • 1970-01-01
            • 2021-02-20
            • 1970-01-01
            相关资源
            最近更新 更多