Agregasi pada chapter sebelumnya memperkaya tabel kita dengan banyak kolom baru — dan di situlah persoalan baru bermula: semakin banyak fitur, semakin besar peluang dua di antaranya mengukur hal yang sama. Jumlah cangkir yang terjual dan pendapatan harian, misalnya, hampir selalu bergerak seiring karena pendapatan memang berasal dari cangkir. Dua kolom yang bergerak bersama seperti itu tidak membawa informasi baru, tetapi menimbulkan multikolinearitas — kondisi yang membuat koefisien model linear sulit ditafsirkan dan mudah menyesatkan. Pada chapter ini kita belajar menemukan pasangan fitur berkorelasi kuat lewat matriks korelasi, lalu merawatnya dengan dua strategi: membuang salah satu anggota pasangan atau menggabungkan keduanya menjadi rasio.

Ketika Dua Fitur Mengukur Hal yang Sama #

Multikolinearitas terjadi ketika dua fitur atau lebih berkorelasi kuat satu sama lain, sehingga model tidak sanggup memisahkan kontribusi masing-masing fitur. Ibarat dua orang bercerita dengan kalimat yang hampir sama, pendengar tahu informasinya penting, tetapi tidak bisa menilai siapa yang sebenarnya menyampaikannya — dan batas ibaratnya ada di situ: komputer tidak menimbang cerita, ia hanya menerima angka. Model linear menghadapi dilema yang persis sama: ketika cangkir dan pendapatan_ribu naik bersamaan, kenaikan penjualan makanan bisa diatribusikan ke salah satunya dengan hasil prediksi yang sama baiknya. Prediksi tetap aman; yang rusak adalah tafsiran koefisiennya.

Kita pakai data dua belas cabang Toko Kopi Kita. Tiga kolom menjadi fitur — jumlah cangkir terjual per hari, pendapatan harian dalam ribuan rupiah, dan jumlah kursi — sedangkan porsi makanan terjual menjadi target yang ingin kita jelaskan. Sebelum menyentuh model, kita periksa dulu korelasi antar kolomnya:

python
import pandas as pd

cabang = pd.DataFrame({
    "cangkir": [120, 95, 150, 80, 200, 175, 110, 140, 90, 160, 130, 105],
    "pendapatan_ribu": [2824, 2228, 3551, 1872, 4713, 4051, 2593, 3332, 2106, 3759, 3053, 2459],
    "kursi": [23, 22, 20, 18, 29, 35, 21, 18, 22, 24, 26, 23],
    "makanan_porsi": [49, 38, 56, 32, 79, 79, 45, 47, 39, 62, 54, 43],
})
print(cabang.head(5).to_string(index=False))
print()
print(cabang.corr().round(2))

Output yang Dihasilkan:

text
 cangkir  pendapatan_ribu  kursi  makanan_porsi
     120             2824     23             49
      95             2228     22             38
     150             3551     20             56
      80             1872     18             32
     200             4713     29             79

                 cangkir  pendapatan_ribu  kursi  makanan_porsi
cangkir             1.00             1.00   0.65           0.96
pendapatan_ribu     1.00             1.00   0.62           0.95
kursi               0.65             0.62   1.00           0.82
makanan_porsi       0.96             0.95   0.82           1.00

Mari kita bedah baris demi baris:

  1. Baris 3-8 (cabang = pd.DataFrame(...)): dua belas cabang dengan tiga fitur dan satu target. Perhatikan kolom pendapatan_ribu: nilainya selalu bermain di sekitar 23 sampai 24 kali cangkir — sekadar harga secangkir kopi dikalikan jumlahnya — sehingga kedua kolom nyaris saling menyalin.
  2. Baris 10 (cabang.corr().round(2)): corr() menghitung korelasi Pearson untuk setiap pasangan kolom numerik, dan round(2) menjaga tabelnya mudah dibaca.
  3. Pembacaan matriks: cangkir dan pendapatan_ribu berkorelasi 1,00 — tumpang tindih nyaris sempurna. Sebaliknya, kursi hanya berkorelasi 0,65 dan 0,62 dengan keduanya, jadi masih membawa informasi tersendiri. Baris makanan_porsi memang berkorelasi tinggi dengan hampir semua fitur, dan itu bukan masalah — korelasi dengan target justru yang kita cari.

Menyaring Pasangan dengan Ambang 0,9 #

Matriks korelasi empat kolom masih mudah dibaca mata; matriks empat puluh kolom jelas tidak. Kita membutuhkan cara terprogram: susun semua pasangan menjadi tabel panjang, pastikan tiap pasangan hanya muncul sekali, lalu saring yang korelasinya melewati ambang. Ambang 0,9 adalah titik awal yang lazim — di atasnya, dua fitur praktis saling menyalin dan salah satu layak dikeluarkan.

python
import numpy as np
import pandas as pd

