Pipeline dan ColumnTransformer

Pola kanonik praproses kolom campuran di dunia nyata.

Daftar isi

Data tabular pada pekerjaan nyata jarang berupa kolom numerik yang seragam. Umumnya satu DataFrame memuat kolom numerik yang membutuhkan scaling dan kolom kategorikal yang membutuhkan encoding, yaitu pengubahan nilai teks menjadi bentuk angka yang dapat dibaca model. Topik ini membahas pola kanonik untuk situasi tersebut, yaitu ColumnTransformer yang disarangkan di dalam Pipeline, beserta contoh lengkap pada DataFrame campuran kecil. Pola inilah yang digunakan berulang pada praproses dan proyek di bagian lain jalur ini.

Kolom campuran membutuhkan dua perlakuan#

ColumnTransformer menerima daftar transformasi berupa pasangan nama, transformer, dan daftar kolom yang dituju. Setiap transformer hanya bekerja pada kolomnya sendiri, kemudian seluruh hasil digabungkan menjadi satu matriks berurutan sesuai urutan daftarnya. Kolom yang tidak dituju oleh transformasi mana pun dibuang secara default, dan perilaku ini diatur oleh parameter remainder apabila kolomnya perlu dipertahankan. Satu objek ColumnTransformer dengan demikian menampung beberapa perlakuan yang berbeda di dalam satu antarmuka yang sama.

Pemisahan ini bukan sekadar kerapian. Transformer numerik seperti StandardScaler memerlukan masukan berupa angka, sedangkan OneHotEncoder bekerja pada teks, sehingga keduanya tidak mungkin dijalankan pada kelompok kolom yang salah tanpa memunculkan error. Dengan menyerahkan pemilihan kolom kepada ColumnTransformer, susunan yang benar menjadi bagian dari struktur objeknya, bukan disiplin yang bergantung pada ketelitian penulisan kode. Kolom yang dituju dapat ditulis sebagai daftar nama, daftar indeks, atau selector seperti make_column_selector yang memilih kolom menurut tipe datanya. Daftar nama digunakan pada contoh topik ini karena paling mudah dibaca, sedangkan selector berguna ketika jumlah kolom banyak dan tipe datanya sudah tertata.

Sub-pipeline numerik dan kategorikal#

Setiap kelompok kolom sering membutuhkan lebih dari satu transformasi. Kolom numerik lazim diimputasi lebih dahulu dengan SimpleImputer untuk mengisi nilai yang hilang, lalu diskalakan dengan StandardScaler. Kolom kategorikal lazim di-encode dengan OneHotEncoder, kadang setelah imputasi tersendiri. Rangkaian ini dinyatakan dengan Pipeline kecil yang disebut sub-pipeline, sehingga satu entri pada ColumnTransformer cukup merujuk satu objek yang berisi seluruh langkahnya.

Penyusunan bertingkat inilah yang membuat pola ini disebut kanonik. ColumnTransformer luar menangani pembagian kolom, sub-pipeline di dalamnya menangani urutan transformasi, dan Pipeline paling luar menghubungkan praproses dengan model. Ketiganya membentuk satu objek yang dipanggil dengan satu fit, tanpa variabel perantara yang rentan salah urutan. Sub-pipeline yang berisi hanya satu transformer, seperti encoder pada contoh di bawah, tetap boleh ditulis demikian agar kedua cabangnya berbentuk seragam. Bentuk seragam memudahkan penambahan langkah kelak, misalnya imputer kategorikal ketika data mengandung kategori yang hilang.

Pola lengkap pada DataFrame campuran#

Contoh berikut menyusun pola lengkapnya pada DataFrame sintetis kecil berisi dua kolom numerik dan satu kolom kategorikal, lalu melatih LogisticRegression di ujung Pipeline.

python pipeline-columntransformer.py
import numpy as np
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler

# DataFrame sintetis kecil: catatan jam belajar dan kehadiran siswa
rng = np.random.default_rng(42)
n = 60
df = pd.DataFrame({
    "jam_belajar": rng.uniform(1, 10, n).round(1),
    "kehadiran": rng.uniform(50, 100, n).round(1),
    "asal_sekolah": rng.choice(["negeri", "swasta"], n),
})

# target lulus mengikuti pola jam belajar dan kehadiran
z = df["jam_belajar"] * 0.6 + (df["kehadiran"] - 50) * 0.04 + rng.normal(0, 1.8, n)
df["lulus"] = (z > np.median(z)).astype(int)

X = df[["jam_belajar", "kehadiran", "asal_sekolah"]]
y = df["lulus"]

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

# sub-pipeline numerik dan kategorikal yang dipisah lalu disarangkan
numeric_pipeline = Pipeline([
    ("imputer", SimpleImputer(strategy="median")),
    ("scaler", StandardScaler()),
])
categorical_pipeline = Pipeline([
    ("encoder", OneHotEncoder(sparse_output=False, handle_unknown="ignore")),
])

