Skip to main content

Persiapan Data: Missing Values dengan Scikit-Learn

Pada materi sebelumnya kita telah mempelajari beberapa cara menangani missing values menggunakan Pandas.

Pendekatan tersebut cukup mudah untuk eksplorasi data. Namun, ketika workflow Machine Learning menjadi lebih kompleks, kita membutuhkan pendekatan yang lebih terstruktur.

Scikit-Learn menyediakan beberapa tools untuk melakukan preprocessing secara sistematis, salah satunya adalah:

SimpleImputer

Dengan SimpleImputer, kita dapat menentukan strategi untuk mengisi missing values.

Kemudian dengan:

ColumnTransformer

kita dapat menerapkan strategi berbeda pada kolom yang berbeda.

Pada materi ini kita akan menggabungkan:

SimpleImputer

ColumnTransformer

OneHotEncoder

RandomForestRegressor

Mengapa Menggunakan Scikit-Learn?

Pada materi sebelumnya kita dapat melakukan:

df["Column"].fillna(...)

Pendekatan tersebut sangat berguna untuk eksplorasi.

Namun, dalam workflow Machine Learning kita sering memiliki banyak tahapan:

Missing Values

Encoding

Scaling

Model

Evaluation

Jika semua dilakukan secara manual, semakin besar kemungkinan terjadi kesalahan.

Scikit-Learn menyediakan tools seperti:

SimpleImputer
OneHotEncoder
ColumnTransformer
Pipeline

yang dapat digunakan untuk membuat workflow preprocessing yang lebih terstruktur.

Apa Itu Imputation?

Imputation adalah proses mengganti missing values dengan nilai pengganti.

Misalnya:

Odometer
---------
100000
120000
NaN
140000

Jika menggunakan mean:

NaN → 120000

Nilai 120000 tersebut merupakan hasil perhitungan dari data yang tersedia.

Apa Itu SimpleImputer?

SimpleImputer adalah class Scikit-Learn yang digunakan untuk menangani missing values menggunakan strategi tertentu.

Import:

from sklearn.impute import SimpleImputer

Contoh:

imputer = SimpleImputer(
strategy="mean"
)

Artinya:

Missing value

Diganti dengan mean

Strategi SimpleImputer

Beberapa strategi yang umum digunakan:

StrategyPenjelasan
meanMenggunakan rata-rata
medianMenggunakan median
most_frequentMenggunakan nilai yang paling sering muncul
constantMenggunakan nilai tertentu

Contoh:

SimpleImputer(strategy="mean")
SimpleImputer(strategy="median")
SimpleImputer(strategy="most_frequent")
SimpleImputer(
strategy="constant",
fill_value="missing"
)

Contoh Dataset

Kita akan menggunakan dataset:

car-sales-extended-missing-data.csv

Dataset tersebut memiliki beberapa kolom seperti:

Make
Colour
Doors
Odometer (KM)
Price

Contoh data:

MakeColourDoorsOdometer (KM)Price
ToyotaBlue41500004000
HondaRed41200005000
BMWNaN28000012000
NaNBlackNaN1000007000

Terdapat missing values pada beberapa feature.

Membaca Dataset

Kita mulai dengan mengimpor Pandas:

import pandas as pd

Kemudian membaca dataset:

car_sales_missing = pd.read_csv(
"car-sales-extended-missing-data.csv"
)

Periksa data:

car_sales_missing.head()

Memeriksa Missing Values

Gunakan:

car_sales_missing.isna().sum()

Contoh:

Make 50
Colour 50
Doors 50
Odometer (KM) 50
Price 50
dtype: int64

Jumlah sebenarnya bergantung pada dataset yang digunakan.

Menghapus Baris dengan Target Missing

Misalnya:

Price

merupakan target yang ingin diprediksi.

Jika Price missing, kita tidak memiliki label untuk sampel tersebut.

Kita dapat menghapus baris yang targetnya missing:

