Penentuan gaji (salary) pemain olahraga profesional dipengaruhi oleh banyak faktor, antara lain performa pemain pada musim berjalan, akumulasi prestasi sepanjang karier, dan lamanya pengalaman bermain. Pada olahraga baseball, performa pemain tercatat secara rinci dalam bentuk statistik seperti jumlah hits, home runs, runs batted in (RBI), dan walks. Ketersediaan data statistik tersebut memungkinkan hubungan antara performa pemain dan besarnya gaji dipelajari secara kuantitatif.
Decision tree merupakan salah satu metode pembelajaran mesin yang populer karena hasilnya mudah diinterpretasi. Metode ini membagi data secara rekursif berdasarkan variabel prediktor sehingga membentuk pohon keputusan berupa aturan “jika-maka”. Salah satu algoritma yang paling dikenal adalah CART (Classification and Regression Trees) yang dapat digunakan untuk dua jenis permasalahan: klasifikasi apabila variabel respons berupa kategori, dan regresi apabila variabel respons berupa numerik.
Pada praktikum ini digunakan dataset Hitters dari
package ISLR untuk memprediksi
Salary pemain baseball menggunakan Decision
Tree (Regression Tree) dengan algoritma CART. Rangkaian
analisis mencakup penanganan missing value, pembagian data
latih dan data uji, pembentukan dan visualisasi pohon, analisis
variable importance, evaluasi model menggunakan Mean
Squared Error (MSE), penentuan nilai complexity parameter
(CP) terbaik, pruning, dan evaluasi ulang model hasil
pruning.
Batasan masalah pada praktikum ini adalah gunakan dataset Hitters dari package ISLR untuk memprediksi Salary menggunakan Decision Tree (Regression Tree). Lakukan pengecekan dan penanganan missing value menggunakan na.omit(), kemudian bagi data menjadi training dan testing. Selanjutnya, bangun dan visualisasikan tree, analisis variable importance, lakukan prediksi dan evaluasi menggunakan MSE, tentukan CP terbaik, lakukan pruning, dan evaluasi kembali model menggunakan MSE.
Decision tree adalah metode pembelajaran terawasi (supervised learning) yang membagi ruang data menjadi subkelompok yang semakin homogen melalui serangkaian aturan pemisahan (split) berdasarkan variabel prediktor. Metode CART (Classification and Regression Trees) diperkenalkan oleh Breiman et al. (1984) dan menghasilkan pohon biner, yaitu setiap simpul keputusan hanya bercabang menjadi dua simpul anak. Struktur pohon terdiri atas root node (simpul akar), internal node (simpul keputusan), dan leaf node (simpul daun) yang memuat hasil prediksi.
Pada Classification Tree, variabel respons berupa kategori dan pemisahan dipilih agar simpul anak menjadi sehomogen mungkin. Ukuran ketidakmurnian (impurity) yang umum digunakan CART adalah indeks Gini:
\[ Gini(t) = 1 - \sum_{k=1}^{K} p(k \mid t)^2 \]
dengan \(p(k \mid t)\) adalah proporsi kelas ke-\(k\) pada simpul \(t\). Nilai Gini sama dengan 0 berarti simpul sepenuhnya murni. Pemisahan terbaik adalah yang menghasilkan penurunan impuritas terbesar:
\[ \Delta Gini = Gini(t) - \left( \frac{n_L}{n_t} Gini(t_L) + \frac{n_R}{n_t} Gini(t_R) \right) \]
dengan \(t_L\) dan \(t_R\) masing-masing adalah simpul anak kiri dan kanan. Prediksi pada simpul daun adalah kelas mayoritas pada simpul tersebut.
Pada Regression Tree, variabel respons berupa numerik. Ruang prediktor dibagi menjadi \(J\) wilayah \(R_1, R_2, \ldots, R_J\), dan prediksi untuk setiap observasi di wilayah \(R_j\) adalah rata-rata respons pada data latih di wilayah tersebut (\(\hat{y}_{R_j}\)). Pemisahan dipilih untuk meminimalkan jumlah kuadrat galat (residual sum of squares, RSS):
\[ RSS = \sum_{j=1}^{J} \sum_{i \in R_j} \left( y_i - \hat{y}_{R_j} \right)^2 \]
Pembentukan pohon dilakukan secara top-down dan
greedy, yaitu pada setiap langkah dipilih pemisahan terbaik
saat itu tanpa mempertimbangkan pemisahan di langkah berikutnya (James
et al., 2013). Pada rpart, regresi dijalankan dengan
method = "anova".
Variable importance pada rpart dihitung dari
total penurunan ukuran kesalahan (impuritas) yang disumbangkan suatu
variabel, baik sebagai pemisah utama (primary split) maupun
pemisah pengganti (surrogate split). Semakin besar nilainya,
semakin besar kontribusi variabel tersebut dalam membentuk pohon
(Therneau & Atkinson, 2023).
Pohon yang dibiarkan tumbuh terlalu besar cenderung mengalami overfitting. CART mengatasinya dengan pemangkasan berbasis cost-complexity, yaitu meminimalkan:
\[ R_{\alpha}(T) = R(T) + \alpha \, |T| \]
dengan \(R(T)\) adalah galat pohon
\(T\), \(|T|\) adalah jumlah simpul daun, dan \(\alpha\) adalah parameter penalti
kompleksitas. Pada rpart, penalti ini dinyatakan sebagai
complexity parameter (CP), yaitu sebuah
pemisahan hanya dipertahankan apabila mampu menurunkan galat relatif
minimal sebesar CP. Nilai CP terbaik umumnya dipilih dari CP yang
menghasilkan galat validasi silang (xerror) terkecil, atau
dengan aturan 1-SE yang memilih pohon paling sederhana dengan
xerror masih berada dalam satu standar error dari nilai
minimum. Proses memangkas cabang pohon berdasarkan CP tersebut disebut
pruning.
Kinerja model regresi dievaluasi menggunakan Mean Squared Error (MSE), yaitu rata-rata kuadrat selisih antara nilai aktual dan nilai prediksi:
\[ MSE = \frac{1}{n} \sum_{i=1}^{n} \left( y_i - \hat{y}_i \right)^2 \]
Semakin kecil nilai MSE, semakin dekat prediksi model dengan nilai aktual. Akar dari MSE disebut RMSE (Root Mean Squared Error) yang memiliki satuan sama dengan variabel respons sehingga lebih mudah diinterpretasi. MSE pada data uji digunakan untuk menilai kemampuan model menggeneralisasi data baru, sedangkan MSE pada data latih hanya menunjukkan kesesuaian model terhadap data yang digunakan untuk membangunnya.
Data yang digunakan adalah dataset Hitters dari
package ISLR di R. Dataset ini berisi statistik
pemain baseball Major League Baseball pada musim 1986 dan 1987
(James et al., 2013). Data dimuat dengan perintah
data(Hitters).
Variabel respons (Y) adalah Salary, sedangkan variabel
prediktor (X) berjumlah 19 variabel sebagai berikut.
| Variabel | Peran | Keterangan |
|---|---|---|
Salary |
Y | Gaji tahunan pemain pada awal musim 1987 (dalam ribuan dolar) |
AtBat |
X | Jumlah kesempatan memukul (at bat) pada 1986 |
Hits |
X | Jumlah pukulan sukses (hits) pada 1986 |
HmRun |
X | Jumlah home run pada 1986 |
Runs |
X | Jumlah runs pada 1986 |
RBI |
X | Jumlah runs batted in pada 1986 |
Walks |
X | Jumlah walks pada 1986 |
Years |
X | Lama bermain di liga utama (tahun) |
CAtBat |
X | Jumlah at bat sepanjang karier |
CHits |
X | Jumlah hits sepanjang karier |
CHmRun |
X | Jumlah home run sepanjang karier |
CRuns |
X | Jumlah runs sepanjang karier |
CRBI |
X | Jumlah RBI sepanjang karier |
CWalks |
X | Jumlah walks sepanjang karier |
League |
X | Liga pemain pada akhir 1986 (A atau N) |
Division |
X | Divisi pemain pada akhir 1986 (E atau W) |
PutOuts |
X | Jumlah put outs pada 1986 |
Assists |
X | Jumlah assists pada 1986 |
Errors |
X | Jumlah errors pada 1986 |
NewLeague |
X | Liga pemain pada awal 1987 (A atau N) |
na.omit().rpart
(method = "anova") pada data latih dan memvisualisasikannya
dengan rpart.plot.xerror minimum dari
validasi silang 10 lipatan.library(ISLR)
library(rpart)
library(rpart.plot)
library(dplyr)
library(ggplot2)
library(knitr)
data(Hitters)
dim(Hitters)## [1] 322 20
## AtBat Hits HmRun Runs RBI Walks Years CAtBat CHits CHmRun
## -Andy Allanson 293 66 1 30 29 14 1 293 66 1
## -Alan Ashby 315 81 7 24 38 39 14 3449 835 69
## -Alvin Davis 479 130 18 66 72 76 3 1624 457 63
## -Andre Dawson 496 141 20 65 78 37 11 5628 1575 225
## -Andres Galarraga 321 87 10 39 42 30 2 396 101 12
## -Alfredo Griffin 594 169 4 74 51 35 11 4408 1133 19
## CRuns CRBI CWalks League Division PutOuts Assists Errors
## -Andy Allanson 30 29 14 A E 446 33 20
## -Alan Ashby 321 414 375 N W 632 43 10
## -Alvin Davis 224 266 263 A W 880 82 14
## -Andre Dawson 828 838 354 N E 200 11 3
## -Andres Galarraga 48 46 33 N E 805 40 4
## -Alfredo Griffin 501 336 194 A W 282 421 25
## Salary NewLeague
## -Andy Allanson NA A
## -Alan Ashby 475.0 N
## -Alvin Davis 480.0 A
## -Andre Dawson 500.0 N
## -Andres Galarraga 91.5 N
## -Alfredo Griffin 750.0 A
## 'data.frame': 322 obs. of 20 variables:
## $ AtBat : int 293 315 479 496 321 594 185 298 323 401 ...
## $ Hits : int 66 81 130 141 87 169 37 73 81 92 ...
## $ HmRun : int 1 7 18 20 10 4 1 0 6 17 ...
## $ Runs : int 30 24 66 65 39 74 23 24 26 49 ...
## $ RBI : int 29 38 72 78 42 51 8 24 32 66 ...
## $ Walks : int 14 39 76 37 30 35 21 7 8 65 ...
## $ Years : int 1 14 3 11 2 11 2 3 2 13 ...
## $ CAtBat : int 293 3449 1624 5628 396 4408 214 509 341 5206 ...
## $ CHits : int 66 835 457 1575 101 1133 42 108 86 1332 ...
## $ CHmRun : int 1 69 63 225 12 19 1 0 6 253 ...
## $ CRuns : int 30 321 224 828 48 501 30 41 32 784 ...
## $ CRBI : int 29 414 266 838 46 336 9 37 34 890 ...
## $ CWalks : int 14 375 263 354 33 194 24 12 8 866 ...
## $ League : Factor w/ 2 levels "A","N": 1 2 1 2 2 1 2 1 2 1 ...
## $ Division : Factor w/ 2 levels "E","W": 1 2 2 1 1 2 1 2 2 1 ...
## $ PutOuts : int 446 632 880 200 805 282 76 121 143 0 ...
## $ Assists : int 33 43 82 11 40 421 127 283 290 0 ...
## $ Errors : int 20 10 14 3 4 25 7 9 19 0 ...
## $ Salary : num NA 475 480 500 91.5 750 70 100 75 1100 ...
## $ NewLeague: Factor w/ 2 levels "A","N": 1 2 1 2 2 1 1 1 2 1 ...
Dataset Hitters terdiri atas 322 observasi dan 20 variabel: satu
variabel respons (Salary) dan 19 variabel prediktor,
terdiri atas 16 variabel numerik dan 3 variabel kategorik
(League, Division, NewLeague).
Statistik deskriptif berikut meringkas nilai minimum, kuartil,
rata-rata, dan maksimum setiap variabel serta jumlah missing
value (NA's).
## AtBat Hits HmRun Runs
## Min. : 16.0 Min. : 1 Min. : 0.00 Min. : 0.00
## 1st Qu.:255.2 1st Qu.: 64 1st Qu.: 4.00 1st Qu.: 30.25
## Median :379.5 Median : 96 Median : 8.00 Median : 48.00
## Mean :380.9 Mean :101 Mean :10.77 Mean : 50.91
## 3rd Qu.:512.0 3rd Qu.:137 3rd Qu.:16.00 3rd Qu.: 69.00
## Max. :687.0 Max. :238 Max. :40.00 Max. :130.00
##
## RBI Walks Years CAtBat
## Min. : 0.00 Min. : 0.00 Min. : 1.000 Min. : 19.0
## 1st Qu.: 28.00 1st Qu.: 22.00 1st Qu.: 4.000 1st Qu.: 816.8
## Median : 44.00 Median : 35.00 Median : 6.000 Median : 1928.0
## Mean : 48.03 Mean : 38.74 Mean : 7.444 Mean : 2648.7
## 3rd Qu.: 64.75 3rd Qu.: 53.00 3rd Qu.:11.000 3rd Qu.: 3924.2
## Max. :121.00 Max. :105.00 Max. :24.000 Max. :14053.0
##
## CHits CHmRun CRuns CRBI
## Min. : 4.0 Min. : 0.00 Min. : 1.0 Min. : 0.00
## 1st Qu.: 209.0 1st Qu.: 14.00 1st Qu.: 100.2 1st Qu.: 88.75
## Median : 508.0 Median : 37.50 Median : 247.0 Median : 220.50
## Mean : 717.6 Mean : 69.49 Mean : 358.8 Mean : 330.12
## 3rd Qu.:1059.2 3rd Qu.: 90.00 3rd Qu.: 526.2 3rd Qu.: 426.25
## Max. :4256.0 Max. :548.00 Max. :2165.0 Max. :1659.00
##
## CWalks League Division PutOuts Assists
## Min. : 0.00 A:175 E:157 Min. : 0.0 Min. : 0.0
## 1st Qu.: 67.25 N:147 W:165 1st Qu.: 109.2 1st Qu.: 7.0
## Median : 170.50 Median : 212.0 Median : 39.5
## Mean : 260.24 Mean : 288.9 Mean :106.9
## 3rd Qu.: 339.25 3rd Qu.: 325.0 3rd Qu.:166.0
## Max. :1566.00 Max. :1378.0 Max. :492.0
##
## Errors Salary NewLeague
## Min. : 0.00 Min. : 67.5 A:176
## 1st Qu.: 3.00 1st Qu.: 190.0 N:146
## Median : 6.00 Median : 425.0
## Mean : 8.04 Mean : 535.9
## 3rd Qu.:11.00 3rd Qu.: 750.0
## Max. :32.00 Max. :2460.0
## NAs :59
ggplot(Hitters, aes(x = Salary)) +
geom_histogram(bins = 30, fill = "#008080", color = "black", na.rm = TRUE) +
labs(x = "Salary (ribu dolar)", y = "Frekuensi", title = "Distribusi Salary") +
theme_minimal()Gambar 1. Distribusi Salary pemain
Distribusi Salary pada Gambar 1 cenderung menjulur ke
kanan, yaitu sebagian besar pemain bergaji relatif rendah hingga
menengah dan hanya sedikit pemain bergaji sangat tinggi. Hal ini perlu
diperhatikan karena MSE sangat sensitif terhadap nilai ekstrem. Namun,
decision tree tidak mengasumsikan distribusi tertentu pada
respons sehingga transformasi tidak diperlukan pada praktikum ini.
## AtBat Hits HmRun Runs RBI Walks Years CAtBat
## 0 0 0 0 0 0 0 0
## CHits CHmRun CRuns CRBI CWalks League Division PutOuts
## 0 0 0 0 0 0 0 0
## Assists Errors Salary NewLeague
## 0 0 59 0
## Total missing value : 59
## Baris yang memuat NA: 59
# Penanganan dengan na.omit()
df <- na.omit(Hitters)
cat("\nJumlah baris sebelum na.omit :", nrow(Hitters), "\n")##
## Jumlah baris sebelum na.omit : 322
## Jumlah baris sesudah na.omit : 263
## AtBat Hits HmRun Runs RBI Walks Years CAtBat
## 0 0 0 0 0 0 0 0
## CHits CHmRun CRuns CRBI CWalks League Division PutOuts
## 0 0 0 0 0 0 0 0
## Assists Errors Salary NewLeague
## 0 0 0 0
Pemeriksaan menunjukkan missing value hanya terdapat pada
variabel respons Salary, sebanyak 59 observasi (18.32% dari
seluruh data). Fungsi na.omit() menghapus seluruh baris
yang memuat missing value sehingga tersisa 263 observasi
lengkap. Penghapusan baris dipilih karena Salary adalah
variabel yang diprediksi, sehingga observasi tanpa nilai
Salary tidak dapat digunakan untuk melatih maupun
mengevaluasi model.
set.seed(123)
idx <- sample(seq_len(nrow(df)), size = round(0.7 * nrow(df)))
train <- df[idx, ]
test <- df[-idx, ]
cat("Jumlah data latih :", nrow(train), "\n")## Jumlah data latih : 184
## Jumlah data uji : 79
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 67.5 188.8 400.0 545.5 750.0 2460.0
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 70.0 195.0 487.5 513.7 762.5 1975.0
Data dibagi menjadi data latih (70%, 184 observasi) dan data uji
(30%, 79 observasi) secara acak menggunakan sample().
Penggunaan set.seed(123) menjamin hasil pembagian dapat
direproduksi. Ringkasan Salary pada kedua bagian digunakan
untuk memastikan sebaran respons pada data latih dan data uji tidak
berbeda terlalu jauh.
Model awal dibangun dengan rpart menggunakan
method = "anova" untuk kasus regresi. Nilai CP yang kecil
(cp = 0.001) dan minsplit = 10 digunakan agar
pohon tumbuh cukup besar sehingga tahap pemilihan CP terbaik dan
pruning memiliki ruang untuk menyederhanakan pohon.
set.seed(123)
tree_full <- rpart(
Salary ~ .,
data = train,
method = "anova",
control = rpart.control(cp = 0.001, minsplit = 10, xval = 10)
)
print(tree_full)## n= 184
##
## node), split, n, deviance, yval
## * denotes terminal node
##
## 1) root 184 43317190.00 545.4638
## 2) CRBI< 321.5 110 7657626.00 300.0258
## 4) CAtBat< 1260 65 4188730.00 188.5077
## 8) AtBat>=135 62 369400.80 158.0753
## 16) CRuns< 98.5 38 48470.19 110.5395 *
## 17) CRuns>=98.5 24 99108.12 233.3403 *
## 9) AtBat< 135 3 2575225.00 817.4443 *
## 5) CAtBat>=1260 45 1492909.00 461.1074
## 10) CHits< 447.5 20 338416.70 372.2000
## 20) Walks< 45 12 171510.50 319.5000
## 40) AtBat>=477 3 11400.00 170.0000 *
## 41) AtBat< 477 9 70709.50 369.3333 *
## 21) Walks>=45 8 83587.50 451.2500 *
## 11) CHits>=447.5 25 869929.70 532.2334
## 22) Years>=9.5 4 20863.28 312.0835 *
## 23) Years< 9.5 21 618276.10 574.1667
## 46) Errors>=6.5 9 313588.00 492.4074 *
## 47) Errors< 6.5 12 199406.10 635.4861
## 94) HmRun>=13.5 6 89180.21 562.9167 *
## 95) HmRun< 13.5 6 47030.09 708.0555 *
## 3) CRBI>=321.5 74 19183150.00 910.3041
## 6) Walks< 61 47 4149361.00 672.1200
## 12) AtBat< 362 20 1460003.00 496.6250
## 24) Walks< 42.5 15 348597.50 401.5000
## 48) Years>=16.5 4 11650.00 270.0000 *
## 49) Years< 16.5 11 242626.10 449.3182
## 98) Hits< 72 5 35950.00 340.0000 *
## 99) Hits>=72 6 97130.21 540.4167 *
## 25) Walks>=42.5 5 568480.00 782.0000 *
## 13) AtBat>=362 27 1617113.00 802.1164
## 26) Errors>=15.5 8 311087.90 632.7679 *
## 27) Errors< 15.5 19 979991.40 873.4210
## 54) Assists< 10.5 6 41509.15 667.7777 *
## 55) Assists>=10.5 13 567638.70 968.3333
## 110) Years>=9.5 8 373288.80 890.8334 *
## 111) Years< 9.5 5 69420.09 1092.3330 *
## 7) Walks>=61 27 7725913.00 1324.9210
## 14) CRuns< 798.5 16 2245915.00 1117.7080
## 28) Walks< 66.5 4 272829.70 710.6250 *
## 29) Walks>=66.5 12 1089262.00 1253.4030
## 58) Assists< 199 9 346559.80 1127.8700 *
## 59) Assists>=199 3 175400.00 1630.0000 *
## 15) CRuns>=798.5 11 3793743.00 1626.3210
## 30) Hits< 143.5 5 1217607.00 1157.1140 *
## 31) Hits>=143.5 6 558048.30 2017.3270 *
rpart.plot(
tree_full,
type = 4, extra = 101, fallen.leaves = TRUE,
box.palette = "GnBu",
main = "Regression Tree Sebelum Pruning"
)Gambar 2. Visualisasi regression tree sebelum pruning
n_leaf <- function(model) sum(model$frame$var == "<leaf>")
cat("Jumlah simpul daun model awal:", n_leaf(tree_full), "\n")## Jumlah simpul daun model awal: 23
Pada Gambar 2, setiap simpul menampilkan nilai prediksi
Salary (rata-rata respons pada simpul), jumlah observasi
(n), dan persentasenya terhadap data latih. Pohon dibaca
dari simpul akar di bagian atas: observasi yang memenuhi kondisi pada
simpul diarahkan ke cabang kiri (“yes”), sedangkan yang tidak memenuhi
diarahkan ke cabang kanan (“no”) hingga mencapai simpul daun yang
memberikan prediksi akhir. Semakin gelap warna simpul, semakin tinggi
nilai prediksi Salary. Model awal memiliki 23 simpul daun
dengan 22 pemisahan.
vi <- data.frame(
Variabel = names(tree_full$variable.importance),
Importance = as.numeric(tree_full$variable.importance)
) %>%
mutate(Persen = round(Importance / sum(Importance) * 100, 2)) %>%
arrange(desc(Importance))
kable(vi, digits = 2, caption = "Tabel 1. Nilai variable importance")| Variabel | Importance | Persen |
|---|---|---|
| CRBI | 19881180.15 | 13.07 |
| CAtBat | 19082839.68 | 12.54 |
| CRuns | 18895761.93 | 12.42 |
| CWalks | 18689360.37 | 12.29 |
| CHits | 18447843.43 | 12.13 |
| CHmRun | 14266199.09 | 9.38 |
| Walks | 10450555.42 | 6.87 |
| Runs | 7529755.05 | 4.95 |
| Hits | 5278355.77 | 3.47 |
| AtBat | 4610669.29 | 3.03 |
| Years | 4337941.20 | 2.85 |
| RBI | 4307817.65 | 2.83 |
| HmRun | 3547767.79 | 2.33 |
| Assists | 1247289.25 | 0.82 |
| Errors | 1104484.96 | 0.73 |
| PutOuts | 400713.43 | 0.26 |
| Division | 42130.56 | 0.03 |
ggplot(vi, aes(x = reorder(Variabel, Importance), y = Importance)) +
geom_col(fill = "#008080", color = "black") +
coord_flip() +
labs(x = "Variabel", y = "Importance", title = "Variable Importance") +
theme_minimal()Gambar 3. Variable importance model awal
Variabel dengan kontribusi terbesar dalam memprediksi
Salary adalah CRBI (13.07%), diikuti
CAtBat (12.54%) dan CRuns (12.42%).
Variabel-variabel tersebut paling banyak menurunkan galat dan paling
menentukan pembagian pemain ke dalam kelompok gaji yang berbeda. Perlu
diperhatikan bahwa banyak variabel statistik karier saling berkorelasi
tinggi (misalnya CHits, CRuns, dan
CRBI), sehingga nilai importance dapat terbagi di
antara variabel yang membawa informasi serupa. Variabel dengan
importance rendah memberikan kontribusi kecil terhadap
model.
mse <- function(aktual, prediksi) mean((aktual - prediksi)^2)
pred_train_full <- predict(tree_full, newdata = train)
pred_test_full <- predict(tree_full, newdata = test)
mse_train_full <- mse(train$Salary, pred_train_full)
mse_test_full <- mse(test$Salary, pred_test_full)
cat("MSE data latih (sebelum pruning) :", round(mse_train_full, 2), "\n")## MSE data latih (sebelum pruning) : 40424.58
## MSE data uji (sebelum pruning) : 107293.4
## RMSE data uji (sebelum pruning) : 327.56
Model awal menghasilkan MSE data latih sebesar 4.042458^{4} dan MSE data uji sebesar 1.0729343^{5} (RMSE data uji 327.56 ribu dolar). MSE data uji lebih besar daripada MSE data latih, yang mengindikasikan model cenderung lebih cocok pada data latih dibandingkan data baru (indikasi overfitting), sehingga pemangkasan pohon perlu dipertimbangkan.
ggplot(data.frame(Aktual = test$Salary, Prediksi = pred_test_full),
aes(x = Aktual, y = Prediksi)) +
geom_point(color = "#008080", size = 2, alpha = 0.8) +
geom_abline(slope = 1, intercept = 0, linetype = "dashed") +
labs(x = "Salary aktual", y = "Salary prediksi",
title = "Aktual vs Prediksi (Sebelum Pruning)") +
theme_minimal()Gambar 4. Salary aktual vs prediksi pada data uji (sebelum pruning)
Semakin dekat titik-titik pada Gambar 4 ke garis putus-putus (garis 45 derajat), semakin akurat prediksi model. Karena prediksi regression tree berupa nilai rata-rata pada simpul daun, prediksi hanya dapat bernilai sejumlah nilai diskrit (sebanyak simpul daun) sehingga titik-titik tampak tersusun dalam kolom-kolom vertikal.
##
## Regression tree:
## rpart(formula = Salary ~ ., data = train, method = "anova", control = rpart.control(cp = 0.001,
## minsplit = 10, xval = 10))
##
## Variables actually used in tree construction:
## [1] Assists AtBat CAtBat CHits CRBI CRuns Errors Hits HmRun
## [10] Walks Years
##
## Root node error: 43317187/184 = 235419
##
## n= 184
##
## CP nsplit rel error xerror xstd
## 1 0.3803665 0 1.00000 1.01491 0.16117
## 2 0.1687062 1 0.61963 0.75458 0.12999
## 3 0.0456167 2 0.45093 0.58021 0.12376
## 4 0.0427583 3 0.40531 0.61175 0.13372
## 5 0.0287208 5 0.31979 0.59721 0.13441
## 6 0.0247533 6 0.29107 0.63075 0.13692
## 7 0.0204035 7 0.26632 0.64359 0.13888
## 8 0.0130965 8 0.24592 0.65686 0.13815
## 9 0.0125337 9 0.23282 0.65355 0.13800
## 10 0.0080439 10 0.22029 0.66934 0.13400
## 11 0.0065693 12 0.20420 0.65920 0.13289
## 12 0.0053279 13 0.19763 0.65516 0.13184
## 13 0.0051209 14 0.19230 0.66020 0.13478
## 14 0.0028841 15 0.18718 0.67428 0.14053
## 15 0.0024305 16 0.18430 0.67507 0.13946
## 16 0.0023532 17 0.18187 0.68108 0.13944
## 17 0.0019937 19 0.17716 0.69235 0.13915
## 18 0.0014589 21 0.17317 0.69203 0.13927
## 19 0.0010000 22 0.17171 0.69297 0.13927
Gambar 5. Plot nilai CP terhadap cross-validation error
cp_tab <- as.data.frame(tree_full$cptable)
best_cp <- cp_tab$CP[which.min(cp_tab$xerror)]
# Sebagai pembanding: aturan 1-SE
row_min <- which.min(cp_tab$xerror)
thr <- cp_tab$xerror[row_min] + cp_tab$xstd[row_min]
cp_1se <- cp_tab$CP[which(cp_tab$xerror <= thr)[1]]
cat("CP terbaik (xerror minimum) :", best_cp, "\n")## CP terbaik (xerror minimum) : 0.04561669
## CP menurut aturan 1-SE : 0.04561669
Tabel CP memuat nilai CP, jumlah pemisahan
(nsplit), galat relatif pada data latih
(rel error), galat validasi silang (xerror),
dan standar errornya (xstd). Seiring bertambahnya
pemisahan, rel error terus menurun, tetapi
xerror pada akhirnya berhenti menurun atau justru naik
karena pohon mulai menyesuaikan noise. Berdasarkan kriteria
xerror minimum, nilai CP terbaik adalah
0.04562. Sebagai pembanding, aturan 1-SE menghasilkan
CP sebesar 0.04562 yang cenderung memberikan pohon lebih sederhana.
Dalam praktikum ini digunakan CP dengan xerror minimum.
## n= 184
##
## node), split, n, deviance, yval
## * denotes terminal node
##
## 1) root 184 43317190 545.4638
## 2) CRBI< 321.5 110 7657626 300.0258 *
## 3) CRBI>=321.5 74 19183150 910.3041
## 6) Walks< 61 47 4149361 672.1200 *
## 7) Walks>=61 27 7725913 1324.9210 *
rpart.plot(
tree_pruned,
type = 4, extra = 101, fallen.leaves = TRUE,
box.palette = "GnBu",
main = "Regression Tree Setelah Pruning"
)Gambar 6. Visualisasi regression tree setelah pruning
Setelah pruning dengan CP = 0.04562, jumlah simpul daun berubah dari 23 menjadi 3, dan jumlah pemisahan dari 22 menjadi 2. Pohon yang lebih ringkas lebih mudah diinterpretasi dan berpotensi lebih baik dalam menggeneralisasi data baru karena risiko overfitting berkurang.
pred_train_pruned <- predict(tree_pruned, newdata = train)
pred_test_pruned <- predict(tree_pruned, newdata = test)
mse_train_pruned <- mse(train$Salary, pred_train_pruned)
mse_test_pruned <- mse(test$Salary, pred_test_pruned)
cat("MSE data latih (setelah pruning) :", round(mse_train_pruned, 2), "\n")## MSE data latih (setelah pruning) : 106157.1
## MSE data uji (setelah pruning) : 113822.2
## RMSE data uji (setelah pruning) : 337.38
ggplot(data.frame(Aktual = test$Salary, Prediksi = pred_test_pruned),
aes(x = Aktual, y = Prediksi)) +
geom_point(color = "#E69F00", size = 2, alpha = 0.8) +
geom_abline(slope = 1, intercept = 0, linetype = "dashed") +
labs(x = "Salary aktual", y = "Salary prediksi",
title = "Aktual vs Prediksi (Setelah Pruning)") +
theme_minimal()Gambar 7. Salary aktual vs prediksi pada data uji (setelah pruning)
r2 <- function(aktual, prediksi) 1 - sum((aktual - prediksi)^2) / sum((aktual - mean(aktual))^2)
perbandingan <- data.frame(
Model = c("Sebelum Pruning", "Setelah Pruning"),
Jumlah_Daun = c(n_leaf(tree_full), n_leaf(tree_pruned)),
MSE_Latih = c(mse_train_full, mse_train_pruned),
MSE_Uji = c(mse_test_full, mse_test_pruned),
RMSE_Uji = c(sqrt(mse_test_full), sqrt(mse_test_pruned)),
R2_Uji = c(r2(test$Salary, pred_test_full), r2(test$Salary, pred_test_pruned))
)
kable(perbandingan, digits = 4, caption = "Tabel 2. Perbandingan kinerja model")| Model | Jumlah_Daun | MSE_Latih | MSE_Uji | RMSE_Uji | R2_Uji |
|---|---|---|---|---|---|
| Sebelum Pruning | 23 | 40424.58 | 107293.4 | 327.5568 | 0.1478 |
| Setelah Pruning | 3 | 106157.07 | 113822.2 | 337.3754 | 0.0959 |
MSE data uji naik dari 107293.43 menjadi 113822.19 setelah pruning. Pada data uji ini pohon yang lebih besar memprediksi sedikit lebih baik, tetapi model hasil pruning jauh lebih sederhana sehingga lebih mudah diinterpretasi; selisih ini dapat dipengaruhi pembagian data latih dan uji yang acak.
rpart pada data latih. Pada praktikum ini algoritma
CART diterapkan untuk kasus regresi (Regression Tree) guna
memprediksi Salary pada dataset Hitters setelah 59
observasi yang memuat missing value dihapus dengan
na.omit(). Data dibagi menjadi 184 data latih dan 79 data
uji.Salary adalah CRBI, CAtBat, dan CRuns.xerror minimum
adalah 0.04562. Setelah pruning, pohon memiliki 3 simpul daun
dengan MSE data uji sebesar 1.1382219^{5}.Breiman, L., Friedman, J. H., Olshen, R. A., & Stone, C. J. (1984). Classification and Regression Trees. Wadsworth.
Hastie, T., Tibshirani, R., & Friedman, J. (2009). The Elements of Statistical Learning: Data Mining, Inference, and Prediction (2nd ed.). Springer.
James, G., Witten, D., Hastie, T., & Tibshirani, R. (2013). An Introduction to Statistical Learning: with Applications in R. Springer.
Therneau, T., & Atkinson, B. (2023). rpart: Recursive Partitioning and Regression Trees (R package). https://CRAN.R-project.org/package=rpart