Rekap bulanan dari Bagian 2 masih berupa deretan angka mentah per outlet. Untuk menyimpulkan arah penjualan, kita butuh tiga sudut pandang yang saling melengkapi: penghalusan jangka pendek dengan rata-rata bergerak, perubahan antar periode dengan pct_change() dan diff(), serta perbandingan dua semester. Bagian ini juga menandai puncak dan lembah deret dengan idxmax() dan idxmin(). Semua angka dihitung deterministik dari dataset yang sama, sehingga kesimpulan naik-turun tiap outlet bisa Anda reproduksi persis.
Rata-rata Bergerak 7 dan 30 Hari #
"""Menganalisis tren dan perubahan penjualan Toko Roti Manis.
Menghitung rata-rata bergerak 7 dan 30 hari, perubahan antar bulan
dengan pct_change() dan diff(), membandingkan dua semester lewat
resample("QE"), serta menemukan puncak dan lembah dengan idxmax()/idxmin().
"""
import numpy as np
import pandas as pd
from buat_dataset import muat_bersih
if __name__ == "__main__":
bersih = muat_bersih()
print("== Rata-rata bergerak per outlet ==")
ma7 = bersih.groupby("outlet")["omzet"].rolling(window=7).mean()
nan7 = ma7.groupby(level=0).apply(lambda s: int(s.isna().sum())).to_dict()
print(f"MA 7 hari | NaN awal: {nan7}")
kem = ma7.xs("Kemang", level=0)
print(f"Kemang MA7 hari ke-7..10: {kem.iloc[6:10].round(0).astype('int64').tolist()}")
ma7_penuh = bersih.groupby("outlet")["omzet"].rolling(window=7, min_periods=1).mean()
print(f"MA 7 hari dengan min_periods=1 -> NaN tersisa: {int(ma7_penuh.isna().sum())}")
ma30 = bersih.groupby("outlet")["omzet"].rolling(window=30).mean()
nan30 = ma30.groupby(level=0).apply(lambda s: int(s.isna().sum())).to_dict()
print(f"MA 30 hari | NaN awal: {nan30}")
print(f"Kemang: MA7 akhir {round(kem.iloc[-1]):,} | MA30 akhir "
f"{round(ma30.xs('Kemang', level=0).iloc[-1]):,}")
print("\n== Perubahan antar bulan ==")
bulanan = bersih.groupby("outlet").resample("ME")["omzet"].sum()
persen = bulanan.groupby(level=0).pct_change() * 100
sebelumnya = bulanan.groupby(level=0).shift(1)
manual = (bulanan - sebelumnya) / sebelumnya * 100
print(f"pct_change sama dengan rumus shift manual: "
f"{bool(np.allclose(persen.dropna(), manual.dropna()))}")
print("pct_change bulanan (%):")
print(persen.unstack(level="outlet").round(1).to_string())
selisih = (bulanan.groupby(level=0).diff() / 1_000_000).unstack(level="outlet")
print("diff bulanan (juta rupiah):")
print(selisih.round(1).to_string())
print("\n== Perbandingan semester (resample QE) ==")
kuartal = bersih.groupby("outlet").resample("QE")["omzet"].sum()
for outlet in ["Cihampelas", "Kemang", "Malioboro"]:
s = kuartal.xs(outlet, level=0)
naik = (s.iloc[1] / s.iloc[0] - 1) * 100
arah = "naik" if naik > 0 else "turun"
print(f"{outlet:11s}: Q2 {s.iloc[0]:>11,.0f} -> Q3 {s.iloc[1]:>11,.0f} "
f"({naik:+.1f}%, {arah})")
print("\n== Puncak dan lembah ==")
baris = []
for outlet in ["Cihampelas", "Kemang", "Malioboro"]:
hari = bersih.loc[bersih["outlet"] == outlet, "omzet"]
bulan = bulanan.xs(outlet, level=0)
puncak = hari.idxmax()
baris.append({
"outlet": outlet,
"hari_terbaik": f"{puncak.strftime('%Y-%m-%d')} ({puncak.day_name()})",
"omzet_hari": int(hari.max()),
"bulan_terbaik": bulan.idxmax().strftime("%Y-%m"),
"bulan_terburuk": bulan.idxmin().strftime("%Y-%m"),
})
print(pd.DataFrame(baris).to_string(index=False))Output yang Dihasilkan:
== Rata-rata bergerak per outlet ==
MA 7 hari | NaN awal: {'Cihampelas': 6, 'Kemang': 6, 'Malioboro': 6}
Kemang MA7 hari ke-7..10: [4342371, 4387657, 4409700, 4285800]
MA 7 hari dengan min_periods=1 -> NaN tersisa: 0
MA 30 hari | NaN awal: {'Cihampelas': 29, 'Kemang': 29, 'Malioboro': 29}
Kemang: MA7 akhir 5,879,650 | MA30 akhir 5,622,015
== Perubahan antar bulan ==
pct_change sama dengan rumus shift manual: True
pct_change bulanan (%):
outlet Cihampelas Kemang Malioboro
tanggal
2026-04-30 NaN NaN NaN
2026-05-31 1.9 9.7 1.0
2026-06-30 0.9 1.9 -8.2
2026-07-31 2.0 5.0 1.8
2026-08-31 -0.3 5.2 -2.8
2026-09-30 -2.0 0.6 -3.2
diff bulanan (juta rupiah):
outlet Cihampelas Kemang Malioboro
tanggal
2026-04-30 NaN NaN NaN
2026-05-31 2.0 13.2 0.8
2026-06-30 1.0 2.8 -6.7
2026-07-31 2.1 7.6 1.3
2026-08-31 -0.4 8.3 -2.1
2026-09-30 -2.2 0.9 -2.4
== Perbandingan semester (resample QE) ==
Cihampelas : Q2 320,381,950 -> Q3 327,785,200 (+2.3%, naik)
Kemang : Q2 436,597,150 -> Q3 495,813,400 (+13.6%, naik)
Malioboro : Q2 238,736,400 -> Q3 223,499,100 (-6.4%, turun)
== Puncak dan lembah ==
outlet hari_terbaik omzet_hari bulan_terbaik bulan_terburuk
Cihampelas 2026-09-05 (Saturday) 4860200 2026-07 2026-04
Kemang 2026-09-27 (Sunday) 6870800 2026-09 2026-04
Malioboro 2026-04-18 (Saturday) 3589600 2026-05 2026-09Mari kita telaah bagian-bagian pentingnya:
bersih.groupby("outlet")["omzet"].rolling(window=7).mean(): rolling window dijalankan per outlet sehingga rata-rata 7 hari tidak pernah mencampur dua outlet; hasilnya Series bertingkat dengan level outlet.- NaN enam hari pertama per outlet: parameter
min_periodsbawaan sama dengan ukuran window, sehingga window 7 hari butuh 7 nilai penuh sebelum menghasilkan angka; window 30 hari menahan 29 NaN. Denganmin_periods=1, nilai pertama langsung terisi dan NaN tersisa nol. Kemang MA7 hari ke-7..10: nilai pertama 4.342.371 pada 7 April adalah rata-rata 1-7 April; deret ini menghaluskan lonjakan akhir pekan sehingga arah gerak outlet terlihat.pct_change()dan verifikasi shift: pertumbuhan persen antar bulan dihitung per outlet; barisnya persis sama dengan rumus manual(bulanan - shift(1)) / shift(1)— pemeriksaanallclosebernilai True, memperlihatkan bahwashift(1)menggeser nilai satu langkah ke bawah.diff(): selisih absolut antar bulan dalam rupiah; bulan April bernilai NaN karena tidak ada bulan sebelumnya.resample("QE"): kuartal kalender memotong enam bulan proyek menjadi dua semester — April-Juni (Q2) dan Juli-September (Q3) — yang dibandingkan langsung per outlet.
Membaca Arah Tren Tiap Outlet #
Bagian “Perbandingan semester” pada output memberi kesimpulan paling ringkas: Kemang tumbuh dari 436,6 juta menjadi 495,8 juta (+13,6%), Cihampelas nyaris datar (+2,3%), dan Malioboro turun dari 238,7 juta menjadi 223,5 juta (-6,4%). Tabel puncak dan lembah menegaskan arah tersebut: hari terbaik Malioboro terjadi di awal periode (Sabtu 18 April), sementara hari terbaik Kemang justru di akhir periode (Minggu 27 September dengan omzet 6.870.800). Hari terbaik Cihampelas jatuh pada Sabtu 5 September, dan bulan terbaik ketiga outlet (2026-07, 2026-09, dan 2026-05) tidak selalu bulan terakhir — derau mingguan tetap bekerja di atas tren.
Teknik-teknik di atas dirangkum dari chapter Rolling Window, Pergeseran Data dengan shift(), dan Menghitung Perubahan dengan diff() pada section Data Waktu dan Deret Waktu. Tiga kesimpulan arah tren ini masih terpencar di beberapa tabel; Bagian 4 menggabungkannya dengan total, rata-rata, dan pangsa penjualan menjadi satu ringkasan siap lapor.
Sumber bacaan resmi: pandas.Series.rolling , pandas.DataFrame.pct_change , dan pandas.Series.idxmax .