Data bersih ibarat bahan mentah yang sudah tersortir — sebelum memasak, kita perlu mencium, menimbang, dan mencicipinya. Pada bagian ini kita menjelajahi 48 baris data bersih Toko Kopi Kita tanpa target jawaban spesifik dahulu: melihat sebaran angkanya, menjumlahkan per menu dan per hari, lalu membandingkan akhir pekan dengan hari kerja. Pola yang muncul dari eksplorasi inilah yang nanti dijawab tuntas pada Bagian 7. Pendekatannya mengikuti kerangka Exploratory Data Analysis yang sudah kita pelajari.

Statistik Deskriptif Sekilas #

Titik awal eksplorasi adalah describe() pada kolom-kolom angka. Ingat: setiap blok kode pada proyek ini berdiri sendiri, jadi fungsi pembersihan dari Bagian 5 ikut dituliskan lengkap:

python
import pandas as pd


def muat_bersih(path):
    """Muat data mentah dan terapkan pembersihan hasil Bagian 5."""
    df = pd.read_csv(path).drop_duplicates().reset_index(drop=True)
    df["menu"] = df["menu"].str.lower()
    df["gelas"] = pd.to_numeric(df["gelas"].replace("-", pd.NA))
    valid = df[df["gelas"].notna() & (df["pendapatan_ribu"] > 0)]
    harga = (valid["pendapatan_ribu"] / valid["gelas"]).groupby(valid["menu"]).median()
    nol = df["pendapatan_ribu"] == 0
    df.loc[nol, "pendapatan_ribu"] = (
        df.loc[nol, "gelas"] * df.loc[nol, "menu"].map(harga)
    ).astype(int)
    kosong = df["gelas"].isna()
    df.loc[kosong, "gelas"] = (
        df.loc[kosong, "pendapatan_ribu"] / df.loc[kosong, "menu"].map(harga)
    )
    df["gelas"] = df["gelas"].astype(int)
    return df


bersih = muat_bersih("../data/penjualan_kotor.csv")

print(bersih[["gelas", "pendapatan_ribu"]].describe().round(2).to_string())

Output yang Dihasilkan:

text
       gelas  pendapatan_ribu
count  48.00            48.00
mean   24.67           492.69
std    10.80           295.56
min     7.00            70.00
25%    17.75           235.00
50%    22.00           444.50
75%    31.50           706.50
max    55.00          1210.00

Mari kita baca statistik tersebut:

  1. count 48 pada kedua kolom: seluruh baris terisi — pembersihan Bagian 5 benar-benar menyisakan data yang utuh.
  2. mean gelas 24,67 dan pendapatan 492,69 (ribu): itulah penjualan rata-rata satu menu pada satu hari. Kalikan 4 menu dan 12 hari, hasilnya konsisten dengan total 48 baris.
  3. min gelas 7 dan max 55: jarak minimum-maksimum yang lebar menandakan penjualan per menu-hari sangat bervariasi — dari hari sepi (14 Januari) sampai puncak akhir pekan. Nilai maksimum 55 gelas itu milik kopi susu pada 11 Januari.
  4. std yang besar (10,8 gelas; 295,56 ribu): sebaran yang lebar ini wajar untuk usaha kuliner dan menjadi alasan kuat mengapa kita harus melihat agregasi per kelompok, bukan hanya rata-rata keseluruhan.

Total per Menu dan per Hari #

Dua agregasi paling mendasar: jumlahkan per menu untuk melihat performa produk, dan jumlahkan per tanggal untuk membaca ritme harian:

python
import pandas as pd


def muat_bersih(path):
    """Muat data mentah dan terapkan pembersihan hasil Bagian 5."""
    df = pd.read_csv(path).drop_duplicates().reset_index(drop=True)
    df["menu"] = df["menu"].str.lower()
    df["gelas"] = pd.to_numeric(df["gelas"].replace("-", pd.NA))
    valid = df[df["gelas"].notna() & (df["pendapatan_ribu"] > 0)]
    harga = (valid["pendapatan_ribu"] / valid["gelas"]).groupby(valid["menu"]).median()
    nol = df["pendapatan_ribu"] == 0
    df.loc[nol, "pendapatan_ribu"] = (
        df.loc[nol, "gelas"] * df.loc[nol, "menu"].map(harga)
    ).astype(int)
    kosong = df["gelas"].isna()
    df.loc[kosong, "gelas"] = (
        df.loc[kosong, "pendapatan_ribu"] / df.loc[kosong, "menu"].map(harga)
    )
    df["gelas"] = df["gelas"].astype(int)
    return df