# ColumnTransformer menyerahkan kolom sesuai daftarnya, lalu
# menggabungkan hasilnya menjadi satu matriks
preprocessor = ColumnTransformer([
    ("num", numeric_pipeline, ["jam_belajar", "kehadiran"]),
    ("cat", categorical_pipeline, ["asal_sekolah"]),
])

model = Pipeline(steps=[
    ("pre", preprocessor),
    ("model", LogisticRegression(max_iter=1000)),
])

model.fit(X_train, y_train)
print(f"Akurasi test: {model.score(X_test, y_test):.3f}")
print("Nama fitur keluaran preprocessor:")
for nama in model.named_steps["pre"].get_feature_names_out():
    print(" ", nama)
text Keluaran
Akurasi test: 0.733
Nama fitur keluaran preprocessor:
  num__jam_belajar
  num__kehadiran
  cat__asal_sekolah_negeri
  cat__asal_sekolah_swasta

Struktur pada kode mengikuti pola yang sudah dijelaskan, yaitu dua sub-pipeline yang disarangkan ke ColumnTransformer bernama pre, lalu pre dan model dirangkai dalam satu Pipeline. Akurasi 0.733 pada 15 baris data uji mencerminkan sinyal yang sengaja dibuat berderau pada data sintetisnya, bukan kegagalan pola. Bagian yang lebih berharga justru daftar nama fitur pada keluaran kedua. Setiap nama diawali label transformasinya, yaitu num__ untuk cabang numerik dan cat__ untuk cabang kategorikal, disusul nama kolom atau kategorinya. Kolom kategorikal tunggal menghasilkan dua kolom keluaran karena OneHotEncoder membuat satu kolom per kategori.

Parameter sparse_output=False membuat keluaran encoder berupa array padat, sehingga hasilnya mudah diperiksa dan kompatibel dengan langkah yang mengharapkan array. Objek pre diakses melalui model.named_steps["pre"], dan get_feature_names_out() pada objek tersebut mengembalikan nama fitur sesudah seluruh transformasi. Daftar nama ini juga menjadi acuan ketika koefisien model perlu disandingkan dengan fiturnya masing-masing.

make_column_transformer#

Sama seperti make_pipeline, terdapat make_column_transformer yang meringkas penamaan. Bentuk argumennya sedikit berbeda, yaitu tuple yang berisi transformer lebih dahulu dan daftar kolom kemudian. Objeknya dapat langsung disarangkan ke dalam Pipeline sebagai langkah praproses.

python Contoh: make_column_transformer dengan tuple dua elemen
from sklearn.compose import make_column_transformer
from sklearn.preprocessing import OneHotEncoder, StandardScaler

# make_column_transformer menamai langkah dari nama kelasnya
pre = make_column_transformer(
    (StandardScaler(), ["jam_belajar", "kehadiran"]),
    (OneHotEncoder(sparse_output=False), ["asal_sekolah"]),
)
print(pre)
text Keluaran
ColumnTransformer(transformers=[('standardscaler', StandardScaler(),
                                 ['jam_belajar', 'kehadiran']),
                                ('onehotencoder',
                                 OneHotEncoder(sparse_output=False),
                                 ['asal_sekolah'])])

Keluaran menunjukkan nama standardscaler dan onehotencoder yang diturunkan dari nama kelasnya, tanpa label num dan cat yang dapat dibaca manusia. Karena itu bentuk eksplisit dengan nama sendiri tetap lebih disukai pada kode yang akan dibaca ulang, sedangkan make_column_transformer cocok untuk percobaan cepat. Keduanya menghasilkan objek ColumnTransformer yang sama dan dapat disarangkan ke Pipeline dengan cara yang identik.

Kesalahan umum#

  • Menyerahkan seluruh DataFrame kepada satu transformer. StandardScaler yang menerima kolom teks langsung memunculkan error konversi, sedangkan OneHotEncoder yang menerima kolom numerik membuat kolom per nilai yang tidak bermakna. Perbaikannya dengan membagi kolom melalui ColumnTransformer sesuai tipenya.
  • Menyebut satu kolom pada dua cabang. Kolom yang dituju dua kali muncul dua kali pada matriks keluaran, sehingga model melihat fitur duplikat. Perbaikannya dengan memastikan daftar kolom tiap cabang saling lepas.
  • Mengasumsikan semua kolom ikut diproses. Kolom yang tidak tercantum pada cabang mana pun dibuang karena nilai default remainder adalah "drop". Perbaikannya dengan menyebutkan kolom tersebut secara eksplisit atau mengatur remainder="passthrough".

Ringkasan#

  • ColumnTransformer membagi kolom kepada transformer yang dituju, lalu menggabungkan seluruh hasil menjadi satu matriks dengan nama fitur berawalan label transformasinya.
  • Sub-pipeline numerik dan kategorikal menampung rangkaian transformasi per kelompok kolom, misalnya imputasi lalu scaling dan encoding.
  • Pola kanoniknya adalah ColumnTransformer sebagai langkah praproses di dalam Pipeline, disusul estimator di langkah terakhir.
  • make_column_transformer menghasilkan objek yang sama dengan penamaan otomatis dari nama kelasnya.

Referensi#