Pada chapter sebelumnya kita menemukan bahwa rata-rata harian Februari terangkat oleh satu hari istimewa — tetapi hari istimewa itu bukan satu-satunya hal yang tersembunyi di dalam deret harian. Data deret waktu biasanya memuat dua hal sekaligus: pola yang berulang teratur (akhir pekan selalu ramai, awal pekan selalu sepi) dan nilai yang menyimpang dari pola itu (satu hari luar biasa ramai, satu hari tiba-tiba sepi). Keduanya sama berharganya, tetapi perlu dibedakan: pola menjadi dasar perencanaan, sedangkan anomali menjadi kandidat investigasi. Pada chapter ini kita memakai dataset penjualan harian yang sama — penjualan_harian.csv — untuk membangun baseline pola mingguan, menandai hari yang menyimpang dengan z-score dan IQR, lalu menilai seberapa jauh anomali itu menggeser kesimpulan analisis.
Pola Mingguan Sebagai Baseline #
Mendeteksi anomali membutuhkan pemahaman tentang “normal” terlebih dahulu. Untuk toko kopi, normal itu berubah mengikuti hari dalam minggu: pelanggan kantoran datang di hari kerja, keluarga dan pengunjung akhir pekan menaikkan omzet Sabtu-Minggu. Kita hitung rata-rata penjualan per nama hari dengan groupby pada .dt.day_name(), lalu kita susun ulang urutannya mengikuti urutan kalender:
import pandas as pd
penjualan = pd.read_csv("penjualan_harian.csv", parse_dates=["tanggal"])
print("Baris dan kolom:", penjualan.shape)
urutan_hari = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
pola = penjualan.groupby(penjualan["tanggal"].dt.day_name())["gelas"].mean()
pola = pola.reindex(urutan_hari)
print(pola.round(1).to_string())Output yang Dihasilkan:
Baris dan kolom: (42, 3)
tanggal
Monday 51.5
Tuesday 54.7
Wednesday 53.3
Thursday 58.7
Friday 73.7
Saturday 85.8
Sunday 88.5Mari kita telaah baris-baris pentingnya:
- Baris 4 (
parse_dates=["tanggal"]): kolom tanggal langsung dikonversi saat membaca CSV, sehingga.dt.day_name()siap dipakai tanpa langkah tambahan. - Baris 7-8 (
groupby+reindex): rata-rata dihitung per nama hari, lalureindex(urutan_hari)menyusun ulang hasilnya dari Senin sampai Minggu — tanpa ini, urutan mengikuti urutan kemunculan pertama di data. - Bacaan hasilnya: terlihat tangga yang jelas — awal pekan di kisaran 51-55 gelas, Jumat mulai naik ke 73,7, dan akhir pekan berada di 85-88 gelas. Angka Jumat yang lebih tinggi dari hari kerja lainnya sudah memberi petunjuk pertama: ada sesuatu pada hari Jumat.
Perhatikan bahwa Jumat bernilai 73,7 — jauh di atas Senin sampai Kamis. Angka ini “tersumbu” oleh satu hari istimewa yang akan kita temukan sebentar lagi; pada tahap ini kita baru mencatat bahwa baseline mingguan tidak rata.
Z-Score: Menyimpang Berapa Simpangan Baku #
Z-score menjawab pertanyaan sederhana: seberapa jauh satu nilai dari rata-rata, diukur dalam satuan simpangan baku? Rumusnya z = (x - rata-rata) / simpangan baku. Nilai z melebihi 2 atau kurang dari -2 layak ditandai sebagai penyimpang pada data berukuran sedang seperti ini — ia berada di sekitar 2,5% ekor terjauh distribusi normal. Kita terapkan pada kolom gelas:
import pandas as pd
penjualan = pd.read_csv("penjualan_harian.csv", parse_dates=["tanggal"])
rata = penjualan["gelas"].mean()
std = penjualan["gelas"].std()
penjualan["z"] = ((penjualan["gelas"] - rata) / std).round(2)
print(f"Rata-rata: {rata:.2f} gelas | simpangan baku: {std:.2f}")
menyimpang = penjualan[penjualan["z"].abs() > 2][["tanggal", "gelas", "z"]]
print(menyimpang.to_string(index=False))Output yang Dihasilkan:
Rata-rata: 66.60 gelas | simpangan baku: 19.20
tanggal gelas z
2025-02-14 137 3.67
2025-02-17 26 -2.11Mari kita telaah baris-baris pentingnya:
- Baris 6-7 (
mean+std): kedua statistik dihitung dari seluruh 42 hari — termasuk hari-hari anomali itu sendiri. Ini kelemahan yang disengaja kita bahas: nilai ekstrem ikut menarik rata-rata dan menggelembungkan simpangan baku, membuat nilai ekstrem lain tampak lebih “normal”. - Baris 8 (kolom
z): setiap hari mendapat skor penyimpangan;.round(2)menjaga tabel tetap ringkas. - Baris 10 (filter
.abs() > 2): dua hari lolos saringan — 14 Februari dengan 137 gelas (3,67 simpangan baku di atas rata-rata) dan 17 Februari dengan 26 gelas (-2,11 di bawah rata-rata). Inilah kandidat anomali kita: satu lonjakan, satu penurunan.
IQR: Pagar yang Tahan terhadap Nilai Ekstrem #
Metode kedua tidak memakai rata-rata dan simpangan baku, melainkan kuartil: IQR (interquartile range) adalah jarak antara kuartil pertama (Q1, 25% data di bawahnya) dan kuartil ketiga (Q3, 75% data di bawahnya). Pagar bawah dan atas dipasang di Q1 - 1,5 × IQR dan Q3 + 1,5 × IQR — nilai di luar pagar dianggap penyimpang. Keunggulannya: kuartil dihitung dari peringkat data, sehingga satu nilai ekstrem tidak menggesernya:
import pandas as pd
penjualan = pd.read_csv("penjualan_harian.csv", parse_dates=["tanggal"])
q1 = penjualan["gelas"].quantile(0.25)
q3 = penjualan["gelas"].quantile(0.75)
iqr = q3 - q1
batas_bawah = q1 - 1.5 * iqr
batas_atas = q3 + 1.5 * iqr
print(f"Q1={q1:.1f} | Q3={q3:.1f} | IQR={iqr:.1f}")
print(f"Pagar bawah: {batas_bawah:.1f} | atas: {batas_atas:.1f}")
luar = penjualan[(penjualan["gelas"] < batas_bawah) | (penjualan["gelas"] > batas_atas)]
print(luar[["tanggal", "gelas"]].to_string(index=False))Output yang Dihasilkan:
Q1=57.0 | Q3=85.5 | IQR=28.5
Pagar bawah: 14.2 | atas: 128.2
tanggal gelas
2025-02-14 137Mari kita telaah baris-baris pentingnya:
- Baris 5-9 (kuartil dan pagar): Q1 di 57 dan Q3 di 85,5 mencerminkan struktur mingguan — blok hari kerja di bawah, blok akhir pekan di atas. Pagar akhirnya terbentang lebar: 14,2 sampai 128,2 gelas.
- Perbandingan dengan z-score: hanya 14 Februari yang tertangkap, sementara 17 Februari (26 gelas) lolos dari pagar IQR. Dengan pagar bawah 14,2, penurunan ke 26 gelas masih dianggap wilayah yang wajar bagi metode ini.
- Pelajarannya: kedua metode sah, tetapi sensitivitasnya berbeda — z-score menandai lebih banyak (karena rata-rata dan simpangan bakunya sendiri terdistorsi lonjakan), IQR lebih konservatif. Menjalankan keduanya lalu membandingkan hasilnya adalah praktik yang sehat; daftar final yang perlu diinvestigasi adalah gabungan keduanya.
Anomali Terhadap Pola, Bukan Hanya Rata-rata #
Dua metode di atas membandingkan setiap hari dengan seluruh dataset. Padahal kita sudah tahu bahwa “normal” toko ini bergantung hari: 55 gelas di hari Senin adalah hari yang baik, tetapi 55 gelas di hari Minggu adalah bencana. Analisis yang lebih tajam membandingkan setiap hari dengan baseline hari yang sama — dan mengukur dampaknya secara jujur:
import pandas as pd
penjualan = pd.read_csv("penjualan_harian.csv", parse_dates=["tanggal"])
penjualan["hari"] = penjualan["tanggal"].dt.day_name()
# rata-rata hari Jumat TANPA 14 Februari
jumat_lain = penjualan[(penjualan["hari"] == "Friday") & (penjualan["tanggal"] != "2025-02-14")]
rata_jumat = jumat_lain["gelas"].mean()
puncak = penjualan.loc[penjualan["tanggal"] == "2025-02-14", "gelas"].iloc[0]
print(f"Rata-rata Jumat biasa (tanpa 14 Feb): {rata_jumat:.1f} gelas")
print(f"Jumat 14 Februari: {puncak} gelas")
print(f"Rasio puncak terhadap Jumat biasa: {puncak / rata_jumat:.2f} kali")
feb = penjualan[penjualan["tanggal"].dt.month == 2]
print(f"Rata-rata harian Februari penuh: {feb['gelas'].mean():.2f}")
print(f"Rata-rata Februari tanpa 14 Feb : {feb[feb['tanggal'] != '2025-02-14']['gelas'].mean():.2f}")Output yang Dihasilkan:
Rata-rata Jumat biasa (tanpa 14 Feb): 61.0 gelas
Jumat 14 Februari: 137 gelas
Rasio puncak terhadap Jumat biasa: 2.25 kali
Rata-rata harian Februari penuh: 65.86
Rata-rata Februari tanpa 14 Feb : 63.22Mari kita telaah baris-baris pentingnya:
- Baris 6-8 (baseline Jumat murni): rata-rata Jumat dihitung dengan mengecualikan hari puncaknya sendiri (
!= "2025-02-14"), sehingga baseline 61 gelas ini benar-benar menggambarkan Jumat biasa — bukan Jumat yang sudah tercemar hari istimewa. - Baris 12 (rasio): 137 gelas berarti 2,25 kali Jumat biasa — kalimat yang jauh lebih komunikatif bagi pemilik toko daripada “z = 3,67”. Rasio terhadap baseline sejenis adalah bahasa pelaporan anomali yang paling mudah dipahami.
- Baris 15-17 (dampak ke kesimpulan): satu hari itu mengangkat rata-rata harian Februari dari 63,22 menjadi 65,86 gelas. Bandingkan bulan dengan dan tanpa hari istimewa sebelum menyimpulkan “Februari lebih baik dari Januari” — persis jebakan yang kita temui di chapter sebelumnya.
Membedakan Pola dari Anomali #
Kapan sebuah nilai berulang disebut pola, dan kapan ia anomali? Kerangka pembedanya sederhana:
| Aspek | Pola | Anomali |
|---|---|---|
| Berulang | Terjadi berkala dan teratur (setiap akhir pekan) | Sekali atau jarang (satu Jumat tertentu) |
| Penjelasan | Struktur bisnis (hari libur, jam kerja) | Peristiwa khusus (promosi, cuaca, gangguan) |
| Perlakuan | Dijadikan baseline dan dasar perencanaan | Diinvestigasi, lalu diputuskan: dilaporkan terpisah atau dikeluarkan dari ringkasan |
| Contoh di data kita | Sabtu-Minggu 85-88 gelas | 14 Februari 137 gelas; 17 Februari 26 gelas |
Anomali yang ditemukan tidak otomatis dihapus dari data. Justru sebaliknya — ia adalah temuan. Lonjakan 14 Februari yang perlu dicatat penyebabnya (hari Kasih Sayang dengan promosi pasangan), sedangkan penurunan 17 Februari menunjuk ke masalah operasional yang perlu dicek (padahal Senin biasanya stabil di 50-an gelas, hari itu cuma 26). Keputusan mengeluarkannya dari ringkasan hanya diambil setelah penyebabnya dipahami dan tujuan analisisnya jelas.
Rangkuman #
- Baseline dulu, baru anomali: pahami pola mingguan lewat
groupbypada.dt.day_name()sebelum menilai hari mana yang “aneh”. - Z-score (
(x - rata-rata) / simpangan baku, tandai|z| > 2) mudah dihitung tetapi rata-rata dan simpangan bakunya ikut terdistorsi nilai ekstrem; hasil kita: 14 dan 17 Februari. - IQR memasang pagar di
Q1 - 1,5 × IQRdanQ3 + 1,5 × IQR; lebih konservatif dan tahan terhadap nilai ekstrem — pada data kita hanya 14 Februari yang tertangkap. - Perbandingan paling tajam adalah terhadap baseline hari yang sama: 137 gelas = 2,25 kali Jumat biasa; dan satu hari itu menggeser rata-rata Februari dari 63,22 ke 65,86 gelas.
- Pola menjadi dasar perencanaan; anomali menjadi temuan yang diinvestigasi — bukan otomatis dihapus.
Kita kini memiliki seluruh peralatan eksplorasi: memeriksa struktur, meringkas, menghitung frekuensi dan distribusi, menelusuri hubungan antar variabel, membaca dimensi waktu, hingga menandai pola dan anomali. Saatnya merangkai semuanya menjadi satu alur kerja yang runtut pada dataset utuh — itulah chapter penutup Section ini: Exploratory Data Analysis (EDA) .