cabang = pd.DataFrame({
    "cangkir": [120, 95, 150, 80, 200, 175, 110, 140, 90, 160, 130, 105],
    "pendapatan_ribu": [2824, 2228, 3551, 1872, 4713, 4051, 2593, 3332, 2106, 3759, 3053, 2459],
    "kursi": [23, 22, 20, 18, 29, 35, 21, 18, 22, 24, 26, 23],
    "makanan_porsi": [49, 38, 56, 32, 79, 79, 45, 47, 39, 62, 54, 43],
})

matriks_fitur = cabang[["cangkir", "pendapatan_ribu", "kursi"]].corr()
segitiga_atas = np.triu(np.ones(matriks_fitur.shape), k=1).astype(bool)
pasangan = matriks_fitur.where(segitiga_atas).stack().reset_index()
pasangan.columns = ["fitur_1", "fitur_2", "korelasi"]
print(pasangan.to_string(index=False))
print()
ambang = 0.9
print(pasangan[pasangan["korelasi"].abs() > ambang].to_string(index=False))

Output yang Dihasilkan:

text
        fitur_1         fitur_2  korelasi
        cangkir pendapatan_ribu  0.999589
        cangkir           kursi  0.645169
pendapatan_ribu           kursi  0.624760

fitur_1         fitur_2  korelasi
cangkir pendapatan_ribu  0.999589

Pekerjaan tiap barisnya:

  1. Baris 5 (cabang[[...]].corr()): matriks korelasi dihitung hanya dari tiga fitur — target sengaja dikeluarkan karena ambang berlaku untuk korelasi antar fitur, bukan korelasi dengan target.
  2. Baris 6 (np.triu(np.ones(...), k=1)): np.triu menandai segitiga atas matriks; k=1 membuang diagonalnya sehingga pasangan A-B terhitung satu kali, tanpa kembarannya B-A dan tanpa pasangan A-A.
  3. Baris 7-9 (where(...).stack().reset_index()): where mengosongkan sel di luar segitiga atas, stack() melipat matriks menjadi tabel panjang tiga kolom, dan reset_index() merapikan nama kolomnya.
  4. Baris 12-13 (penyaringan abs() > ambang): hanya cangkir–pendapatan_ribu yang lolos ambang, dengan r = 0.999589. Dua pasangan lain aman karena berada di bawah 0,7.

Multikolinearitas Mengaburkan Koefisien #

Apa sebenarnya bahayanya jika kedua fitur tetap kita masukkan? Untuk menjawabnya, kita latih dua model regresi linear dengan target makanan_porsi: model pertama memakai semua fitur, model kedua membuang pendapatan_ribu. Bila multikolinearitas tidak berefek, koefisien kedua model seharusnya tidak jauh berbeda.

python
import pandas as pd
from sklearn.linear_model import LinearRegression

cabang = pd.DataFrame({
    "cangkir": [120, 95, 150, 80, 200, 175, 110, 140, 90, 160, 130, 105],
    "pendapatan_ribu": [2824, 2228, 3551, 1872, 4713, 4051, 2593, 3332, 2106, 3759, 3053, 2459],
    "kursi": [23, 22, 20, 18, 29, 35, 21, 18, 22, 24, 26, 23],
    "makanan_porsi": [49, 38, 56, 32, 79, 79, 45, 47, 39, 62, 54, 43],
})

X = cabang[["cangkir", "pendapatan_ribu", "kursi"]]
y = cabang["makanan_porsi"]

model_penuh = LinearRegression().fit(X, y)
model_ringkas = LinearRegression().fit(X[["cangkir", "kursi"]], y)

koef_penuh = pd.Series(model_penuh.coef_, index=X.columns, name="semua_fitur")
koef_ringkas = pd.Series(model_ringkas.coef_, index=["cangkir", "kursi"],
                         name="tanpa_pendapatan")
print(pd.concat([koef_penuh, koef_ringkas], axis=1).round(3))
print()
print("R2 semua fitur     :", round(model_penuh.score(X, y), 3))
print("R2 tanpa pendapatan:", round(model_ringkas.score(X[["cangkir", "kursi"]], y), 3))

Output yang Dihasilkan:

text
                 semua_fitur  tanpa_pendapatan
cangkir                1.703             0.302
pendapatan_ribu       -0.058               NaN
kursi                  0.746             1.107

R2 semua fitur     : 0.994
R2 tanpa pendapatan: 0.993

Mari kita baca hasilnya:

  1. Baris 6-7 (X, y): X memuat tiga fitur dan y adalah target. Konvensi huruf besar-kecil ini mengikuti kebiasaan scikit-learn.
  2. Baris 9-10 (dua LinearRegression().fit(...)): model penuh belajar dari tiga fitur; model ringkas hanya dari cangkir dan kursi.
  3. Baris 12-16 (tabel koefisien): koefisien kedua model disusun berdampingan lewat pd.concat; sel NaN menandakan fitur yang tidak dipakai oleh model kedua.
  4. Pembacaan koefisien: koefisien cangkir melompat dari 1.703 menjadi 0.302 hanya karena satu kolom dibuang. Lebih mencolok lagi, koefisien pendapatan_ribu bernilai -0.058 padahal korelasinya dengan target +0,95 — model penuh seolah berkata bahwa menambah pendapatan justru menurunkan penjualan makanan, tafsiran yang jelas keliru. Padahal R2 kedua model nyaris sama: masalahnya bukan kemampuan prediksi, melainkan interpretasi.

