Dodawanie etykiet wartości na wykresie słupkowym matplotlib

95

Utknąłem w czymś, co wydaje się stosunkowo łatwe. Kod, który przedstawiam poniżej, jest przykładem opartym na większym projekcie, nad którym pracuję. Nie widziałem powodu, aby publikować wszystkie szczegóły, więc zaakceptuj struktury danych, które przyniosłem.

Zasadniczo tworzę wykres słupkowy i mogę po prostu dowiedzieć się, jak dodać etykiety wartości do słupków (na środku słupka lub tuż nad nim). Oglądałem próbki w Internecie, ale bez powodzenia implementowałem je na własnym kodzie. Uważam, że rozwiązaniem jest albo „tekst”, albo „adnotacja”, ale ja: a) nie wiem, którego użyć (i ogólnie rzecz biorąc, nie wiem, kiedy użyć którego). b) nie widzą żadnego z nich, aby przedstawić etykiety wartości. Byłbym wdzięczny za pomoc, mój kod poniżej. Z góry dziękuję!

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)
Optimesh
źródło

Odpowiedzi:

119

Po pierwsze freq_series.plotzwraca oś, a nie cyfrę, więc aby moja odpowiedź była trochę bardziej przejrzysta, zmieniłem podany kod, aby odnosić się do niego, axa nie figbyć bardziej spójnym z innymi przykładami kodu.

Możesz pobrać listę prętów wyprodukowanych na działce od ax.patchesczłonka. Następnie możesz użyć techniki przedstawionej w tym matplotlibprzykładzie galerii, aby dodać etykiety przy użyciu ax.textmetody.

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')

Spowoduje to utworzenie opisanego wykresu, który wygląda następująco:

wprowadź opis obrazu tutaj

Simon Gibbons
źródło
Cześć Simon! Po pierwsze, wielkie dzięki za odpowiedź! Po drugie, chyba nie było jasne - chciałem pokazać wartość y. Właśnie zastąpiłem etykiety w zip (,) częstotliwościami. Czy mógłbyś rzucić więcej światła na topór figowy V? Zdezorientował mnie. Dobra wyszukiwana fraza / zasób również byłaby świetna, ponieważ jest trochę ogólna dla wyszukiwania goog. Bardzo cenione!
Optimesh
Rysunek to zbiór jednej lub więcej osi, np. W tym przykładzie matplotlib.org/examples/statistics/… jest to jedna figura złożona z 4 różnych osi.
Simon Gibbons
Dzięki jeszcze raz. Czy możesz mi pomóc zrozumieć różnice między adnotacjami a tekstem? Dzięki!
Optimesh
2
Oba mogą być używane do dodawania tekstu do wykresu. textpo prostu drukuje jakiś tekst na wykresie, będąc jednocześnie annotatepomocnikiem, którego można użyć do łatwego dodania strzałki z tekstu wskazującej na określony punkt wykresu, do którego odnosi się tekst.
Simon Gibbons,
10
Niezłe rozwiązanie. Napisałem post na blogu, który opiera się na rozwiązaniu tutaj i podaje nieco bardziej niezawodną wersję, która skaluje się zgodnie z wysokością osi, więc ten sam kod działa dla różnych wykresów, które mają różne wysokości osi: composition.al/blog/2015/ 11/29 /…
Lindsey Kuper
65

W oparciu o funkcję wspomnianą w tej odpowiedzi na inne pytanie , znalazłem bardzo ogólnie stosowane rozwiązanie do umieszczania etykiet na wykresie słupkowym.

Inne rozwiązania niestety nie sprawdzają się w wielu przypadkach, ponieważ odstęp między etykietą a prętem jest albo podawany w jednostkach bezwzględnych prętów, albo jest skalowany o wysokość pręta . Pierwsza działa tylko dla wąskiego zakresu wartości, a druga zapewnia niespójne odstępy na jednym wykresie. Żadne z nich nie działa dobrze z osiami logarytmicznymi.

Proponowane przeze mnie rozwiązanie działa niezależnie od skali (tj. Dla małych i dużych liczb), a nawet poprawnie umieszcza etykiety dla wartości ujemnych i ze skalami logarytmicznymi, ponieważ wykorzystuje jednostkę wizualną pointsdo przesunięć.

Dodałem liczbę ujemną, aby pokazać prawidłowe umieszczenie etykiet w takim przypadku.

Wartość wysokości każdego słupka jest używana jako etykieta. Inne etykiety mogą być łatwo używane z fragmentem Simonafor 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")

Edycja: wyodrębniłem odpowiednią funkcjonalność w funkcji, zgodnie z sugestią barnhillec .

Daje to następujący wynik:

Wykres słupkowy z automatycznie umieszczonymi etykietami na każdym słupku

A ze skalą logarytmiczną (i pewną korektą danych wejściowych, aby pokazać skalowanie logarytmiczne), oto wynik:

Wykres słupkowy ze skalą logarytmiczną z automatycznie umieszczanymi etykietami na każdym słupku

justfortherec
źródło
1
Fantastyczna odpowiedź! Dzięki. To działało bezbłędnie w przypadku pand w wbudowanych barach.
m4p85r
1
Sugerowane ulepszenie: użyj ax.annotate zamiast plt.annotate. Ta zmiana umożliwiłaby zamknięcie całej procedury w funkcji, której przechodzi oś osi, którą można następnie uwzględnić w użytecznej samodzielnej funkcji użytkowej wykresu.
barnhillec
@barnhillec, dzięki za sugestię. Dokładnie to zrobiłem w swoim montażu. Zauważ, że obecnie działa to tylko z pionowymi wykresami słupkowymi, a nie z innymi typami wykresów (być może z histogramami). Uogólnienie tej funkcji również utrudniłoby jej zrozumienie, a tym samym utrudniłoby udzielenie odpowiedzi.
justfortherec
Bardzo solidna odpowiedź niż inne, które znalazłem. Ładnie wyjaśnij każdą linijkę komentarzem, pomóż mi przyswoić całe pojęcie.
code_conundrum
34

Opierając się na powyższej (świetnie!) Odpowiedzi, możemy również wykonać poziomy wykres słupkowy z kilkoma poprawkami:

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

poziomy wykres słupkowy z adnotacjami

oleson
źródło
1
Siatka do pokazania:freq_series.plot(kind='barh', grid=True)
sinapan
Działa doskonale nawet z grupowymi wykresami słupkowymi. dzięki.
Prabah
Ładnie zrobione z poziomym wykresem słupkowym!
code_conundrum
Dla mnie liczby przecinają się z ramką otaczającą wykres słupkowy. Czy jest sposób, aby temu zapobiec?
bweber13
Rozwiązałem własny problem, używającax.set_xlim([0, 1.1*max_value])
bweber13
14

Jeśli chcesz po prostu oznaczyć punkty danych powyżej słupka, możesz użyć plt.annotate ()

Mój kod:

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()

Określając poziome i pionowe wyrównanie 'center'i 'bottom'odpowiednio, można uzyskać wyśrodkowane adnotacje.

oznaczony wykres słupkowy

Ajay
źródło
1
czysty i prosty
Ethan Yanjia Li
Czy możesz dodać, jak możemy umieścić etykietę dokładnie w środku?
x89
@ x89 Możesz określić poziome i pionowe wyrównanie tekstu, które wykonuje centrowanie. - Zredagowałem odpowiedź, aby ją poprawić.
Simon Gibbons,
0

Jeśli chcesz dodać tylko punkty danych powyżej pasków, możesz to łatwo zrobić za pomocą:

 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
źródło