Додавання міток значень на гістограму 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')

Це створює позначений сюжет, який виглядає так:

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


Привіт Саймоне! По-перше, велике спасибі за відповідь! По-друге, я думаю, мені було незрозуміло - я хотів показати значення y. Я просто замінив мітки в zip (,) на частоти. А тепер, будь ласка, пролийте трохи більше світла на сокиру з фіг Vs? Збентежив мене. Хороша пошукова фраза / ресурс буде також чудовою, оскільки вона є трохи загальною для пошуку goog. Цінується!
Оптімеш

Рисунок - це сукупність однієї або декількох осей, наприклад, у цьому прикладі matplotlib.org/examples/statistics/… це одна фігура, яка складається з 4 різних осей.
Саймон Гіббонс

Знову дякую. Чи можете ви допомогти мені зрозуміти різницю між анотацією та текстом? Дякую!
Оптімеш

2
І те, і інше можна використовувати для додавання тексту до сюжету. textпросто друкує якийсь текст на сюжеті, тоді як annotateє помічником, за допомогою якого ви також можете легко додати стрілку з тексту, що вказує на певну точку на сюжеті, на яку посилається текст.
Саймон Гіббонс

10
Гарне рішення. Я написав публікацію в блозі, яка спирається на рішення тут і дає трохи більш надійну версію, яка масштабується відповідно до висоти осі, тому один і той же код працює для різних графіків, які мають різну висоту осі: композиція.al/
Ліндсі Купер

64

На основі функції, згаданої у цій відповіді на інше питання, я знайшов дуже загальноприйнятне рішення для розміщення ярликів на гістограмі.

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

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

Це дає такий результат:

Гістограма з автоматично розміщеними мітками на кожному стовпчику

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

Гістограма з логарифмічною шкалою з автоматично розміщеними мітками на кожному стовпці


1
Фантастична відповідь! Дякую. Це бездоганно працювало з пандами у побудованому заготівлі бару.
m4p85r

1
Запропоноване вдосконалення: використовуйте ax.annotate, а не plt.annotate. Ця зміна дозволить усьому підпрограму бути інкапсульовано у функцію, яка передається осі осі, яка потім може бути врахована в корисну самостійну функцію утиліти діаграми.
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)
Сінапан

Відмінно працює навіть із гістограмами групи. Дякую.
Prabah

Чудово зроблено з горизонтальною гістограмою!
code_conundrum

Для мене цифри перетинаються з рамкою, що оточує гістограму. Чи є спосіб запобігти цьому?
bweber13

Вирішив власну проблему за допомогоюax.set_xlim([0, 1.1*max_value])
bweber13

13

Якщо ви хочете просто позначити точки даних над стовпчиком, ви можете використовувати 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.