Rasio: Menyimpan Informasi tanpa Menyalin #

Membuang salah satu anggota pasangan adalah obat paling sederhana, tetapi bukan satu-satunya. Dua fitur yang berkorelasi kadang bisa digabungkan menjadi satu fitur baru yang memuat informasi berbeda: pendapatan dibagi jumlah cangkir menghasilkan harga rata-rata per cangkir — ukuran strategi harga yang nyaris bebas dari ramai-sepi cabang.

python
import pandas as pd

cabang = pd.DataFrame({
    "cangkir": [120, 95, 150, 80, 200, 175, 110, 140, 90, 160, 130, 105],
    "pendapatan_ribu": [2824, 2228, 3551, 1872, 4713, 4051, 2593, 3332, 2106, 3759, 3053, 2459],
    "kursi": [23, 22, 20, 18, 29, 35, 21, 18, 22, 24, 26, 23],
    "makanan_porsi": [49, 38, 56, 32, 79, 79, 45, 47, 39, 62, 54, 43],
})

cabang["harga_rata"] = (cabang["pendapatan_ribu"] / cabang["cangkir"]).round(2)
print(cabang[["cangkir", "pendapatan_ribu", "harga_rata"]].head(4).to_string(index=False))
print()
print(cabang[["cangkir", "harga_rata", "kursi"]].corr().round(2))

Output yang Dihasilkan:

text
 cangkir  pendapatan_ribu  harga_rata
     120             2824       23.53
      95             2228       23.45
     150             3551       23.67
      80             1872       23.40

            cangkir  harga_rata  kursi
cangkir        1.00        0.09   0.65
harga_rata     0.09        1.00  -0.64
kursi          0.65       -0.64   1.00

Yang terjadi pada kode di atas:

  1. Baris 5 (cabang["harga_rata"] = ...): pembagian dua kolom dijalankan baris demi baris, menghasilkan harga rata-rata tiap cabang; round(2) menjaga tampilannya rapi.
  2. Baris 8-9: matriks korelasi dihitung ulang dengan kehadiran kolom baru.
  3. Pembacaan hasil: korelasi harga_rata dengan cangkir hanya 0,09. Dari pasangan yang tadinya nyaris kembar, kita kini memegang dua informasi mandiri: lalu lintas (cangkir) dan harga (harga_rata). Kedua kolom lama pun bisa digantikan oleh cangkir + harga_rata tanpa kehilangan apa pun.

Dua strategi di atas bukan pilihan satu-satunya. Tabel berikut merangkum empat jalan yang umum ditempuh:

StrategiCara kerjaKapan tepat
Buang salah satudrop satu kolom dari pasangan terkuatkedua fitur mengukur konstruk yang sama
Bentuk rasiobagi atau gabungkan menjadi fitur barukombinasinya punya makna bisnis, misal harga
Reduksi dimensipadatkan banyak fitur menjadi komponenbanyak fitur saling tumpang tindih sekaligus
Biarkan sajatidak diubahfokus prediksi dengan model pohon, tanpa perlu menafsirkan koefisien

Jalan ketiga, reduksi dimensi, kita bahas tuntas pada chapter khusus di ujung section ini. Untuk sekarang, dua strategi pertama sudah cukup untuk merapikan kebanyakan tabel.

Rangkuman #

  • Multikolinearitas muncul ketika fitur saling berkorelasi kuat; prediksi biasanya tetap baik, tetapi koefisien model linear menjadi tidak stabil dan mudah menyesatkan.
  • df.corr() memberi matriks korelasi; pasangan dengan |r| di atas ambang (misalnya 0,9) ditemukan terprogram lewat np.triu + stack(), dengan target dikeluarkan dari matriks.
  • Pada contoh kita, koefisien cangkir bergeser dari 1.703 ke 0.302 dan koefisien pendapatan_ribu berubah menjadi negatif padahal korelasinya dengan target positif — bukti nyata koefisien tak bisa dipercaya saat fitur saling menyalin.
  • Dua perawatan utama: buang salah satu anggota pasangan, atau bentuk rasio (pendapatan / cangkir) yang menghasilkan informasi baru nyaris bebas dari fitur aslinya.

Masalah pasangan berkorelasi sesungguhnya adalah kasus khusus dari pertanyaan yang lebih luas: dari sekian banyak kolom, mana yang benar-benar layak masuk model? Pertanyaan itu kita jawab dengan feature selection pada chapter berikutnya.

Referensi #

  1. pandas — User Guide
  2. scikit-learn — LinearRegression