Stacked bar pada chapter sebelumnya menggabungkan dua variabel dalam satu batang: hari dan jenis minuman. Data nyata jarang berhenti di dua variabel — satu tabel menu bisa memuat harga, jumlah terjual, jenis, dan rating sekaligus — dan memaksakan semuanya ke satu grafik sederhana justru membuat temuan penting tersembunyi. Pada chapter ini kita memperluas kotak peralatan visualisasi ke lebih dari dua variabel: warna dan ukuran titik pada scatter plot, dua seri batang berdampingan, heatmap untuk matriks, serta panel kecil yang diulang per kategori.
Warna dan Ukuran Titik: Dua Variabel Tambahan pada Scatter Plot #
Scatter plot dasar memuat dua variabel: sumbu mendatar dan sumbu tegak. Dua saluran persepsi masih tersedia untuk memuat variabel lain — warna titik untuk kategori, dan besar titik untuk nilai numerik — sehingga satu bidang mampu memuat empat variabel sekaligus. Data latihan kita adalah rekap delapan menu Toko Kopi Kita:
import pandas as pd
menu = pd.DataFrame({
"nama": ["es kopi susu", "kopi susu gula aren", "kopi tubruk",
"cappuccino", "matcha latte", "es teh melati",
"cokelat panas", "teh tawar"],
"jenis": ["kopi", "kopi", "kopi", "kopi",
"non-kopi", "non-kopi", "non-kopi", "non-kopi"],
"harga": [22, 20, 12, 24, 24, 10, 20, 8],
"terjual": [182, 121, 95, 88, 76, 64, 58, 42],
"rating": [4.8, 4.7, 4.4, 4.6, 4.3, 4.0, 4.5, 3.9],
})
print(menu.to_string(index=False))
print()
print("Korelasi harga vs terjual:", round(menu["harga"].corr(menu["terjual"]), 2))Output yang Dihasilkan:
nama jenis harga terjual rating
es kopi susu kopi 22 182 4.8
kopi susu gula aren kopi 20 121 4.7
kopi tubruk kopi 12 95 4.4
cappuccino kopi 24 88 4.6
matcha latte non-kopi 24 76 4.3
es teh melati non-kopi 10 64 4.0
cokelat panas non-kopi 20 58 4.5
teh tawar non-kopi 8 42 3.9
Korelasi harga vs terjual: 0.46Mari kita telaah baris-barisnya:
- Baris 3-12 (
pd.DataFrame({...})): delapan menu dengan lima kolom — bentuk “satu baris satu menu” yang siap digambar; pada analisis nyata tabel seperti ini adalah hasil agregasigroupby()dari tabel transaksi. - Baris 14 (
to_string(index=False)): tabel dicetak tanpa indeks agar lima kolomnya terbaca dalam lebar halaman. - Baris 16 (
corr()): korelasi harga dengan penjualan bernilai 0,46 — positif tetapi lemah, pertanda ada struktur lain yang mengatur penjualan selain harga.
Struktur lain itu terlihat begitu jenis minuman kita angkat sebagai warna dan rating sebagai ukuran titik:
import matplotlib.pyplot as plt
import pandas as pd
menu = pd.DataFrame({
"nama": ["es kopi susu", "kopi susu gula aren", "kopi tubruk",
"cappuccino", "matcha latte", "es teh melati",
"cokelat panas", "teh tawar"],
"jenis": ["kopi", "kopi", "kopi", "kopi",
"non-kopi", "non-kopi", "non-kopi", "non-kopi"],
"harga": [22, 20, 12, 24, 24, 10, 20, 8],
"terjual": [182, 121, 95, 88, 76, 64, 58, 42],
"rating": [4.8, 4.7, 4.4, 4.6, 4.3, 4.0, 4.5, 3.9],
})
warna = {"kopi": "tab:brown", "non-kopi": "tab:green"}
fig, ax = plt.subplots(figsize=(6, 4))
for jenis, bagian in menu.groupby("jenis"):
ax.scatter(bagian["harga"], bagian["terjual"],
s=(bagian["rating"] - 3.5) * 150,
color=warna[jenis], label=jenis, alpha=0.8,
edgecolor="white")
ax.set_title("Harga, Penjualan, dan Rating Delapan Menu")
ax.set_xlabel("Harga (ribu rupiah)")
ax.set_ylabel("Gelas terjual sepekan")
ax.legend(title="Jenis minuman")
plt.show()Gambar yang muncul memuat delapan titik dalam dua warna: titik cokelat (minuman kopi) dan titik hijau (non-kopi). Kelompok cokelat menempati kiri atas — es kopi susu (harga 22, terjual 182) berdiri paling tinggi — sedangkan kelompok hijau berbaris di kanan bawah dengan penjualan di bawah 80 gelas. Korelasi harga-penjualan yang tadinya tampak lemah ternyata adalah dua pola berbeda: menu kopi murah laris, menu non-kopi mahal pun penjualannya rendah. Titik es kopi susu juga paling gemuk karena ratingnya tertinggi (4,8); teh tawar (3,9) menjadi titik terkecil.
Tiga keputusan teknisnya:
- Baris 18-22 (loop
groupby("jenis")): titik digambar per kelompok jenis agar tiap kelompok mendapat warna sendiri sekaligus masuk ke legend secara otomatis lewatlabel=jenis. - Baris 20 (
s=(bagian["rating"] - 3.5) * 150): ukuran titik dihitung dari rating — 3,9 menghasilkan titik seluas 60 poin persegi, 4,8 menghasilkan 195; pengurangan 3,5 menjaga titik berrating rendah tetap terlihat, bukan nyaris nol. - Baris 21-22 (
alpha=0.8,edgecolor="white"): transparansi ringan dan garis tepi putih membuat titik yang berhimpit tetap terbaca batasnya.
Grouped Bar: Menyandingkan Dua Seri #
Ketika tiap kategori memiliki dua angka yang sejenis — pekan lalu dan pekan ini, dua cabang toko, target dan realisasi — grouped bar menaruh dua batang berdampingan dalam satu kategori. Perbandingan antar seri menjadi perbandingan panjang batang yang berdekatan, dan perbandingan antar kategori tetap terjaga karena pasangan batangnya sejajar:
import matplotlib.pyplot as plt
import numpy as np
menu = ["es kopi susu", "gula aren", "kopi tubruk", "matcha latte"]
pekan_lalu = [165, 108, 102, 84]
pekan_ini = [182, 121, 95, 76]
posisi = np.arange(len(menu))
fig, ax = plt.subplots(figsize=(6.5, 3.5))
ax.bar(posisi - 0.2, pekan_lalu, width=0.4, color="lightgray",
label="pekan lalu")
ax.bar(posisi + 0.2, pekan_ini, width=0.4, color="tab:blue",
label="pekan ini")
ax.set_xticks(posisi, menu)
ax.bar_label(ax.containers[0], padding=2)
ax.bar_label(ax.containers[1], padding=2)
ax.set_title("Empat Menu Terlaris: Pekan Lalu vs Pekan Ini")
ax.set_xlabel("Menu")
ax.set_ylabel("Gelas terjual")
ax.set_ylim(0, 205)
ax.legend()
plt.show()Gambar yang dihasilkan memuat empat pasang batang: batang abu-abu (pekan lalu) dan batang biru (pekan ini) berdampingan. Tiga pasang pertama memperlihatkan es kopi susu naik dari 165 ke 182, gula aren naik dari 108 ke 121, dan kopi tubruk justru turun dari 102 ke 95; matcha latte turun dari 84 ke 76. Kenaikan dan penurunan terbaca dari pasangan batang yang bersebelahan, tanpa perlu memutar angka.
Mekanisme penggesernya berada pada tiga baris: np.arange(len(menu)) menghasilkan posisi [0, 1, 2, 3], lalu posisi - 0.2 dan posisi + 0.2 menggeser tiap seri ke kiri dan kanan setengah lebar batang (0,4), sehingga kedua batang menempel rapat tanpa bertumpuk. ax.set_xticks(posisi, menu) kemudian menuliskan nama menu tepat di tengah tiap pasangan.
Heatmap: Matriks Hari dan Jam #
Dua saluran sebelumnya menambahkan satu-dua variabel. Untuk matriks — dua kategori bersilangan dengan satu nilai di tiap selnya — heatmap mengambil pendekatan berbeda: nilai sel dipetakan ke warna, dan seluruh pola matriks terlihat sekaligus. Pertanyaan contohnya: kapan Toko Kopi Kita ramai, per hari dan per jam?
Bahan bakunya adalah tabel panjang satu-baris-satu-catatan, lalu pd.pivot_table() memutar tabel itu menjadi matriks hari × jam:
import pandas as pd
hari = ["Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu", "Minggu"]
jam = [7, 9, 11, 13, 15, 17, 19]
nilai = [
16, 31, 24, 18, 20, 27, 12, # Senin
17, 32, 23, 19, 21, 28, 13, # Selasa
15, 30, 25, 18, 20, 26, 12, # Rabu
18, 33, 24, 20, 22, 29, 14, # Kamis
19, 34, 26, 21, 24, 33, 18, # Jumat
22, 36, 30, 26, 30, 41, 26, # Sabtu
23, 35, 31, 27, 31, 40, 24, # Minggu
]
catatan = pd.DataFrame({
"hari": [h for h in hari for _ in jam],
"jam": jam * len(hari),
"pelanggan": nilai,
})
ramai = pd.pivot_table(catatan, index="hari", columns="jam",
values="pelanggan")
ramai = ramai.reindex(hari).astype(int)
print(ramai.to_string())
print()
print("Jam tersibuk rata-rata:", ramai.mean().idxmax(), "sekitar",
round(ramai.mean().max(), 1), "pelanggan")
print("Hari tersibuk :", ramai.mean(axis=1).idxmax())Output yang Dihasilkan:
jam 7 9 11 13 15 17 19
hari
Senin 16 31 24 18 20 27 12
Selasa 17 32 23 19 21 28 13
Rabu 15 30 25 18 20 26 12
Kamis 18 33 24 20 22 29 14
Jumat 19 34 26 21 24 33 18
Sabtu 22 36 30 26 30 41 26
Minggu 23 35 31 27 31 40 24
Jam tersibuk rata-rata: 9 sekitar 33.0 pelanggan
Hari tersibuk : SabtuMari kita telaah baris-barisnya:
- Baris 14-18 (
pd.DataFrame({...})): tabel panjang 49 baris dibangun dengan pemahaman list — kolomharimengulang tiap nama hari tujuh kali, kolomjammengulang daftar jam tujuh kali, dan kolompelangganmenampung 49 nilainya; bentuk panjang inilah format asli tabel catatan kasir. - Baris 20-21 (
pd.pivot_table(...)): tabel panjang diputar menjadi matriks —index="hari"menjadi baris,columns="jam"menjadi kolom, danvalues="pelanggan"mengisi selnya. - Baris 22 (
reindex(hari).astype(int)):pivot_tablemengurutkan hari secara alfabetis (Kamis, Minggu, Rabu, …) sehingga kita susun ulang mengikuti urutan kalender, laluastype(int)mengembalikan tipe bilangan bulat karena hasil pivot berupa bilangan desimal.
Matriksnya siap; sekarang kita gambar dengan ax.imshow(), yang menerjemahkan tiap sel menjadi warna:
import matplotlib.pyplot as plt
import pandas as pd
hari = ["Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu", "Minggu"]
jam = [7, 9, 11, 13, 15, 17, 19]
nilai = [
16, 31, 24, 18, 20, 27, 12, # Senin
17, 32, 23, 19, 21, 28, 13, # Selasa
15, 30, 25, 18, 20, 26, 12, # Rabu
18, 33, 24, 20, 22, 29, 14, # Kamis
19, 34, 26, 21, 24, 33, 18, # Jumat
22, 36, 30, 26, 30, 41, 26, # Sabtu
23, 35, 31, 27, 31, 40, 24, # Minggu
]
catatan = pd.DataFrame({
"hari": [h for h in hari for _ in jam],
"jam": jam * len(hari),
"pelanggan": nilai,
})
ramai = pd.pivot_table(catatan, index="hari", columns="jam",
values="pelanggan")
ramai = ramai.reindex(hari).astype(int)
fig, ax = plt.subplots(figsize=(6.5, 4))
gambar = ax.imshow(ramai.values, cmap="YlOrRd", aspect="auto")
ax.set_xticks(range(len(jam)), jam)
ax.set_yticks(range(len(hari)), hari)
ax.set_title("Pelanggan per Jam dan Hari")
ax.set_xlabel("Jam")
ax.set_ylabel("Hari")
for i in range(len(hari)):
for j in range(len(jam)):
ax.text(j, i, ramai.values[i, j], ha="center", va="center",
fontsize=9)
fig.colorbar(gambar, ax=ax, label="pelanggan")
plt.show()Gambar yang muncul berupa kisi 7×7 yang diisi warna kuning-merah: makin pekat, makin ramai. Dua kolom pekat terlihat jelas — kolom jam 09 (puncak pagi, terpekat pada Sabtu dengan 36) dan kolom jam 17 (puncak sore) — dengan lembah terang di kolom jam 13 (siang) dan baris Senin yang paling kuning sebagai hari paling sepi. Angka di tiap sel memungkinkan pembacaan nilai persis, sedangkan warna memperlihatkan bentuk besarnya: dua puncak harian yang menanjak menjelang akhir pekan.
Perhatikan tiga baris kuncinya: ax.imshow(ramai.values, ...) menggambar matriks sel utuh dari larik NumPy-nya; loop ganda ax.text(j, i, ...) menuliskan angka di pusat tiap sel dengan koordinat (kolom, baris) — kebalikan dari kebiasaan (baris, kolom); dan fig.colorbar(gambar, ...) menambahkan batang warna agar intensitas warna punya skala yang bisa dibaca.
Small Multiples: Satu Grafik per Panel #
Ketika jumlah kombinasinya makin banyak, warna dan ukuran cepat kehabisan daya muat. Pola small multiples mengambil jalan lain: satu jenis grafik diulang di beberapa panel kecil, satu panel per kategori, dengan sumbu yang sama. Mata pembaca cukup belajar membaca satu panel, lalu membandingkan antar panel:
import matplotlib.pyplot as plt
import pandas as pd
menu = pd.DataFrame({
"nama": ["es kopi susu", "kopi susu gula aren", "kopi tubruk",
"cappuccino", "matcha latte", "es teh melati",
"cokelat panas", "teh tawar"],
"jenis": ["kopi", "kopi", "kopi", "kopi",
"non-kopi", "non-kopi", "non-kopi", "non-kopi"],
"harga": [22, 20, 12, 24, 24, 10, 20, 8],
"terjual": [182, 121, 95, 88, 76, 64, 58, 42],
"rating": [4.8, 4.7, 4.4, 4.6, 4.3, 4.0, 4.5, 3.9],
})
fig, panel = plt.subplots(1, 2, figsize=(10, 3.5), sharey=True)
for ax, (jenis, bagian) in zip(panel, menu.groupby("jenis")):
ax.bar(bagian["nama"], bagian["terjual"], color="tab:blue")
ax.bar_label(ax.containers[0], padding=2)
ax.set_title(f"minuman {jenis}")
ax.set_xlabel("Menu")
ax.tick_params(axis="x", labelrotation=20)
panel[0].set_ylabel("Gelas terjual sepekan")
fig.suptitle("Penjualan Menu per Jenis Minuman")
fig.tight_layout()
plt.show()Gambar yang dihasilkan memuat dua panel berbagi sumbu tegak: panel kiri “minuman kopi” berisi empat batang dengan es kopi susu tertinggi (182) dan cappuccino terendah (88); panel kanan “minuman non-kopi” berisi empat batang yang seluruhnya di bawah 80 gelas, dipimpin matcha latte (76) dan diakhiri teh tawar (42). Karena sumbu tegaknya dibagi (sharey=True), perbandingan lintas panel sah secara visual: batang tertinggi di panel kanan pun masih di bawah setengah batang es kopi susu.
Tiga baris pentingnya: plt.subplots(1, 2, ..., sharey=True) menyiapkan dua bidang berdampingan dengan satu sumbu tegak bersama; zip(panel, menu.groupby("jenis")) memasangkan tiap bidang dengan satu kelompok jenis; dan fig.suptitle() memberi judul induk yang meliputi kedua panel, karena judul tiap panel hanya menjelaskan bagiannya sendiri.
Rangkuman #
- Scatter plot memuat empat variabel sekaligus: dua sumbu, warna titik untuk kategori, dan besar titik (
s=) untuk nilai numerik — pesan utama tetap dibawa posisi, bukan besar titik. - Grouped bar menyandingkan dua seri pada kategori yang sama lewat posisi batang yang digeser (
posisi - 0.2danposisi + 0.2); legend menjadi wajib begitu seri lebih dari satu. pd.pivot_table()memutar tabel panjang menjadi matriks;ax.imshow()menggambar matriks itu sebagai warna, ditambah angka per sel danfig.colorbar()sebagai skalanya.- Small multiples mengulang satu grafik per kategori pada panel-panel dengan sumbu bersama — solusi paling tahan banting saat kombinasinya banyak.
Teknik-teknik di atas memperlihatkan pola ramai dan sepi toko kita lewat matriks hari-jam. Heatmap tadi memetakan dua kategori bersilangan; namun untuk memeriksa satu variabel numerik secara saksama, dua grafik tertua masih menjadi standar praktiknya. Kita bedah keduanya pada chapter berikutnya, Histogram dan Boxplot.
Referensi #
- Matplotlib — Plot Types
— referensi resmi scatter plot (parameter
s=dan warna) serta bar chart. - Pandas — DataFrame.pivot_table — dokumentasi resmi pivot tabel sebagai bahan baku heatmap.
- Matplotlib — pyplot API Summary
— ringkasan fungsi
pyplot, termasukimshowdancolorbar.