Angka di data nyata jarang datang dalam bentuk yang nyaman bagi model. Satu kolom bernilai 18, kolom lain bernilai 480.000; satu kolom berhubungan mulus dengan target, kolom lain hanya berhubungan setelah digabung dengan kolom ketiga. Transformasi variabel adalah upaya menulis ulang nilai-nilai itu — bukan mengubah datanya, tetapi mengubah cara datanya tersajikan — supaya pola menjadi lebih mudah dipelajari. Pada chapter ini kita bahas tiga transformasi fundamental: log transform untuk menundukkan nilai besar, fitur interaksi untuk menangkap efek gabungan, dan binning untuk merangkum numerik menjadi kelompok.
Mengapa Variabel Perlu Ditransformasi #
Ada dua keluhan klasik yang mendorong kita mentransformasi variabel. Pertama, skala yang berbeda-beda: nilai transaksi 18 ribu dan 480 ribu berbagi satu kolom, sehingga model berbasis jarak atau gradien tertarik hanya pada angka besar. Kedua, hubungan non-linear atau gabungan: pengaruh suhu terhadap penjualan es kopi misalnya baru terasa saat promosi juga berjalan — efek semacam ini tidak tertangkap bila kedua kolom berdiri sendiri.
Tiga transformasi pada chapter ini menjawab keluhan tersebut dari arah berbeda:
| Transformasi | Yang dilakukan | Masalah yang dijawab |
|---|---|---|
| Log transform | memampatkan nilai besar via np.log1p | sebaran miring ke kanan, jangkauan ekstrem |
| Fitur interaksi | mengalikan dua kolom menjadi kolom baru | efek yang baru muncul saat dua kondisi terpenuhi |
| Binning | memotong numerik menjadi kelompok | nilai ekstrem, kebutuhan segmen yang mudah dibaca |
Meluruskan Skala dengan Log Transform #
Nilai transaksi di kafe hampir selalu miring ke kanan (right-skewed): mayoritas pelanggan membayar puluhan ribu rupiah, tetapi beberapa pesanan besar mencapai ratusan ribu. Log transform memampatkan ekor kanan itu — jarak 18 ke 22 tetap terasa, sedangkan jarak 260 ke 480 menyusut — sehingga sebaran menjadi lebih seimbang. NumPy menyediakan log1p yang menghitung log(1 + x) dan pasangannya expm1 untuk mengembalikan nilai asli:
import numpy as np
transaksi_ribu = np.array([18, 22, 25, 30, 42, 55, 88, 150, 260, 480])
log_trans = np.log1p(transaksi_ribu)
kembali = np.expm1(log_trans)
selisih_maks = np.abs(kembali - transaksi_ribu).max()
print("nilai asli :", transaksi_ribu)
print("log1p :", log_trans.round(2))
print("expm1(log) :", kembali.round(2))
print("selisih maksimum asli vs balikan:", round(selisih_maks, 9))
print("rentang asli:", transaksi_ribu.max() - transaksi_ribu.min(),
"| rentang log:", round(log_trans.max() - log_trans.min(), 2))Output yang Dihasilkan:
nilai asli : [ 18 22 25 30 42 55 88 150 260 480]
log1p : [2.94 3.14 3.26 3.43 3.76 4.03 4.49 5.02 5.56 6.18]
expm1(log) : [ 18. 22. 25. 30. 42. 55. 88. 150. 260. 480.]
selisih maksimum asli vs balikan: 0.0
rentang asli: 462 | rentang log: 3.23Mari kita bedah baris per baris:
- Baris 3 (
transaksi_ribu = ...): sepuluh nilai transaksi dalam ribuan rupiah. Dua nilai terakhir adalah pesanan katering — ekor kanan yang membuat rata-rata tidak mewakili mayoritas pelanggan. - Baris 5 (
np.log1p): menghitunglog(1 + x)untuk setiap nilai. Awalan 1 padalog1pmembuat nilai 0 aman diproses, sesuatu yang tidak bisa dilakukannp.logbiasa. - Baris 6 (
np.expm1): kebalikanlog1p, yaituexp(x) - 1. Pasangan ini penting: prediksi model di ranah log harus bisa dikembalikan ke rupiah saat hasilnya disajikan. - Baris 12-13 (
print("rentang asli:", ...)): bukti daya kompresinya. Jangkauan yang semula 462 ribu rupiah menyusut menjadi 3,23 satuan log — nilai ekstrem tidak lagi mendominasi perhitungan jarak atau gradien.
Fitur Interaksi: Mengalikan Dua Kolom #
Model linear memperlakukan setiap feature secara terpisah: koefisien pengunjung dikalikan pengunjung, koefisien durasi dikalikan durasi, lalu dijumlahkan. Padahal sering kali pengaruh kedua kolom bersifat perkalian — pendapatan kafe kira-kira sebanding dengan banyaknya pengunjung dikali lama mereka bertahan. Kita uji dugaan itu pada delapan hari data:
import pandas as pd
hari = pd.DataFrame({
"pengunjung": [200, 300, 150, 250, 350, 70, 220, 280],
"durasi_menit": [30, 10, 12, 25, 8, 35, 15, 28],
"pendapatan_ribu": [520, 400, 250, 500, 380, 260, 330, 560],
})
hari["total_menit"] = hari["pengunjung"] * hari["durasi_menit"]
print(hari)
print()
print("korelasi pengunjung vs pendapatan:",
round(hari["pengunjung"].corr(hari["pendapatan_ribu"]), 3))
print("korelasi durasi_menit vs pendapatan:",
round(hari["durasi_menit"].corr(hari["pendapatan_ribu"]), 3))
print("korelasi total_menit vs pendapatan:",
round(hari["total_menit"].corr(hari["pendapatan_ribu"]), 3))Output yang Dihasilkan:
pengunjung durasi_menit pendapatan_ribu total_menit
0 200 30 520 6000
1 300 10 400 3000
2 150 12 250 1800
3 250 25 500 6250
4 350 8 380 2800
5 70 35 260 2450
6 220 15 330 3300
7 280 28 560 7840
korelasi pengunjung vs pendapatan: 0.533
korelasi durasi_menit vs pendapatan: 0.313
korelasi total_menit vs pendapatan: 0.938Tiga angka pada tiga baris terakhir adalah inti demonstrasi ini:
- Baris 9 (
hari["total_menit"] = ...): fitur interaksi dibuat semudah mengalikan dua kolom. Kolom baru ini menyimpan “total menit kehadiran” — besar bila pengunjung banyak dan mereka bertahan lama. - Korelasi 0,533 dan 0,313: masing-masing feature asli hanya berhubungan sedang dan lemah dengan pendapatan. Hari dengan durasi panjang bisa saja sepimu pengunjungnya (baris 5), sehingga durasi sendirian menyesatkan.
- Korelasi 0,938: hasil kalinya jauh lebih informatif daripada kedua komponennya. Inilah pola khas interaksi — informasi yang tersembunyi di relasi antar-kolom, bukan di satu kolom mana pun.
Interaksi tidak harus perkalian; rasio dua kolom (belanja / kunjungan) dan kombinasi boolean (promo * akhir_pekan) bekerja dengan prinsip sama. Kuncinya selalu ada dugaan domain yang jelas, bukan mengalikan semua kolom secara membabi buta — pasangan yang banyak akan memperbanyak feature dan menuntut lebih banyak data.
Binning: Merangkum Numerik Menjadi Kelompok #
Kadang kita justru ingin menghaluskan detail angka. Untuk segmentasi pelanggan, perbedaan transaksi 42 dan 45 ribu rupiah tidak bermakna — yang penting keduanya masuk kelompok “sedang”. pd.cut memotong kolom numerik menjadi kelompok berdasarkan batas nilai yang kita tetapkan:
import pandas as pd
transaksi = pd.DataFrame({
"nilai_ribu": [18, 22, 25, 30, 42, 55, 60, 88, 95, 150, 260, 480],
})
transaksi["kelompok"] = pd.cut(
transaksi["nilai_ribu"],
bins=[0, 25, 60, float("inf")],
labels=["kecil", "sedang", "besar"],
right=False,
)
print(transaksi)
print()
print("jumlah per kelompok:")
print(transaksi["kelompok"].value_counts(sort=False))Output yang Dihasilkan:
nilai_ribu kelompok
0 18 kecil
1 22 kecil
2 25 sedang
3 30 sedang
4 42 sedang
5 55 sedang
6 60 besar
7 88 besar
8 95 besar
9 150 besar
10 260 besar
11 480 besar
jumlah per kelompok:
kelompok
kecil 2
sedang 4
besar 6
Name: count, dtype: int64Mari kita amati perannya baris demi baris:
- Baris 9-13 (
pd.cut(...)): argumenbinsmenetapkan batas kelompok — di bawah 25 ribu, 25 sampai di bawah 60, dan 60 ke atas.labelsmemberi nama tiap kelompok, danright=Falsemembuat batas kiri diikutkan (nilai 25 masuk kelompok “sedang”, bukan “kecil”). - Baris 18-19 (
value_counts(sort=False)): menghitung isi tiap kelompok.sort=Falsemenjaga urutan mengikuti kategori dari kecil ke besar, bukan urutan frekuensi, sehingga hasil selalu terbaca dalam urutan alami.
Hasil binning adalah kolom kategorikal — kelima belas ribunya hilang dari pandangan model linear, dan pesanan 95 ribu dianggap setara dengan 480 ribu. Itu kelemahan sekaligus kekuatannya: detail hilang, tetapi pencilan ikut terkadung dalam kelompok “besar” dan hasilnya mudah dikomunikasikan ke tim bisnis.
Rangkuman #
- Log transform via
np.log1pmemampatkan ekor kanan sebaran dan aman untuk nilai nol;np.expm1mengembalikan hasil ke skala rupiah. - Fitur interaksi (perkalian dua kolom) membuka efek gabungan yang tak terlihat pada masing-masing kolom — pada contoh kita, korelasi melonjak dari 0,533 dan 0,313 menjadi 0,938.
pd.cutmerangkum numerik menjadi kelompok bernama; detail hilang, tetapi pencilan terkendali dan hasilnya mudah dibaca manusia.- Setiap transformasi sebaiknya digerakkan dugaan domain yang jelas, bukan sekadar mencoba-coba seluruh kemungkinan.
Kolom kelompok hasil binning tadi kini berisi teks: “kecil”, “sedang”, “besar”. Model belum bisa membacanya — dan di sanalah encoding data kategorikal berperan, pembahasan chapter berikutnya.