138 votes

Python matplotlib barres multiples

Comment tracer des barres multiples dans matplotlib, lorsque j'ai essayé d'appeler la fonction bar plusieurs fois, elles se chevauchent et comme on peut le voir sur la figure ci-dessous la valeur la plus élevée rouge peut être vue seulement. Comment puis-je tracer les barres multiples avec les dates sur les axes x ?

Jusqu'à présent, j'ai essayé ceci :

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

J'ai eu ça :

enter image description here

Les résultats devraient ressembler à ceci, mais avec les dates sur les axes x et les barres les unes à côté des autres :

enter image description here

145voto

HYRY Points 26340
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()

enter image description here

Je ne sais pas ce que signifie "les valeurs y se chevauchent également", le code suivant résout-il votre problème ?

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

enter image description here

78voto

jozzas Points 3408

Le problème avec l'utilisation de dates comme valeurs x, c'est que si vous voulez un diagramme à barres comme dans votre deuxième image, elles seront fausses. Vous devez soit utiliser un diagramme à barres empilées (couleurs superposées), soit regrouper par date (une "fausse" date sur l'axe des x, ce qui revient à regrouper les points de données).

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

enter image description here

52voto

pascscha Points 336

Après avoir cherché une solution similaire et n'avoir rien trouvé d'assez souple, j'ai décidé d'écrire ma propre fonction pour cela. Elle vous permet d'avoir autant de barres par groupe que vous le souhaitez et de spécifier à la fois la largeur d'un groupe et les largeurs individuelles des barres dans les groupes.

Profitez-en :

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

Sortie :

enter image description here

45voto

liwt31 Points 98

Je sais que c'est à propos matplotlib mais en utilisant pandas y seaborn peut vous faire gagner beaucoup de temps :

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

enter image description here

5voto

Trenton_M Points 734
  • Compte tenu des réponses existantes, la solution la plus simple, étant donné les données dans l'OP, est de charger les données dans un cadre de données et de tracer avec pandas.DataFrame.plot .
    • Chargez les listes de valeurs dans pandas à l'aide d'un fichier dict et préciser x comme indice. L'index sera automatiquement défini comme l'axe des x, et les colonnes seront tracées comme les barres.
    • pandas.DataFrame.plot utilise matplotlib comme backend par défaut.
  • Testé dans 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')

enter image description here

  • Barres horizontales pour lorsqu'il y a plus de colonnes

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

enter image description here

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X