car_sales_missing = car_sales_missing.dropna(
subset=["Price"]
)

Atau:

car_sales_missing.dropna(
subset=["Price"],
inplace=True
)

Untuk workflow modern, assignment seperti:

car_sales_missing = car_sales_missing.dropna(
subset=["Price"]
)

sering lebih eksplisit dan mudah dilacak.

Mengapa Target Missing Dihapus?

Misalnya:

MakeColourDoorsOdometerPrice
ToyotaBlue41500005000
HondaRed4120000NaN

Kita mengetahui:

Make
Colour
Doors
Odometer

tetapi tidak mengetahui:

Price

Untuk supervised learning, model membutuhkan pasangan:

X → y

Jika y tidak tersedia, baris tersebut tidak dapat digunakan sebagai contoh training berlabel biasa.

Memisahkan X dan y

Setelah target missing ditangani:

X = car_sales_missing.drop(
"Price",
axis=1
)

y = car_sales_missing["Price"]

Sekarang:

X → features
y → target

Periksa:

X.head()

dan:

y.head()

Menentukan Kolom

Sekarang kita perlu menentukan jenis feature.

Misalnya:

Make
Colour

adalah categorical features.

Sedangkan:

Doors
Odometer (KM)

merupakan numerical features secara tipe data, tetapi Doors dapat diperlakukan sebagai kategori jika angka tersebut merepresentasikan kategori jumlah pintu.

Dalam contoh ini kita akan mempertahankan pendekatan:

Make
Colour
→ categorical

Doors
→ categorical

Odometer (KM)
→ numerical

Membuat SimpleImputer untuk Categorical Features

Untuk:

Make
Colour

kita dapat menggunakan:

cat_imputer = SimpleImputer(
strategy="constant",
fill_value="missing"
)

Artinya:

NaN

missing

Contoh:

Make

Toyota
Honda
NaN
BMW

menjadi:

Toyota
Honda
missing
BMW

Membuat SimpleImputer untuk Doors

Misalnya kita ingin menggunakan nilai:

4

sebagai pengganti missing value pada Doors.

Kita dapat membuat:

door_imputer = SimpleImputer(
strategy="constant",
fill_value=4
)

Maka:

Doors

4
4
NaN
2

menjadi:

4
4
4
2

Nilai 4 merupakan keputusan preprocessing berdasarkan konteks dataset. Pada dataset lain, strategi yang berbeda mungkin lebih sesuai.

Membuat SimpleImputer untuk Numerical Features

Untuk:

Odometer (KM)

kita dapat menggunakan mean:

num_imputer = SimpleImputer(
strategy="mean"
)

Jika:

Odometer

100000
120000
NaN
140000

maka missing value akan diganti dengan mean yang dipelajari oleh imputer.

Menentukan Kolom untuk Setiap Imputer

Kita dapat membuat list:

cat_features = [
"Make",
"Colour"
]

door_feature = [
"Doors"
]

num_features = [
"Odometer (KM)"
]

Sekarang kita memiliki:

cat_features

Make, Colour

door_feature

Doors

num_features

Odometer (KM)

Menggabungkan dengan ColumnTransformer

Import:

from sklearn.compose import ColumnTransformer

Kemudian:

imputer = ColumnTransformer(
transformers=[
(
"cat_imputer",
cat_imputer,
cat_features
),
(
"door_imputer",
door_imputer,
door_feature
),
(
"num_imputer",
num_imputer,
num_features
)
]
)

Sekarang kita memiliki satu object yang mengetahui:

Make + Colour

"missing"

Doors

4

Odometer

mean

Mengapa Menggunakan ColumnTransformer?

Setiap kolom dapat membutuhkan strategi preprocessing yang berbeda.

Misalnya:

Make

"missing"

Colour

"missing"

Doors

4

Odometer

mean

Daripada melakukan semuanya secara manual, ColumnTransformer memungkinkan kita menentukan preprocessing berdasarkan kelompok kolom.

