Lompat ke konten
nalar

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.

Unduh lab.ipynb

Lab Minggu 2 — Data Cleaning & Preprocessing

Tujuan lab:

  1. Membersihkan dataset pelanggan "kotor" langkah demi langkah.
  2. Membandingkan distribusi data sebelum vs. sesudah dibersihkan.
  3. Menerapkan normalisasi, diskretisasi, one-hot encoding.
  4. 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

  1. Ganti imputasi median pendapatan dengan median per kota (df.groupby("kota")["pendapatan"].transform("median")). Bandingkan hasilnya.
  2. Gunakan robust scaling (x - median) / IQR untuk total_belanja. Bandingkan dengan z-score.
  3. Berapa komponen PCA yang dibutuhkan untuk menjelaskan ≥ 90% varians?
  4. 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.