Anda baru saja menerima tugas menganalisis kinerja penjualan Toko Sembako Berkah, jaringan toko sembako fiktif dengan empat cabang di Jakarta, Bandung, Surabaya, dan Yogyakarta. Sumber datanya satu berkas penjualan.csv hasil ekspor sistem kasir, berisi transaksi Januari hingga Maret 2025. Seperti berkas ekspor pada umumnya, datanya tidak langsung rapi: ada baris ganda, harga yang tidak masuk akal, kolom kategori yang kosong, dan tanggal yang ditulis dengan dua format berbeda. Sebelum membersihkan apa pun, kita perlu tahu dengan pasti apa isi berkas tersebut dan di mana letak masalahnya. Pada bagian ini kita menyiapkan dataset latihan, memuatnya dengan pandas, memeriksa strukturnya, lalu menyusun daftar masalah sebagai kontrak kerja Bagian 2.
Dataset Latihan yang Dapat Direproduksi #
Dataset proyek ini dibuat oleh skrip, bukan diunduh dari internet, agar semua pembaca bekerja pada data yang sama. Skrip buat_dataset.py membangun 240 transaksi dasar memakai np.random.default_rng(42) sehingga hasilnya identik di komputer mana pun, lalu menambahkan enam salinan baris sebagai duplikat. Pola ini mengikuti chapter Reproducible Data Processing pada section Pengujian dan Debugging.
| Kolom | Tipe di CSV | Isi |
|---|---|---|
id_transaksi | teks | kode TRX-0001 sampai TRX-0240 |
tanggal | teks | tanggal transaksi, 1 Januari sampai 31 Maret 2025 |
produk | teks | 10 produk sembako, misalnya Beras Premium 5kg |
kategori | teks | Bahan Pokok, Minuman, Perawatan, atau Kebersihan |
kota | teks | cabang: Jakarta, Bandung, Surabaya, Yogyakarta |
jumlah | bilangan bulat | banyak unit per transaksi (1 sampai 10) |
harga | bilangan bulat | harga satuan dalam rupiah |
Skrip yang sama juga menyediakan fungsi buat_modal() berisi harga beli tiap produk; tabel kecil ini baru dipakai pada Bagian 3. Jalankan skrip berikut untuk melihat ukuran dataset yang dihasilkan.
"""Generator dataset transaksi penjualan Toko Sembako Berkah.
Dataset mentah dibuat secara deterministik (seed 42) dan sengaja memuat
beberapa masalah kualitas data untuk dilatih:
1. label kategori tidak seragam (kapitalisasi dan spasi),
2. harga 0 atau negatif,
3. kategori kosong (NaN),
4. format tanggal campur (ISO dan DD/MM/YYYY),
5. baris duplikat.
"""
from __future__ import annotations
import numpy as np
import pandas as pd
SEED = 42
JUMLAH_BARIS = 240
JUMLAH_DUPLIKAT = 6
PRODUK_HARGA = {
"Beras Premium 5kg": 68000,
"Gula Pasir 1kg": 14500,
"Minyak Goreng 2L": 33000,
"Tepung Terigu 1kg": 12000,
"Kopi Bubuk 200g": 24000,
"Teh Celup Isi 50": 9500,
"Susu Kental Manis": 11500,
"Sabun Mandi Batang": 4500,
"Shampoo Sachet": 3000,
"Deterjen Bubuk 800g": 18500,
}
PRODUK_MODAL = {
"Beras Premium 5kg": 52000,
"Gula Pasir 1kg": 11000,
"Minyak Goreng 2L": 26000,
"Tepung Terigu 1kg": 9000,
"Kopi Bubuk 200g": 17500,
"Teh Celup Isi 50": 7000,
"Susu Kental Manis": 8800,
"Sabun Mandi Batang": 3200,
"Shampoo Sachet": 2100,
"Deterjen Bubuk 800g": 13500,
}
PRODUK_KATEGORI = {
"Beras Premium 5kg": "Bahan Pokok",
"Gula Pasir 1kg": "Bahan Pokok",
"Minyak Goreng 2L": "Bahan Pokok",
"Tepung Terigu 1kg": "Bahan Pokok",
"Kopi Bubuk 200g": "Minuman",
"Teh Celup Isi 50": "Minuman",
"Susu Kental Manis": "Minuman",
"Sabun Mandi Batang": "Perawatan",
"Shampoo Sachet": "Perawatan",
"Deterjen Bubuk 800g": "Kebersihan",
}
KOTA_CABANG = ["Jakarta", "Bandung", "Surabaya", "Yogyakarta"]
VARIAN_LABEL = {
"Bahan Pokok": ["bahan pokok", "BAHAN POKOK", " Bahan Pokok "],
"Minuman": ["minuman", "MINUMAN", " Minuman "],
"Perawatan": ["perawatan", "PERAWATAN", " Perawatan "],
"Kebersihan": ["kebersihan", "KEBERSIHAN", " Kebersihan "],
}
def buat_dataset() -> pd.DataFrame:
"""Menghasilkan DataFrame mentah 246 baris dengan masalah yang dirancang."""
rng = np.random.default_rng(SEED)
n = JUMLAH_BARIS
produk = rng.choice(list(PRODUK_HARGA), size=n)
tanggal = pd.Series(
pd.Timestamp("2025-01-01")
+ pd.to_timedelta(rng.integers(0, 90, size=n), unit="D")
)
df = pd.DataFrame({
"id_transaksi": [f"TRX-{i + 1:04d}" for i in range(n)],
"tanggal": tanggal.dt.strftime("%Y-%m-%d"),
"produk": produk,
"kategori": pd.Series(produk).map(PRODUK_KATEGORI).to_numpy(),
"kota": rng.choice(KOTA_CABANG, size=n),
"jumlah": rng.integers(1, 11, size=n),
"harga": pd.Series(produk).map(PRODUK_HARGA).to_numpy(dtype="int64"),
})
# 1) label kategori tidak seragam (30 baris)
iks = rng.choice(n, size=30, replace=False)
for i, label in zip(iks, df.loc[iks, "kategori"]):
varian = VARIAN_LABEL[label]
df.loc[i, "kategori"] = varian[int(rng.integers(0, len(varian)))]
# 2) harga tidak valid: 0 atau negatif (6 baris)
iks = rng.choice(n, size=6, replace=False)
df.loc[iks[:4], "harga"] = 0
df.loc[iks[4:], "harga"] = -df.loc[iks[4:], "harga"]
# 3) kategori kosong (8 baris)
iks = rng.choice(n, size=8, replace=False)
df.loc[iks, "kategori"] = np.nan
# 4) format tanggal campur (36 baris, hanya hari 13-31 agar tidak ambigu)
kandidat = tanggal.index[tanggal.dt.day > 12].to_numpy()
iks = rng.choice(kandidat, size=36, replace=False)
df.loc[iks, "tanggal"] = tanggal.loc[iks].dt.strftime("%d/%m/%Y").to_numpy()
# 5) baris duplikat (6 baris salinan persis)
iks = rng.choice(n, size=JUMLAH_DUPLIKAT, replace=False)
df = pd.concat([df, df.loc[iks]], ignore_index=True)
# acak urutan baris agar masalah tersebar, lalu rapikan index
urutan = rng.permutation(len(df))
return df.iloc[urutan].reset_index(drop=True)
def buat_modal() -> pd.DataFrame:
"""Tabel harga modal (harga beli) per produk untuk latihan merge."""
return pd.DataFrame({
"produk": list(PRODUK_MODAL),
"harga_modal": list(PRODUK_MODAL.values()),
})
if __name__ == "__main__":
import tempfile
from pathlib import Path
df = buat_dataset()
with tempfile.TemporaryDirectory() as folder:
berkas = Path(folder) / "penjualan.csv"
df.to_csv(berkas, index=False)
print(
f"penjualan.csv siap: {df.shape[0]} baris x {df.shape[1]} kolom "
f"({berkas.stat().st_size} byte)"
)Output yang Dihasilkan:
penjualan.csv siap: 246 baris x 7 kolom (15797 byte)Fungsi buat_dataset() mengembalikan DataFrame 246 baris: 240 transaksi asli ditambah 6 salinan persis. Bagian paling bawah skrip, blok if __name__ == "__main__", menuliskan dataset ke penjualan.csv di dalam tempfile.TemporaryDirectory() hanya untuk melaporkan ukurannya; folder sementara itu dihapus otomatis sehingga tidak ada berkas yang tertinggal.
Memuat CSV dan Memeriksa Struktur #
Sekarang kita memperlakukan dataset seperti berkas kasir sungguhan: tulis ke CSV, muat ulang dengan read_csv(), lalu periksa strukturnya. Skrip berikut mencetak bentuk data, tipe tiap kolom, lima baris pertama, statistik ringkas, dan rekap masalah yang berhasil dideteksi.
"""Memuat penjualan.csv lalu memeriksa struktur dan masalah awal dataset."""
from pathlib import Path
import tempfile
import pandas as pd
from buat_dataset import PRODUK_KATEGORI, buat_dataset
if __name__ == "__main__":
with tempfile.TemporaryDirectory() as folder:
berkas = Path(folder) / "penjualan.csv"
buat_dataset().to_csv(berkas, index=False)
df = pd.read_csv(berkas)
print("== Struktur dataset ==")
print(f"bentuk: {df.shape[0]} baris x {df.shape[1]} kolom")
print(df.dtypes.to_string())
print("\n== Lima baris pertama ==")
print(df.head(5).to_string(index=False))
print("\n== Statistik ringkas kolom angka (dibulatkan 2 desimal) ==")
print(df[["jumlah", "harga"]].describe().round(2).to_string())
peta = df["produk"].map(PRODUK_KATEGORI)
tidak_seragam = (df["kategori"].notna() & (df["kategori"] != peta)).sum()
print("\n== Masalah yang ditemukan ==")
print(f"baris duplikat : {df.duplicated().sum()}")
print(f"harga 0 atau negatif : {(df['harga'] <= 0).sum()}")
print(f"kategori kosong (NaN) : {df['kategori'].isna().sum()}")
print(f"tanggal format DD/MM/YYYY : {df['tanggal'].str.contains('/').sum()}")
print(
f"label kategori tidak seragam: {tidak_seragam} baris, "
f"{df['kategori'].dropna().nunique()} bentuk penulisan"
)Output yang Dihasilkan:
== Struktur dataset ==
bentuk: 246 baris x 7 kolom
id_transaksi str
tanggal str
produk str
kategori str
kota str
jumlah int64
harga int64
== Lima baris pertama ==
id_transaksi tanggal produk kategori kota jumlah harga
TRX-0164 2025-03-29 Kopi Bubuk 200g Minuman Bandung 2 24000
TRX-0190 2025-03-06 Tepung Terigu 1kg Bahan Pokok Jakarta 1 12000
TRX-0048 2025-02-21 Shampoo Sachet Perawatan Jakarta 5 3000
TRX-0238 2025-01-02 Kopi Bubuk 200g Minuman Bandung 7 0
TRX-0017 2025-03-02 Teh Celup Isi 50 Minuman Jakarta 8 9500
== Statistik ringkas kolom angka (dibulatkan 2 desimal) ==
jumlah harga
count 246.00 246.00
mean 5.46 18239.84
std 3.01 18302.10
min 1.00 -68000.00
25% 2.25 4500.00
50% 6.00 12000.00
75% 8.00 24000.00
max 10.00 68000.00
== Masalah yang ditemukan ==
baris duplikat : 6
harga 0 atau negatif : 6
kategori kosong (NaN) : 8
tanggal format DD/MM/YYYY : 37
label kategori tidak seragam: 26 baris, 14 bentuk penulisanMari kita telaah bagian-bagian pentingnya:
- Blok
with tempfile.TemporaryDirectory(): dataset ditulis kepenjualan.csvdi folder sementara lalu dimuat ulang denganpd.read_csv(); pada pekerjaan nyata argumennya cukup diganti lokasi berkas Anda. df.shapedandf.dtypes: hasilnya 246 baris x 7 kolom. Perhatikan bahwa lima kolom teks bertipestr— ini perilaku bawaan pandas 3.0, bukan lagiobjectseperti pandas 2.x — sementarajumlahdanhargaterbacaint64.df.head(5): lima baris pertama sudah memperlihatkan bukti masalah, yaitu transaksiTRX-0238dengan harga0.describe(): ringkasan statistik memperlihatkanminharga-68000.00— harga negatif jelas tidak bermakna — sedangkan rentangjumlah1 sampai 10 masih wajar.- Pembandingan dengan
PRODUK_KATEGORI: karena satu produk selalu satu kategori, nilaikategoriyang berbeda dari peta produk ke kategori dihitung sebagai penulisan yang tidak seragam.
Daftar Masalah yang Terdeteksi #
Rekap di akhir output merangkum temuan dalam angka yang pasti:
| Masalah | Jumlah | Cara deteksi |
|---|---|---|
| Baris duplikat | 6 | df.duplicated() |
| Harga 0 atau negatif | 6 | (df["harga"] <= 0).sum() |
| Kategori kosong (NaN) | 8 | df["kategori"].isna() |
| Tanggal format DD/MM/YYYY | 37 | df["tanggal"].str.contains("/") |
| Label kategori tidak seragam | 26 baris, 14 bentuk | pembandingan dengan peta produk |
Lima masalah ini menjadi target kerja Bagian 2: enam baris duplikat dihapus, enam harga tidak valid dibuang, delapan kategori kosong diisi, 37 tanggal format campur diubah menjadi datetime64, dan 14 bentuk penulisan kategori diseragamkan. Teori masing-masing teknik sudah dibahas pada section Data Cleaning — antara lain chapter Menangani Nilai Duplikat, Mengisi Nilai Kosong, Memperbaiki Tipe Data, dan Standardisasi Label Kategori — sehingga di proyek ini kita fokus merangkainya menjadi satu prosedur utuh.
Sumber bacaan resmi: pandas.read_csv , pandas.DataFrame.duplicated , dan pandas.DataFrame.describe .