Train-Test Split Sebelum Fitting Imputer

Ini merupakan bagian yang sangat penting.

Kita sebaiknya membagi data terlebih dahulu:

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(
X,
y,
test_size=0.2,
random_state=42
)

Kemudian imputer di-fit menggunakan training data:

X_train_filled = imputer.fit_transform(
X_train
)

Test data hanya ditransformasi:

X_test_filled = imputer.transform(
X_test
)

Perhatikan perbedaannya:

Training

fit_transform()

Test

transform()

Mengapa Tidak Langsung fit_transform(X)?

Jika kita melakukan:

filled_X = imputer.fit_transform(X)

sebelum train-test split, imputer mempelajari parameter dari seluruh dataset.

Misalnya num_imputer menggunakan:

strategy="mean"

maka mean tersebut dihitung menggunakan data training dan test.

Hal tersebut dapat menyebabkan informasi dari test set masuk ke preprocessing.

Ini merupakan bentuk data leakage.

Memahami Data Leakage

Data leakage terjadi ketika informasi yang seharusnya tidak tersedia pada saat training masuk ke proses pembelajaran atau preprocessing.

Contoh yang kurang tepat:

Seluruh Dataset


Fit Imputer


Train-Test Split

Pendekatan yang lebih aman:

Dataset


Train-Test Split

├──────────────┐
│ │
Training Test
│ │
▼ │
Fit Imputer │
│ │
▼ │
Transform │


Transform

Melihat Hasil Imputation

Setelah:

X_train_filled = imputer.fit_transform(
X_train
)

hasilnya dapat berupa array atau sparse representation tergantung transformer yang digunakan.

Kita dapat melihat:

print(X_train_filled)

Untuk memeriksa apakah masih terdapat missing values, kita dapat mengubah hasil menjadi DataFrame.

Contoh:

X_train_filled_df = pd.DataFrame(
X_train_filled,
columns=X_train.columns
)

Kemudian:

X_train_filled_df.isna().sum()

Hasilnya seharusnya:

Make 0
Colour 0
Doors 0
Odometer (KM) 0
dtype: int64

Menggabungkan Imputation dengan One-Hot Encoding

Setelah missing values ditangani, kita masih memiliki categorical features:

Make
Colour
Doors

Model membutuhkan representasi numerik.

Karena itu kita dapat menggunakan:

OneHotEncoder

Import:

from sklearn.preprocessing import OneHotEncoder

Kemudian:

one_hot = OneHotEncoder(
handle_unknown="ignore"
)

Mengapa handle_unknown="ignore"?

Bayangkan training data memiliki:

Toyota
Honda
BMW

Kemudian ketika model digunakan pada data baru terdapat:

Tesla

Jika encoder tidak dikonfigurasi untuk menangani kategori yang belum pernah dilihat, proses transformasi dapat menghasilkan error.

Dengan:

handle_unknown="ignore"

kategori yang tidak dikenal dapat ditangani tanpa membuat proses transformasi gagal.

ColumnTransformer untuk Encoding

Setelah imputation, kita dapat melakukan encoding.

Namun, ada pendekatan yang lebih baik daripada membuat dua tahap transformasi manual secara terpisah.

Kita dapat menggunakan Pipeline sehingga:

Imputation

Encoding

Model

dapat dikelola sebagai satu workflow.

Pipeline untuk Categorical Features

Import:

from sklearn.pipeline import Pipeline

Kemudian:

categorical_pipeline = Pipeline(
steps=[
(
"imputer",
SimpleImputer(
strategy="constant",
fill_value="missing"
)
),
(
"onehot",
OneHotEncoder(
handle_unknown="ignore"
)
)
]
)

Pipeline tersebut melakukan:

Categorical Data


SimpleImputer


OneHotEncoder


Numerical Representation

Pipeline untuk Numerical Features

