Sabtu–Minggu — EDA End-to-End & Mini Project: Analisis Penjualan + Segmentasi Pelanggan
Sabtu 4 jam + Minggu 4 jam (+lanjutan). Ini kickoff resmi portfolio project ecommerce-sales-analysis yang disebut di minggu_2.md — akan terus dikembangkan sampai Minggu 8.
Perbedaan penting dengan file
hari_1–hari_5: mulai file ini, dataset yang dipakai adalah data publik sungguhan (Sabtu) dan Online Retail II (Minggu — dataset asli, bukan versi mini yang dipakai Hari 1–5). Karena itu, kode di file ini adalah template/panduan, bukan hasil yang sudah diverifikasi angka pastinya seperti file-file sebelumnya — angka hasil analisis akan beda tergantung data yang benar-benar di-download peserta. Instruktur: jalankan sendiri dulu sebelum sesi untuk tahu angka aktualnya.
Tujuan Belajar
- Menjalankan alur EDA end-to-end secara mandiri pada dataset publik yang belum pernah dilihat sebelumnya
- Membangun ulang alur SQL + Python yang sudah dipelajari 2 minggu ini pada dataset dunia nyata yang jauh lebih besar & lebih kotor
- Menghasilkan repo GitHub pertama untuk portfolio (
ecommerce-sales-analysis) dengan struktur yang akan terus dipakai sampai Minggu 8
Untuk Instruktur
Ini sesi paling “lepas tangan” — tujuannya peserta latihan bekerja tanpa dituntun step-by-step seperti Hari 1–5. Peran instruktur di sini lebih ke fasilitator & code reviewer, bukan pengajar materi baru. Kalau peserta stuck di step teknis (bukan step konseptual), itu sinyal balik ke modul hari terkait di Minggu 1/2 — jangan langsung kasih jawaban.
Bagian 1 (Sabtu, 4 jam): EDA End-to-End pada Dataset Publik
Kenapa Latihan Ini Dulu, Bukan Langsung Online Retail II
Online Retail II (dipakai mulai besok) itu dataset besar (500rb+ baris) dengan masalah data quality yang nyata dan kadang butuh riset tambahan untuk dipahami. Sabtu ini pemanasan dengan dataset yang lebih kecil dan lebih rapi, supaya peserta latihan alur EDA lengkap tanpa terbebani kompleksitas data quality dulu.
Dataset Rekomendasi
Sample Superstore (Kaggle, sangat umum dipakai untuk latihan EDA) — kolom utamanya: Order ID, Order Date, Ship Date, Customer Name, Segment, Country, City, State, Category, Sub-Category, Sales, Quantity, Discount, Profit.
Alternatif: dataset publik lain dari Kaggle yang sudah disebut di minggu_1.md/minggu_2.md (Titanic, Northwind). Yang penting: data tabular, minimal 1 kolom tanggal, minimal 1 kolom kategori, minimal 1 kolom numerik untuk dianalisis.
Checklist Alur EDA (Framework Umum — Bisa Dipakai untuk Dataset Apapun)
1. Load & Inspect -> shape, dtypes, head(), info(), describe()
2. Data Quality Check -> missing values, duplicate, outlier kasar (describe() + boxplot)
3. Univariate Analysis -> distribusi tiap kolom penting (histogram numerik, value_counts kategorikal)
4. Bivariate Analysis -> hubungan 2 kolom (mis. Sales vs Discount, Sales per Category)
5. Time-based Analysis -> tren dari waktu ke waktu, kalau ada kolom tanggal
6. Ringkasan Temuan -> 3-5 insight dalam bahasa manusia, bukan cuma angka
Template Kode
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
df = pd.read_csv("superstore.csv", encoding="latin-1") # dataset Kaggle sering perlu encoding non-default
# 1. Load & Inspect
print(df.shape)
df.info()
df.describe(include="all")
# 2. Data Quality Check
print(df.isna().sum())
print(df.duplicated().sum())
# 3. Univariate
df["Sales"].hist(bins=30)
df["Category"].value_counts().plot(kind="bar")
# 4. Bivariate
sns.boxplot(data=df, x="Category", y="Sales")
df.groupby("Category")["Sales"].sum().sort_values(ascending=False)
# 5. Time-based
df["Order Date"] = pd.to_datetime(df["Order Date"])
df.set_index("Order Date")["Sales"].resample("M").sum().plot()
Deliverable Sabtu
Notebook (eda_superstore.ipynb atau nama dataset yang dipakai) berisi ke-6 langkah checklist di atas, dengan minimal 3 insight tertulis dalam bentuk teks (bukan cuma grafik tanpa penjelasan) di akhir notebook.
Bagian 2 (Minggu + lanjutan): Mini Project — Online Retail II
Mengikuti desain di
minggu_2.mdbagian “Mini Project: Analisis Performa Penjualan & Segmentasi Pelanggan E-Commerce”. File ini menjabarkan tiap tahap dengan kode konkret.
Tentang Dataset
Online Retail II — transaksi e-commerce UK, periode 2009–2011. Download dari Kaggle (“Online Retail II UCI”) atau UCI ML Repository. Kolom asli (perhatikan — beda dari dataset “Online Retail” versi lama yang sering dipakai di tutorial lain):
| Kolom | Isi |
|---|---|
Invoice | Nomor invoice. Kalau diawali 'C', itu transaksi pembatalan/retur. |
StockCode | Kode produk |
Description | Nama produk (ada baris dengan deskripsi aneh seperti "POSTAGE", "Manual", "DOTCOM POSTAGE" — itu bukan produk asli, tapi entri biaya/adjustment) |
Quantity | Jumlah unit. Bisa negatif — artinya retur/pembatalan, sama seperti pola yang sudah dibahas di hari_5_data_cleaning.md |
InvoiceDate | Tanggal & waktu transaksi |
Price | Harga per unit (bukan UnitPrice — nama kolom di versi “II” ini beda dari dataset “Online Retail” versi lama) |
Customer ID | ID pelanggan. Banyak baris NaN — transaksi tanpa akun/guest |
Country | Negara pelanggan |
Dataset ini datang sebagai 1 file flat (1 baris = 1 baris item dalam 1 invoice) — bukan sudah dinormalisasi seperti dataset mini Minggu 1–2. Normalisasi penuh jadi star schema baru jadi fokus eksplisit Minggu 3 (minggu_3.md) — minggu ini cukup 1 tabel bersih hasil cleaning.
Struktur Repo (sesuai minggu_2.md)
ecommerce-sales-analysis/
├── README.md
├── data/
├── sql/
│ ├── 01_data_cleaning.sql
│ ├── 02_sales_analysis.sql
│ └── 03_rfm_analysis.sql
├── notebooks/
│ └── analysis.ipynb
└── outputs/
└── charts/
mkdir -p ecommerce-sales-analysis/{data,sql,notebooks,outputs/charts}
cd ecommerce-sales-analysis && git init
Tahap 1: Setup & Data Cleaning (±2 jam)
Load & tulis ke database (pakai PostgreSQL yang sudah di-setup Minggu 1, pg-belajar):
import pandas as pd
from sqlalchemy import create_engine
raw = pd.read_excel("data/online_retail_II.xlsx", sheet_name="Year 2010-2011")
# atau pd.read_csv(...) kalau versi Kaggle yang dipakai formatnya CSV
engine = create_engine("postgresql://postgres:belajar@localhost:5432/postgres")
raw.to_sql("retail_raw", engine, if_exists="replace", index=False)
Cek & tangani data quality (checklist sesuai minggu_2.md: missing values, duplicate invoice, quantity negatif, harga 0/negatif — pola yang sudah dilatih persis di hari_5_data_cleaning.md):
print(raw.isna().sum()) # Customer ID biasanya paling banyak NaN
print(raw.duplicated().sum())
print((raw["Quantity"] < 0).sum()) # retur/pembatalan
print((raw["Price"] <= 0).sum()) # harga janggal — cek manual dulu, jangan langsung dibuang
# Dokumentasikan before/after row count (wajib, sesuai instruksi minggu_2.md)
n_before = len(raw)
clean = raw.dropna(subset=["Customer ID"]).copy() # buang transaksi tanpa customer ID
clean = clean.drop_duplicates()
clean = clean[clean["Price"] > 0] # buang baris harga 0/negatif (adjustment/error entri)
clean["is_return"] = clean["Invoice"].astype(str).str.startswith("C") # tandai retur, JANGAN dibuang — retur itu data valid
clean["revenue"] = clean["Quantity"] * clean["Price"]
n_after = len(clean)
print(f"Rows: {n_before} -> {n_after} ({n_before - n_after} dibuang, {100*(n_before-n_after)/n_before:.1f}%)")
clean.to_sql("retail_clean", engine, if_exists="replace", index=False)
Simpan query cleaning setara di sql/01_data_cleaning.sql (versi SQL dari langkah di atas, dijalankan di atas retail_raw untuk menghasilkan retail_clean — latihan bagus menerjemahkan balik logic Pandas ke SQL, kebalikan dari yang biasa dilakukan sepanjang Minggu 2 ini).
Catatan desain: dataset dimuat sebagai 1 tabel flat (retail_clean), bukan dipecah jadi customers/orders/order_items seperti dataset mini Minggu 1–2 — itu keputusan sadar (lihat catatan skema di atas), bukan langkah yang terlewat.
Tahap 2: Analisis dengan SQL (±3 jam)
Simpan di sql/02_sales_analysis.sql dan sql/03_rfm_analysis.sql. Semua query ini kelanjutan langsung teknik yang sudah dilatih di materi/minggu_1/ — kalau ada yang lupa sintaksnya, itu sinyal balik ke file hari_X terkait.
1. Top 10 produk terlaris (by revenue & by quantity)
SELECT "StockCode", "Description",
SUM(quantity) AS total_qty,
SUM(revenue) AS total_revenue
FROM retail_clean
WHERE is_return = FALSE
GROUP BY "StockCode", "Description"
ORDER BY total_revenue DESC
LIMIT 10;
2. Revenue per bulan/kuartal
SELECT DATE_TRUNC('month', "InvoiceDate") AS month, SUM(revenue) AS revenue
FROM retail_clean
WHERE is_return = FALSE
GROUP BY 1
ORDER BY 1;
3. Customer dengan total belanja tertinggi (CTE) — pola identik materi/minggu_1/hari_4_subquery_cte.md
WITH customer_revenue AS (
SELECT "Customer ID", SUM(revenue) AS total_revenue
FROM retail_clean
WHERE is_return = FALSE
GROUP BY "Customer ID"
)
SELECT * FROM customer_revenue ORDER BY total_revenue DESC LIMIT 10;
4. RFM Analysis (Recency, Frequency, Monetary) — window function
Ini pemakaian window function paling langsung berguna secara bisnis dari semua yang dipelajari sejauh ini. NTILE(n) — fungsi window baru, satu keluarga dengan RANK/ROW_NUMBER yang sudah dipelajari di materi/minggu_1/hari_5_window_function.md — membagi baris jadi n kelompok berukuran setara berdasarkan urutan, dipakai di sini untuk bikin skor 1–5 per dimensi RFM:
WITH rfm_raw AS (
SELECT "Customer ID",
MAX("InvoiceDate") AS last_purchase,
CURRENT_DATE - MAX("InvoiceDate")::date AS recency_days,
COUNT(DISTINCT "Invoice") AS frequency,
SUM(revenue) AS monetary
FROM retail_clean
WHERE is_return = FALSE
GROUP BY "Customer ID"
),
rfm_scored AS (
SELECT *,
NTILE(5) OVER (ORDER BY recency_days DESC) AS r_score, -- recency kecil = baru beli = skor tinggi
NTILE(5) OVER (ORDER BY frequency ASC) AS f_score,
NTILE(5) OVER (ORDER BY monetary ASC) AS m_score
FROM rfm_raw
)
SELECT * FROM rfm_scored;
5. Growth rate bulanan — LAG/LEAD, pola identik contoh #3 hari_5_window_function.md
WITH monthly AS (
SELECT DATE_TRUNC('month', "InvoiceDate") AS month, SUM(revenue) AS revenue
FROM retail_clean
WHERE is_return = FALSE
GROUP BY 1
)
SELECT month, revenue,
LAG(revenue) OVER (ORDER BY month) AS prev_month,
ROUND(100.0 * (revenue - LAG(revenue) OVER (ORDER BY month)) / LAG(revenue) OVER (ORDER BY month), 1) AS growth_pct
FROM monthly
ORDER BY month;
Tahap 3: Analisis & Visualisasi dengan Python (±3 jam)
rfm = pd.read_sql("SELECT * FROM rfm_scored", engine) # atau jalankan ulang query #4 di atas lewat pandas.read_sql
rfm["rfm_total"] = rfm["r_score"] + rfm["f_score"] + rfm["m_score"]
def segment_customer(row):
if row["r_score"] >= 4 and row["f_score"] >= 4 and row["m_score"] >= 4:
return "Champions"
elif row["f_score"] >= 4:
return "Loyal"
elif row["r_score"] <= 2:
return "At Risk"
elif row["rfm_total"] <= 6:
return "Lost"
else:
return "Regular"
rfm["segment"] = rfm.apply(segment_customer, axis=1)
print(rfm["segment"].value_counts())
Catatan penting: aturan segment_customer di atas adalah contoh titik awal, bukan aturan baku yang harus diikuti persis — batas angka (>= 4, <= 2, dst.) adalah keputusan bisnis yang sebaiknya didiskusikan/disesuaikan, bukan angka ajaib. Bagian ini bagus untuk didiskusikan di code review sesama peserta: kenapa pilih threshold segitu?
Visualisasi (simpan ke outputs/charts/):
import matplotlib.pyplot as plt
monthly_revenue = pd.read_sql("SELECT * FROM monthly", engine) # atau dari query #2 Tahap 2
monthly_revenue.plot(x="month", y="revenue", kind="line", title="Trend Revenue Bulanan")
plt.savefig("outputs/charts/monthly_revenue_trend.png")
rfm["segment"].value_counts().plot(kind="bar", title="Distribusi Customer Segment")
plt.savefig("outputs/charts/customer_segment_distribution.png")
Tahap 4: Business Insight & Recommendation (±2 jam)
Tulis di README.md, minimal 3–5 insight konkret dan actionable — bukan cuma deskripsi angka. Format yang berguna: Temuan → Implikasi Bisnis → Rekomendasi.
Contoh kerangka (isi angkanya sesuai hasil analisis kamu sendiri, karena datanya berbeda tiap peserta setelah proses cleaning):
Temuan: Segmen “At Risk” menyumbang X% dari total revenue historis tapi tidak ada transaksi dalam N hari terakhir. Implikasi: Ada risiko kehilangan revenue signifikan kalau segmen ini benar-benar churn. Rekomendasi: Campaign retensi tertarget (mis. diskon khusus) untuk segmen ini, prioritas di atas akuisisi customer baru.
Kriteria “Selesai” Minggu 2
- Repo
ecommerce-sales-analysisada di GitHub dengan struktur sesuai di atas sql/01_data_cleaning.sqlmendokumentasikan keputusan cleaning (bukan cuma kode, tapi juga row count before/after)- Semua 5 query di Tahap 2 jalan dan hasilnya masuk ke
notebooks/analysis.ipynb - Ada minimal 2 chart tersimpan di
outputs/charts/ README.mdberisi 3–5 insight bisnis dalam format Temuan → Implikasi → Rekomendasi- Bisa menjelaskan ke orang lain kenapa setiap keputusan cleaning diambil (bukan cuma “karena Pandas bisa”)
Kalau semua tercentang, lanjut ke minggu_3.md — repo yang sama akan di-upgrade jadi pipeline otomatis dengan Spark + Airflow.