Deret harian 549 baris dari Bagian 1 terlalu halus untuk membaca arah penjualan: lonjakan akhir pekan dan derau harian menutupi pergerakan yang sedang berlangsung. Agregasi per periode menyingkirkan derau itu dengan meringkas banyak baris harian menjadi satu angka per minggu atau per bulan. Pada bagian ini kita memakai resample() di atas groupby() untuk menghasilkan rekap mingguan (“W”) dan bulanan (“ME”) per outlet, membandingkannya dengan pola pd.Grouper, lalu menyusun pivot outlet x bulan yang siap dibaca. Tabel bulanan itulah bahan mentah analisis tren pada Bagian 3.

Rekap Mingguan dan Bulanan per Outlet #

Skrip berikut memuat dataset bersih lewat muat_bersih() dari buat_dataset.py, lalu membangun rekap mingguan, rekap bulanan, dan dua pivot outlet x bulan (total dan rata-rata harian).

python
"""Mengagregasi deret waktu harian Toko Roti Manis per periode.

Meringkas 549 baris harian menjadi rekap mingguan (resample "W") dan
bulanan (resample "ME") per outlet, membandingkan pola groupby().resample()
dengan pd.Grouper, lalu menyusun pivot outlet x bulan.
"""
import pandas as pd

from buat_dataset import muat_bersih

if __name__ == "__main__":
    bersih = muat_bersih()
    print("== Deret harian bersih ==")
    print(f"{bersih.shape[0]} baris x {bersih.shape[1]} kolom "
          f"| index: {bersih.index.dtype}")

    print("\n== Rekap mingguan per outlet ==")
    mingguan = bersih.groupby("outlet").resample("W").agg(
        omzet=("omzet", "sum"),
        unit_rata=("unit_terjual", "mean"),
    )
    print(f"{len(mingguan)} baris | minggu per outlet: "
          f"{mingguan.groupby(level=0).size().to_dict()}")
    print(mingguan.xs("Cihampelas", level=0).head(3).round(1).to_string())

    print("\n== Rekap bulanan per outlet ==")
    bulanan = bersih.groupby("outlet").resample("ME").agg(
        omzet=("omzet", "sum"),
        unit_rata=("unit_terjual", "mean"),
    )
    print(f"{len(bulanan)} baris (3 outlet x 6 bulan)")
    print(bulanan.round(1).to_string())

    alt = bersih.groupby(["outlet", pd.Grouper(freq="ME")])["omzet"].sum()
    print(f"pola pd.Grouper hasil sama: {alt.equals(bulanan['omzet'])}")

    print("\n== Pivot outlet x bulan, total omzet (juta rupiah) ==")
    harian = bersih.copy()
    harian["bulan"] = harian.index.strftime("%Y-%m")
    tabel = harian.pivot_table(
        index="outlet", columns="bulan", values="omzet",
        aggfunc="sum", margins=True,
    )
    print((tabel / 1_000_000).round(1).to_string())

    print("\n== Pivot outlet x bulan, rata-rata harian (juta rupiah) ==")
    rata = harian.pivot_table(
        index="outlet", columns="bulan", values="omzet", aggfunc="mean",
    )
    print((rata / 1_000_000).round(2).to_string())

Output yang Dihasilkan:

text
== Deret harian bersih ==
549 baris x 3 kolom | index: datetime64[us]

== Rekap mingguan per outlet ==
81 baris | minggu per outlet: {'Cihampelas': 27, 'Kemang': 27, 'Malioboro': 27}
               omzet  unit_rata
tanggal                        
2026-04-05  17689900      352.0
2026-04-12  25827950      366.9
2026-04-19  24477100      355.4

== Rekap bulanan per outlet ==
18 baris (3 outlet x 6 bulan)
                           omzet  unit_rata
outlet     tanggal                         
Cihampelas 2026-04-30  105114750      354.3
           2026-05-31  107153200      351.6
           2026-06-30  108114000      364.7
           2026-07-31  110241700      359.4
           2026-08-31  109858650      362.2
           2026-09-30  107684850      368.1
Kemang     2026-04-30  135818450      427.6
           2026-05-31  148975300      456.0
           2026-06-30  151803400      480.5
           2026-07-31  159417250      490.5
           2026-08-31  167735700      521.2
           2026-09-30  168660450      538.9
