Python matplotlib plusieurs barres

92

Comment tracer plusieurs barres dans matplotlib, lorsque j'ai essayé d'appeler la fonction de barre plusieurs fois, elles se chevauchent et comme le montre la figure ci-dessous, la valeur rouge la plus élevée ne peut être vue que. Comment puis-je tracer les multiples barres avec des dates sur les axes des 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 ceci:

entrez la description de l'image ici

Les résultats devraient être quelque chose comme, mais avec les dates sont sur les axes des x et les barres sont côte à côte:

entrez la description de l'image ici

John Smith
la source
vous devez changer les valeurs x
jterrace
2
Que voulez-vous dire ? Les valeurs X sont des dates ...
John Smith
4
pourquoi n'est-ce pas simplement pris en charge par matplotlib?!
ihadanny

Réponses:

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

entrez la description de l'image ici

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

entrez la description de l'image ici

HYRY
la source
Merci, mais si j'ai 3 barres, ça a l'air bien. Quand j'essaye 40 bars, ça gâche. Pouvez-vous mettre à jour votre solution pour qu'elle soit plus évolutive?
John Smith
Définir «gâchis»? Le chevauchement des étiquettes X peut être corrigé en utilisant autofmt_xdate(), qui fait pivoter automatiquement les étiquettes.
John Lyon
Le problème n'est pas de chevaucher les étiquettes X, mais les valeurs y se chevauchent également. Comment le réparer ?
John Smith
et aussi la largeur = 0,2 est trop petite pour une longue période. Si j'utilise des valeurs plus grandes, je n'obtiens pas le même résultat.
John Smith
Une autre chose est que, les espaces au début et à la fin. Comment éliminer les espaces et commencer directement la première date et terminer de la même manière la dernière date sans espace ou moins d'espace.
John Smith
61

Le problème avec l'utilisation des dates comme valeurs x, c'est que si vous voulez un diagramme à barres comme dans votre deuxième image, ils vont être faux. Vous devez soit utiliser un graphique à barres empilées (les couleurs les unes sur les autres) ou grouper par date (une «fausse» date sur l'axe des x, regroupant simplement 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()

entrez la description de l'image ici

John Lyon
la source
si je veux montrer comme 100 jours sur les axes x, comment les ajustez-vous?
John Smith
1
Vous pouvez facilement générer les dates requises avec son numpy datetime64: par exemple une valeur d' un mois: np.arange('2012-02', '2012-03', dtype='datetime64[D]'). Vous devrez peut-être réfléchir davantage à la meilleure façon de représenter ces données si vous disposez de 40 ensembles de données (selon un autre commentaire) couvrant plus de 100 jours.
John Lyon
Et aussi, utiliser ax.xaxis_date () est très avantageux, car il ajuste vos dates dans les axes x.
John Smith
3
Pourquoi n'essayez-vous pas d'abord? J'essaie de vous aider à apprendre, pas d'écrire votre code pour vous. Je suis sûr que vous pouvez le faire avec, xaxis_datemais vous devrez adapter ce que j'ai écrit pour compenser vos valeurs de date (par exemple par un certain nombre d'heures d'utilisation timedelta) pour chaque série afin d'éviter qu'elles ne se chevauchent. L'autre réponse fait exactement cela, mais vous devrez peut-être vous débarrasser des étiquettes par la suite.
John Lyon
ok, mais quand j'exécute np.arange ('2012-02', '2012-03, dtype =' datetime64 [D] '), j'obtiens ceci: type (s) d'opérande non pris en charge pour -:' str 'et' str '
John Smith
25

Je sais qu'il s'agit de matplotlib, mais utiliser pandaset seabornpeut 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()

entrez la description de l'image ici

liwt31
la source
Excellente réponse mais elle est quelque peu incomplète en raison de l'axe des x. Pouvez-vous le rendre plus présentable?
Spinor8
Vous pourriez, je présume, faire aussi le sien avec des pandas et matplotlib
Vicki B
18

après avoir cherché une solution similaire et n'avoir rien trouvé d'assez flexible, j'ai décidé d'écrire ma propre fonction. Il vous permet d'avoir autant de barres par groupe que vous le souhaitez et de spécifier à la fois la largeur d'un groupe ainsi que les largeurs individuelles des barres au sein des groupes.

Prendre plaisir:

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

Production:

entrez la description de l'image ici

pascscha
la source
Comment pouvons-nous modifier cela pour ajouter des étiquettes à l'axe des x? Comme dans chaque groupe de bars?
x89
changer le xticksde l'intrigue, par exempleplt.xticks(range(5), ["one", "two", "three", "four", "five"])
pascscha
belle fonction, très utile, merci. La seule chose que j'ai changé est que je pense que la légende est plus facile si vous mettez simplement label = data.keys [i] dans l'appel barplot et que vous n'avez pas besoin de créer la liste des barres.
Adrian Tompkins
0

J'ai fait cette solution: si vous voulez tracer plus d'une parcelle dans une figure, assurez-vous avant de tracer les prochaines parcelles que vous avez réglé le droit matplotlib.pyplot.hold(True) d'ajouter d'autres parcelles.

Concernant les valeurs datetime sur l'axe X, une solution utilisant l'alignement des barres fonctionne pour moi. Lorsque vous créez un autre diagramme à barres avec matplotlib.pyplot.bar(), utilisez simplement align='edge|center'et définissez width='+|-distance'.

Lorsque vous définissez toutes les barres (tracés) à droite, vous verrez les barres bien.

Dave
la source
il semble qu'il matplotlib.pyplot.holdest obsolète depuis la v2.0, comme mentionné dans la documentation
engineervix