Ajout d'étiquettes de valeur sur un graphique à barres matplotlib

92

Je suis resté coincé sur quelque chose qui devrait être relativement facile. Le code que j'apporte ci-dessous est un exemple basé sur un projet plus vaste sur lequel je travaille. Je n'ai vu aucune raison de publier tous les détails, veuillez donc accepter les structures de données que j'apporte telles quelles.

Fondamentalement, je crée un graphique à barres et je peux simplement comprendre comment ajouter des étiquettes de valeur sur les barres (au centre de la barre, ou juste au-dessus). J'ai regardé des exemples sur le Web, mais sans succès l'implémentation sur mon propre code. Je pense que la solution est soit avec «texte» ou «annoter», mais je: a) ne sais pas lequel utiliser (et de manière générale, je n'ai pas compris quand utiliser lequel). b) ne peut pas voir pour obtenir non plus de présenter les étiquettes de valeur. J'apprécierais votre aide, mon code ci-dessous. Merci d'avance!

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
la source
2
Matplotlib a une démo: matplotlib.org/examples/api/barchart_demo.html
Dan

Réponses:

114

Tout d'abord, freq_series.plotretourne un axe et non un chiffre, donc pour rendre ma réponse un peu plus claire, j'ai changé votre code donné pour y faire référence axplutôt que figpour être plus cohérent avec d'autres exemples de code.

Vous pouvez obtenir la liste des barres produites dans le tracé auprès du ax.patchesmembre. Ensuite, vous pouvez utiliser la technique illustrée dans cet matplotlibexemple de galerie pour ajouter les étiquettes à l'aide de la ax.textméthode.

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

Cela produit un tracé étiqueté qui ressemble à:

entrez la description de l'image ici

Simon Gibbons
la source
Salut Simon! Tout d'abord, merci beaucoup d'avoir répondu! Deuxièmement, je suppose que je n'étais pas clair - je voulais montrer la valeur y. Je viens de remplacer les étiquettes dans le zip (,) par des fréquences. Maintenant, pourriez-vous s'il vous plaît jeter un peu plus de lumière sur la hache Fig Vs? Je suis confus. Une bonne expression / ressource de recherche serait également excellente, car elle est un peu générique pour une recherche goog. Très appréciée!
Optimesh
Une figure est une collection d'un ou plusieurs axes, par exemple dans cet exemple matplotlib.org/examples/statistics/ ... c'est une figure qui est composée de 4 axes différents.
Simon Gibbons
Merci encore. Pouvez-vous s'il vous plaît m'aider à comprendre les différences entre annoter et texte? Merci!
Optimesh
2
Les deux peuvent être utilisés pour ajouter du texte sur un tracé. textimprime simplement du texte sur le tracé, tandis que annotatec'est une aide que vous pouvez utiliser pour ajouter facilement une flèche à partir du texte pointant vers un point spécifique du tracé auquel le texte fait référence.
Simon Gibbons
10
Belle solution. J'ai écrit un article de blog qui s'appuie sur la solution ici et donne une version légèrement plus robuste qui s'échelonne en fonction de la hauteur de l'axe, donc le même code fonctionne pour différents tracés qui ont des hauteurs d'axe différentes: composition.al/blog/2015/ 11/29 /…
Lindsey Kuper
62

Sur la base d'une fonctionnalité mentionnée dans cette réponse à une autre question, j'ai trouvé une solution très généralement applicable pour placer des étiquettes sur un graphique à barres.

D'autres solutions ne fonctionnent malheureusement pas dans de nombreux cas, car l'espacement entre l'étiquette et la barre est soit donné en unités absolues des barres, soit mis à l'échelle par la hauteur de la barre . Le premier ne fonctionne que pour une plage étroite de valeurs et le second donne un espacement incohérent dans un graphique. Ni l'un ni l'autre ne fonctionne bien avec les axes logarithmiques.

La solution que je propose fonctionne indépendamment de l'échelle (ie pour les petits et grands nombres) et place même correctement les étiquettes pour les valeurs négatives et avec des échelles logarithmiques car elle utilise l'unité visuelle pointspour les décalages.

J'ai ajouté un nombre négatif pour montrer le placement correct des étiquettes dans un tel cas.

La valeur de la hauteur de chaque barre est utilisée comme étiquette pour celle-ci. D'autres étiquettes peuvent facilement être utilisées avec l' for rect, label in zip(rects, labels)extrait de Simon .

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

Edit: J'ai extrait la fonctionnalité pertinente dans une fonction, comme suggéré par barnhillec .

Cela produit la sortie suivante:

Diagramme à barres avec étiquettes placées automatiquement sur chaque barre

Et avec l'échelle logarithmique (et quelques ajustements aux données d'entrée pour présenter la mise à l'échelle logarithmique), voici le résultat:

Diagramme à barres avec échelle logarithmique avec étiquettes placées automatiquement sur chaque barre

justfortherec
la source
1
Réponse fantastique! Merci. Cela a parfaitement fonctionné avec les pandas dans le traçage de barres construit.
m4p85r
1
Amélioration suggérée: utilisez ax.annotate plutôt que plt.annotate. Ce changement permettrait à la routine entière d'être encapsulée dans une fonction à laquelle est passé un axe d'axe, qui peut ensuite être factorisé en une fonction utilitaire de tracé autonome utile.
barnhillec
@barnhillec, merci pour la suggestion. J'ai fait exactement cela dans mon montage. Notez que cela ne fonctionne actuellement qu'avec les graphiques à barres verticales et pas avec d'autres types de tracés (peut-être avec des histogrammes). Rendre la fonction plus générique la rendrait également plus difficile à comprendre et donc moins appropriée pour une réponse ici.
justfortherec
Réponse très robuste que d'autres que j'ai trouvées. Bien expliquer chaque ligne par un commentaire m'aide à assimiler toute la notion.
code_conundrum
31

En nous basant sur la (excellente!) Réponse ci-dessus, nous pouvons également créer un graphique à barres horizontales avec juste quelques ajustements:

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

diagramme à barres horizontales avec annotations

Oléon
la source
1
Pour la grille à afficher:freq_series.plot(kind='barh', grid=True)
sinapan
Fonctionne parfaitement même avec les graphiques à barres de groupe. Merci.
Prabah
Bien fait avec un graphique à barres horizontales!
code_conundrum
10

Si vous souhaitez simplement étiqueter les points de données au-dessus de la barre, vous pouvez utiliser plt.annotate ()

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

production

Eh bien, le texte avec plusieurs caractères peut être affiché légèrement décentré. Mais cela peut être surmonté en diminuant légèrement la coordonnée x dans le paramètre xy en fonction de la taille du texte

Ajay
la source
1
propre et simple
Ethan Yanjia Li
Pouvez-vous ajouter comment nous pouvons placer l'étiquette exactement au centre?
x89
0

Si vous souhaitez uniquement ajouter des points de données au-dessus des barres, vous pouvez facilement le faire avec:

 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
la source