Untuk Odometer (KM):

numeric_pipeline = Pipeline(
steps=[
(
"imputer",
SimpleImputer(
strategy="mean"
)
)
]
)

Workflow:

Numerical Data


SimpleImputer


Mean

Menggabungkan Pipeline dengan ColumnTransformer

Sekarang kita dapat membuat:

categorical_features = [
"Make",
"Colour",
"Doors"
]

numeric_features = [
"Odometer (KM)"
]

Kemudian:

preprocessor = ColumnTransformer(
transformers=[
(
"categorical",
categorical_pipeline,
categorical_features
),
(
"numeric",
numeric_pipeline,
numeric_features
)
]
)

Workflow menjadi:

Dataset

┌────────────┴────────────┐
│ │
Categorical Numerical
│ │
▼ ▼
SimpleImputer SimpleImputer
│ │
▼ │
OneHotEncoder │
│ │
└────────────┬────────────┘


Preprocessed Data

Membuat Model

Kita akan menggunakan:

RandomForestRegressor

Import:

from sklearn.ensemble import RandomForestRegressor

Kemudian:

model = RandomForestRegressor(
n_estimators=100,
random_state=42
)

Parameter:

n_estimators=100

berarti model menggunakan 100 decision trees dalam Random Forest.

Sedangkan:

random_state=42

membantu reproducibility untuk proses yang menggunakan randomness.

Menggabungkan Preprocessor dan Model

Sekarang kita dapat membuat Pipeline utama:

model_pipeline = Pipeline(
steps=[
(
"preprocessor",
preprocessor
),
(
"model",
model
)
]
)

Workflow:

Raw Data


Preprocessor

├── Categorical
│ ├── Imputation
│ └── One-Hot Encoding

└── Numerical
└── Imputation


RandomForestRegressor


Prediction

Training Model

Setelah Pipeline dibuat:

model_pipeline.fit(
X_train,
y_train
)

Di belakang layar, Scikit-Learn akan melakukan:

X_train


Fit Preprocessor


Transform X_train


Fit Random Forest


Model siap

Evaluasi Model

Setelah model dilatih:

score = model_pipeline.score(
X_test,
y_test
)

Kemudian:

print(
f"Model Score: {score:.4f}"
)

Perlu diingat bahwa arti score() bergantung pada estimator.

Untuk RandomForestRegressor, score() secara default menggunakan R² score.

Apa Itu R²?

R² atau coefficient of determination mengukur seberapa besar variasi target yang dapat dijelaskan oleh model relatif terhadap baseline tertentu.

Secara sederhana:

R² tinggi
→ model menjelaskan lebih banyak variasi target

R² rendah
→ model menjelaskan lebih sedikit variasi target

R² dapat bernilai negatif pada data test jika model bekerja lebih buruk daripada baseline tertentu.

Karena itu, jangan menganggap semua nilai score() sebagai accuracy.

Contoh Lengkap

Berikut implementasi lengkap menggunakan SimpleImputer, ColumnTransformer, OneHotEncoder, Pipeline, dan RandomForestRegressor.

import pandas as pd

from sklearn.model_selection import train_test_split
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.ensemble import RandomForestRegressor

# ==========================================
# 1. Membaca dataset
# ==========================================

car_sales = pd.read_csv(
"car-sales-extended-missing-data.csv"
)

# ==========================================
# 2. Menghapus baris yang tidak memiliki target
# ==========================================

car_sales = car_sales.dropna(
subset=["Price"]
)

# ==========================================
# 3. Memisahkan X dan y
# ==========================================

X = car_sales.drop(
"Price",
axis=1
)

y = car_sales["Price"]

# ==========================================
# 4. Menentukan feature
# ==========================================

categorical_features = [
"Make",
"Colour",
"Doors"
]

numeric_features = [
"Odometer (KM)"
]

# ==========================================
# 5. Membuat categorical pipeline
# ==========================================