Malioboro  2026-04-30   81247050      292.8
           2026-05-31   82091950      287.5
           2026-06-30   75397400      272.2
           2026-07-31   76721300      269.6
           2026-08-31   74595400      264.2
           2026-09-30   72182400      259.2
pola pd.Grouper hasil sama: True

== Pivot outlet x bulan, total omzet (juta rupiah) ==
bulan       2026-04  2026-05  2026-06  2026-07  2026-08  2026-09     All
outlet                                                                  
Cihampelas    105.1    107.2    108.1    110.2    109.9    107.7   648.2
Kemang        135.8    149.0    151.8    159.4    167.7    168.7   932.4
Malioboro      81.2     82.1     75.4     76.7     74.6     72.2   462.2
All           322.2    338.2    335.3    346.4    352.2    348.5  2042.8

== Pivot outlet x bulan, rata-rata harian (juta rupiah) ==
bulan       2026-04  2026-05  2026-06  2026-07  2026-08  2026-09
outlet                                                          
Cihampelas     3.50     3.46     3.60     3.56     3.54     3.59
Kemang         4.53     4.81     5.06     5.14     5.41     5.62
Malioboro      2.71     2.65     2.51     2.47     2.41     2.41

Mari kita telaah bagian-bagian pentingnya:

  1. bersih.groupby("outlet").resample("W"): GroupBy.resample() memotong deret tiap outlet ke bin mingguan; label bin adalah hari Minggu penutup minggu, dan hasilnya 27 minggu per outlet (81 baris untuk tiga outlet).
  2. .agg(omzet=("omzet", "sum"), unit_rata=("unit_terjual", "mean")): named aggregation menjalankan dua agregasi berbeda dalam satu panggilan — total omzet dan rata-rata unit harian per minggu.
  3. resample("ME"): alias "ME" (month end) menghasilkan enam bin berlabel akhir bulan, 30 April sampai 30 September, sehingga rekap bulanan berisi 18 baris. Alias "M" sudah tidak didukung pandas 3.0 dan memunculkan ValueError.
  4. pd.Grouper(freq="ME"): pola kedua yang setara — groupby(["outlet", pd.Grouper(freq="ME")]) menghasilkan angka yang persis sama (equals bernilai True), memakai Grouper bila Anda lebih senang mengelompokkan lewat satu daftar kunci.
  5. harian.index.strftime("%Y-%m"): kolom bulan dibuat dari index datetime, lalu pivot_table() menyusun baris outlet x kolom bulan; margins=True menambahkan baris dan kolom All berisi total jaringan (2.042,8 juta rupiah).
  6. Dua pivot dengan aggfunc berbeda: aggfunc="sum" menunjukkan ukuran total per bulan, sedangkan aggfunc="mean" menunjukkan rata-rata harian yang menghilangkan pengaruh panjang bulan.

Membaca Hasil Agregasi dengan Benar #

Dua tabel pivot terakhir memperlihatkan mengapa keduanya perlu ada. Untuk Kemang, total bulanan dan rata-rata harian sama-sama naik: 135,8 juta dengan 4,53 juta per hari pada April, menjadi 168,7 juta dengan 5,62 juta per hari pada September. Untuk Cihampelas, keduanya bisa berlawanan arah.

PeriodeArgumen freqLabel binHasil di proyek ini
Mingguan"W"Minggu penutup minggu27 minggu per outlet
Bulanan"ME"akhir bulan6 bulan per outlet

Teori resampling sudah dibahas tuntas pada chapter Resampling Data dan Frekuensi dan Offset pada section Data Waktu dan Deret Waktu; di proyek ini kita fokus merangkainya dengan groupby() dan pivot_table() untuk data multi-outlet. Tabel bulanan outlet x bulan yang baru terbentuk adalah bahan mentah Bagian 3: dari situ kita menghitung perubahan antar bulan, menghaluskan deret dengan rata-rata bergerak, dan menyimpulkan arah tren tiap outlet.

Sumber bacaan resmi: pandas.DataFrame.resample , pandas.Grouper , dan pandas.DataFrame.pivot_table .