graficos - matplotlib python 3
Agregar etiquetas de valor en un gráfico de barras matplotlib (4)
Me quedé atrapado 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 tal como están.
Básicamente, estoy creando un gráfico de barras, y solo puedo descubrir cómo agregar etiquetas de valor en las barras (en el centro de la barra, o justo encima). He estado buscando muestras en la web pero sin éxito implementando en mi propio código. Creo que la solución es con ''texto'' o ''anotar'', pero yo: a) no sé cuál usar (y, en términos generales, no he descubierto cuándo usar qué). b) no puedo ver si ninguno de los dos presenta las etiquetas de valor. Agradecería su ayuda, mi código a continuación. ¡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)
Basado en una característica mencionada en esta respuesta a otra pregunta , he encontrado una solución muy generalmente aplicable para colocar etiquetas 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 por la altura de la barra . El primero solo funciona para un rango estrecho de valores y el segundo proporciona un espaciado inconsistente dentro de un gráfico. Ninguno de los dos 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 etiquetas 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 etiquetas en tal caso.
El valor de la altura de cada barra se utiliza como una etiqueta para ello.
Se pueden usar fácilmente otras etiquetas con
el fragmento
for 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")
Editar: he extraído la funcionalidad relevante en una función, como lo sugiere barnhillec .
Esto produce el siguiente resultado:
Y con la escala logarítmica (y algunos ajustes en los datos de entrada para mostrar la escala logarítmica), este es el resultado:
En primer lugar,
freq_series.plot
devuelve un eje,
no
una figura, por lo que para que mi respuesta sea un poco más clara, he cambiado su código dado para referirme a él como
ax
lugar de
fig
para ser más coherente con otros ejemplos de código.
Puede obtener la lista de las barras producidas en el diagrama del miembro
ax.patches
.
Luego puede usar la técnica demostrada en
este ejemplo de la galería
matplotlib
para agregar las etiquetas 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 un diagrama etiquetado que se ve así:
Partiendo de la respuesta anterior (¡genial!), También podemos hacer un diagrama de barras horizontales con solo unos pocos 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")
Si solo desea agregar puntos de datos sobre las barras, puede hacerlo fácilmente con:
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