bersih = muat_bersih("../data/penjualan_kotor.csv")

per_menu = (
    bersih.groupby("menu")[["gelas", "pendapatan_ribu"]]
    .sum()
    .sort_values("pendapatan_ribu", ascending=False)
)
print("Total per menu (12 hari):")
print(per_menu.to_string())

per_hari = bersih.groupby("tanggal")["pendapatan_ribu"].sum().reset_index()
per_hari.columns = ["tanggal", "total_ribu"]
print("\nTotal pendapatan per hari (ribu rupiah):")
print(per_hari.to_string(index=False))

Output yang Dihasilkan:

text
Total per menu (12 hari):
              gelas  pendapatan_ribu
menu                                
kopi susu       459            10098
matcha latte    291             7275
americano       242             4356
teh tawar       192             1920

Total pendapatan per hari (ribu rupiah):
   tanggal  total_ribu
2026-01-05        1747
2026-01-06        1565
2026-01-07        1824
2026-01-08        1662
2026-01-09        1956
2026-01-10        2743
2026-01-11        2915
2026-01-12        2053
2026-01-13        1881
2026-01-14         903
2026-01-15        2128
2026-01-16        2272
python
import pandas as pd


def muat_bersih(path):
    """Muat data mentah dan terapkan pembersihan hasil Bagian 5."""
    df = pd.read_csv(path).drop_duplicates().reset_index(drop=True)
    df["menu"] = df["menu"].str.lower()
    df["gelas"] = pd.to_numeric(df["gelas"].replace("-", pd.NA))
    valid = df[df["gelas"].notna() & (df["pendapatan_ribu"] > 0)]
    harga = (valid["pendapatan_ribu"] / valid["gelas"]).groupby(valid["menu"]).median()
    nol = df["pendapatan_ribu"] == 0
    df.loc[nol, "pendapatan_ribu"] = (
        df.loc[nol, "gelas"] * df.loc[nol, "menu"].map(harga)
    ).astype(int)
    kosong = df["gelas"].isna()
    df.loc[kosong, "gelas"] = (
        df.loc[kosong, "pendapatan_ribu"] / df.loc[kosong, "menu"].map(harga)
    )
    df["gelas"] = df["gelas"].astype(int)
    return df


bersih = muat_bersih("../data/penjualan_kotor.csv")

AKHIR_PEKAN = ("2026-01-10", "2026-01-11")  # Sabtu dan Minggu
per_hari = bersih.groupby("tanggal")["pendapatan_ribu"].sum().reset_index()
per_hari["akhir_pekan"] = per_hari["tanggal"].isin(AKHIR_PEKAN)

ringkas = per_hari.groupby("akhir_pekan")["pendapatan_ribu"].agg(["count", "mean"])
print("Rata-rata pendapatan harian (ribu rupiah):")
print(ringkas.round(1).to_string())

rasio = ringkas.loc[True, "mean"] / ringkas.loc[False, "mean"]
print(f"\nRasio akhir pekan terhadap hari kerja: {rasio:.2f}")

Output yang Dihasilkan:

text
Rata-rata pendapatan harian (ribu rupiah):
             count    mean
akhir_pekan               
False           10  1799.1
True             2  2829.0

Rasio akhir pekan terhadap hari kerja: 1.57

Pembacaannya:

  1. count 2 vs 10: kelompok akhir pekan hanya berisi dua hari, jadi angka rata-ratanya lebih mudah goyah dibanding kelompok hari kerja yang berisi sepuluh hari. Keterbatasan ini kita catat untuk laporan.
  2. Rata-rata 2.829,0 vs 1.799,1: akhir pekan unggul hampir 1.030 ribu rupiah per hari.
  3. Rasio 1,57: pendapatan akhir pekan rata-rata 1,57 kali hari kerja — selisih yang terlalu besar untuk dianggap kebetulan, meski tetap perlu dipantau beberapa pekan lagi sebelum menjadi kebijakan tetap.

Eksplorasi telah memetakan medannya: satu menu penopang, akhir pekan yang ramai, dan satu hari yang menyimpang. Kini lima pertanyaan dari Bagian 2 tinggal dijawab satu per satu dengan angka — itu tugas Bagian 7: Analisis Penjualan .