Добавление меток значений на гистограмму matplotlib

Я застрял на чем-то, что кажется, должно быть относительно легко. Код, который я привожу ниже, является примером, основанным на более крупном проекте, над которым я работаю. Я не видел причин публиковать все детали, поэтому, пожалуйста, примите структуры данных, которые я привел как есть.

По сути, я создаю столбчатую диаграмму и просто могу понять, как добавить метки значений на столбцы (в центре столбца или чуть выше него). Я просматривал примеры в Интернете, но безуспешно реализовывал свой собственный код. Я полагаю, что решение заключается либо в "тексте", либо в "аннотировании", но я: а) не знаю, какой использовать (и вообще говоря, не определился, когда и какой использовать). б) не вижу, чтобы получить либо представить метки значения. Буду признателен за вашу помощь, мой код ниже. Заранее спасибо!

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)

8 ответов

Во -первых freq_series.plot возвращает ось, а не цифру, поэтому, чтобы сделать мой ответ немного более понятным, я изменил данный код, чтобы обозначить его как ax скорее, чем fig чтобы быть более согласованным с другими примерами кода.

Вы можете получить список баров, произведенных на графике из ax.patches член. Тогда вы можете использовать технику, продемонстрированную в этом matplotlib пример галереи для добавления меток с помощьюax.text метод.

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)

rects = ax.patches

# Make some labels.
labels = ["label%d" % i for i in xrange(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')

Это создает помеченный график, который выглядит следующим образом:

Основываясь на функции, упомянутой в этом ответе на другой вопрос, я нашел очень общее решение для размещения меток на гистограмме.

Другие решения, к сожалению, не работают во многих случаях, потому что расстояние между меткой и полосой либо задается в абсолютных единицах полос, либо масштабируется по высоте полосы. Первый работает только для узкого диапазона значений, а второй дает непоследовательный интервал в пределах одного графика. Ни один из них не работает хорошо с логарифмическими осями.

Предлагаемое мной решение работает независимо от масштаба (т. Е. Для малых и больших чисел) и даже правильно размещает метки для отрицательных значений и с логарифмическими масштабами, поскольку использует визуальную единицу. points для смещений.

Я добавил отрицательное число, чтобы продемонстрировать правильное размещение меток в таком случае.

Значение высоты каждого столбца используется в качестве метки для него. Другие ярлыки могут быть легко использованы с Саймоном for rect, label in zip(rects, labels) фрагмент

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)

rects = ax.patches

# For each bar: Place a label
for rect in rects:
    # 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 = 5
    # 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
    plt.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.

plt.savefig("image.png")

Это дает следующий вывод:

Гистограмма с автоматически размещенными метками на каждой полосе

И с логарифмическим масштабом (и некоторой корректировкой входных данных для демонстрации логарифмического масштабирования), это результат:

Гистограмма с логарифмической шкалой с автоматически размещенными метками на каждой полосе

  • Из matplotlib v3.4.2, использовать matplotlib.pyplot.bar_label
    • Положение метки по умолчанию, установленное параметром label_type, является 'edge'. Чтобы центрировать метки в середине полосы, используйте 'center'
  • Дополнительные параметры см. На форматированиястранице matplotlib: Bar Label Demo .
  • Протестировано с pandas v1.2.4, который использует matplotlib как движок сюжета.
      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[-1], label_type='edge')

# pad the spacing between the number and the edge of the figure
ax.margins(y=0.1)

Основываясь на вышеприведенном (великолепном!) Ответе, мы также можем сделать горизонтальную линейную диаграмму с помощью нескольких настроек:

# 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")

турник с аннотациями

Если вы хотите просто пометить точки данных над полосой, вы можете использовать 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]))

plt.show()

Что ж, текст с несколькими символами может отображаться немного не по центру. Но это можно преодолеть, немного уменьшив координату x в параметре xy в зависимости от размера текста.

Мне также понадобились метки столбцов, обратите внимание, что моя ось 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')

Если вы хотите добавить точки данных только над полосами, вы можете легко сделать это с помощью:

 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

если у вас есть список счетчиков и меток, вы можете сделать следующее. Это решение работает, даже если у вас есть категориальная переменная на оси X и ее счетчик на оси Y.

      import numpy as np
import matplotlib.pyplot as plt
counts = [100,200,300]#list of counts 
labels = ['Orange','Apple','Grape']#list of labels 

plt.bar(np.arange(1,len(counts)+1,1) , counts) 
plt.xticks(np.arange(1,len(counts)+1,1), labels)
plt.ylim(0,max(counts) * 1.2 ) #to increase the ylim by 0.2
for i,j in zip(np.arange(1,len(counts)+1,1) , counts ):
    plt.text(i,(j*0.02) + j,str(j),ha = 'ce')

Другие вопросы по тегам