Python matplotlib banyak batang

92

Cara memplot beberapa batang di matplotlib, ketika saya mencoba memanggil fungsi batang beberapa kali, mereka tumpang tindih dan seperti yang terlihat pada gambar di bawah ini, nilai tertinggi merah hanya dapat dilihat. Bagaimana cara memplot beberapa batang dengan tanggal di sumbu x?

Sejauh ini, saya mencoba ini:

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

Saya mengerti:

masukkan deskripsi gambar di sini

Hasilnya harus seperti ini, tetapi dengan tanggal berada di sumbu x dan batang bersebelahan:

masukkan deskripsi gambar di sini

John Smith
sumber
Anda perlu mengubah nilai x
jterrace
2
Apa maksudmu ? Nilai X adalah tanggal ...
John Smith
4
mengapa ini tidak hanya didukung oleh matplotlib ?!
ihadanny

Jawaban:

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

masukkan deskripsi gambar di sini

Saya tidak tahu apa artinya "nilai y juga tumpang tindih", apakah kode berikut menyelesaikan masalah Anda?

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

masukkan deskripsi gambar di sini

HYRY
sumber
Terima kasih, tetapi jika saya memiliki 3 batang, itu terlihat bagus. Ketika saya mencoba seperti 40 batang, itu kacau. Bisakah Anda memperbarui solusi Anda agar lebih skalabel?
John Smith
Tentukan "mengacaukan"? Label X yang tumpang tindih dapat diperbaiki dengan menggunakan autofmt_xdate(), yang memutar label secara otomatis.
John Lyon
Masalahnya adalah tidak tumpang tindih label X, masalahnya adalah nilai y juga tumpang tindih. Bagaimana memperbaikinya ?
John Smith
dan juga lebar = 0,2 terlalu kecil untuk rentang waktu yang lama. Jika saya menggunakan nilai yang lebih besar, saya tidak mendapatkan hasil yang sama.
John Smith
Hal lainnya adalah, ruang di awal dan akhir. Cara menghilangkan spasi dan langsung memulai tanggal pertama dan juga mengakhiri tanggal terakhir tanpa spasi atau kurang.
John Smith
61

Masalah dengan menggunakan tanggal sebagai nilai x, adalah jika Anda menginginkan diagram batang seperti di gambar kedua, hasilnya salah. Anda sebaiknya menggunakan diagram batang bertumpuk (warna di atas satu sama lain) atau mengelompokkan menurut tanggal (tanggal "palsu" pada sumbu x, pada dasarnya hanya mengelompokkan titik data).

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

masukkan deskripsi gambar di sini

John Lyon
sumber
jika saya ingin menunjukkan seperti 100 hari pada sumbu x, bagaimana Anda menyesuaikannya?
John Smith
1
Anda bisa dengan mudah menghasilkan tanggal yang Anda inginkan dengan numpy ini datetime64: misalnya satu bulan senilai: np.arange('2012-02', '2012-03', dtype='datetime64[D]'). Anda mungkin perlu berpikir lebih keras tentang cara terbaik untuk merepresentasikan data ini jika Anda memiliki 40 set data (sesuai komentar lainnya) yang mencakup lebih dari 100 hari.
John Lyon
Dan juga, menggunakan ax.xaxis_date () sangat menguntungkan, karena cocok dengan tanggal Anda ke sumbu x.
John Smith
3
Mengapa Anda tidak mencoba dulu? Saya mencoba membantu Anda belajar, bukan menulis kode untuk Anda. Saya yakin Anda dapat melakukannya xaxis_datetetapi Anda harus menyesuaikan apa yang telah saya tulis untuk mengimbangi nilai tanggal Anda (misalnya dengan menggunakan beberapa jam timedelta) untuk setiap seri agar tidak tumpang tindih. Jawaban lain hanya melakukan ini, tetapi Anda mungkin perlu mengosongkan label sesudahnya.
John Lyon
ok, tetapi ketika saya menjalankan np.arange ('2012-02', '2012-03, dtype =' datetime64 [D] '), saya mendapatkan ini: jenis operan yang tidak didukung untuk -:' str 'and' str '
John Smith
25

Saya tahu ini tentang matplotlib, tetapi menggunakan pandasdan seaborndapat menghemat banyak waktu:

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

masukkan deskripsi gambar di sini

liwt31
sumber
Jawaban yang bagus tetapi agak tidak lengkap karena sumbu x. Bisakah Anda membuatnya lebih rapi?
Spinor8
Anda bisa, saya kira, juga melakukannya dengan panda dan matplotlib
Vicki B
18

setelah mencari solusi serupa dan tidak menemukan sesuatu yang cukup fleksibel, saya memutuskan untuk menulis fungsi saya sendiri untuk itu. Ini memungkinkan Anda untuk memiliki batang per grup sebanyak yang Anda inginkan dan menentukan lebar grup serta lebar individu batang di dalam grup.

Nikmati:

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

Keluaran:

masukkan deskripsi gambar di sini

pascscha
sumber
Bagaimana kita bisa memodifikasi ini untuk menambahkan label ke sumbu x? Bagaimana dengan setiap kelompok bar?
x89
ubah xticksplot, misalnyaplt.xticks(range(5), ["one", "two", "three", "four", "five"])
pascscha
fungsi bagus, sangat membantu, terima kasih. Satu-satunya hal yang saya ubah adalah menurut saya legenda lebih mudah jika Anda hanya meletakkan label = data.keys [i] di panggilan barplot dan kemudian Anda tidak perlu membuat daftar batang.
Adrian Tompkins
0

Saya melakukan solusi ini: jika Anda ingin plot lebih dari satu plot dalam satu gambar, pastikan sebelum plot plot berikutnya Anda telah mengatur hak matplotlib.pyplot.hold(True) untuk dapat menambahkan plot lain.

Mengenai nilai datetime pada sumbu X, solusi yang menggunakan penyelarasan batang berfungsi untuk saya. Saat Anda membuat plot batang lain dengan matplotlib.pyplot.bar(), cukup gunakan align='edge|center'dan atur width='+|-distance'.

Jika Anda menyetel semua batang (plot) dengan benar, Anda akan melihat batangnya baik-baik saja.

Dave
sumber
sepertinya matplotlib.pyplot.holdsudah tidak digunakan lagi sejak v2.0, seperti yang disebutkan di dokumen
engineervix