Python matplotlib wiele pasków

92

Jak wykreślić wiele słupków w matplotlib, kiedy próbowałem wielokrotnie wywołać funkcję słupka, nakładają się one i jak widać na poniższym rysunku, najwyższa wartość jest czerwona. Jak narysować wiele słupków z datami na osiach X?

Do tej pory próbowałem tego:

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

Mam to:

wprowadź opis obrazu tutaj

Wyniki powinny wyglądać mniej więcej tak, ale z datami są na osiach x, a słupki obok siebie:

wprowadź opis obrazu tutaj

John Smith
źródło
musisz zmienić wartości x
jterrace
2
Co masz na myśli ? Wartości X to daty ...
John Smith
4
dlaczego nie jest to po prostu obsługiwane przez matplotlib ?!
ihadanny

Odpowiedzi:

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

wprowadź opis obrazu tutaj

Nie wiem, co oznacza „wartości y również się nakładają”, czy poniższy kod rozwiązuje problem?

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

wprowadź opis obrazu tutaj

HYRY
źródło
Dzięki, ale jeśli mam 3 paski, to wygląda dobrze. Kiedy próbuję 40 taktów, to się psuje. Czy możesz zaktualizować swoje rozwiązanie, aby było bardziej skalowalne?
John Smith
Zdefiniować „bałagan”? Nakładanie się etykiet X można naprawić za pomocą autofmt_xdate(), która automatycznie obraca etykiety.
John Lyon
Problem nie polega na nakładaniu się etykiet X, chodzi o to, że wartości y również się nakładają. Jak to naprawić ?
John Smith
a także szerokość = 0,2 jest zbyt mała dla dużego przedziału czasu. Jeśli użyję większych wartości, nie uzyskam tego samego wyniku.
John Smith
Inną rzeczą jest to, że spacje na początku i na końcu. Jak usunąć spacje i rozpocząć od razu pierwszą datę i podobnie zakończyć ostatnią datę bez spacji lub mniej spacji.
John Smith
61

Problem z używaniem dat jako wartości x polega na tym, że jeśli chcesz mieć wykres słupkowy taki jak na drugim zdjęciu, będą one błędne. Należy użyć skumulowanego wykresu słupkowego (kolory nakładające się na siebie) lub grupować według daty („fałszywa” data na osi X, w zasadzie tylko grupowanie punktów danych).

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

wprowadź opis obrazu tutaj

John Lyon
źródło
jeśli chcę pokazać 100 dni na osiach x, jak je dopasujesz?
John Smith
1
Można łatwo wygenerować daty wymagany z numpy na datetime64: Np jednomiesięcznej wartości: np.arange('2012-02', '2012-03', dtype='datetime64[D]'). Być może będziesz musiał dokładniej przemyśleć najlepszy sposób przedstawienia tych danych, jeśli masz 40 zestawów danych (jak w innym komentarzu) obejmujących ponad 100 dni.
John Lyon
A także, użycie ax.xaxis_date () jest bardzo korzystne, ponieważ dopasowuje twoje daty do osi x.
John Smith
3
Dlaczego nie spróbujesz najpierw? Próbuję pomóc Ci się uczyć, a nie napisać kod za Ciebie. Jestem pewien, że możesz to zrobić, xaxis_dateale musisz dostosować to, co napisałem, aby przesunąć wartości dat (np. O liczbę godzin używania timedelta) dla każdej serii, aby zapobiec ich nakładaniu się. Druga odpowiedź właśnie to robi, ale być może później będziesz musiał pogrzebać z etykietami.
John Lyon
ok, ale kiedy uruchamiam np.arange ('2012-02', '2012-03, dtype =' datetime64 [D] '), otrzymuję to: nieobsługiwane typy operandów dla -:' str 'i' str '
John Smith
25

Wiem, że o to chodzi matplotlib, ale używanie pandasi seabornpozwala zaoszczędzić sporo czasu:

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

wprowadź opis obrazu tutaj

liwt31
źródło
Świetna odpowiedź, ale jest trochę niekompletna ze względu na oś X. Czy możesz uczynić to bardziej reprezentacyjnym?
Spinor8
Przypuszczam, że mógłbyś też zrobić jego z pandami i matplotlib
Vicki B
18

szukając podobnego rozwiązania i nie znajdując niczego wystarczająco elastycznego, postanowiłem napisać dla niego własną funkcję. Pozwala na utworzenie dowolnej liczby słupków w grupie i określenie zarówno szerokości grupy, jak i indywidualnych szerokości słupków w ramach grup.

Cieszyć się:

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

Wynik:

wprowadź opis obrazu tutaj

pascscha
źródło
Jak możemy to zmodyfikować, aby dodać etykiety do osi X? Jak w każdej grupie barów?
x89
zmienić xticksdziałkę, np.plt.xticks(range(5), ["one", "two", "three", "four", "five"])
pascscha
fajna funkcja, bardzo pomocna, dzięki. Jedyną rzeczą, którą zmieniłem, jest to, że myślę, że legenda jest łatwiejsza, jeśli po prostu wstawisz label = data.keys [i] w wywołaniu barplot, a następnie nie musisz tworzyć listy słupków.
Adrian Tompkins
0

Zrobiłem to rozwiązanie: jeśli chcesz wykreślić więcej niż jedną działkę na jednym rysunku, upewnij się, że przed wykreśleniem kolejnych wykresów ustawiłeś prawo matplotlib.pyplot.hold(True) do dodawania kolejnych wykresów.

Jeśli chodzi o wartości daty i godziny na osi X, rozwiązanie wykorzystujące wyrównanie słupków działa dla mnie. Kiedy tworzysz kolejny wykres słupkowy za pomocą matplotlib.pyplot.bar(), po prostu użyj align='edge|center'i ustaw width='+|-distance'.

Kiedy ustawisz wszystkie słupki (wykresy) w prawo, zobaczysz słupki w porządku.

Dave
źródło
wygląda na to, że matplotlib.pyplot.holdzostała wycofana od wersji 2.0, jak wspomniano w dokumentacji
engineervix