Feature Engineering: Resep Membuat Kolom yang Bikin Model Berguna
TL;DR
Feature engineering adalah proses bikin kolom baru dari data mentah supaya model punya sinyal yang lebih jelas buat belajar. Lima resep yang paling sering ngasih hasil: fitur dari kolom tanggal, rasio antar angka, agregat per pelanggan, encoding kolom kategori, dan binning angka jadi kelompok. Semua langkahnya harus dihitung dari data latih doang biar nggak bocor.
Feature engineering adalah proses bikin kolom baru dari data mentah supaya model punya sinyal yang lebih jelas buat dipelajari.
Di dataset latihan ngulikdata, ganti algoritma dari regresi logistik ke random forest naikin skor F1 dari 0,612 ke 0,647. Nambah 13 kolom turunan naikin skornya ke 0,741, pakai algoritma yang sama persis kayak awal.
Selisihnya jelas. Dan kolom turunan itu bikinnya nggak butuh teori berat, cuma butuh tahu apa yang penting di bisnisnya.
Apa itu feature engineering?
Feature engineering adalah kegiatan mengubah, menggabungkan, atau menurunkan kolom baru dari data mentah supaya pola yang relevan jadi lebih gampang ditangkap model. Contohnya ngubah kolom tanggal jadi jumlah hari sejak transaksi terakhir, atau ngubah dua kolom angka jadi satu kolom rasio.
Datanya nggak nambah. Yang nambah cuma cara nyajiin informasi yang udah ada.
Model nggak punya pengetahuan bisnis. Dia nggak tahu bahwa pelanggan yang belanja tiap 7 hari lalu tiba-tiba absen 40 hari itu tanda bahaya, kecuali kamu bikinin kolomnya.
Kenapa kolom turunan sering lebih ngefek daripada ganti algoritma?
Karena algoritma cuma bisa belajar dari informasi yang tersedia di kolom. Kalau sinyalnya kesimpan dalam bentuk selisih atau rasio yang belum dihitung, model harus nemuin sendiri lewat kombinasi aturan yang rumit. Bikinin kolomnya langsung, dan pekerjaan model jadi jauh lebih ringan.
Ini kelihatan jelas di model linear. Regresi logistik nggak bisa bikin pembagian antar kolom sendiri, jadi rasio harus kamu siapkan.
Di model berbasis pohon, efeknya lebih halus tapi tetap nyata. Pohon bisa niru rasio pakai banyak potongan aturan, tapi butuh jauh lebih banyak data buat nyampe hasil yang sama.
Resep 1: gimana bikin fitur dari kolom tanggal?
Kolom tanggal mentah hampir nggak berguna buat model. Pecah jadi komponen yang punya arti bisnis: hari dalam minggu, tanggal dalam bulan, jam, dan yang paling sering ngefek, selisih hari antara dua kejadian.
import pandas as pd
df["tanggal"] = pd.to_datetime(df["tanggal"])
tanggal_acuan = pd.Timestamp("2025-09-30")
df["hari_dalam_minggu"] = df["tanggal"].dt.dayofweek
df["tanggal_dalam_bulan"] = df["tanggal"].dt.day
df["jam_pesan"] = df["tanggal"].dt.hour
df["akhir_pekan"] = (df["tanggal"].dt.dayofweek >= 5).astype(int)
df["hari_sejak_beli"] = (tanggal_acuan - df["tanggal"]).dt.days
Buat konteks Indonesia, dua kolom tambahan sering ngasih lompatan besar. Pertama, penanda tanggal kembar seperti 9.9, 10.10, dan 11.11 karena kampanye marketplace ngumpul di situ. Kedua, penanda periode gajian yaitu tanggal 25 sampai akhir bulan.
df["tanggal_kembar"] = (
df["tanggal"].dt.day == df["tanggal"].dt.month
).astype(int)
df["periode_gajian"] = df["tanggal"].dt.day.between(25, 31).astype(int)
Kolom tanggal_kembar ini contoh fitur yang lahir dari pengetahuan pasar lokal. Nggak ada di tutorial mana pun, dan di data e-commerce Indonesia dampaknya kelihatan.
Detail cara ngolah tanggal di pandas ada di artikel accessor dt di pandas.
Resep 2: kapan rasio lebih berguna dari angka mentah?
Rasio lebih berguna waktu ukuran mutlak nggak sebanding antar baris. Belanja Rp 500.000 dari pelanggan yang biasa belanja Rp 2 juta itu penurunan, sementara angka yang sama dari pelanggan baru itu lonjakan. Rasio yang bikin dua kasus itu kebaca beda.
df["rasio_diskon"] = df["nilai_diskon"] / df["nilai_kotor"]
df["belanja_per_pesanan"] = df["total_belanja"] / df["jumlah_pesanan"]
df["rasio_batal"] = df["pesanan_batal"] / df["jumlah_pesanan"]
df["ongkir_per_belanja"] = df["total_ongkir"] / df["total_belanja"]
Satu hal yang wajib: lindungi dari pembagian nol.
import numpy as np
df["belanja_per_pesanan"] = np.where(
df["jumlah_pesanan"] > 0,
df["total_belanja"] / df["jumlah_pesanan"],
0
)
Rasio juga bikin nilai ekstrem lebih jinak. Pelanggan grosir yang belanja Rp 40 juta bakal jadi outlier di kolom total belanja, tapi rasio diskonnya bisa jadi normal aja.
Resep 3: gimana bikin fitur agregat per pelanggan?
Kelompokkan data transaksi per pelanggan, hitung ringkasannya, lalu tempelkan balik ke tiap baris. Fitur agregat ngasih konteks perilaku yang nggak kelihatan dari satu baris transaksi.
Tiga angka yang paling sering kepakai di ritel: kapan terakhir belanja, seberapa sering belanja, dan berapa nilainya.
agg = (
trx.groupby("customer_id")
.agg(
jumlah_pesanan=("order_id", "nunique"),
total_belanja=("nilai_bersih", "sum"),
rata_belanja=("nilai_bersih", "mean"),
std_belanja=("nilai_bersih", "std"),
pesanan_pertama=("tanggal", "min"),
pesanan_terakhir=("tanggal", "max"),
jumlah_kategori=("kategori", "nunique"),
)
.reset_index()
)
agg["umur_pelanggan_hari"] = (
agg["pesanan_terakhir"] - agg["pesanan_pertama"]
).dt.days
agg["jeda_rata_hari"] = (
agg["umur_pelanggan_hari"] / agg["jumlah_pesanan"].clip(lower=1)
)
Kolom jeda_rata_hari itu salah satu fitur paling kuat di prediksi pelanggan berhenti belanja. Dia nangkap ritme belanja tiap orang, bukan cuma jumlahnya.
Fitur turunan berikutnya bandingin jeda terakhir sama jeda biasanya.
agg["hari_sejak_terakhir"] = (
tanggal_acuan - agg["pesanan_terakhir"]
).dt.days
agg["rasio_jeda"] = (
agg["hari_sejak_terakhir"] / agg["jeda_rata_hari"].clip(lower=1)
)
Nilai rasio_jeda di atas 3 artinya pelanggan itu udah absen tiga kali lebih lama dari kebiasaannya. Kolom ini yang paling banyak nyumbang di contoh kasus di bawah.
Kalau kamu mau nempelin hasil agregat ke tiap baris tanpa merge, pakai pola di artikel groupby transform.
Resep 4: gimana nangani kolom kategori?
Pilih metodenya berdasarkan jumlah nilai unik. Di bawah 15 nilai, pakai one hot encoding yang bikin satu kolom nol dan satu per kategori. Di atas itu, pertimbangkan frequency encoding atau target encoding supaya jumlah kolomnya nggak meledak.
| Jumlah nilai unik | Metode | Catatan |
|---|---|---|
| 2 | Ubah jadi 0 dan 1 | Nggak perlu kolom tambahan |
| 3 sampai 15 | One hot encoding | Paling aman dan gampang dijelaskan |
| 15 sampai 50 | Frequency encoding | Ganti kategori dengan jumlah kemunculannya |
| Di atas 50 | Target encoding | Hitung di dalam lipatan cross validation |
| Punya urutan | Ordinal encoding | Contoh: kecil, sedang, besar |
from sklearn.preprocessing import OneHotEncoder
enc = OneHotEncoder(handle_unknown="ignore", sparse_output=False)
kolom_kategori = ["kanal", "metode_bayar", "tipe_pelanggan"]
hasil = enc.fit_transform(X_train[kolom_kategori])
Parameter handle_unknown="ignore" penting banget. Tanpa itu, kode kamu bakal error di produksi begitu ketemu metode bayar baru yang belum ada waktu latihan.
Buat frequency encoding, satu baris cukup.
peta_frekuensi = X_train["kode_pos"].value_counts(normalize=True)
X_train["kode_pos_freq"] = X_train["kode_pos"].map(peta_frekuensi)
X_test["kode_pos_freq"] = X_test["kode_pos"].map(peta_frekuensi).fillna(0)
Perhatikan barisnya. Peta frekuensi dihitung dari data latih, lalu dipakai ke data uji. Kalau kamu hitung dari seluruh data, itu kebocoran.
Cara lain pakai pandas ada di artikel get_dummies.
Resep 5: kapan angka perlu dijadiin kelompok?
Kelompokkan angka jadi rentang waktu hubungannya sama target nggak lurus. Contohnya umur pelanggan, di mana kelompok 25-34 tahun bisa berperilaku beda jauh dari kelompok 18-24 dan 35-44 tanpa pola naik turun yang teratur.
df["kelompok_umur"] = pd.cut(
df["umur"],
bins=[0, 24, 34, 44, 54, 100],
labels=["18-24", "25-34", "35-44", "45-54", "55+"]
)
df["tingkat_belanja"] = pd.qcut(
df["total_belanja"], q=5,
labels=["sangat rendah", "rendah", "sedang", "tinggi", "sangat tinggi"]
)
Fungsi cut pakai batas yang kamu tentukan. Fungsi qcut bagi rata berdasarkan jumlah baris, jadi tiap kelompok isinya sama banyak. Bedanya aku jelasin di artikel cut dan qcut.
Hati-hati satu hal: batas qcut harus dihitung dari data latih dan dipakai ulang ke data uji. Kalau dihitung ulang di data uji, kamu bocorin distribusi.
Hasil pengelompokan ini juga sering langsung kepakai buat segmentation di sisi marketing.
Gimana cara tahu fitur baru kamu beneran ngebantu?
Latih model dua kali dengan pembagian data yang sama, sekali tanpa fitur baru dan sekali dengan fitur baru, lalu bandingin skornya pakai cross validation. Kalau kenaikannya lebih kecil dari simpangan baku antar lipatan, fitur itu belum terbukti ngebantu.
from sklearn.model_selection import cross_val_score
skor_lama = cross_val_score(pipe, X_dasar, y, cv=5, scoring="f1")
skor_baru = cross_val_score(pipe, X_lengkap, y, cv=5, scoring="f1")
print(f"tanpa fitur baru: {skor_lama.mean():.3f} +/- {skor_lama.std():.3f}")
print(f"dengan fitur baru: {skor_baru.mean():.3f} +/- {skor_baru.std():.3f}")
Buat lihat kontribusi tiap kolom, pakai permutation importance yang ngukur seberapa turun skor kalau satu kolom diacak.
from sklearn.inspection import permutation_importance
hasil = permutation_importance(
model, X_test, y_test, n_repeats=10, random_state=42
)
urutan = hasil.importances_mean.argsort()[::-1]
for i in urutan[:10]:
print(f"{X_test.columns[i]:25s} {hasil.importances_mean[i]:.4f}")
Daftar teknik transformasi lain yang tersedia ada di dokumentasi preprocessing scikit-learn.
Contoh kasus: toko_berkah, dari 11 kolom jadi 24
Dataset latihan ngulikdata punya 4.180 pelanggan toko_berkah dengan 11 kolom mentah. Targetnya prediksi pelanggan berhenti belanja dalam 90 hari.
Aku uji tiga skenario pakai cross validation 5 lipatan dan metrik F1.
| Skenario | Jumlah kolom | Skor F1 | Simpangan |
|---|---|---|---|
| Kolom mentah, regresi logistik | 11 | 0,612 | 0,028 |
| Kolom mentah, random forest | 11 | 0,647 | 0,031 |
| Kolom turunan, regresi logistik | 24 | 0,741 | 0,024 |
Ganti algoritma naikin 0,035 poin. Nambah 13 kolom turunan naikin 0,129 poin, hampir empat kali lipatnya.
Tiga kolom yang paling banyak nyumbang menurut permutation importance:
rasio_jeda, yaitu jeda belanja terakhir dibagi jeda rata-rata pelanggan itu.jumlah_kategori, yaitu berapa kategori produk berbeda yang pernah dia beli.rasio_diskon, yaitu porsi diskon dari nilai belanja kotor.
Kolom kedua ngasih temuan bisnis yang langsung kepakai. Pelanggan yang pernah beli dari 4 kategori berbeda punya tingkat berhenti belanja 8,3 persen, sementara yang cuma pernah beli 1 kategori mencapai 31,7 persen.
Selisih 23,4 poin itu jadi dasar program rekomendasi lintas kategori buat pembeli baru. Angka itu lahir dari satu kolom turunan yang bikinnya cuma satu baris kode.
Kesalahan umum di feature engineering
- Ngitung statistik dari seluruh dataset. Rata-rata, batas bin, dan peta frekuensi harus dihitung dari data latih doang.
- Bikin fitur dari kolom yang belum ada saat prediksi. Kolom tanggal pembatalan nggak boleh dipakai buat nebak pesanan bakal batal.
- Nambah kolom tanpa alasan bisnis. Kalau kamu nggak bisa jelasin kenapa kolom itu masuk akal, kemungkinan besar dia cuma nambah risiko overfitting.
- Lupa handle_unknown di encoder. Kategori baru di produksi bikin pipeline mati.
- Nyimpen ratusan kolom hasil one hot dari kode pos. Ganti ke frequency encoding sebelum tabelnya melebar.
- Nggak nyimpen kode pembuatan fiturnya. Fitur yang nggak bisa dihitung ulang di produksi sama aja nggak ada.
Poin nomor 1 dan 2 penyebab paling sering skor lompat mendadak. Selalu split dulu, baru bikin fitur.
Ringkasan dan langkah berikutnya
Lima resep yang paling sering ngasih hasil: pecah kolom tanggal, bikin rasio, hitung agregat per pelanggan, encode kategori sesuai jumlah nilai uniknya, dan kelompokkan angka yang hubungannya nggak lurus.
Urutan kerjanya selalu sama. Split dulu, bikin fitur dari data latih, ukur kontribusinya pakai cross validation, buang yang nggak terbukti ngebantu.
Kalau skormu masih goyang setelah nambah fitur, cek dulu cara evaluasinya lewat cross validation. Dan buat ngitung agregat awal dari data yang masih di spreadsheet, SUMIFS bisa jadi titik mulai sebelum pindah ke Python.
Mau praktek langsung? Mulai latihan SQL gratis
Latihan interaktif, langsung di browser.
Artikel terkait
Pandas groupby: Agregasi Data ala Pivot di Python (2026)
groupby ngelompokin baris berdasarkan kolom, lalu ngitung ringkasan per grup kayak total atau rata-rata. Ini pivot-nya pandas.
Pandas to_excel: Ekspor DataFrame ke Excel (2026)
to_excel nyimpen DataFrame pandas jadi file Excel .xlsx. Ini cara pakainya, dari satu sheet sampai banyak sheet, plus setelan yang bikin hasilnya rapi.
Pandas read_excel: Baca File Excel di Python
pandas read_excel baca file Excel jadi DataFrame, bisa pilih sheet tertentu dan lompatin baris judul yang berantakan. Ini cara pakai plus parameter penting dengan contoh data toko.