Hinzufügen von Wertelabels zu einem Matplotlib-Balkendiagramm

89

Ich blieb bei etwas hängen, das sich relativ einfach anfühlt. Der Code, den ich unten bringe, ist ein Beispiel, das auf einem größeren Projekt basiert, an dem ich arbeite. Ich habe keinen Grund gesehen, alle Details zu veröffentlichen. Bitte akzeptieren Sie die Datenstrukturen, die ich mitbringe, so wie sie sind.

Grundsätzlich erstelle ich ein Balkendiagramm und kann nur herausfinden, wie Wertelabels zu den Balken hinzugefügt werden (in der Mitte des Balkens oder direkt darüber). Ich habe mir Beispiele im Internet angesehen, aber keinen Erfolg bei der Implementierung meines eigenen Codes. Ich glaube, die Lösung besteht entweder aus 'Text' oder 'Annotate', aber ich: a) weiß nicht, welche ich verwenden soll (und im Allgemeinen habe ich nicht herausgefunden, wann ich welche verwenden soll). b) kann nicht sehen, um die Wertelabels zu präsentieren. Würde mich über Ihre Hilfe freuen, mein Code unten. Danke im Voraus!

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
quelle
2
Matplotlib hat eine Demo: matplotlib.org/examples/api/barchart_demo.html
Dan

Antworten:

112

Gibt zunächst freq_series.ploteine Achse zurück, die keine Zahl ist. Um meine Antwort ein wenig klarer zu machen, habe ich Ihren angegebenen Code geändert, um darauf zu verweisen, axanstatt figmit anderen Codebeispielen konsistenter zu sein.

Sie können die Liste der im Plot erzeugten Balken vom ax.patchesMitglied erhalten. Anschließend können Sie die in diesem matplotlibGaleriebeispiel gezeigte Technik verwenden , um die Beschriftungen mithilfe der ax.textMethode hinzuzufügen .

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

Dies erzeugt ein beschriftetes Diagramm, das wie folgt aussieht:

Geben Sie hier die Bildbeschreibung ein

Simon Gibbons
quelle
Hallo Simon! Zunächst einmal vielen Dank für die Antwort! Zweitens war ich wohl unklar - ich wollte den y-Wert zeigen. Ich habe gerade die Beschriftungen im Reißverschluss (,) durch Frequenzen ersetzt. Könnten Sie bitte noch etwas Licht auf Feige gegen Axt werfen? Hat mich verwirrt. Eine gute Suchphrase / Ressource wäre auch großartig, da sie für eine Goog-Suche etwas allgemein gehalten ist. Sehr geschätzt!
Optimesh
Eine Figur ist eine Sammlung von einer oder mehreren Achsen, z. B. in diesem Beispiel matplotlib.org/examples/statistics/… ist es eine Figur, die aus 4 verschiedenen Achsen besteht.
Simon Gibbons
Danke noch einmal. Können Sie mir bitte helfen, den Unterschied zwischen Anmerkungen und Text zu verstehen? Vielen Dank!
Optimesh
2
Beide können verwendet werden, um einem Plot Text hinzuzufügen. textdruckt einfach Text auf das Diagramm, während annotatees ein Hilfsmittel ist, mit dem Sie auf einfache Weise auch einen Pfeil aus dem Text hinzufügen können, der auf einen bestimmten Punkt im Diagramm zeigt, auf den sich der Text bezieht.
Simon Gibbons
10
Schöne Lösung. Ich habe hier einen Blog-Beitrag geschrieben, der auf der Lösung aufbaut und eine etwas robustere Version bietet, die entsprechend der Höhe der Achse skaliert, sodass derselbe Code für verschiedene Diagramme mit unterschiedlichen Achsenhöhen funktioniert: Komposition.al/blog/2015/ 29.11. /…
Lindsey Kuper
61

