Data Cleaning & Preprocessing
Penanganan nilai hilang, noise/outlier, inkonsistensi, normalisasi, diskretisasi (binning), binerisasi, dan reduksi dimensi dengan PCA.
Notebook lab (pratinjau)
Unduh lalu jalankan di Jupyter, VS Code, atau Google Colab.
Lab Minggu 2 — Data Cleaning & Preprocessing
Tujuan lab:
- Membersihkan dataset pelanggan "kotor" langkah demi langkah.
- Membandingkan distribusi data sebelum vs. sesudah dibersihkan.
- Menerapkan normalisasi, diskretisasi, one-hot encoding.
- Mereduksi dimensi dengan PCA (manual NumPy vs. scikit-learn).
Baca lesson.md dulu. Jalankan sel satu per satu (Shift+Enter).
[1]
import sys
sys.path.insert(0, "..")
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from common.datasets import pelanggan_kotor
df_asli = pelanggan_kotor()
df = df_asli.copy()
print(df.shape)
df.head()
1. Seragamkan kategori
[2]
print("Sebelum:", df["kota"].nunique(), "nilai unik kota")
df["kota"] = df["kota"].str.strip().str.title()
print("Sesudah:", df["kota"].nunique(), "nilai unik kota")
peta_gender = {"L": "L", "l": "L", "Laki-laki": "L", "P": "P", "p": "P", "Perempuan": "P"}
df["gender"] = df["gender"].map(peta_gender)
df["gender"].value_counts()
2. Hapus duplikat
[3]
print("Duplikat:", df.duplicated().sum())
df = df.drop_duplicates().reset_index(drop=True)
print("Ukuran baru:", df.shape)
3. Noise & outlier
Umur di luar rentang wajar (17–80) adalah error input → jadikan NaN.
[4]
mustahil = (df["umur"] < 17) | (df["umur"] > 80)
print(df.loc[mustahil, "umur"].tolist())
df.loc[mustahil, "umur"] = np.nan
total_belanja punya beberapa nilai sangat besar. Kita clip ke batas Tukey dengan k = 3
(lebih longgar, karena pelanggan besar memang ada).
[5]
q1, q3 = df["total_belanja"].quantile([0.25, 0.75])
iqr = q3 - q1
atas = q3 + 3 * iqr
print(f"Batas atas: {atas:,.0f}; jumlah di atas batas: {(df['total_belanja'] > atas).sum()}")
df["total_belanja"] = df["total_belanja"].clip(upper=atas, lower=q1 - 3 * iqr)
4. Imputasi nilai hilang
[6]
print(df.isna().sum())
for kol in ["umur", "pendapatan", "frekuensi_beli", "total_belanja"]:
df[kol] = df[kol].fillna(df[kol].median())
df["kota"] = df["kota"].fillna(df["kota"].mode().iloc[0])
print("Sisa NaN:", df.isna().sum().sum())
5. Sebelum vs. sesudah
[7]
fig, axes = plt.subplots(2, 2, figsize=(12, 6))
for j, kol in enumerate(["umur", "total_belanja"]):
axes[0, j].hist(df_asli[kol].dropna(), bins=40, color="tab:red")
axes[0, j].set_title(f"{kol} — sebelum")
axes[1, j].hist(df[kol], bins=40, color="tab:green")
axes[1, j].set_title(f"{kol} — sesudah")
plt.tight_layout()
plt.show()
[8]
pd.DataFrame({
"mean_sebelum": df_asli[["umur", "total_belanja"]].mean(),
"mean_sesudah": df[["umur", "total_belanja"]].mean(),
"std_sebelum": df_asli[["umur", "total_belanja"]].std(),
"std_sesudah": df[["umur", "total_belanja"]].std(),
}).round(1)
6. Normalisasi
[9]
from sklearn.preprocessing import MinMaxScaler, StandardScaler
num = ["umur", "pendapatan", "frekuensi_beli", "total_belanja"]
mm = pd.DataFrame(MinMaxScaler().fit_transform(df[num]), columns=num)
zs = pd.DataFrame(StandardScaler().fit_transform(df[num]), columns=num)
print("Min-max:\n", mm.describe().loc[["min", "max"]].round(2))
print("\nZ-score:\n", zs.describe().loc[["mean", "std"]].round(2))
Manual (harus sama dengan scikit-learn):
[10]
x = df["pendapatan"]
manual = (x - x.mean()) / x.std(ddof=0)
print(np.allclose(manual, zs["pendapatan"]))
7. Diskretisasi & encoding
[11]
df["kelompok_umur_lebar"] = pd.cut(df["umur"], 5)
df["kelompok_umur_frek"] = pd.qcut(df["umur"], 5)
print(df["kelompok_umur_lebar"].value_counts().sort_index())
print(df["kelompok_umur_frek"].value_counts().sort_index())
[12]
df["member_kode"] = df["member"].map({"silver": 0, "gold": 1, "platinum": 2}) # ordinal
oh = pd.get_dummies(df[["kota", "gender"]], dtype=int) # nominal
oh.head()
8. PCA — manual vs. scikit-learn
[13]
from sklearn.decomposition import PCA
X = zs.to_numpy()
Xc = X - X.mean(axis=0)
C = np.cov(Xc, rowvar=False)
eigval, eigvec = np.linalg.eigh(C)
urut = np.argsort(eigval)[::-1]
eigval, eigvec = eigval[urut], eigvec[:, urut]
print("Explained variance ratio (manual) :", (eigval / eigval.sum()).round(3))
pca = PCA().fit(X)
print("Explained variance ratio (sklearn):", pca.explained_variance_ratio_.round(3))
[14]
plt.plot(range(1, 5), np.cumsum(pca.explained_variance_ratio_), marker="o")
plt.axhline(0.9, ls="--", c="gray")
plt.xlabel("Jumlah komponen"); plt.ylabel("Kumulatif explained variance")
plt.title("Scree plot (kumulatif)")
plt.show()
[15]
Z = PCA(2).fit_transform(X)
plt.scatter(Z[:, 0], Z[:, 1], c=df["member_kode"], cmap="viridis", s=10)
plt.xlabel("PC1"); plt.ylabel("PC2"); plt.title("Proyeksi PCA 2D (warna = member)")
plt.colorbar(label="member")
plt.show()
9. Versi scikit-learn Pipeline (praktik industri)
[16]
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder
pre = ColumnTransformer([
("num", Pipeline([("imp", SimpleImputer(strategy="median")), ("sc", StandardScaler())]), num),
("cat", Pipeline([("imp", SimpleImputer(strategy="most_frequent")),
("oh", OneHotEncoder(handle_unknown="ignore"))]), ["kota", "gender"]),
])
hasil = pre.fit_transform(df)
print(hasil.shape)
✍️ Coba sendiri
- Ganti imputasi median
pendapatandengan median per kota (df.groupby("kota")["pendapatan"].transform("median")). Bandingkan hasilnya. - Gunakan robust scaling
(x - median) / IQRuntuktotal_belanja. Bandingkan dengan z-score. - Berapa komponen PCA yang dibutuhkan untuk menjelaskan ≥ 90% varians?
- Tulis laporan ringkas (3–5 kalimat): perbedaan distribusi sebelum vs. sesudah dibersihkan.
[17]
# 1.
[18]
# 2.
[19]
# 3.
Laporan no. 4:
- ...
Selanjutnya
Kerjakan kuis dan exercise.py, lalu jalankan python check.py 2 dari folder utama.