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

95

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

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

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)
Оптимеш
источник
2
У Matplotlib есть демо: matplotlib.org/examples/api/barchart_demo.html
Дэн

Ответы:

119

Во-первых, 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')

Это дает помеченный график, который выглядит так:

введите описание изображения здесь

Саймон Гиббонс
источник
Привет, Саймон! Во-первых, большое спасибо за ответ! 2-й, думаю, я был неясен - я хотел показать значение y. Я просто заменил метки в zip (,) на частоты. А теперь не могли бы вы пролить еще немного света на фигу против топора? Запутал меня. Хорошая поисковая фраза / ресурс также подойдет, поскольку она немного универсальна для поиска в Google. Очень признателен!
Optimesh 08
Фигура - это набор из одной или нескольких осей, например, в этом примере matplotlib.org/examples/statistics/… это одна фигура, состоящая из 4 различных осей.
Саймон Гиббонс
Еще раз спасибо. Не могли бы вы помочь мне понять разницу между аннотацией и текстом? Благодарность!
Optimesh 09
2
Оба могут использоваться для добавления текста на сюжет. textпросто печатает текст на графике, а annotateэто помощник, который вы можете использовать, чтобы легко также добавить стрелку из текста, указывающую на конкретную точку на графике, на которую ссылается текст.
Саймон Гиббонс,
10
Хорошее решение. Я написал сообщение в блоге, которое основано на решении здесь и дает немного более надежную версию, масштабируемую в соответствии с высотой оси, поэтому один и тот же код работает для разных графиков с разной высотой оси: композиция.al/
ноября
66

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

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

Предлагаемое мной решение работает независимо от масштаба (то есть для малых и больших чисел) и даже правильно размещает метки для отрицательных значений и с логарифмическими масштабами, поскольку оно использует визуальную единицу 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)


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 .

Это дает следующий результат:

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

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

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

Justfortherec
источник
1
Фантастический ответ! Спасибо. Это безупречно работало с пандами во встроенном графике столбцов.
m4p85r 05
1
Предлагаемое улучшение: используйте ax.annotate вместо plt.annotate. Это изменение позволило бы всей программе быть инкапсулировано в функцию, которой передается ось ax, которая затем может быть преобразована в полезную автономную функцию полезности графика.
barnhillec
@barnhillec, спасибо за предложение. Я сделал именно это в своей редакции. Обратите внимание, что в настоящее время это работает только с вертикальными столбчатыми диаграммами, но не с любыми другими типами графиков (возможно, с гистограммами). Если сделать функцию более универсальной, ее будет сложнее понять и, следовательно, она будет менее подходящей для ответа здесь.
justfortherec
Очень надежный ответ, чем другие, которые я нашел. Четко объясните каждую строчку комментариями, помогите мне усвоить все понятие.
code_conundrum
34

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

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

горизонтальная полоса с аннотациями

олесон
источник
1
Для отображения в сетке:freq_series.plot(kind='barh', grid=True)
sinapan
Прекрасно работает даже с гистограммами группы. Спасибо.
Прабах
Красиво сделано с горизонтальной гистограммой!
code_conundrum
Для меня числа пересекаются с рамкой вокруг гистограммы. Есть ли способ предотвратить это?
bweber13,
Решил мою проблему с помощьюax.set_xlim([0, 1.1*max_value])
bweber13
14

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

маркированная гистограмма

Аджай
источник
1
чисто и просто
Итан Яньцзя Ли
Вы можете добавить, как мы можем разместить этикетку точно по центру?
x89,
@ x89 Вы можете указать горизонтальное и вертикальное выравнивание текста, при котором выполняется центрирование. - Я отредактировал ответ, чтобы улучшить его.
Саймон Гиббонс,
0

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

 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
user11638654
источник