Basierend auf einer in dieser Antwort auf eine andere Frage erwähnten Funktion habe ich eine sehr allgemein anwendbare Lösung zum Platzieren von Beschriftungen auf einem Balkendiagramm gefunden.

Andere Lösungen funktionieren leider in vielen Fällen nicht, da der Abstand zwischen Etikett und Balken entweder in absoluten Einheiten der Balken angegeben oder durch die Höhe des Balkens skaliert wird . Ersteres funktioniert nur für einen engen Wertebereich und letzteres ergibt einen inkonsistenten Abstand innerhalb eines Diagramms. Beides funktioniert nicht gut mit logarithmischen Achsen.

Die von mir vorgeschlagene Lösung funktioniert unabhängig von der Skalierung (dh für kleine und große Zahlen) und platziert sogar Beschriftungen für negative Werte und mit logarithmischen Skalierungen korrekt, da die visuelle Einheit pointsfür Offsets verwendet wird.

Ich habe eine negative Zahl hinzugefügt, um die korrekte Platzierung der Etiketten in einem solchen Fall anzuzeigen.

Der Wert der Höhe jedes Balkens wird als Beschriftung dafür verwendet. Andere Labels können problemlos mit Simons for rect, label in zip(rects, labels)Snippet verwendet werden .

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

Bearbeiten: Ich habe die relevante Funktionalität in einer Funktion extrahiert, wie von Barnhillec vorgeschlagen .

Dies erzeugt die folgende Ausgabe:

Balkendiagramm mit automatisch platzierten Beschriftungen auf jedem Balken

Und mit der logarithmischen Skalierung (und einigen Anpassungen an den Eingabedaten, um die logarithmische Skalierung darzustellen) ist dies das Ergebnis:

Balkendiagramm mit logarithmischer Skala mit automatisch platzierten Beschriftungen auf jedem Balken

justfortherec
quelle
1
Fantastische Antwort! Vielen Dank. Dies funktionierte einwandfrei mit Pandas bei der Erstellung von Barplots.
m4p85r
1
Verbesserungsvorschlag: Verwenden Sie ax.annotate anstelle von plt.annotate. Diese Änderung würde es ermöglichen, die gesamte Routine in eine Funktion zu kapseln, die an einer Achsenachse übergeben wird, die dann in eine nützliche eigenständige Plot-Dienstprogrammfunktion zerlegt werden kann.
Barnhillec
@barnhillec, danke für den Vorschlag. Ich habe genau das in meiner Bearbeitung getan. Beachten Sie, dass dies derzeit nur mit vertikalen Balkendiagrammen und nicht mit anderen Arten von Plots (möglicherweise mit Histogrammen) funktioniert. Eine allgemeinere Funktion würde auch das Verständnis erschweren und daher für eine Antwort hier weniger geeignet sein.
Justfortherec
30

Aufbauend auf der obigen (großartigen!) Antwort können wir mit nur wenigen Anpassungen auch ein horizontales Balkendiagramm erstellen:

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

horizontales Balkendiagramm mit Anmerkungen

Oleson
quelle
1
Damit das Raster freq_series.plot(kind='barh', grid=True)
angezeigt wird
Funktioniert auch mit Gruppen-Balkendiagrammen einwandfrei. Vielen Dank.
Prabah
7

Wenn Sie nur die Datenpunkte über der Leiste beschriften möchten, können Sie plt.annotate () verwenden.

Mein Code:

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

Ausgabe

Nun, Text mit mehreren Zeichen wird möglicherweise etwas außermittig angezeigt. Dies kann jedoch überwunden werden, indem die x-Koordinate im xy-Parameter in Abhängigkeit von der Größe des Textes geringfügig verringert wird

Ajay
quelle
sauber und einfach
Ethan Yanjia Li
Können Sie hinzufügen, wie wir das Etikett genau in der Mitte platzieren können?
x89
0

Wenn Sie nur Datenpunkte über den Balken hinzufügen möchten, können Sie dies problemlos tun mit:

 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
quelle