categorical_pipeline = Pipeline(
steps=[
(
"imputer",
SimpleImputer(
strategy="constant",
fill_value="missing"
)
),
(
"onehot",
OneHotEncoder(
handle_unknown="ignore"
)
)
]
)

# ==========================================
# 6. Membuat numerical pipeline
# ==========================================

numeric_pipeline = Pipeline(
steps=[
(
"imputer",
SimpleImputer(
strategy="mean"
)
)
]
)

# ==========================================
# 7. Menggabungkan preprocessing
# ==========================================

preprocessor = ColumnTransformer(
transformers=[
(
"categorical",
categorical_pipeline,
categorical_features
),
(
"numeric",
numeric_pipeline,
numeric_features
)
]
)

# ==========================================
# 8. Membuat model
# ==========================================

model = RandomForestRegressor(
n_estimators=100,
random_state=42
)

# ==========================================
# 9. Membuat pipeline utama
# ==========================================

model_pipeline = Pipeline(
steps=[
(
"preprocessor",
preprocessor
),
(
"model",
model
)
]
)

# ==========================================
# 10. Train-test split
# ==========================================

X_train, X_test, y_train, y_test = train_test_split(
X,
y,
test_size=0.2,
random_state=42
)

# ==========================================
# 11. Training
# ==========================================

model_pipeline.fit(
X_train,
y_train
)

# ==========================================
# 12. Evaluasi
# ==========================================

score = model_pipeline.score(
X_test,
y_test
)

print(
f"Model Score: {score:.4f}"
)

Mengapa Pipeline Ini Lebih Baik?

Pada pendekatan manual, kita mungkin melakukan:

Fill Missing Values

One-Hot Encoding

Train-Test Split

Model

Jika urutannya salah, kita dapat mengalami data leakage.

Dengan Pipeline dan train-test split yang benar:

Raw Data


Train-Test Split

├─────────────┐
│ │
Training Test
│ │
▼ │
Preprocessor │
│ │
▼ │
Model │
│ │
└──────┐ │
│ │
▼ ▼
Evaluation

Parameter preprocessing dipelajari dari training data.

Test data hanya melalui transformasi yang sama.

Membandingkan Pendekatan Pandas dan Scikit-Learn

Pandas

Contoh:

df["Make"] = df["Make"].fillna(
"Missing"
)

df["Odometer (KM)"] = df[
"Odometer (KM)"
].fillna(
df["Odometer (KM)"].mean()
)

Kelebihan:

  • mudah dipahami;
  • praktis untuk eksplorasi;
  • cepat untuk manipulasi sederhana.

Kekurangan:

  • preprocessing perlu dikelola secara manual;
  • lebih mudah melakukan preprocessing dengan urutan yang salah;
  • kurang praktis ketika workflow semakin kompleks.

Scikit-Learn

Contoh:

SimpleImputer

dikombinasikan dengan:

ColumnTransformer

dan:

Pipeline

Kelebihan:

  • preprocessing terstruktur;
  • dapat digabungkan dengan model;
  • cocok untuk cross-validation;
  • cocok untuk hyperparameter tuning;
  • membantu menjaga konsistensi preprocessing;
  • mengurangi risiko data leakage ketika workflow dirancang dengan benar.

Perbedaan fit, transform, dan fit_transform

Memahami tiga method ini sangat penting.

fit()

Digunakan untuk mempelajari parameter dari data.

Contoh:

imputer.fit(X_train)

Jika menggunakan mean:

Training Data

fit()

Belajar nilai mean

transform()

Menggunakan parameter yang sudah dipelajari untuk mengubah data.

X_test_filled = imputer.transform(
X_test
)

fit_transform()

Menggabungkan:

fit()
+
transform()

Contoh:

X_train_filled = imputer.fit_transform(
X_train
)

Aturan Penting

Gunakan:

Training
→ fit_transform()

dan:

Test
→ transform()

