Adición de tags de valor en un gráfico de barras de matplotlib

Me quedé atascado en algo que parece que debería ser relativamente fácil. El código que traigo a continuación es una muestra basada en un proyecto más grande en el que estoy trabajando. No vi ninguna razón para publicar todos los detalles, así que acepte las estructuras de datos que traigo como están.

Básicamente, estoy creando un gráfico de barras, y solo puedo averiguar cómo agregar tags de valor en las barras (en el centro de la barra, o justo encima de ella). He estado buscando muestras en la web pero sin éxito implementando mi propio código. Creo que la solución es con ‘texto’ o ‘anotar’, pero yo: a) no sé cuál usar (y, en general, no he averiguado cuándo usar cuál). b) no se puede ver para conseguir presentar las tags de valor. Agradecería su ayuda, mi código de abajo. ¡Gracias por adelantado!

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) 

En primer lugar, freq_series.plot devuelve un eje, no una figura, así que para que mi respuesta sea un poco más clara, he cambiado el código dado para referirme a él como ax lugar de fig para que sea más consistente con otros ejemplos de código.

Puede obtener la lista de las barras producidas en el gráfico desde el miembro ax.patches . Luego, puede usar la técnica que se muestra en este ejemplo de la galería matplotlib para agregar las tags usando el método 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') 

Esto produce una ttwig etiquetada que se parece a:

introduzca la descripción de la imagen aquí

En base a una característica mencionada en esta respuesta a otra pregunta , he encontrado una solución de aplicación muy general para colocar tags en un gráfico de barras.

Desafortunadamente, otras soluciones no funcionan en muchos casos, porque el espacio entre la etiqueta y la barra se da en unidades absolutas de las barras o se escala según la altura de la barra . Lo primero solo funciona para un rango estrecho de valores y lo segundo da un espacio inconsistente dentro de una gráfica. Tampoco funciona bien con ejes logarítmicos.

La solución que propongo funciona independientemente de la escala (es decir, para números pequeños y grandes) e incluso coloca correctamente las tags para valores negativos y con escalas logarítmicas porque utiliza los points unidad visual para las compensaciones.

He agregado un número negativo para mostrar la ubicación correcta de las tags en ese caso.

El valor de la altura de cada barra se utiliza como una etiqueta para ello. Otras tags se pueden usar fácilmente con Simon’s for rect, label in zip(rects, labels) snippet .

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

Edición: he extraído la funcionalidad relevante en una función, según lo sugerido por barnhillec .

Esto produce el siguiente resultado:

Gráfico de barras con etiquetas colocadas automáticamente en cada barra

Y con la escala logarítmica (y algunos ajustes a los datos de entrada para mostrar el escalamiento logarítmico), este es el resultado:

Gráfico de barras con escala logarítmica con etiquetas colocadas automáticamente en cada barra

Partiendo de la respuesta anterior (¡genial!), También podemos hacer una gráfica de barras horizontales con solo algunos ajustes:

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

gráfico de barras horizontales con anotaciones