John Smith
Cómo trazar múltiples barras en matplotlib, cuando traté de llamar a la función de barra varias veces, se superponen y, como se ve en la figura a continuación, solo se puede ver el valor rojo más alto. ¿Cómo puedo trazar las barras múltiples con fechas en los ejes x?
Hasta ahora, probé esto:
import matplotlib.pyplot as plt
import datetime
x = [
datetime.datetime(2011, 1, 4, 0, 0),
datetime.datetime(2011, 1, 5, 0, 0),
datetime.datetime(2011, 1, 6, 0, 0)
]
y = [4, 9, 2]
z = [1, 2, 3]
k = [11, 12, 13]
ax = plt.subplot(111)
ax.bar(x, y, width=0.5, color="b", align='center')
ax.bar(x, z, width=0.5, color="g", align='center')
ax.bar(x, k, width=0.5, color="r", align='center')
ax.xaxis_date()
plt.show()
Tengo esto:
Los resultados deberían ser algo así, pero con las fechas en los ejes x y las barras una al lado de la otra:
HYRY
import matplotlib.pyplot as plt
from matplotlib.dates import date2num
import datetime
x = [
datetime.datetime(2011, 1, 4, 0, 0),
datetime.datetime(2011, 1, 5, 0, 0),
datetime.datetime(2011, 1, 6, 0, 0)
]
x = date2num(x)
y = [4, 9, 2]
z = [1, 2, 3]
k = [11, 12, 13]
ax = plt.subplot(111)
ax.bar(x-0.2, y, width=0.2, color="b", align='center')
ax.bar(x, z, width=0.2, color="g", align='center')
ax.bar(x+0.2, k, width=0.2, color="r", align='center')
ax.xaxis_date()
plt.show()
No sé qué significa “los valores y también se superponen”, ¿el siguiente código resuelve su problema?
ax = plt.subplot(111)
w = 0.3
ax.bar(x-w, y, width=w, color="b", align='center')
ax.bar(x, z, width=w, color="g", align='center')
ax.bar(x+w, k, width=w, color="r", align='center')
ax.xaxis_date()
ax.autoscale(tight=True)
plt.show()
El problema con el uso de fechas como valores x es que si desea un gráfico de barras como en su segunda imagen, estarán equivocados. Debe usar un gráfico de barras apiladas (colores uno encima del otro) o agrupar por fecha (una fecha “falsa” en el eje x, básicamente agrupando los puntos de datos).
import numpy as np
import matplotlib.pyplot as plt
N = 3
ind = np.arange(N) # the x locations for the groups
width = 0.27 # the width of the bars
fig = plt.figure()
ax = fig.add_subplot(111)
yvals = [4, 9, 2]
rects1 = ax.bar(ind, yvals, width, color="r")
zvals = [1,2,3]
rects2 = ax.bar(ind+width, zvals, width, color="g")
kvals = [11,12,13]
rects3 = ax.bar(ind+width*2, kvals, width, color="b")
ax.set_ylabel('Scores')
ax.set_xticks(ind+width)
ax.set_xticklabels( ('2011-Jan-4', '2011-Jan-5', '2011-Jan-6') )
ax.legend( (rects1[0], rects2[0], rects3[0]), ('y', 'z', 'k') )
def autolabel(rects):
for rect in rects:
h = rect.get_height()
ax.text(rect.get_x()+rect.get_width()/2., 1.05*h, '%d'%int(h),
ha="center", va="bottom")
autolabel(rects1)
autolabel(rects2)
autolabel(rects3)
plt.show()
después de buscar una solución similar y no encontrar nada lo suficientemente flexible, decidí escribir mi propia función para ella. Le permite tener tantas barras por grupo como desee y especificar tanto el ancho de un grupo como el ancho individual de las barras dentro de los grupos.
Disfrutar:
from matplotlib import pyplot as plt
def bar_plot(ax, data, colors=None, total_width=0.8, single_width=1, legend=True):
"""Draws a bar plot with multiple bars per data point.
Parameters
----------
ax : matplotlib.pyplot.axis
The axis we want to draw our plot on.
data: dictionary
A dictionary containing the data we want to plot. Keys are the names of the
data, the items is a list of the values.
Example:
data = {
"x":[1,2,3],
"y":[1,2,3],
"z":[1,2,3],
}
colors : array-like, optional
A list of colors which are used for the bars. If None, the colors
will be the standard matplotlib color cyle. (default: None)
total_width : float, optional, default: 0.8
The width of a bar group. 0.8 means that 80% of the x-axis is covered
by bars and 20% will be spaces between the bars.
single_width: float, optional, default: 1
The relative width of a single bar within a group. 1 means the bars
will touch eachother within a group, values less than 1 will make
these bars thinner.
legend: bool, optional, default: True
If this is set to true, a legend will be added to the axis.
"""
# Check if colors where provided, otherwhise use the default color cycle
if colors is None:
colors = plt.rcParams['axes.prop_cycle'].by_key()['color']
# Number of bars per group
n_bars = len(data)
# The width of a single bar
bar_width = total_width / n_bars
# List containing handles for the drawn bars, used for the legend
bars = []
# Iterate over all data
for i, (name, values) in enumerate(data.items()):
# The offset in x direction of that bar
x_offset = (i - n_bars / 2) * bar_width + bar_width / 2
# Draw a bar for every value of that type
for x, y in enumerate(values):
bar = ax.bar(x + x_offset, y, width=bar_width * single_width, color=colors[i % len(colors)])
# Add a handle to the last drawn bar, which we'll need for the legend
bars.append(bar[0])
# Draw legend if we need
if legend:
ax.legend(bars, data.keys())
if __name__ == "__main__":
# Usage example:
data = {
"a": [1, 2, 3, 2, 1],
"b": [2, 3, 4, 3, 1],
"c": [3, 2, 1, 4, 2],
"d": [5, 9, 2, 1, 8],
"e": [1, 3, 2, 2, 3],
"f": [4, 3, 1, 1, 4],
}
fig, ax = plt.subplots()
bar_plot(ax, data, total_width=.8, single_width=.9)
plt.show()
Producción:
-
¿Cómo podemos modificar esto para agregar etiquetas al eje x? ¿Como en cada grupo de barras?
– x89
2 de agosto de 2020 a las 18:02
-
cambiar el
xticks
de la trama, por ejemploplt.xticks(range(5), ["one", "two", "three", "four", "five"])
– pascscha
3 de agosto de 2020 a las 11:59
-
buena función, muy útil, gracias. Lo único que cambié es que creo que la leyenda es más fácil si solo pones label=data.keys[i] en la llamada de gráfico de barras y luego no necesita construir la lista de barras.
– Adrián Tompkins
31 oct 2020 a las 15:53
-
Este es un ejemplo increíblemente sucinto que responde a la pregunta formulada, realmente me gusta el uso de una función aquí. +1
– TornadoEric
17 de diciembre de 2021 a las 4:10
-
@pascscha, ¿hay alguna manera de mantener fijo el ancho para que, si tengo muchas, las barras no se vuelvan muy pequeñas? Entiendo que esto significa que el diagrama de barras no cabrá en mi pantalla, así que me pregunto si es posible hacer que la GUI tenga una barra de desplazamiento o tal vez solo para guardar la imagen y el visor de imágenes tendrá una barra de desplazamiento.
– usuario3494047
5 de enero de 2022 a las 15:49
Sé que se trata de matplotlib
pero utilizando pandas
y seaborn
puede ahorrarle mucho tiempo:
df = pd.DataFrame(zip(x*3, ["y"]*3+["z"]*3+["k"]*3, y+z+k), columns=["time", "kind", "data"])
plt.figure(figsize=(10, 6))
sns.barplot(x="time", hue="kind", y="data", data=df)
plt.show()
trenton mckinney
- Dadas las respuestas existentes, la solución más fácil, dados los datos en el OP, es cargar los datos en un marco de datos y trazar con
pandas.DataFrame.plot
.- Cargue las listas de valores en pandas con un
dict
y especificarx
como índice. El índice se establecerá automáticamente como el eje x y las columnas se trazarán como las barras. pandas.DataFrame.plot
usosmatplotlib
como back-end predeterminado.
- Cargue las listas de valores en pandas con un
- Consulte Cómo agregar etiquetas de valor en un gráfico de barras para obtener detalles completos sobre el uso
.bar_label
. - Probado en
python 3.8.11
,pandas 1.3.2
,matplotlib 3.4.3
import pandas as pd
# using the existing lists from the OP, create the dataframe
df = pd.DataFrame(data={'y': y, 'z': z, 'k': k}, index=x)
# since there's no time component and x was a datetime dtype, set the index to be just the date
df.index = df.index.date
# display(df)
y z k
2011-01-04 4 1 11
2011-01-05 9 2 12
2011-01-06 2 3 13
# plot bars or kind='barh' for horizontal bars; adjust figsize accordingly
ax = df.plot(kind='bar', rot=0, xlabel="Date", ylabel="Value", title="My Plot", figsize=(6, 4))
# add some labels
for c in ax.containers:
# set the bar label
ax.bar_label(c, fmt="%.0f", label_type="edge")
# add a little space at the top of the plot for the annotation
ax.margins(y=0.1)
# move the legend out of the plot
ax.legend(title="Columns", bbox_to_anchor=(1, 1.02), loc="upper left")
- Barras horizontales para cuando hay más columnas
ax = df.plot(kind='barh', ylabel="Date", title="My Plot", figsize=(5, 4))
ax.set(xlabel="Value")
for c in ax.containers:
# set the bar label
ax.bar_label(c, fmt="%.0f", label_type="edge")
ax.margins(x=0.1)
# move the legend out of the plot
ax.legend(title="Columns", bbox_to_anchor=(1, 1.02), loc="upper left")
-
Trenton McKinney, su respuesta es, con mucho, la mejor porque utiliza una funcionalidad que está disponible a través de una biblioteca, por lo que no es necesario escribir código complejo. ¡Bien hecho!
– ouba64
16 dic 2021 a las 15:11
fr_andres
Modifiqué la solución de pascscha extendiendo la interfaz, ¡espero que esto ayude a alguien más! Características clave:
- Número variable de entradas por grupo de barras
- Colores personalizables
- Manejo de x ticks
- Etiquetas de barras totalmente personalizables en la parte superior de las barras
def bar_plot(ax, data, group_stretch=0.8, bar_stretch=0.95,
legend=True, x_labels=True, label_fontsize=8,
colors=None, barlabel_offset=1,
bar_labeler=lambda k, i, s: str(round(s, 3))):
"""
Draws a bar plot with multiple bars per data point.
:param dict data: The data we want to plot, wher keys are the names of each
bar group, and items is a list of bar values for the corresponding group.
:param float group_stretch: 1 means groups occupy the most (largest groups
touch side to side if they have equal number of bars).
:param float bar_stretch: If 1, bars within a group will touch side to side.
:param bool x_labels: If true, x-axis will contain labels with the group
names given at data, centered at the bar group.
:param int label_fontsize: Font size for the label on top of each bar.
:param float barlabel_offset: Distance, in y-values, between the top of the
bar and its label.
:param function bar_labeler: If not None, must be a functor with signature
``f(group_name, i, scalar)->str``, where each scalar is the entry found at
data[group_name][i]. When given, returns a label to put on the top of each
bar. Otherwise no labels on top of bars.
"""
sorted_data = list(sorted(data.items(), key=lambda elt: elt[0]))
sorted_k, sorted_v = zip(*sorted_data)
max_n_bars = max(len(v) for v in data.values())
group_centers = np.cumsum([max_n_bars
for _ in sorted_data]) - (max_n_bars / 2)
bar_offset = (1 - bar_stretch) / 2
bars = defaultdict(list)
#
if colors is None:
colors = {g_name: [f"C{i}" for _ in values]
for i, (g_name, values) in enumerate(data.items())}
#
for g_i, ((g_name, vals), g_center) in enumerate(zip(sorted_data,
group_centers)):
n_bars = len(vals)
group_beg = g_center - (n_bars / 2) + (bar_stretch / 2)
for val_i, val in enumerate(vals):
bar = ax.bar(group_beg + val_i + bar_offset,
height=val, width=bar_stretch,
color=colors[g_name][val_i])[0]
bars[g_name].append(bar)
if bar_labeler is not None:
x_pos = bar.get_x() + (bar.get_width() / 2.0)
y_pos = val + barlabel_offset
barlbl = bar_labeler(g_name, val_i, val)
ax.text(x_pos, y_pos, barlbl, ha="center", va="bottom",
fontsize=label_fontsize)
if legend:
ax.legend([bars[k][0] for k in sorted_k], sorted_k)
#
ax.set_xticks(group_centers)
if x_labels:
ax.set_xticklabels(sorted_k)
else:
ax.set_xticklabels()
return bars, group_centers
Ejemplo de ejecución:
fig, ax = plt.subplots()
data = {"Foo": [1, 2, 3, 4], "Zap": [0.1, 0.2], "Quack": [6], "Bar": [1.1, 2.2, 3.3, 4.4, 5.5]}
bar_plot(ax, data, group_stretch=0.8, bar_stretch=0.95, legend=True,
labels=True, label_fontsize=8, barlabel_offset=0.05,
bar_labeler=lambda k, i, s: str(round(s, 3)))
fig.show()
-
Trenton McKinney, su respuesta es, con mucho, la mejor porque utiliza una funcionalidad que está disponible a través de una biblioteca, por lo que no es necesario escribir código complejo. ¡Bien hecho!
– ouba64
16 dic 2021 a las 15:11
Adrián Tompkins
Hice esta solución: si desea trazar más de un gráfico en una figura, asegúrese de que antes de trazar los siguientes gráficos haya configurado correctamente matplotlib.pyplot.hold(True)
poder añadir otras parcelas.
Con respecto a los valores de fecha y hora en el eje X, una solución que utiliza la alineación de barras funciona para mí. Cuando crea otro gráfico de barras con matplotlib.pyplot.bar()
Solo usa align='edge|center'
y establecer width="+|-distance"
.
Cuando configure todas las barras (gráficos) correctamente, verá las barras bien.
-
parece que
matplotlib.pyplot.hold
ha quedado en desuso desde v2.0, ya que mencionado en los documentos– ingenierovix
1 de noviembre de 2020 a las 15:46