web-dev-qa-db-fra.com

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

Je me suis retrouvé 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 ne voyais aucune raison d'afficher tous les détails, acceptez donc les structures de données que j'apporte en l'état.

Fondamentalement, je crée un graphique à barres et je peux juste comprendre comment ajouter des étiquettes de valeur sur les barres (au centre de la barre ou juste au-dessus). Je cherche des exemples sur le Web, mais sans succès avec mon propre code. Je crois que la solution est soit avec 'text' soit 'annotate', mais je: a) ne sais pas lequel utiliser (et d'une manière générale, je n'ai pas compris quand utiliser lequel). b) ne peut pas voir non plus 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)
69
Optimesh

Tout d'abord _freq_series.plot_ renvoie un axe et non un chiffre afin de clarifier ma réponse, j'ai changé votre code pour y faire référence comme ax plutôt que fig pour être plus cohérent avec d'autres exemples de code.

Vous pouvez obtenir la liste des barres produites dans le graphique à partir du membre _ax.patches_. Vous pouvez ensuite utiliser la technique présentée dans cet exemple de galerie matplotlib pour ajouter les étiquettes à l'aide de la méthode 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')
_

Cela produit un graphique étiqueté qui ressemble à:

enter image description here

98
Simon Gibbons

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

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

La solution que je propose fonctionne indépendamment de l’échelle (c’est-à-dire pour les petits et les grands nombres) et place même correctement les étiquettes pour les valeurs négatives et avec les échelles logarithmiques, car elle utilise l’unité visuelle points pour les décalages.

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

La valeur de la hauteur de chaque barre sert d’étiquette. D'autres étiquettes peuvent facilement être utilisées avec extrait de for rect, label in Zip(rects, labels) 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 les fonctionnalités pertinentes d'une fonction, comme suggéré par barnhillec .

Cela produit la sortie suivante:

Bar chart with automatically placed labels on each bar

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

Bar chart with logarithmic scale with automatically placed labels on each bar

33
justfortherec

En nous appuyant sur la réponse ci-dessus (très bien!), Nous pouvons également créer un tracé graphique en 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")

horizontal bar plot with annotations

18
oleson

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
0
user11638654