Dari 13.412 baris data transaksi yang berantakan menjadi dashboard yang bisa dipakai tim manajemen untuk memutuskan stok dan promosi.
Data saya ekspor dari sistem kasir dalam bentuk CSV. Sebelum mengubah apa pun, saya buka di Excel dan baca sekilas untuk mencari kejanggalan. Berikut cuplikan 8 baris pertama (total 13.412 baris, 8 kolom). Sel merah adalah masalah yang saya temukan.
| order_id | tgl_order | pelanggan | kota | kategori | qty | harga | status |
|---|---|---|---|---|---|---|---|
| 1001 | 05/01/2025 | Budi Santoso | Jakarta | Elektronik | 2 | Rp 350.000 | Selesai |
| 1002 | 2025-01-07 | siti aminah | jkt | Fashion | 1 | 150000 | selesai |
| 1002 | 2025-01-07 | siti aminah | jkt | Fashion | 1 | 150000 | selesai |
| 1003 | 12 Jan 2025 | Andi | Bandung | elektronik | -1 | 350000 | Batal |
| 1004 | 15/01/2025 | Dewi Lestari | Surabaya | Rumah Tangga | 3 | (kosong) | Selesai |
| 1005 | 20/01/2025 | Rina | Bandung | Fashion | 2 | Rp 120.000 | Selesai |
| 1006 | 3/2/25 | Agus | Medan | Makanan | 5 | 45000 | Selesai |
| 1007 | 03/02/2025 | Budi Santoso | Jakarta | Elektronik | 1 | 350000 | selesai |
Masalah yang ditemukan: format tanggal campur aduk, harga bertipe teks ("Rp 350.000"), penulisan kota dan kategori tidak seragam (jkt, Jakarta, elektronik), order_id ganda, qty negatif, dan harga kosong.
Saya kerjakan dengan Python (pandas) karena datanya sudah belasan ribu baris dan aturannya perlu bisa diulang. Setiap keputusan saya catat supaya bisa dipertanggungjawabkan.
import pandas as pd
df = pd.read_csv("penjualan_mentah.csv")
# 1. Hapus duplikat berdasarkan order_id
df = df.drop_duplicates(subset="order_id")
# 2. Samakan format tanggal
df["tgl_order"] = pd.to_datetime(df["tgl_order"], dayfirst=True, errors="coerce")
# 3. Harga: buang "Rp" dan titik, ubah ke angka
df["harga"] = (df["harga"].astype(str)
.str.replace(r"[^0-9]", "", regex=True)
.replace("", None).astype(float))
# 4. Standarisasi teks
df["kota"] = df["kota"].str.strip().str.title().replace({"Jkt": "Jakarta"})
df["kategori"] = df["kategori"].str.strip().str.title()
df["status"] = df["status"].str.strip().str.capitalize()
# 5. Harga kosong diisi median per kategori
df["harga"] = df["harga"].fillna(df.groupby("kategori")["harga"].transform("median"))
# 6. Buang pesanan batal dan qty tidak valid
df = df[(df["status"] == "Selesai") & (df["qty"] > 0)]
Hasilnya, sel yang tadinya merah kini seragam (hijau). Baris duplikat dan pesanan batal (dicoret) dibuang, dan kolom total saya tambahkan.
| order_id | tgl_order | pelanggan | kota | kategori | qty | harga | total |
|---|---|---|---|---|---|---|---|
| 1001 | 2025-01-05 | Budi Santoso | Jakarta | Elektronik | 2 | 350000 | 700000 |
| 1002 | 2025-01-07 | Siti Aminah | Jakarta | Fashion | 1 | 150000 | 150000 |
| 1003 | 2025-01-12 | Andi | Bandung | Elektronik | -1 | 350000 | dibuang (batal) |
| 1004 | 2025-01-15 | Dewi Lestari | Surabaya | Rumah Tangga | 3 | 85000 | 255000 |
| 1005 | 2025-01-20 | Rina | Bandung | Fashion | 2 | 120000 | 240000 |
| 1006 | 2025-02-03 | Agus | Medan | Makanan | 5 | 45000 | 225000 |
Rata-rata mudah tergeser harga barang mahal, sedangkan median lebih stabil. Hanya 0,6% baris yang diisi, jadi dampaknya ke total kecil. Asumsi ini saya catat di dokumentasi.
Data bersih saya muat ke MySQL lalu saya hitung metrik yang menjawab pertanyaan bisnis. Contoh query untuk pendapatan bulanan dan kategori:
-- Pendapatan per bulan
SELECT DATE_FORMAT(tgl_order, '%Y-%m') AS bulan,
COUNT(*) AS jumlah_order,
SUM(qty * harga) AS pendapatan
FROM penjualan_bersih
GROUP BY bulan
ORDER BY bulan;
-- Kontribusi tiap kategori
SELECT kategori,
SUM(qty * harga) AS pendapatan,
ROUND(100 * SUM(qty * harga) /
(SELECT SUM(qty * harga) FROM penjualan_bersih), 1) AS persen
FROM penjualan_bersih
GROUP BY kategori
ORDER BY pendapatan DESC;
Hasil query saya hubungkan ke Power BI. Versi di bawah adalah tampilan ringkasnya.