Python matplotlib várias barras

94

Como plotar várias barras em matplotlib, quando tentei chamar a função de barra várias vezes, elas se sobrepõem e, como pode ser visto na figura abaixo, o maior valor vermelho pode ser visto apenas. Como posso plotar as várias barras com datas nos eixos x?

Até agora, tentei isto:

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

Eu tenho esse:

insira a descrição da imagem aqui

Os resultados devem ser algo como, mas com as datas no eixo x e as barras próximas umas das outras:

insira a descrição da imagem aqui

John smith
fonte
você precisa alterar os valores x
jterrace
2
O que você quer dizer ? Os valores X são datas ...
John Smith
4
por que isso não é simplesmente suportado por matplotlib ?!
ihadanny

Respostas:

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

insira a descrição da imagem aqui

Não sei o que significa "os valores y também estão sobrepostos", o código a seguir resolve seu 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()

insira a descrição da imagem aqui

HYRY
fonte
Obrigado, mas se eu tiver 3 barras, parece bom. Quando tento umas 40 barras, dá errado. Você pode atualizar sua solução para ser mais escalonável?
John Smith
Definir "bagunça"? A sobreposição de rótulos X pode ser corrigida usando autofmt_xdate(), que gira automaticamente os rótulos.
John Lyon
O problema não é sobrepor os rótulos X, o problema é que os valores de y também se sobrepõem. Como corrigi-lo ?
John Smith
e também a largura = 0,2 é muito pequena para um grande intervalo de tempo. Se eu usar valores maiores, não obtenho o mesmo resultado.
John Smith
Outra coisa é isso, os espaços no início e no fim. Como eliminar os espaços e iniciar diretamente a primeira data e, da mesma forma, terminar a última data sem qualquer espaço ou menos espaço.
John Smith
61

O problema de usar datas como valores x é que, se você quiser um gráfico de barras como em sua segunda imagem, eles estarão errados. Você deve usar um gráfico de barras empilhadas (cores umas sobre as outras) ou agrupar por data (uma data "falsa" no eixo x, basicamente agrupando os pontos de dados).

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

insira a descrição da imagem aqui

John Lyon
fonte
se eu quiser mostrar como 100 dias nos eixos x, como você os ajusta?
John Smith
1
Você poderia facilmente gerar nas datas solicitadas, com numpy de datetime64: por exemplo, vale a pena Um mês: np.arange('2012-02', '2012-03', dtype='datetime64[D]'). Você pode precisar pensar mais sobre a melhor maneira de representar esses dados se tiver 40 conjuntos de dados (conforme outro comentário) abrangendo mais de 100 dias.
John Lyon
E também, usar ax.xaxis_date () é muito vantajoso, pois ele ajusta suas datas nos eixos x.
John Smith
3
Por que você não tenta primeiro? Estou tentando ajudá-lo a aprender, não a escrever seu código para você. Tenho certeza que você pode fazer isso, xaxis_datemas você precisará adaptar o que escrevi para compensar seus valores de data (por exemplo, por um número de horas usando timedelta) para cada série para evitar que se sobreponham. A outra resposta faz exatamente isso, mas você pode precisar mexer com os rótulos depois.
John Lyon
ok, mas quando executo np.arange ('2012-02', '2012-03, dtype =' datetime64 [D] '), recebo o seguinte: tipo (s) de operando não suportado (s) para -:' str 'e' str '
John Smith
25

Eu sei que se trata matplotlib, mas usar pandase seabornpode poupar muito tempo:

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

insira a descrição da imagem aqui

liwt31
fonte
Ótima resposta, mas está um tanto incompleta por causa do eixo x. Você pode torná-lo mais apresentável?
Spinor8 de
Você poderia, eu presumo, também fazer o dele com pandas e matplotlib
Vicki B
19

depois de procurar uma solução semelhante e não encontrar nada flexível o suficiente, decidi escrever minha própria função para ela. Ele permite que você tenha quantas barras por grupo desejar e especifique a largura de um grupo, bem como as larguras individuais das barras dentro dos grupos.

Apreciar:

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

Resultado:

insira a descrição da imagem aqui

pascscha
fonte
Como podemos modificar isso para adicionar rótulos ao eixo x? Como em cada grupo de bares?
x89
mude o xticksdo gráfico, por exemploplt.xticks(range(5), ["one", "two", "three", "four", "five"])
pascscha
boa função, muito útil, obrigado. A única coisa que mudei é que acho que a legenda é mais fácil se você apenas colocar label = data.keys [i] na chamada do barplot e, em seguida, você não precisa construir a lista de barras.
Adrian Tompkins
0

Eu fiz esta solução: se você quiser plotar mais de um gráfico em uma figura, certifique-se antes de plotar os próximos gráficos que você definiu corretamente matplotlib.pyplot.hold(True) para poder adicionar outros gráficos.

Em relação aos valores de data e hora no eixo X, uma solução usando o alinhamento de barras funciona para mim. Ao criar outro gráfico de barra com matplotlib.pyplot.bar(), basta usar align='edge|center'e definir width='+|-distance'.

Quando você definir todas as barras (plotagens) corretamente, verá que as barras estão bem.

Dave
fonte
parece que matplotlib.pyplot.holdestá obsoleto desde a v2.0, conforme mencionado nos documentos
engineervix