Jangan:

Training
→ fit_transform()

Test
→ fit_transform()

karena test set tidak seharusnya digunakan untuk mempelajari parameter preprocessing.

Mengapa Data Tanpa Label Dihapus?

Misalnya dataset memiliki:

1000 rows

dan:

50 rows

tidak memiliki Price.

Jika kita menghapusnya:

1000

950

Kita kehilangan 50 sampel.

Namun, untuk supervised learning, model membutuhkan target untuk setiap sampel training.

Karena itu:

car_sales = car_sales.dropna(
subset=["Price"]
)

dapat menjadi langkah yang masuk akal.

Perlu diingat bahwa kehilangan data juga dapat memengaruhi ukuran sampel dan representasi dataset. Karena itu, keputusan tersebut tetap perlu mempertimbangkan konteks dataset.

Apakah Menghapus Data Selalu Lebih Buruk?

Tidak selalu.

Misalnya hanya:

1%

data yang memiliki target missing.

Menghapus data tersebut mungkin tidak terlalu berdampak pada ukuran dataset.

Namun jika:

40%

data tidak memiliki target, masalahnya menjadi jauh lebih serius.

Kita perlu mempertimbangkan:

  • mengapa target hilang;
  • berapa banyak data yang hilang;
  • apakah missingness berkaitan dengan karakteristik tertentu;
  • apakah ada sumber data lain;
  • apakah dataset masih representatif setelah removal.

Checklist

Sebelum menggunakan SimpleImputer, pastikan:

  • Dataset sudah dipahami.
  • Target sudah ditentukan.
  • Baris dengan target missing sudah ditangani.
  • Features dan target sudah dipisahkan.
  • Categorical features sudah diidentifikasi.
  • Numerical features sudah diidentifikasi.
  • Strategi imputation sudah dipilih.
  • Train-test split dilakukan sebelum fitting preprocessing.
  • SimpleImputer digunakan sesuai tipe data.
  • OneHotEncoder digunakan untuk categorical features.
  • ColumnTransformer digunakan untuk menggabungkan transformasi.
  • Pipeline digunakan untuk workflow yang terintegrasi.
  • Test set tidak digunakan untuk fitting preprocessing.

Ringkasan

Pada materi ini kita mempelajari cara menangani missing values menggunakan Scikit-Learn.

Konsep utamanya:

SimpleImputer

Mengisi Missing Values

Kemudian:

ColumnTransformer

Menerapkan preprocessing
pada kolom yang berbeda

Untuk categorical features:

SimpleImputer

OneHotEncoder

Untuk numerical features:

SimpleImputer

Kemudian semuanya dapat digabungkan:

Categorical Pipeline

├── Imputation
└── One-Hot Encoding


Numerical Pipeline

└── Imputation


ColumnTransformer


Model

Workflow lengkap:

Dataset

Remove missing target

X dan y

Train-Test Split

ColumnTransformer

SimpleImputer

OneHotEncoder

RandomForestRegressor

Evaluation

Hal terpenting dari materi ini bukan hanya bagaimana mengisi NaN, tetapi bagaimana melakukan preprocessing dengan cara yang konsisten dan bebas dari data leakage.

Cheat Sheet

KebutuhanKode
Import SimpleImputerfrom sklearn.impute import SimpleImputer
MeanSimpleImputer(strategy="mean")
MedianSimpleImputer(strategy="median")
Nilai konstanSimpleImputer(strategy="constant", fill_value=...)
Import ColumnTransformerfrom sklearn.compose import ColumnTransformer
Import Pipelinefrom sklearn.pipeline import Pipeline
One-Hot EncodingOneHotEncoder(handle_unknown="ignore")
Fit + Transform trainingfit_transform(X_train)
Transform testtransform(X_test)
Train modelpipeline.fit(X_train, y_train)
Prediksipipeline.predict(X_test)
Evaluasipipeline.score(X_test, y_test)