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:

TransformasiYang dilakukanMasalah yang dijawab
Log transformmemampatkan nilai besar via np.log1psebaran miring ke kanan, jangkauan ekstrem
Fitur interaksimengalikan dua kolom menjadi kolom baruefek yang baru muncul saat dua kondisi terpenuhi
Binningmemotong numerik menjadi kelompoknilai 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:

python
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:

text
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.23

Mari kita bedah baris per baris:

  1. 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.
  2. Baris 5 (np.log1p): menghitung log(1 + x) untuk setiap nilai. Awalan 1 pada log1p membuat nilai 0 aman diproses, sesuatu yang tidak bisa dilakukan np.log biasa.
  3. Baris 6 (np.expm1): kebalikan log1p, yaitu exp(x) - 1. Pasangan ini penting: prediksi model di ranah log harus bisa dikembalikan ke rupiah saat hasilnya disajikan.
  4. 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:

python
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:

text
   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.938

Tiga angka pada tiga baris terakhir adalah inti demonstrasi ini:

  1. 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.
  2. 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.
  3. 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:

python
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:

text
    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: int64

Mari kita amati perannya baris demi baris:

  1. Baris 9-13 (pd.cut(...)): argumen bins menetapkan batas kelompok — di bawah 25 ribu, 25 sampai di bawah 60, dan 60 ke atas. labels memberi nama tiap kelompok, dan right=False membuat batas kiri diikutkan (nilai 25 masuk kelompok “sedang”, bukan “kecil”).
  2. Baris 18-19 (value_counts(sort=False)): menghitung isi tiap kelompok. sort=False menjaga 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.log1p memampatkan ekor kanan sebaran dan aman untuk nilai nol; np.expm1 mengembalikan 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.cut merangkum 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.

Referensi #

  1. NumPy — The Absolute Basics for Beginners
  2. pandas — User Guide
  3. scikit-learn — Preprocessing Data