Sektor bangunan merupakan salah satu konsumen energi terbesar di dunia, dan sistem pemanas, ventilasi, serta pendingin udara (heating, ventilation, and air conditioning/HVAC) menyerap porsi terbesar dari energi tersebut. Perancangan bangunan yang hemat energi karenanya memerlukan estimasi beban pemanasan (heating load, HL) dan beban pendinginan (cooling load, CL) yang andal sejak tahap desain, agar kapasitas peralatan HVAC dapat ditentukan secara tepat (Tsanas & Xifara, 2012).
Simulasi energi bangunan menghasilkan estimasi yang andal, tetapi memakan waktu dan menuntut keahlian perangkat lunak khusus. Model statistika menawarkan alternatif yang lebih cepat sekaligus memberi pemahaman kuantitatif mengenai parameter desain yang paling berkontribusi. Data Energy Efficiency (ENB2012) dari Tsanas dan Xifara (2012), yang tersedia pada UCI Machine Learning Repository, memuat 768 konfigurasi bangunan hasil simulasi Ecotect dengan delapan parameter desain (X1 sampai X8) dan dua respons, yaitu beban pemanasan (Y1) dan beban pendinginan (Y2).
Data ini memiliki tiga karakteristik yang menyulitkan regresi linear klasik. Pertama, luas permukaan (X2), luas dinding (X3), dan luas atap (X4) terikat oleh identitas geometri eksak \(X_2 = X_3 + 2X_4\) sehingga terjadi multikolinearitas sempurna, dan penduga kuadrat terkecil biasa (ordinary least squares, OLS) tidak memiliki solusi tunggal. Kedua, kekompakan relatif (X1) dan tinggi keseluruhan (X5) berkorelasi sangat kuat dengan variabel luas. Ketiga, sebaran Y1 dan Y2 tidak berbentuk lonceng tunggal, melainkan bimodal, yaitu memiliki dua puncak yang terpisah. Response yang bimodal menimbulkan pertanyaan apakah satu garis regresi dengan galat Gaussian tunggal sudah memadai, atau data lebih tepat dipandang sebagai campuran dari dua rezim desain dengan hubungan prediktor-respons yang berbeda.
Regresi Lasso (Tibshirani, 1996) menangani masalah pertama dan kedua karena penalti \(\ell_1\) membuat masalah optimasi tetap terdefinisi meskipun matriks desain singular, sekaligus menyusutkan sebagian koefisien hingga tepat nol sehingga terjadi seleksi variabel. Untuk masalah ketiga, laporan ini memperluas Lasso menjadi Mixture Lasso, yaitu model campuran regresi Gaussian berhingga dengan penalti \(\ell_1\) pada setiap komponen (Khalili & Chen, 2007; Städler et al., 2010). Model campuran diestimasi dengan algoritme expectation-maximization (EM), dan jumlah komponennya dipilih dengan Bayesian information criterion (BIC). Regresi linear (OLS) tetap disertakan sebagai model pembanding dan sebagai alat diagnostik. Seluruh analisis dikerjakan dengan bahasa pemrograman R.
Pertanyaan utama penelitian (Langkah 2 pada skrip analisis) adalah: variabel desain arsitektural manakah, di antara X1 sampai X8, yang tetap terpilih oleh Lasso (koefisien tidak sama dengan nol) saat memprediksi Heating Load (Y1) dan Cooling Load (Y2) secara terpisah, dan apakah bimodalitas kedua respons perlu dimodelkan sebagai campuran dua rezim? Pertanyaan tersebut dijabarkan sebagai berikut.
Secara praktis, hasil penelitian memberi gambaran parameter desain yang paling berkontribusi terhadap beban pemanasan dan pendinginan pada ruang desain simulasi. Secara metodologis, laporan ini mendokumentasikan alur analisis yang dapat direproduksi untuk regresi terpenalti pada data dengan multikolinearitas sempurna dan respons bimodal, termasuk diagnosis bimodalitas, model campuran terpenalti, dan evaluasi interval prediksi.
Tsanas dan Xifara (2012) membangun 768 bangunan simulasi yang masing-masing tersusun atas 18 kubus elementer berukuran \(3{,}5 \times 3{,}5 \times 3{,}5\) m dengan volume tetap 771,75 m\(^3\). Terdapat 12 bentuk bangunan, tiga tingkat luas kaca (10%, 25%, dan 40% dari luas lantai) dengan lima skenario distribusi, dan empat orientasi, ditambah bangunan tanpa kaca, sehingga total observasi adalah \(12 \times 3 \times 5 \times 4 + 12 \times 4 = 768\). Variabel pada data tersebut ditunjukkan pada Tabel 2.1.
| Kode | Variabel | Nilai unik |
|---|---|---|
| X1 | Relative compactness (kekompakan relatif) | 12 |
| X2 | Surface area (luas permukaan) | 12 |
| X3 | Wall area (luas dinding) | 7 |
| X4 | Roof area (luas atap) | 4 |
| X5 | Overall height (tinggi keseluruhan) | 2 |
| X6 | Orientation (orientasi) | 4 |
| X7 | Glazing area (luas kaca) | 4 |
| X8 | Glazing area distribution (distribusi luas kaca) | 6 |
| Y1 | Heating load (beban pemanasan) | 586 |
| Y2 | Cooling load (beban pendinginan) | 636 |
Penelitian tersebut membandingkan regresi linear robust iteratively reweighted least squares (IRLS) dan random forest (RF) dengan validasi silang 10-lipat yang diulang 100 kali. Galat luar-sampel yang dilaporkan disajikan pada Tabel 2.2. Penulis menyimpulkan bahwa data bersifat non-Gaussian, RF jauh lebih akurat daripada IRLS, dan regresi klasik dapat menghasilkan koefisien yang besar dengan tanda berlawanan akibat multikolinearitas. Temuan terakhir menjadi motivasi penggunaan regresi terpenalti pada laporan ini, sedangkan sifat non-Gaussian (bimodal) menjadi motivasi model campuran.
| Ukuran galat | Respons | IRLS | Random forest |
|---|---|---|---|
| MAE | Y1 (HL) | 2,14 ± 0,24 | 0,51 ± 0,11 |
| MAE | Y2 (CL) | 2,21 ± 0,28 | 1,42 ± 0,25 |
| MSE | Y1 (HL) | 9,87 ± 2,41 | 1,03 ± 0,54 |
| MSE | Y2 (CL) | 11,46 ± 3,63 | 6,59 ± 1,56 |
Bimodalitas respons didiagnosis dengan empat ukuran yang saling melengkapi.
Koefisien bimodalitas pada butir 3 dihitung sebagai
\[\text{BC} = \frac{g^2 + 1}{\kappa + \dfrac{3(n-1)^2}{(n-2)(n-3)}}.\]
Bimodalitas marginal pada \(Y\) belum tentu memerlukan model campuran. Jika kedua puncak sepenuhnya dijelaskan oleh prediktor diskret yang teramati (misalnya X5 yang hanya bernilai 3,5 atau 7), maka \(Y\) akan unimodal di dalam tiap level prediktor tersebut, dan residual model linear juga akan unimodal. Model campuran baru dibenarkan apabila setelah prediktor dimasukkan, residual masih multimodal, atau apabila kinerja prediksi dan interval prediksinya membaik secara berarti.
Model regresi linear berganda adalah \(y = X\beta + \varepsilon\) dengan \(\varepsilon \sim (0, \sigma^2 I)\), dan penduga OLS
\[\hat{\beta}_{\text{OLS}} = (X^\top X)^{-1} X^\top y\]
mensyaratkan \(X^\top X\) dapat dibalik. Diagnostik yang dilakukan adalah sebagai berikut.
Karena terdapat multikolinearitas sempurna, uji-t dan p-value koefisien OLS tidak dipakai untuk menyatakan variabel “penting”. Diagnostik OLS dipakai untuk memahami kondisi data.
Pada multikolinearitas sempurna, \(X^\top X\) singular sehingga \(\hat{\beta}_{\text{OLS}}\) tidak tunggal. Regularisasi mengatasinya dengan menambahkan penalti pada besar koefisien. Regresi ridge (Hoerl & Kennard, 1970) memakai penalti \(\ell_2\), sedangkan Lasso (Tibshirani, 1996; Hastie et al., 2009) memakai penalti \(\ell_1\):
\[\hat{\beta}^{\text{lasso}} = \underset{\beta_0,\,\beta}{\arg\min}\; \frac{1}{2n}\sum_{i=1}^{n}\left(y_i - \beta_0 - x_i^\top\beta\right)^2 + \lambda\sum_{j=1}^{p}\left|\beta_j\right|.\]
Fungsi objektif ini konveks sehingga selalu memiliki solusi meskipun
\(X^\top X\) singular. Penalti \(\ell_1\) tidak dapat diturunkan di titik
nol sehingga sebagian koefisien menjadi tepat nol. Karena penalti
sensitif terhadap skala, prediktor numerik dibakukan terlebih dahulu.
Nilai \(\lambda\) dipilih dengan
validasi silang 5-lipat, yaitu \(\lambda\) yang meminimumkan rerata galat
kuadrat (lambda.min). Solusi dihitung dengan coordinate
descent pada paket glmnet (Friedman et al., 2010).
Pada kelompok prediktor yang saling berkorelasi tinggi, Lasso cenderung
memilih sebagian anggota kelompok saja (Zou &
Hastie, 2005), sehingga tereliminasinya suatu variabel dibaca
sebagai pemilihan perwakilan informasi, bukan bukti ketiadaan
pengaruh.
Model campuran regresi Gaussian dengan \(K\) komponen (rezim) menyatakan densitas bersyarat respons sebagai
\[f(y \mid x) = \sum_{k=1}^{K} \pi_k(x)\, \phi\!\left(y;\; \beta_{0k} + x^\top \beta_k,\; \sigma_k^2\right), \qquad \sum_{k=1}^{K}\pi_k(x) = 1,\]
dengan \(\phi(\cdot\,;\mu,\sigma^2)\) densitas normal, \(\beta_k\) vektor koefisien komponen ke-\(k\), dan \(\pi_k(x)\) peluang keanggotaan komponen. Seleksi variabel per komponen diperoleh dengan menambahkan penalti \(\ell_1\) pada setiap \(\beta_k\) (Khalili & Chen, 2007; Städler et al., 2010). Parameter diestimasi dengan algoritme EM (Dempster et al., 1977).
Langkah-E. Responsibility (peluang posterior keanggotaan) observasi ke-\(i\) pada komponen ke-\(k\) dihitung sebagai
\[w_{ik} = \frac{\pi_k\, \phi\!\left(y_i;\, \mu_{ik},\, \sigma_k^2\right)}{\sum_{l=1}^{K} \pi_l\, \phi\!\left(y_i;\, \mu_{il},\, \sigma_l^2\right)}, \qquad \mu_{ik} = \beta_{0k} + x_i^\top\beta_k.\]
Langkah-M. Untuk setiap komponen diselesaikan Lasso berbobot
\[\left(\hat\beta_{0k}, \hat\beta_k\right) = \underset{\beta_{0},\,\beta}{\arg\min}\; \frac{1}{2\sum_i w_{ik}} \sum_{i=1}^{n} w_{ik}\left(y_i - \beta_{0} - x_i^\top\beta\right)^2 + \lambda \lVert \beta \rVert_1,\]
lalu \(\hat\sigma_k^2 = \sum_i w_{ik}(y_i - \hat\mu_{ik})^2 / \sum_i w_{ik}\) dan \(\hat\pi_k = n^{-1}\sum_i w_{ik}\). Kedua langkah diulang hingga perubahan log-likelihood relatif lebih kecil dari \(10^{-6}\). Karena EM dapat terjebak pada optimum lokal, digunakan beberapa inisialisasi (k-means pada \(y\)), dan inisialisasi dengan komponen berbobot kurang dari 5% dibuang.
Pemilihan \(K\). Untuk \(K = 1, 2, 3\) dihitung
\[\text{BIC}_K = -2\,\ell(\hat\theta_K) + \log(n)\cdot \text{df}_K, \qquad \text{df}_K = \sum_{k=1}^{K}\#\{j: \hat\beta_{jk} \neq 0\} + K + K + (K-1),\]
yaitu banyaknya koefisien tidak nol, intersep, simpangan baku, dan proporsi campuran. Penggunaan banyaknya koefisien tidak nol sebagai derajat bebas Lasso mengikuti Zou et al. (2007). \(K\) dengan BIC terkecil dipilih. Pada \(K = 1\) model campuran tereduksi menjadi Lasso tunggal.
Penyempurnaan setelah \(K\) terpilih. Nilai \(\lambda\) setiap komponen dipilih ulang dengan validasi silang berbobot, dan peluang keanggotaan \(\pi_k(x)\) dipelajari dengan regresi logistik multinomial ber-penalti Lasso pada label komponen, mengikuti gagasan gating network pada mixture of experts (Jacobs et al., 1991), sehingga data baru dapat diprediksi.
Model campuran menghasilkan dua jenis prediksi titik. Prediksi soft adalah ekspektasi bersyarat
\[\hat y^{\,\text{soft}}(x) = \mathbb{E}[Y \mid x] = \sum_{k=1}^{K} \hat\pi_k(x)\,\hat\mu_k(x),\]
yang meminimumkan galat kuadrat. Prediksi hard adalah \(\hat y^{\,\text{hard}}(x) = \hat\mu_{k^*}(x)\) dengan \(k^* = \arg\max_k \hat\pi_k(x)\), yang mempertahankan struktur dua puncak tetapi berisiko salah komponen.
Interval prediksi \(100(1-\alpha)\%\) dari model campuran diperoleh dari kuantil \(\alpha/2\) dan \(1-\alpha/2\) fungsi distribusi campuran \(F(y \mid x) = \sum_k \hat\pi_k(x)\,\Phi\!\left((y - \hat\mu_k(x))/\hat\sigma_k\right)\). Sebagai pembanding, interval Gaussian tunggal untuk OLS dan Lasso adalah \(\hat y \pm z_{0,95}\, s\) dengan \(s\) simpangan baku residual data latih. Kualitas interval dinilai melalui coverage empiris (target 0,90) dan lebar rata-rata.
Kecocokan distribusi model campuran diperiksa dengan probability integral transform (PIT), \(u_i = F(y_i \mid x_i)\). Jika model benar, \(u_i \sim \text{Uniform}(0,1)\) (Gneiting et al., 2007), dan residual kuantil \(r_i = \Phi^{-1}(u_i)\) berdistribusi \(N(0,1)\) (Dunn & Smyth, 1996).
Kinerja prediksi diukur dengan
\[\text{RMSE} = \sqrt{\frac{1}{n}\sum_{i=1}^{n}(y_i-\hat{y}_i)^2},\qquad \text{MAE} = \frac{1}{n}\sum_{i=1}^{n}\lvert y_i-\hat{y}_i\rvert,\qquad R^2 = 1-\frac{\sum_{i}(y_i-\hat{y}_i)^2}{\sum_{i}(y_i-\bar{y})^2}.\]
Ukuran tersebut dihitung pada data uji dan pada validasi silang berulang 10-lipat dengan 5 pengulangan. Pembagian data latih-uji dan lipatan validasi silang distratifikasi menurut nilai respons agar kedua puncak terwakili di setiap bagian. Ringkasan validasi silang disajikan sebagai rerata, simpangan baku, dan interval empiris 2,5% sampai 97,5%. Interval empiris ini bukan selang kepercayaan inferensial klasik karena lipatan-lipatan validasi silang saling bergantung (Bates et al., 2024).
Data yang digunakan adalah ENB2012 (768 observasi, 10 variabel) dengan unit observasi berupa konfigurasi desain bangunan hasil simulasi. Prediktor numerik adalah X1, X2, X3, X4, X5, dan X7, sedangkan X6 dan X8 diperlakukan sebagai kategorik. Untuk model linear dan campuran, prediktor numerik dibakukan (rerata 0, simpangan baku populasi 1) dan X6 serta X8 dikodekan menjadi variabel dummy dengan kategori pertama sebagai referensi, sehingga terdapat 14 kolom prediktor (6 numerik, 3 dummy X6, dan 5 dummy X8). Parameter pembakuan dan daftar level dipelajari hanya dari data latih untuk mencegah kebocoran informasi.
set.seed(42)), lalu estimasi
OLS, Lasso, dan Mixture Lasso untuk Y1 dan Y2 secara terpisah, serta
seleksi variabel pada level X1 sampai X8.| Komponen | Pengaturan |
|---|---|
| OLS | lm pada matriks desain terbakukan;
koefisien teralias bernilai NA, prediksi tetap valid |
| Lasso | glmnet, alpha = 1,
standardize = FALSE, kisi 200 nilai \(\lambda\) dari \(10^{2}\) hingga \(10^{-4}\), CV dalam 5-lipat,
lambda.min |
| Mixture Lasso | EM, \(K \in
\{1,2,3\}\), 3 inisialisasi per \(K\), maksimum 100 iterasi, toleransi \(10^{-6}\), proporsi minimum 5%, \(\lambda\) awal = lambda.min
Lasso global, \(K\) dipilih BIC |
| Penyempurnaan campuran | \(\lambda\) per komponen dipilih ulang dengan CV berbobot; \(\pi_k(x)\) dengan Lasso multinomial |
| Validasi | Uji 20% terstratifikasi; CV berulang 10-lipat \(\times\) 5 terstratifikasi
(set.seed(42 + ulangan)) |
Analisis menggunakan R version 4.6.1 (2026-06-24 ucrt) (R Core Team, 2024) dengan paket
tidyverse (Wickham et al.,
2019), readxl (Wickham & Bryan,
2023), glmnet, lmtest, patchwork (Pedersen, 2024),
diptest, mclust, serta knitr dan rmarkdown untuk penyusunan laporan.
Paket yang belum terpasang dipasang otomatis pada chunk
setup. Seluruh angka pada narasi dihitung langsung dari
hasil eksekusi kode.
suppressPackageStartupMessages({
library(tidyverse) # dplyr, tidyr, ggplot2, purrr, tibble
library(readxl) # membaca file .xlsx
library(glmnet) # Lasso
library(lmtest) # uji Breusch-Pagan
library(patchwork) # menggabungkan grafik ggplot
library(diptest) # uji dip Hartigan (unimodalitas)
library(mclust) # campuran normal univariat
})
# PENTING: beberapa paket menimpa fungsi tidyverse bila ikut termuat, misalnya
# mclust::map, plyr::count/summarise/mutate/arrange/rename/desc,
# MASS::select, dan stats::filter. Fungsi-fungsi berikut dikunci ke versi
# tidyverse agar laporan tetap berjalan apa pun paket lain yang aktif di R Anda.
map <- purrr::map
map_dfr <- purrr::map_dfr
map2_dbl <- purrr::map2_dbl
count <- dplyr::count
summarise <- dplyr::summarise
summarize <- dplyr::summarize
mutate <- dplyr::mutate
arrange <- dplyr::arrange
rename <- dplyr::rename
desc <- dplyr::desc
select <- dplyr::select
filter <- dplyr::filter
group_by <- dplyr::group_by
ungroup <- dplyr::ungroup
theme_set(theme_bw(base_size = 12) +
theme(text = element_text(family = "serif")))
options(width = 160, tibble.width = Inf, tibble.print_max = Inf)Chunk berikut mencari berkas data pada beberapa lokasi. Jika
berkas tidak ditemukan, data diunduh otomatis dari UCI Machine Learning
Repository. Ubah FILE_PATH bila berkas disimpan di lokasi
lain.
FILE_PATH <- "C:/Users/USER/Downloads/energy+efficiency/ENB2012_data.xlsx"
candidates <- c(FILE_PATH,
"ENB2012_data.xlsx",
file.path("energy+efficiency", "ENB2012_data.xlsx"),
"C:/Users/USER/Downloads/Energy Efficiency.xlsx")
found <- candidates[file.exists(candidates)]
if (length(found) > 0) {
data_file <- found[1]
} else {
# Unduh otomatis dari UCI bila berkas lokal tidak tersedia
zip_file <- file.path(tempdir(), "energy_efficiency.zip")
ok <- tryCatch({
download.file("https://archive.ics.uci.edu/static/public/242/energy+efficiency.zip",
zip_file, mode = "wb", quiet = TRUE)
unzip(zip_file, exdir = tempdir())
TRUE
}, error = function(e) FALSE)
xl <- list.files(tempdir(), pattern = "ENB2012.*\\.xlsx$", recursive = TRUE, full.names = TRUE)
if (!ok || length(xl) == 0) {
stop("Berkas ENB2012_data.xlsx tidak ditemukan. Ubah FILE_PATH ke lokasi berkas Anda.")
}
data_file <- xl[1]
}
df_raw <- read_excel(data_file, sheet = 1)
# Nama kolom standar X1..X8, Y1, Y2 (berkas UCI sudah memakai nama ini)
if (!all(c(paste0("X", 1:8), "Y1", "Y2") %in% names(df_raw))) {
names(df_raw)[1:10] <- c(paste0("X", 1:8), "Y1", "Y2")
}
# Ambil 10 kolom utama dan buang baris yang seluruhnya kosong
df <- df_raw %>%
select(all_of(c(paste0("X", 1:8), "Y1", "Y2"))) %>%
mutate(across(everything(), as.numeric)) %>%
filter(!if_all(everything(), is.na))
cat("Berkas dibaca :", data_file, "\n")#> Berkas dibaca : ENB2012_data.xlsx
#> Shape : 768 x 10
| X1 | X2 | X3 | X4 | X5 | X6 | X7 | X8 | Y1 | Y2 |
|---|---|---|---|---|---|---|---|---|---|
| 0.98 | 514.5 | 294.0 | 110.25 | 7 | 2 | 0 | 0 | 15.55 | 21.33 |
| 0.98 | 514.5 | 294.0 | 110.25 | 7 | 3 | 0 | 0 | 15.55 | 21.33 |
| 0.98 | 514.5 | 294.0 | 110.25 | 7 | 4 | 0 | 0 | 15.55 | 21.33 |
| 0.98 | 514.5 | 294.0 | 110.25 | 7 | 5 | 0 | 0 | 15.55 | 21.33 |
| 0.90 | 563.5 | 318.5 | 122.50 | 7 | 2 | 0 | 0 | 20.84 | 28.28 |
| 0.90 | 563.5 | 318.5 | 122.50 | 7 | 3 | 0 | 0 | 21.46 | 25.38 |
FEATURES <- c("X1", "X2", "X3", "X4", "X5", "X6", "X7", "X8")
TARGETS <- c("Y1", "Y2")
NUMERIC_FEATURES <- c("X1", "X2", "X3", "X4", "X5", "X7")
CATEGORICAL_FEATURES <- c("X6", "X8")
variable_info <- tibble(
Variable = c(FEATURES, TARGETS),
Description = c("Relative Compactness", "Surface Area", "Wall Area", "Roof Area",
"Overall Height", "Orientation", "Glazing Area",
"Glazing Area Distribution", "Heating Load", "Cooling Load"),
Role = c(rep("Predictor", 8), rep("Response", 2))
)
tbl(variable_info, "Tabel 4.1 Definisi variabel")| Variable | Description | Role |
|---|---|---|
| X1 | Relative Compactness | Predictor |
| X2 | Surface Area | Predictor |
| X3 | Wall Area | Predictor |
| X4 | Roof Area | Predictor |
| X5 | Overall Height | Predictor |
| X6 | Orientation | Predictor |
| X7 | Glazing Area | Predictor |
| X8 | Glazing Area Distribution | Predictor |
| Y1 | Heating Load | Response |
| Y2 | Cooling Load | Response |
Data terdiri atas 768 observasi dengan delapan prediktor dan dua respons. Y1 adalah beban pemanasan dan Y2 adalah beban pendinginan.
vars <- c(FEATURES, TARGETS)
quality <- tibble(
Variable = vars,
dtype = vapply(df[vars], function(x) class(x)[1], character(1), USE.NAMES = FALSE),
missing = vapply(df[vars], function(x) sum(is.na(x)), numeric(1), USE.NAMES = FALSE),
n_unique = vapply(df[vars], function(x) n_distinct(x), numeric(1), USE.NAMES = FALSE)
)
tbl(quality, "Tabel 4.2 Kualitas data")| Variable | dtype | missing | n_unique |
|---|---|---|---|
| X1 | numeric | 0 | 12 |
| X2 | numeric | 0 | 12 |
| X3 | numeric | 0 | 7 |
| X4 | numeric | 0 | 4 |
| X5 | numeric | 0 | 2 |
| X6 | numeric | 0 | 4 |
| X7 | numeric | 0 | 4 |
| X8 | numeric | 0 | 6 |
| Y1 | numeric | 0 | 587 |
| Y2 | numeric | 0 | 636 |
#> Duplicate rows: 0
describe_tbl <- function(data) {
q <- function(p) vapply(data, function(x) quantile(x, p, na.rm = TRUE, names = FALSE),
numeric(1), USE.NAMES = FALSE)
tibble(
Variable = names(data),
count = vapply(data, function(x) sum(!is.na(x)), numeric(1), USE.NAMES = FALSE),
mean = vapply(data, mean, numeric(1), na.rm = TRUE, USE.NAMES = FALSE),
std = vapply(data, sd, numeric(1), na.rm = TRUE, USE.NAMES = FALSE),
min = vapply(data, min, numeric(1), na.rm = TRUE, USE.NAMES = FALSE),
`25%` = q(0.25), `50%` = q(0.50), `75%` = q(0.75),
max = vapply(data, max, numeric(1), na.rm = TRUE, USE.NAMES = FALSE)
)
}
tbl(describe_tbl(df[vars]), "Tabel 4.3 Statistika deskriptif")| Variable | count | mean | std | min | 25% | 50% | 75% | max |
|---|---|---|---|---|---|---|---|---|
| X1 | 768 | 0.7642 | 0.1058 | 0.62 | 0.6825 | 0.75 | 0.8300 | 0.98 |
| X2 | 768 | 671.7083 | 88.0861 | 514.50 | 606.3750 | 673.75 | 741.1250 | 808.50 |
| X3 | 768 | 318.5000 | 43.6265 | 245.00 | 294.0000 | 318.50 | 343.0000 | 416.50 |
| X4 | 768 | 176.6042 | 45.1660 | 110.25 | 140.8750 | 183.75 | 220.5000 | 220.50 |
| X5 | 768 | 5.2500 | 1.7511 | 3.50 | 3.5000 | 5.25 | 7.0000 | 7.00 |
| X6 | 768 | 3.5000 | 1.1188 | 2.00 | 2.7500 | 3.50 | 4.2500 | 5.00 |
| X7 | 768 | 0.2344 | 0.1332 | 0.00 | 0.1000 | 0.25 | 0.4000 | 0.40 |
| X8 | 768 | 2.8125 | 1.5510 | 0.00 | 1.7500 | 3.00 | 4.0000 | 5.00 |
| Y1 | 768 | 22.3072 | 10.0902 | 6.01 | 12.9925 | 18.95 | 31.6675 | 43.10 |
| Y2 | 768 | 24.5878 | 9.5133 | 10.90 | 15.6200 | 22.08 | 33.1325 | 48.03 |
Dataset memiliki 768 observasi dengan 0 nilai hilang dan 0 baris duplikat, sehingga tidak diperlukan imputasi maupun penghapusan baris. Prediktor X1 sampai X8 hanya memiliki 2 sampai 12 nilai unik, sedangkan Y1 dan Y2 masing-masing memiliki 587 dan 636 nilai unik. Hal ini sesuai dengan rancangan simulasi, yaitu prediktor merupakan parameter desain diskret dan respons berupa besaran kontinu. Sedikitnya nilai unik pada X6 dan X8 yang merupakan kode kategori mendasari perlakuan keduanya sebagai variabel kategorik.
dependency <- df$X2 - (df$X3 + 2 * df$X4)
cat("Maximum absolute difference X2 - (X3 + 2*X4):", max(abs(dependency)), "\n")#> Maximum absolute difference X2 - (X3 + 2*X4): 0
Selisih absolut maksimum antara X2 dan \(X_3 + 2X_4\) adalah 0 (hanya galat pembulatan numerik), sehingga identitas \(X_2 = X_3 + 2X_4\) berlaku pada seluruh observasi. Ketiga variabel ini bergantung linear secara sempurna dan akan membuat matriks desain OLS singular.
plot_dist <- function(target) {
p1 <- ggplot(df, aes(x = .data[[target]])) +
geom_histogram(aes(y = after_stat(density)), bins = 30,
fill = "steelblue", alpha = 0.6, color = "white") +
geom_density(linewidth = 1) +
labs(title = paste("Distribution of", target), x = target, y = "Density")
p2 <- ggplot(df, aes(x = .data[[target]])) +
geom_boxplot(fill = "steelblue", alpha = 0.6) +
labs(title = paste("Boxplot of", target), x = target) +
theme(axis.text.y = element_blank(), axis.ticks.y = element_blank())
p1 | p2
}
print(plot_dist("Y1") / plot_dist("Y2"))Gambar 4.1 Histogram, densitas, dan boxplot Y1 dan Y2
Rerata Y1 adalah 22,31 (median 18,95, simpangan baku 10,09), dan rerata Y2 adalah 24,59 (median 22,08, simpangan baku 9,51). Histogram kedua respons memperlihatkan kepadatan yang terkonsentrasi pada dua wilayah nilai dengan lembah di antaranya, sehingga sebaran respons tidak dapat dihampiri oleh satu distribusi normal. Bukti formal bimodalitas diuji pada subbab berikut.
count_modes <- function(x, adjust = 1) {
d <- density(x, adjust = adjust)
sum(diff(sign(diff(d$y))) == -2)
}
bimodality_coef <- function(x) {
n <- length(x); m <- x - mean(x)
g <- mean(m^3) / mean(m^2)^1.5
k <- mean(m^4) / mean(m^2)^2 - 3
(g^2 + 1) / (k + 3 * (n - 1)^2 / ((n - 2) * (n - 3)))
}
bimodality_tbl <- map_dfr(TARGETS, function(t) {
y <- df[[t]]
dp <- dip.test(y)
mc <- Mclust(y, G = 1:4, verbose = FALSE)
tibble(
Target = t,
Modes = count_modes(y),
Dip_stat = unname(dp$statistic),
Dip_p = dp$p.value,
BC = bimodality_coef(y),
Mclust_G = mc$G,
Mclust_means = paste(round(sort(mc$parameters$mean), 2), collapse = " | ")
)
})
tbl(bimodality_tbl, "Tabel 4.4 Diagnosis bimodalitas respons")| Target | Modes | Dip_stat | Dip_p | BC | Mclust_G | Mclust_means |
|---|---|---|---|---|---|---|
| Y1 | 2 | 0.0468 | 0.00e+00 | 0.6394 | 3 | 13.52 | 28.36 | 37.29 |
| Y2 | 2 | 0.0400 | 0.00e+00 | 0.6202 | 2 | 15.54 | 31.73 |
Untuk Y1, densitas kernel memiliki 2 puncak, uji dip menghasilkan p-value 0,00e+00, BC sebesar 0,639 (ambang 0,555), dan mclust memilih 3 komponen dengan rerata 13.52 | 28.36 | 37.29. Untuk Y2, densitas memiliki 2 puncak, p-value uji dip 0,00e+00, BC 0,620, dan mclust memilih 2 komponen dengan rerata 15.54 | 31.73. Dengan demikian, 4 dari 4 ukuran mendukung multimodalitas pada Y1 dan 4 dari 4 ukuran pada Y2. Perlu dicatat bahwa p-value uji dip yang kecil menolak unimodalitas, sedangkan BC dan mclust bersifat deskriptif dan dapat dipengaruhi kemencengan.
within_x5 <- map_dfr(TARGETS, function(t) {
df %>%
group_by(X5) %>%
summarise(Target = t, n = n(), Mean = mean(.data[[t]]), SD = sd(.data[[t]]),
Modes = count_modes(.data[[t]]),
Dip_p = dip.test(.data[[t]])$p.value, .groups = "drop")
}) %>% relocate(Target)
tbl(within_x5, "Tabel 4.5 Distribusi respons di dalam tiap level X5")| Target | X5 | n | Mean | SD | Modes | Dip_p |
|---|---|---|---|---|---|---|
| Y1 | 3.5 | 384 | 13.3385 | 2.6718 | 3 | 0.00e+00 |
| Y1 | 7.0 | 384 | 31.2759 | 5.9546 | 3 | 7.05e-06 |
| Y2 | 3.5 | 384 | 16.0714 | 2.4208 | 2 | 3.61e-01 |
| Y2 | 7.0 | 384 | 33.1041 | 5.4724 | 2 | 1.24e-03 |
plot_by_x5 <- function(target) {
ggplot(df, aes(x = .data[[target]], fill = factor(X5))) +
geom_histogram(bins = 40, alpha = 0.7, position = "identity", color = "white") +
labs(title = paste(target, "menurut X5 (Overall Height)"), x = target, fill = "X5")
}
print(plot_by_x5("Y1") | plot_by_x5("Y2"))Gambar 4.2 Sebaran Y1 dan Y2 menurut X5
X5 hanya memiliki dua nilai, yaitu bangunan rendah dan tinggi. Untuk Y1 diperoleh rerata 13,34 pada X5 = 3,5 dan 31,28 pada X5 = 7,0, sedangkan untuk Y2 diperoleh rerata 16,07 pada X5 = 3,5 dan 33,10 pada X5 = 7,0. Histogram memperlihatkan bahwa kedua puncak sebagian besar berimpit dengan dua level X5. Di dalam tiap level X5, uji dip tidak menolak unimodalitas pada 0 dari 2 level untuk Y1 dan 1 dari 2 level untuk Y2 (taraf 5%). Artinya, bimodalitas marginal sebagian besar merupakan campuran dari dua subpopulasi yang teramati melalui X5 (dan variabel geometri yang berkorelasi dengannya). Apakah masih ada sisa bimodalitas setelah seluruh prediktor dimasukkan diperiksa melalui residual OLS pada Subbab 4.4.
spearman_cols <- c("X1", "X2", "X3", "X4", "X5", "X7", "Y1", "Y2")
spearman_corr <- cor(df[spearman_cols], method = "spearman")
spearman_long <- as.data.frame(as.table(spearman_corr)) %>% setNames(c("Var1", "Var2", "rho"))
p_heat <- ggplot(spearman_long, aes(x = Var1, y = Var2, fill = rho)) +
geom_tile(color = "white") +
geom_text(aes(label = sprintf("%.2f", rho)), size = 3.5, family = "serif") +
scale_fill_gradient2(low = "#3B4CC0", mid = "white", high = "#B40426",
midpoint = 0, limits = c(-1, 1)) +
scale_x_discrete(limits = spearman_cols) +
scale_y_discrete(limits = rev(spearman_cols)) +
coord_fixed() +
labs(title = "Spearman Rank Correlation", x = NULL, y = NULL)
print(p_heat)Gambar 4.3 Matriks korelasi peringkat Spearman
rho_tbl <- as.data.frame(round(spearman_corr[c("X1", "X2", "X3", "X4", "X5", "X7"), c("Y1", "Y2")], 4))
tbl(rownames_to_column(rho_tbl, "Variable"), "Tabel 4.6 Korelasi Spearman prediktor numerik dengan respons")| Variable | Y1 | Y2 |
|---|---|---|
| X1 | 0.6221 | 0.6510 |
| X2 | -0.6221 | -0.6510 |
| X3 | 0.4715 | 0.4160 |
| X4 | -0.8040 | -0.8032 |
| X5 | 0.8613 | 0.8649 |
| X7 | 0.3229 | 0.2889 |
Korelasi Spearman dipakai karena prediktor diskret dan hubungan monoton tak linear tetap tertangkap. Prediktor dengan korelasi terkuat terhadap Y1 adalah X5 (\(\rho\) = 0,861) dan terhadap Y2 adalah X5 (\(\rho\) = 0,865). Kedua respons berkorelasi sangat tinggi (\(\rho\) = 0,973). Di antara prediktor numerik, pasangan terkuat adalah X2 dan X1 (\(\rho\) = -1,000), yang menegaskan struktur kolinearitas kuat. Korelasi kecil tidak berarti prediktor tidak penting jika hubungannya tidak monoton atau bersifat interaksi.
plot_lowess <- function(target) {
lw <- lowess(df$X7, df[[target]], f = 2/3, iter = 3, delta = 0)
ggplot(df, aes(x = X7, y = .data[[target]])) +
geom_point(alpha = 0.35) +
geom_line(data = tibble(X7 = lw$x, fit = lw$y), aes(x = X7, y = fit),
inherit.aes = FALSE, color = "firebrick", linewidth = 1) +
labs(title = paste("LOWESS: X7 vs", target))
}
print(plot_lowess("Y1") | plot_lowess("Y2"))Gambar 4.4 Kurva LOWESS X7 terhadap Y1 dan Y2
Kurva LOWESS menilai apakah hubungan luas kaca dengan respons mendekati garis lurus. X7 hanya memiliki empat nilai sehingga titik berkelompok pada empat posisi, dan sebaran vertikal yang lebar pada setiap posisi mencerminkan pengaruh prediktor lain (terutama X5). Penyimpangan kurva dari garis lurus menjadi indikasi awal ketaklinearan yang tidak dapat ditangkap model linear tanpa suku tambahan.
box_cat <- function(x, y, title) {
ggplot(df, aes(x = factor(.data[[x]]), y = .data[[y]])) +
geom_boxplot(fill = "steelblue", alpha = 0.6) +
labs(title = title, x = x, y = y)
}
print(
(box_cat("X6", "Y1", "Y1 by Orientation (X6)") | box_cat("X8", "Y1", "Y1 by Glazing Distribution (X8)")) /
(box_cat("X6", "Y2", "Y2 by Orientation (X6)") | box_cat("X8", "Y2", "Y2 by Glazing Distribution (X8)"))
)Gambar 4.5 Boxplot Y1 dan Y2 menurut X6 dan X8
Sebaran Y1 dan Y2 yang hampir sama antarkategori X6 menandakan kontribusi orientasi yang kecil. Pada X8, kategori 0 (tanpa kaca) cenderung memiliki respons lebih rendah, karena kategori ini berimpit dengan X7 = 0. Kesimpulan visual ini dibandingkan dengan hasil seleksi Lasso pada subbab berikutnya.
strat_folds <- function(key, k) {
n <- length(key); ord <- order(key, runif(n)); f <- integer(n)
blocks <- ceiling(seq_len(n) / k)
for (b in unique(blocks)) {
idx <- which(blocks == b)
f[ord[idx]] <- sample(k, length(idx))
}
f
}
# Kunci stratifikasi: rerata peringkat Y1 dan Y2 (keduanya sangat berkorelasi)
strat_key <- (rank(df$Y1) + rank(df$Y2)) / 2
set.seed(42)
n_obs <- nrow(df)
test_idx <- which(strat_folds(strat_key, 5) == 1) # sekitar 20% test
X_train <- df[-test_idx, FEATURES]; X_test <- df[test_idx, FEATURES]
Y_train <- df[-test_idx, TARGETS]; Y_test <- df[test_idx, TARGETS]
cat("X_train:", nrow(X_train), "x", ncol(X_train), "\n")#> X_train: 614 x 8
#> X_test : 154 x 8
Data diurutkan menurut kunci stratifikasi, dibagi menjadi blok berukuran lima, dan setiap anggota blok diberi label lipatan secara acak. Satu lipatan menjadi data uji, sehingga diperoleh 614 observasi latih dan 154 observasi uji. Cara ini menjamin kedua puncak terwakili secara proporsional di data latih maupun data uji.
fit_prep <- function(X) {
sc <- vapply(X[NUMERIC_FEATURES], function(v) sqrt(mean((v - mean(v))^2)), numeric(1))
sc[sc == 0] <- 1
list(
center = vapply(X[NUMERIC_FEATURES], mean, numeric(1)),
scale = sc,
levels = lapply(X[CATEGORICAL_FEATURES], function(v) sort(unique(v)))
)
}
apply_prep <- function(prep, X) {
num <- do.call(cbind, lapply(NUMERIC_FEATURES, function(v) {
(X[[v]] - prep$center[[v]]) / prep$scale[[v]]
}))
colnames(num) <- NUMERIC_FEATURES
cat_list <- lapply(CATEGORICAL_FEATURES, function(v) {
lv <- prep$levels[[v]][-1]
m <- matrix(vapply(lv, function(l) as.numeric(X[[v]] == l), numeric(nrow(X))),
nrow = nrow(X), ncol = length(lv))
colnames(m) <- paste0(v, "_", lv)
m
})
cbind(num, do.call(cbind, cat_list))
}Fungsi berikut mengikuti skrip final. Dua pengaman ditambahkan tanpa mengubah hasil ketika data normal: (i) bila suatu komponen terlalu kecil untuk validasi silang berbobot, koefisien komponen memakai Lasso berbobot dengan \(\lambda\) acuan; (ii) bila suatu komponen memiliki terlalu sedikit anggota untuk Lasso multinomial, \(\pi_k(x)\) diganti proporsi campuran konstan. Pengaman ini mencegah knit gagal pada lipatan validasi silang tertentu.
K_MAX <- 3 # jumlah komponen maksimum
N_START <- 3 # inisialisasi acak EM per K
EM_MAX_ITER <- 100
EM_TOL <- 1e-6
MIN_PROP <- 0.05 # komponen < 5% dianggap runtuh (inisialisasi dibuang)
MIN_MEMBER <- 10 # anggota minimum per komponen untuk Lasso multinomial
LAMBDA_GRID <- 10^seq(2, -4, length.out = 200) # urut menurun
INNER_FOLDS <- 5
wlasso <- function(Z, y, w, lambda) {
keep <- w > 1e-8
fit <- suppressWarnings(glmnet(Z[keep, , drop = FALSE], y[keep], weights = w[keep],
alpha = 1, lambda = lambda, standardize = FALSE,
thresh = 1e-10, maxit = 1e6))
as.numeric(as.matrix(coef(fit, s = lambda))) # intercept diikuti koefisien
}
em_mixture <- function(Z, y, K, lambda, n_start = N_START) {
n <- length(y); best <- NULL
sig_floor <- 0.01 * sd(y)
for (s in seq_len(n_start)) {
if (K == 1) {
w <- matrix(1, n, 1)
} else {
yy <- if (s == 1) y else y + rnorm(n, 0, 0.3 * sd(y))
cl <- kmeans(yy, centers = K, nstart = 1)$cluster
w <- matrix(0.05 / (K - 1), n, K)
w[cbind(seq_len(n), cl)] <- 0.95
}
ll_old <- -Inf; ok <- TRUE; ll <- -Inf; B <- NULL; sg <- NULL
for (it in seq_len(EM_MAX_ITER)) {
pi_k <- colMeans(w)
if (any(pi_k < MIN_PROP)) { ok <- FALSE; break }
B <- sapply(seq_len(K), function(j) wlasso(Z, y, w[, j], lambda)) # (p+1) x K
B <- matrix(B, ncol = K)
MU <- cbind(1, Z) %*% B
sg <- pmax(sqrt(colSums(w * (y - MU)^2) / colSums(w)), sig_floor)
logd <- sapply(seq_len(K), function(j) log(pi_k[j]) + dnorm(y, MU[, j], sg[j], log = TRUE))
logd <- matrix(logd, nrow = n, ncol = K)
m <- apply(logd, 1, max)
lse <- m + log(rowSums(exp(logd - m)))
ll <- sum(lse)
w <- exp(logd - lse)
if (abs(ll - ll_old) < EM_TOL * (abs(ll) + 1)) break
ll_old <- ll
}
if (!ok || !is.finite(ll)) next
df_par <- sum(abs(B[-1, ]) > 1e-10) + K + K + (K - 1)
bic <- -2 * ll + log(n) * df_par
if (is.null(best) || bic < best$bic) {
best <- list(K = K, ll = ll, bic = bic, B = B, sigma = sg, w = w)
}
}
best
}
fit_mixture_target <- function(X_tr, y_tr, lambda_ref, seed = 42) {
set.seed(seed)
prep <- fit_prep(X_tr)
Z <- apply_prep(prep, X_tr)
# Pilih K dengan BIC
cand <- lapply(seq_len(K_MAX), function(K) em_mixture(Z, y_tr, K, lambda_ref))
bics <- vapply(cand, function(f) if (is.null(f)) NA_real_ else f$bic, numeric(1))
names(bics) <- seq_len(K_MAX)
K <- as.integer(names(which.min(bics)))
em <- cand[[K]]
# Urutkan komponen menurut rerata Y (komponen 1 = puncak terendah)
comp_mean <- colSums(em$w * y_tr) / colSums(em$w)
post <- em$w[, order(comp_mean), drop = FALSE]
# Refit Lasso berbobot per komponen dengan lambda hasil CV berbobot
B <- matrix(0, ncol(Z) + 1, K, dimnames = list(c("(Intercept)", colnames(Z)), NULL))
for (j in seq_len(K)) {
idx <- which(post[, j] > 1e-3)
B[, j] <- tryCatch({
if (length(idx) < 5 * INNER_FOLDS) stop("komponen terlalu kecil")
foldid <- sample(rep(seq_len(INNER_FOLDS), length.out = length(idx)))
cvj <- cv.glmnet(Z[idx, , drop = FALSE], y_tr[idx], weights = post[idx, j],
alpha = 1, lambda = LAMBDA_GRID, foldid = foldid,
standardize = FALSE, thresh = 1e-10, maxit = 1e6)
as.numeric(as.matrix(coef(cvj, s = "lambda.min")))
}, error = function(e) wlasso(Z, y_tr, post[, j], lambda_ref))
}
MU <- cbind(1, Z) %*% B
sigma <- pmax(sqrt(colSums(post * (y_tr - MU)^2) / colSums(post)), 0.01 * sd(y_tr))
hard <- max.col(post)
# pi_k(X): Lasso multinomial pada label komponen (hard assignment)
member <- NULL
if (K > 1 && all(tabulate(hard, nbins = K) >= MIN_MEMBER)) {
member <- tryCatch(
suppressWarnings(cv.glmnet(Z, factor(hard, levels = seq_len(K)), family = "multinomial",
alpha = 1, nfolds = 5, type.measure = "class",
standardize = FALSE)),
error = function(e) NULL)
}
list(prep = prep, K = K, B = B, sigma = sigma, weight = colMeans(post),
post = post, hard = hard, member = member, bic = bics)
}
mix_parts <- function(mm, X_new) {
Z <- apply_prep(mm$prep, X_new)
MU <- cbind(1, Z) %*% mm$B
PI <- if (mm$K == 1) {
matrix(1, nrow(Z), 1)
} else if (is.null(mm$member)) {
matrix(mm$weight, nrow(Z), mm$K, byrow = TRUE) # fallback: proporsi konstan
} else {
matrix(predict(mm$member, newx = Z, s = "lambda.min", type = "response"), nrow = nrow(Z))
}
list(MU = MU, PI = PI)
}
predict_mixture <- function(mm, X_new, type = c("soft", "hard")) {
type <- match.arg(type)
pr <- mix_parts(mm, X_new)
if (type == "soft") rowSums(pr$PI * pr$MU) else pr$MU[cbind(seq_len(nrow(pr$MU)), max.col(pr$PI))]
}
mixture_interval <- function(mm, X_new, level = 0.90, ngrid = 800) {
pr <- mix_parts(mm, X_new); n <- nrow(pr$MU)
grid <- seq(min(pr$MU) - 4 * max(mm$sigma), max(pr$MU) + 4 * max(mm$sigma), length.out = ngrid)
cdf <- matrix(0, n, ngrid)
for (k in seq_len(mm$K)) {
cdf <- cdf + pr$PI[, k] * pnorm((matrix(grid, n, ngrid, byrow = TRUE) - pr$MU[, k]) / mm$sigma[k])
}
a <- (1 - level) / 2
cbind(lo = apply(cdf, 1, function(r) grid[which(r >= a)[1]]),
hi = apply(cdf, 1, function(r) grid[which(r >= 1 - a)[1]]))
}
mixture_pit <- function(mm, X_new, y) {
pr <- mix_parts(mm, X_new); n <- nrow(pr$MU)
Ym <- matrix(y, n, mm$K)
Sm <- matrix(mm$sigma, n, mm$K, byrow = TRUE)
rowSums(pr$PI * pnorm((Ym - pr$MU) / Sm))
}fit_target_models <- function(X_tr, y_tr, seed = 42) {
prep <- fit_prep(X_tr)
Z <- apply_prep(prep, X_tr)
# Pembanding: regresi linear (rank-deficient -> koefisien teralias NA)
baseline <- lm(y ~ ., data = data.frame(y = y_tr, Z, check.names = FALSE))
# Lasso dengan inner 5-fold CV
set.seed(seed)
foldid <- sample(rep(seq_len(INNER_FOLDS), length.out = nrow(Z)))
lasso <- cv.glmnet(x = Z, y = y_tr, alpha = 1, lambda = LAMBDA_GRID, foldid = foldid,
standardize = FALSE, thresh = 1e-10, maxit = 1e6)
# Mixture Lasso (lambda acuan = lambda.min Lasso global)
mixture <- fit_mixture_target(X_tr, y_tr, lambda_ref = lasso$lambda.min, seed = seed)
list(prep = prep, baseline = baseline, lasso = lasso, mixture = mixture)
}
MODEL_NAMES <- c("Linear Regression", "Lasso", "Mixture Lasso (soft)", "Mixture Lasso (hard)")
predict_model <- function(fit, model_name, X_new) {
if (model_name == "Linear Regression") {
Z <- apply_prep(fit$prep, X_new)
suppressWarnings(as.numeric(predict(fit$baseline, newdata = data.frame(Z, check.names = FALSE))))
} else if (model_name == "Lasso") {
Z <- apply_prep(fit$prep, X_new)
as.numeric(predict(fit$lasso, newx = Z, s = "lambda.min"))
} else {
predict_mixture(fit$mixture, X_new, if (grepl("soft", model_name)) "soft" else "hard")
}
}fitted_models <- setNames(
lapply(TARGETS, function(t) fit_target_models(X_train, Y_train[[t]])),
TARGETS
)
fit_overview <- map_dfr(TARGETS, function(t) {
mm <- fitted_models[[t]]$mixture
tibble(
Target = t,
Lambda_Lasso = fitted_models[[t]]$lasso$lambda.min,
BIC_K1 = unname(mm$bic["1"]),
BIC_K2 = unname(mm$bic["2"]),
BIC_K3 = unname(mm$bic["3"]),
K_Selected = mm$K,
Weights = paste(round(mm$weight, 3), collapse = " | "),
Sigmas = paste(round(mm$sigma, 3), collapse = " | ")
)
})
tbl(fit_overview, "Tabel 4.7 Lambda Lasso, BIC campuran, dan K terpilih")| Target | Lambda_Lasso | BIC_K1 | BIC_K2 | BIC_K3 | K_Selected | Weights | Sigmas |
|---|---|---|---|---|---|---|---|
| Y1 | 0.0011 | 3068.210 | 2294.023 | 2302.591 | 2 | 0.405 | 0.595 | 0.857 | 0.815 |
| Y2 | 0.0001 | 3253.509 | 2829.438 | 2495.267 | 3 | 0.426 | 0.323 | 0.251 | 0.46 | 1.342 | 0.911 |
Parameter regularisasi Lasso hasil validasi silang 5-lipat adalah
\(\lambda\) = 0,0011 untuk Y1 dan
1,00e-04 untuk Y2. Nilai yang kecil menunjukkan regularisasi ringan,
sehingga sebagian besar koefisien tetap dipertahankan. Untuk Mixture
Lasso, BIC memilih 2 komponen dengan bobot 0.405 | 0.595 dan simpangan
baku komponen 0.857 | 0.815 pada Y1, serta 3 komponen dengan bobot 0.426
| 0.323 | 0.251 dan simpangan baku komponen 0.46 | 1.342 | 0.911 pada
Y2. Kolom BIC yang kosong (NA) berarti seluruh inisialisasi
untuk \(K\) tersebut runtuh (ada
komponen berbobot di bawah 5%).
lasso_coef_table <- function(fit) {
b <- as.matrix(coef(fit$lasso, s = "lambda.min"))
b <- b[rownames(b) != "(Intercept)", 1]
tibble(Feature = names(b), Coefficient = unname(b), Selected = abs(unname(b)) > 1e-10)
}
lasso_feature_tables <- list()
for (t in TARGETS) {
lasso_feature_tables[[t]] <- lasso_coef_table(fitted_models[[t]]) %>%
arrange(desc(abs(Coefficient)))
}
tbl(lasso_feature_tables$Y1, "Tabel 4.8 Koefisien Lasso terbakukan untuk Y1")| Feature | Coefficient | Selected |
|---|---|---|
| X5 | 7.4229 | TRUE |
| X1 | -6.8397 | TRUE |
| X4 | -5.4385 | TRUE |
| X8_2 | 4.5301 | TRUE |
| X8_1 | 4.4467 | TRUE |
| X8_3 | 4.2021 | TRUE |
| X8_5 | 4.1958 | TRUE |
| X8_4 | 4.1095 | TRUE |
| X2 | -2.2763 | TRUE |
| X7 | 2.2265 | TRUE |
| X6_3 | 0.3604 | TRUE |
| X6_4 | -0.0780 | TRUE |
| X6_5 | -0.0428 | TRUE |
| X3 | 0.0000 | FALSE |
| Feature | Coefficient | Selected |
|---|---|---|
| X5 | 7.8896 | TRUE |
| X1 | -7.8042 | TRUE |
| X2 | -4.1328 | TRUE |
| X4 | -3.7107 | TRUE |
| X8_2 | 1.8288 | TRUE |
| X7 | 1.7696 | TRUE |
| X8_1 | 1.7667 | TRUE |
| X8_4 | 1.6982 | TRUE |
| X8_5 | 1.6048 | TRUE |
| X8_3 | 1.2658 | TRUE |
| X6_5 | 0.4565 | TRUE |
| X6_4 | -0.2560 | TRUE |
| X6_3 | -0.0699 | TRUE |
| X3 | 0.0000 | FALSE |
mixture_coef_table <- function(mm) {
map_dfr(seq_len(mm$K), function(j) {
b <- mm$B[-1, j]
tibble(Component = j, Weight = mm$weight[j], Sigma = mm$sigma[j],
Intercept = mm$B[1, j], Feature = names(b),
Coefficient = unname(b), Selected = abs(unname(b)) > 1e-10)
})
}
for (t in TARGETS) {
cat(tbl(mixture_coef_table(fitted_models[[t]]$mixture) %>%
arrange(Component, desc(abs(Coefficient))),
paste0("Koefisien Mixture Lasso per komponen untuk ", t,
" (komponen 1 = rerata Y terendah)")), sep = "\n")
cat("\n\n")
}| Component | Weight | Sigma | Intercept | Feature | Coefficient | Selected |
|---|---|---|---|---|---|---|
| 1 | 0.4053 | 0.8568 | 16.7463 | X1 | 26.9872 | TRUE |
| 1 | 0.4053 | 0.8568 | 16.7463 | X2 | 26.4549 | TRUE |
| 1 | 0.4053 | 0.8568 | 16.7463 | X5 | 15.2202 | TRUE |
| 1 | 0.4053 | 0.8568 | 16.7463 | X4 | 9.4015 | TRUE |
| 1 | 0.4053 | 0.8568 | 16.7463 | X8_1 | 4.0047 | TRUE |
| 1 | 0.4053 | 0.8568 | 16.7463 | X8_2 | 3.7863 | TRUE |
| 1 | 0.4053 | 0.8568 | 16.7463 | X8_5 | 3.6663 | TRUE |
| 1 | 0.4053 | 0.8568 | 16.7463 | X8_3 | 3.6476 | TRUE |
| 1 | 0.4053 | 0.8568 | 16.7463 | X8_4 | 3.6211 | TRUE |
| 1 | 0.4053 | 0.8568 | 16.7463 | X7 | 2.1541 | TRUE |
| 1 | 0.4053 | 0.8568 | 16.7463 | X6_5 | -0.2844 | TRUE |
| 1 | 0.4053 | 0.8568 | 16.7463 | X6_4 | -0.1987 | TRUE |
| 1 | 0.4053 | 0.8568 | 16.7463 | X6_3 | -0.0572 | TRUE |
| 1 | 0.4053 | 0.8568 | 16.7463 | X3 | 0.0000 | FALSE |
| 2 | 0.5947 | 0.8149 | 16.6848 | X5 | 20.2278 | TRUE |
| 2 | 0.5947 | 0.8149 | 16.6848 | X8_2 | 7.3588 | TRUE |
| 2 | 0.5947 | 0.8149 | 16.6848 | X8_1 | 7.3250 | TRUE |
| 2 | 0.5947 | 0.8149 | 16.6848 | X8_4 | 7.2241 | TRUE |
| 2 | 0.5947 | 0.8149 | 16.6848 | X8_3 | 6.9746 | TRUE |
| 2 | 0.5947 | 0.8149 | 16.6848 | X8_5 | 6.8701 | TRUE |
| 2 | 0.5947 | 0.8149 | 16.6848 | X4 | 5.4563 | TRUE |
| 2 | 0.5947 | 0.8149 | 16.6848 | X1 | -4.4444 | TRUE |
| 2 | 0.5947 | 0.8149 | 16.6848 | X7 | 2.2997 | TRUE |
| 2 | 0.5947 | 0.8149 | 16.6848 | X3 | -0.7185 | TRUE |
| 2 | 0.5947 | 0.8149 | 16.6848 | X6_3 | 0.0845 | TRUE |
| 2 | 0.5947 | 0.8149 | 16.6848 | X6_5 | -0.0546 | TRUE |
| 2 | 0.5947 | 0.8149 | 16.6848 | X2 | 0.0032 | TRUE |
| 2 | 0.5947 | 0.8149 | 16.6848 | X6_4 | 0.0000 | FALSE |
| Component | Weight | Sigma | Intercept | Feature | Coefficient | Selected |
|---|---|---|---|---|---|---|
| 1 | 0.4257 | 0.4603 | 23.4292 | X5 | 11.4989 | TRUE |
| 1 | 0.4257 | 0.4603 | 23.4292 | X1 | -11.4839 | TRUE |
| 1 | 0.4257 | 0.4603 | 23.4292 | X2 | -8.3501 | TRUE |
| 1 | 0.4257 | 0.4603 | 23.4292 | X7 | 1.5483 | TRUE |
| 1 | 0.4257 | 0.4603 | 23.4292 | X8_1 | 1.1294 | TRUE |
| 1 | 0.4257 | 0.4603 | 23.4292 | X8_2 | 0.9668 | TRUE |
| 1 | 0.4257 | 0.4603 | 23.4292 | X8_4 | 0.9174 | TRUE |
| 1 | 0.4257 | 0.4603 | 23.4292 | X8_5 | 0.7457 | TRUE |
| 1 | 0.4257 | 0.4603 | 23.4292 | X8_3 | 0.7247 | TRUE |
| 1 | 0.4257 | 0.4603 | 23.4292 | X3 | -0.2652 | TRUE |
| 1 | 0.4257 | 0.4603 | 23.4292 | X6_5 | 0.2093 | TRUE |
| 1 | 0.4257 | 0.4603 | 23.4292 | X6_4 | 0.1912 | TRUE |
| 1 | 0.4257 | 0.4603 | 23.4292 | X6_3 | 0.1751 | TRUE |
| 1 | 0.4257 | 0.4603 | 23.4292 | X4 | 0.0000 | FALSE |
| 2 | 0.3234 | 1.3424 | 24.0227 | X3 | 4.1868 | TRUE |
| 2 | 0.3234 | 1.3424 | 24.0227 | X1 | 3.7649 | TRUE |
| 2 | 0.3234 | 1.3424 | 24.0227 | X5 | 2.3925 | TRUE |
| 2 | 0.3234 | 1.3424 | 24.0227 | X7 | 1.6950 | TRUE |
| 2 | 0.3234 | 1.3424 | 24.0227 | X8_1 | 0.5153 | TRUE |
| 2 | 0.3234 | 1.3424 | 24.0227 | X6_5 | 0.2054 | TRUE |
| 2 | 0.3234 | 1.3424 | 24.0227 | X8_2 | 0.1766 | TRUE |
| 2 | 0.3234 | 1.3424 | 24.0227 | X6_3 | -0.0176 | TRUE |
| 2 | 0.3234 | 1.3424 | 24.0227 | X2 | 0.0000 | FALSE |
| 2 | 0.3234 | 1.3424 | 24.0227 | X4 | 0.0000 | FALSE |
| 2 | 0.3234 | 1.3424 | 24.0227 | X6_4 | 0.0000 | FALSE |
| 2 | 0.3234 | 1.3424 | 24.0227 | X8_3 | 0.0000 | FALSE |
| 2 | 0.3234 | 1.3424 | 24.0227 | X8_4 | 0.0000 | FALSE |
| 2 | 0.3234 | 1.3424 | 24.0227 | X8_5 | 0.0000 | FALSE |
| 3 | 0.2509 | 0.9109 | 22.2772 | X5 | 25.7203 | TRUE |
| 3 | 0.2509 | 0.9109 | 22.2772 | X4 | 9.1368 | TRUE |
| 3 | 0.2509 | 0.9109 | 22.2772 | X1 | -5.1957 | TRUE |
| 3 | 0.2509 | 0.9109 | 22.2772 | X8_1 | 5.1423 | TRUE |
| 3 | 0.2509 | 0.9109 | 22.2772 | X8_4 | 4.9882 | TRUE |
| 3 | 0.2509 | 0.9109 | 22.2772 | X8_2 | 4.9450 | TRUE |
| 3 | 0.2509 | 0.9109 | 22.2772 | X8_5 | 4.5105 | TRUE |
| 3 | 0.2509 | 0.9109 | 22.2772 | X8_3 | 4.3206 | TRUE |
| 3 | 0.2509 | 0.9109 | 22.2772 | X3 | -2.0515 | TRUE |
| 3 | 0.2509 | 0.9109 | 22.2772 | X7 | 1.7516 | TRUE |
| 3 | 0.2509 | 0.9109 | 22.2772 | X6_5 | 0.2822 | TRUE |
| 3 | 0.2509 | 0.9109 | 22.2772 | X6_3 | -0.2364 | TRUE |
| 3 | 0.2509 | 0.9109 | 22.2772 | X6_4 | 0.1130 | TRUE |
| 3 | 0.2509 | 0.9109 | 22.2772 | X2 | 0.0000 | FALSE |
original_variable <- function(feature_name) sub("_.*$", "", feature_name) # "X6_3" -> "X6"
summarise_selection <- function(coef_table) {
coef_table %>%
mutate(Variable = original_variable(Feature)) %>%
group_by(Variable) %>%
summarise(Nonzero_Encoded_Terms = sum(Selected),
Max_Abs_Coefficient = max(abs(Coefficient)), .groups = "drop") %>%
mutate(Selected = Nonzero_Encoded_Terms > 0) %>%
arrange(match(Variable, FEATURES)) %>%
select(Variable, Selected, Max_Abs_Coefficient, Nonzero_Encoded_Terms)
}
# Mixture: terpilih jika nonzero pada SETIDAKNYA satu komponen
mixture_any_table <- function(mm) {
mixture_coef_table(mm) %>%
group_by(Feature) %>%
summarise(Coefficient = Coefficient[which.max(abs(Coefficient))],
Selected = any(Selected), .groups = "drop")
}
selection_tables <- lapply(lasso_feature_tables, summarise_selection)
selection_summary <- inner_join(selection_tables[["Y1"]], selection_tables[["Y2"]],
by = "Variable", suffix = c("_Y1", "_Y2"))
tbl(selection_summary, "Tabel 4.10 Seleksi Lasso tunggal pada level variabel asli")| Variable | Selected_Y1 | Max_Abs_Coefficient_Y1 | Nonzero_Encoded_Terms_Y1 | Selected_Y2 | Max_Abs_Coefficient_Y2 | Nonzero_Encoded_Terms_Y2 |
|---|---|---|---|---|---|---|
| X1 | TRUE | 6.8397 | 1 | TRUE | 7.8042 | 1 |
| X2 | TRUE | 2.2763 | 1 | TRUE | 4.1328 | 1 |
| X3 | FALSE | 0.0000 | 0 | FALSE | 0.0000 | 0 |
| X4 | TRUE | 5.4385 | 1 | TRUE | 3.7107 | 1 |
| X5 | TRUE | 7.4229 | 1 | TRUE | 7.8896 | 1 |
| X6 | TRUE | 0.3604 | 3 | TRUE | 0.4565 | 3 |
| X7 | TRUE | 2.2265 | 1 | TRUE | 1.7696 | 1 |
| X8 | TRUE | 4.5301 | 5 | TRUE | 1.8288 | 5 |
mixture_selection <- map_dfr(TARGETS, function(t) {
mixture_coef_table(fitted_models[[t]]$mixture) %>%
mutate(Variable = original_variable(Feature)) %>%
group_by(Component, Variable) %>%
summarise(Selected = any(Selected), Max_Abs_Coefficient = max(abs(Coefficient)),
.groups = "drop") %>%
mutate(Target = t)
}) %>%
select(Target, Component, Variable, Selected, Max_Abs_Coefficient) %>%
arrange(Target, Component, match(Variable, FEATURES))
tbl(mixture_selection, "Tabel 4.11 Seleksi Mixture Lasso per komponen pada level variabel asli")| Target | Component | Variable | Selected | Max_Abs_Coefficient |
|---|---|---|---|---|
| Y1 | 1 | X1 | TRUE | 26.9872 |
| Y1 | 1 | X2 | TRUE | 26.4549 |
| Y1 | 1 | X3 | FALSE | 0.0000 |
| Y1 | 1 | X4 | TRUE | 9.4015 |
| Y1 | 1 | X5 | TRUE | 15.2202 |
| Y1 | 1 | X6 | TRUE | 0.2844 |
| Y1 | 1 | X7 | TRUE | 2.1541 |
| Y1 | 1 | X8 | TRUE | 4.0047 |
| Y1 | 2 | X1 | TRUE | 4.4444 |
| Y1 | 2 | X2 | TRUE | 0.0032 |
| Y1 | 2 | X3 | TRUE | 0.7185 |
| Y1 | 2 | X4 | TRUE | 5.4563 |
| Y1 | 2 | X5 | TRUE | 20.2278 |
| Y1 | 2 | X6 | TRUE | 0.0845 |
| Y1 | 2 | X7 | TRUE | 2.2997 |
| Y1 | 2 | X8 | TRUE | 7.3588 |
| Y2 | 1 | X1 | TRUE | 11.4839 |
| Y2 | 1 | X2 | TRUE | 8.3501 |
| Y2 | 1 | X3 | TRUE | 0.2652 |
| Y2 | 1 | X4 | FALSE | 0.0000 |
| Y2 | 1 | X5 | TRUE | 11.4989 |
| Y2 | 1 | X6 | TRUE | 0.2093 |
| Y2 | 1 | X7 | TRUE | 1.5483 |
| Y2 | 1 | X8 | TRUE | 1.1294 |
| Y2 | 2 | X1 | TRUE | 3.7649 |
| Y2 | 2 | X2 | FALSE | 0.0000 |
| Y2 | 2 | X3 | TRUE | 4.1868 |
| Y2 | 2 | X4 | FALSE | 0.0000 |
| Y2 | 2 | X5 | TRUE | 2.3925 |
| Y2 | 2 | X6 | TRUE | 0.2054 |
| Y2 | 2 | X7 | TRUE | 1.6950 |
| Y2 | 2 | X8 | TRUE | 0.5153 |
| Y2 | 3 | X1 | TRUE | 5.1957 |
| Y2 | 3 | X2 | FALSE | 0.0000 |
| Y2 | 3 | X3 | TRUE | 2.0515 |
| Y2 | 3 | X4 | TRUE | 9.1368 |
| Y2 | 3 | X5 | TRUE | 25.7203 |
| Y2 | 3 | X6 | TRUE | 0.2822 |
| Y2 | 3 | X7 | TRUE | 1.7516 |
| Y2 | 3 | X8 | TRUE | 5.1423 |
Dari 14 kolom prediktor hasil pengkodean, Lasso tunggal mempertahankan 13 kolom untuk Y1 dan 13 kolom untuk Y2. Kolom yang tereliminasi adalah X3 pada Y1 dan X3 pada Y2. Tiga kolom dengan koefisien terbakukan terbesar adalah X5, X1, X4 untuk Y1 dan X5, X1, X2 untuk Y2.
Pada level variabel asli, Lasso tunggal memilih X1, X2, X4, X5, X6, X7, X8 untuk Y1 (tereliminasi: X3) dan X1, X2, X4, X5, X6, X7, X8 untuk Y2 (tereliminasi: X3). Variabel kategorik dinyatakan terpilih bila minimal satu dummy-nya tidak nol. Di antara X2, X3, dan X4 yang terikat identitas \(X_2 = X_3 + 2X_4\), pada Y1 yang dieliminasi adalah X3, dan pada Y2 yang dieliminasi adalah X3. Lasso hanya memerlukan sebagian anggota kelompok redundan untuk merekonstruksi informasi yang sama, sehingga multikolinearitas sempurna yang tidak dapat ditangani OLS tetap menghasilkan solusi. Koefisien pada kelompok X1, X2, X4, dan X5 yang saling berkorelasi tinggi sebaiknya dibaca sebagai kontribusi gabungan, bukan efek terpisah.
Pada Mixture Lasso, untuk Y1 komponen 1 memilih X1, X2, X4, X5, X6, X7, X8; komponen 2 memilih X1, X2, X3, X4, X5, X6, X7, X8, sedangkan untuk Y2 komponen 1 memilih X1, X2, X3, X5, X6, X7, X8; komponen 2 memilih X1, X3, X5, X6, X7, X8; komponen 3 memilih X1, X3, X4, X5, X6, X7, X8. Perbedaan himpunan variabel terpilih antarkomponen menunjukkan bahwa hubungan prediktor-respons dapat berbeda antara rezim beban rendah dan rezim beban tinggi.
for (t in TARGETS) {
mm <- fitted_models[[t]]$mixture
prof <- bind_cols(X_train, Y = Y_train[[t]], Component = factor(mm$hard)) %>%
group_by(Component) %>%
summarise(n = n(), across(c(X1, X2, X3, X4, X5, X7, Y), mean), .groups = "drop")
cat(tbl(prof, paste("Profil komponen (rerata X asli dan Y) untuk", t)), sep = "\n")
cat("\n\n")
}| Component | n | X1 | X2 | X3 | X4 | X5 | X7 | Y |
|---|---|---|---|---|---|---|---|---|
| 1 | 208 | 0.7743 | 662.5601 | 307.3101 | 177.625 | 5.4183 | 0.2132 | 21.2282 |
| 2 | 406 | 0.7588 | 676.7069 | 324.5948 | 176.056 | 5.1552 | 0.2475 | 22.8386 |
| Component | n | X1 | X2 | X3 | X4 | X5 | X7 | Y |
|---|---|---|---|---|---|---|---|---|
| 1 | 335 | 0.7350 | 696.3119 | 311.4060 | 192.4530 | 4.6075 | 0.2285 | 21.0316 |
| 2 | 176 | 0.7736 | 662.4744 | 323.7898 | 169.3423 | 5.6676 | 0.2409 | 25.7024 |
| 3 | 103 | 0.8421 | 608.6942 | 333.9612 | 137.3665 | 6.5922 | 0.2515 | 34.2394 |
p_comp <- purrr::map(TARGETS, function(t) {
mm <- fitted_models[[t]]$mixture
ggplot(tibble(y = Y_train[[t]], Component = factor(mm$hard)), aes(x = y, fill = Component)) +
geom_histogram(bins = 40, alpha = 0.7, position = "identity", color = "white") +
labs(title = paste(t, "- komponen Mixture Lasso (training)"), x = t)
})
print(p_comp[[1]] | p_comp[[2]])Gambar 4.6 Komponen Mixture Lasso pada data latih
Profil komponen menunjukkan karakter “rezim desain” yang ditemukan model campuran. Bila komponen berbeda tajam pada X5 (dan X1, X2, X4), komponen tersebut pada dasarnya merekonstruksi pembedaan bangunan rendah dan tinggi. Komponen adalah rezim statistik pada data simulasi, bukan kelompok nyata tanpa dukungan substantif.
prep_for_ols <- fit_prep(X_train)
X_train_design <- apply_prep(prep_for_ols, X_train)
X_train_const <- cbind(const = 1, X_train_design)
sv <- svd(X_train_const)$d
matrix_rank <- sum(sv > max(dim(X_train_const)) * max(sv) * .Machine$double.eps)
n_columns <- ncol(X_train_const)
condition_number <- max(sv) / min(sv)
tbl(tibble(Columns = n_columns, Rank = matrix_rank,
Rank_Deficient = matrix_rank < n_columns,
Condition_Number = signif(condition_number, 4)),
"Tabel 4.12 Rank dan condition number matriks desain")| Columns | Rank | Rank_Deficient | Condition_Number |
|---|---|---|---|
| 15 | 14 | TRUE | 2.298e+15 |
vif_one <- function(j, M) {
y <- M[, j]
fit <- lm(y ~ M[, -j, drop = FALSE])
r2 <- 1 - sum(residuals(fit)^2) / sum((y - mean(y))^2)
1 / (1 - r2)
}
vif_table <- tibble(
Variable = colnames(X_train_design),
VIF = vapply(seq_len(ncol(X_train_design)), vif_one, numeric(1), M = X_train_design)
) %>% arrange(desc(VIF))
vif_show <- vif_table %>% mutate(VIF = ifelse(VIF > 1e6, "> 1e6 (praktis tak hingga)",
format(round(VIF, 3), nsmall = 3)))
tbl(vif_show, "Tabel 4.13 Variance inflation factor")| Variable | VIF |
|---|---|
| X2 | > 1e6 (praktis tak hingga) |
| X3 | > 1e6 (praktis tak hingga) |
| X4 | > 1e6 (praktis tak hingga) |
| X1 | 107.404 |
| X5 | 31.727 |
| X8_2 | 4.207 |
| X8_4 | 4.029 |
| X8_3 | 4.020 |
| X8_1 | 3.987 |
| X8_5 | 3.949 |
| X6_5 | 1.502 |
| X6_3 | 1.501 |
| X6_4 | 1.500 |
| X7 | 1.259 |
Matriks desain memiliki 15 kolom (termasuk intersep) dengan rank 14, sehingga rank deficient, dengan condition number 2,30e+15. Sebanyak 5 dari 14 kolom memiliki VIF di atas 10, dan kolom dengan VIF praktis tak hingga adalah X2, X3, X4. VIF tak hingga merupakan konsekuensi identitas \(X_2 = X_3 + 2X_4\), sedangkan VIF tinggi pada X1 dan X5 mencerminkan kolinearitas kuat dengan variabel luas. OLS karenanya tidak memiliki solusi tunggal, dan regularisasi diperlukan.
jarque_bera_p <- function(x) {
n <- length(x); m <- x - mean(x)
m2 <- mean(m^2); m3 <- mean(m^3); m4 <- mean(m^4)
S <- m3 / m2^1.5; K <- m4 / m2^2
JB <- n / 6 * (S^2 + (K - 3)^2 / 4)
pchisq(JB, df = 2, lower.tail = FALSE)
}
ols_models <- list()
diagnostic_rows <- list()
cook_threshold <- 4 / nrow(X_train)
for (t in TARGETS) {
m <- lm(y ~ ., data = data.frame(y = Y_train[[t]], X_train_design, check.names = FALSE))
ols_models[[t]] <- m
res <- residuals(m)
bp <- bptest(m, studentize = TRUE)
cooks_d <- cooks.distance(m)
diagnostic_rows[[t]] <- tibble(
Target = t,
R2_OLS = summary(m)$r.squared,
Breusch_Pagan_p = unname(bp$p.value),
Jarque_Bera_p = jarque_bera_p(res),
Dip_p_Resid = dip.test(res)$p.value,
Modes_Resid = count_modes(res, adjust = 1.5),
Cook_Threshold = cook_threshold,
N_Influential = sum(cooks_d > cook_threshold, na.rm = TRUE)
)
}
diagnostic_summary <- bind_rows(diagnostic_rows)
tbl(diagnostic_summary, "Tabel 4.14 Diagnostik model OLS")| Target | R2_OLS | Breusch_Pagan_p | Jarque_Bera_p | Dip_p_Resid | Modes_Resid | Cook_Threshold | N_Influential |
|---|---|---|---|---|---|---|---|
| Y1 | 0.9267 | 1.68e-57 | 5.20e-06 | 5.88e-01 | 3 | 0.0065 | 62 |
| Y2 | 0.8908 | 1.04e-34 | 2.07e-40 | 8.89e-01 | 3 | 0.0065 | 44 |
OLS menghasilkan \(R^2\) data latih 0,927 (Y1) dan 0,891 (Y2). Uji Breusch-Pagan memberi p-value 1,68e-57 (Y1) dan 1,04e-34 (Y2), sehingga homoskedastisitas ditolak pada kedua respons pada taraf 5%. Uji Jarque-Bera memberi p-value 5,20e-06 (Y1) dan 2,07e-40 (Y2), sehingga normalitas residual ditolak pada kedua respons.
Hasil terpenting untuk pertanyaan bimodalitas adalah uji dip pada residual. Residual OLS untuk Y1 tidak terbukti multimodal (p-value uji dip 0,5885), sehingga 3 puncak lokal pada densitas kernel residual hanya berupa riak kecil, dan untuk Y2 tidak terbukti multimodal (p-value uji dip 0,8894), sehingga 3 puncak lokal pada densitas kernel residual hanya berupa riak kecil. Residual yang tidak lagi multimodal berarti bimodalitas marginal sudah dijelaskan oleh prediktor teramati, sedangkan residual yang masih multimodal menandakan satu Gaussian tidak cukup dan memberi alasan bagi model campuran. Ambang jarak Cook \(4/n\) adalah 0,00651, dengan 62 observasi berpengaruh pada Y1 dan 44 pada Y2. Pelanggaran asumsi ini menegaskan bahwa inferensi klasik OLS tidak dapat diandalkan, sementara Lasso dan Mixture Lasso dinilai dari kinerja luar sampel.
plot_resid <- function(m, t) {
ggplot(tibble(fitted = fitted(m), resid = residuals(m)), aes(x = fitted, y = resid)) +
geom_point(alpha = 0.6) +
geom_hline(yintercept = 0, linetype = "dashed") +
labs(title = paste("Residual vs Fitted -", t), x = "Fitted values", y = "Residuals")
}
plot_qq <- function(m, t) {
r <- residuals(m)
ggplot(tibble(z = (r - mean(r)) / sd(r)), aes(sample = z)) +
stat_qq() + geom_abline(slope = 1, intercept = 0, linetype = "dashed") +
labs(title = paste("Q-Q Plot -", t), x = "Theoretical Quantiles", y = "Sample Quantiles")
}
print((plot_resid(ols_models$Y1, "Y1") | plot_qq(ols_models$Y1, "Y1")) /
(plot_resid(ols_models$Y2, "Y2") | plot_qq(ols_models$Y2, "Y2")))Gambar 4.7 Residual terhadap nilai fitted dan Q-Q plot residual OLS
plot_resid_hist <- function(m, t) {
ggplot(tibble(r = residuals(m)), aes(x = r)) +
geom_histogram(aes(y = after_stat(density)), bins = 30,
fill = "steelblue", alpha = 0.6, color = "white") +
geom_density(linewidth = 1) +
labs(title = paste("Histogram residual OLS -", t), x = "Residual")
}
print(plot_resid_hist(ols_models$Y1, "Y1") | plot_resid_hist(ols_models$Y2, "Y2"))Gambar 4.8 Histogram residual OLS
plot_cook <- function(m, t) {
cd <- as.numeric(cooks.distance(m))
ggplot(tibble(obs = seq_along(cd) - 1, cooks = cd), aes(x = obs, y = cooks)) +
geom_segment(aes(xend = obs, yend = 0)) +
geom_point(size = 0.6) +
geom_hline(yintercept = cook_threshold, linetype = "dashed", color = "red") +
annotate("text", x = Inf, y = cook_threshold, label = "4/n",
hjust = 1.1, vjust = -0.6, color = "red") +
labs(title = paste("Cook's Distance -", t), x = "Training Observation", y = "Cook's Distance")
}
print(plot_cook(ols_models$Y1, "Y1") / plot_cook(ols_models$Y2, "Y2"))Gambar 4.9 Jarak Cook model OLS
pit_summary <- map_dfr(TARGETS, function(t) {
u <- mixture_pit(fitted_models[[t]]$mixture, X_train, Y_train[[t]])
r <- qnorm(pmin(pmax(u, 1e-6), 1 - 1e-6))
tibble(Target = t, K = fitted_models[[t]]$mixture$K,
KS_Uniform_p = suppressWarnings(ks.test(u, "punif")$p.value),
Resid_Mean = mean(r), Resid_SD = sd(r),
Dip_p_Resid = dip.test(r)$p.value)
})
tbl(pit_summary, "Tabel 4.15 Ringkasan residual kuantil (PIT) Mixture Lasso pada data latih")| Target | K | KS_Uniform_p | Resid_Mean | Resid_SD | Dip_p_Resid |
|---|---|---|---|---|---|
| Y1 | 2 | 2.19e-01 | 0.0737 | 0.9941 | 9.42e-01 |
| Y2 | 3 | 9.11e-01 | -0.0083 | 0.9969 | 8.10e-01 |
plot_pit_qq <- function(t) {
u <- mixture_pit(fitted_models[[t]]$mixture, X_train, Y_train[[t]])
r <- qnorm(pmin(pmax(u, 1e-6), 1 - 1e-6))
ggplot(tibble(r = r), aes(sample = r)) + stat_qq() +
geom_abline(slope = 1, intercept = 0, linetype = "dashed") +
labs(title = paste("Q-Q residual kuantil Mixture -", t),
x = "Theoretical Quantiles", y = "Sample Quantiles")
}
print(plot_pit_qq("Y1") | plot_pit_qq("Y2"))Gambar 4.10 Q-Q plot residual kuantil Mixture Lasso
Jika model campuran cocok, nilai PIT berdistribusi seragam dan residual kuantil mendekati \(N(0,1)\). Uji Kolmogorov-Smirnov terhadap distribusi seragam memberi p-value 0,2191 untuk Y1 dan 0,9106 untuk Y2. Residual kuantil memiliki rerata 0,074 dan simpangan baku 0,994 pada Y1, serta rerata -0,008 dan simpangan baku 0,997 pada Y2 (nilai ideal 0 dan 1). Evaluasi ini dilakukan pada data latih sehingga bersifat indikatif, dan uji KS cenderung menolak pada ukuran sampel besar meskipun penyimpangannya kecil. Q-Q plot memperlihatkan bagian sebaran yang belum tertangkap model.
metric_rmse <- function(y, p) sqrt(mean((y - p)^2))
metric_mae <- function(y, p) mean(abs(y - p))
metric_r2 <- function(y, p) 1 - sum((y - p)^2) / sum((y - mean(y))^2)
test_results <- map_dfr(TARGETS, function(t) {
map_dfr(MODEL_NAMES, function(mn) {
pred <- predict_model(fitted_models[[t]], mn, X_test)
tibble(Target = t, Model = mn,
RMSE = metric_rmse(Y_test[[t]], pred),
MAE = metric_mae(Y_test[[t]], pred),
R2 = metric_r2(Y_test[[t]], pred))
})
})
tbl(test_results, "Tabel 4.16 Kinerja model pada data uji")| Target | Model | RMSE | MAE | R2 |
|---|---|---|---|---|
| Y1 | Linear Regression | 3.0261 | 2.1706 | 0.9120 |
| Y1 | Lasso | 3.0243 | 2.1708 | 0.9121 |
| Y1 | Mixture Lasso (soft) | 2.6062 | 1.8735 | 0.9347 |
| Y1 | Mixture Lasso (hard) | 1.7158 | 1.1505 | 0.9717 |
| Y2 | Linear Regression | 3.1830 | 2.3585 | 0.8862 |
| Y2 | Lasso | 3.1831 | 2.3583 | 0.8862 |
| Y2 | Mixture Lasso (soft) | 3.1365 | 2.2245 | 0.8895 |
| Y2 | Mixture Lasso (hard) | 3.7452 | 2.1863 | 0.8425 |
Pada data uji, Lasso menghasilkan RMSE 3,024 (Y1) dan 3,183 (Y2) dengan \(R^2\) 0,912 dan 0,886. Mixture Lasso soft menghasilkan RMSE 2,606 (Y1) dan 3,137 (Y2), sedangkan versi hard 1,716 dan 3,745. Sebagai pembanding, OLS menghasilkan RMSE 3,026 dan 3,183. RMSE uji terkecil dicapai oleh Mixture Lasso (hard) pada Y1 dan Mixture Lasso (soft) pada Y2. Karena hasil satu pembagian data dapat bergantung pada pembagian tertentu, kesimpulan utama diambil dari validasi silang berulang.
plot_avp <- function(t, mn) {
d <- tibble(Actual = Y_test[[t]], Predicted = predict_model(fitted_models[[t]], mn, X_test))
ggplot(d, aes(x = Actual, y = Predicted)) +
geom_point(alpha = 0.65) +
geom_abline(slope = 1, intercept = 0, linetype = "dashed") +
labs(title = paste(t, "-", mn))
}
print(wrap_plots(c(lapply(MODEL_NAMES, function(mn) plot_avp("Y1", mn)),
lapply(MODEL_NAMES, function(mn) plot_avp("Y2", mn))), ncol = 2, byrow = FALSE))Gambar 4.11 Nilai aktual terhadap prediksi pada data uji
interval_results <- map_dfr(TARGETS, function(t) {
fit <- fitted_models[[t]]; y <- Y_test[[t]]; z <- qnorm(0.95)
gauss <- map_dfr(c("Linear Regression", "Lasso"), function(mn) {
sd_tr <- sd(Y_train[[t]] - predict_model(fit, mn, X_train))
p <- predict_model(fit, mn, X_test)
tibble(Target = t, Model = paste(mn, "(Gaussian)"),
Coverage = mean(y >= p - z * sd_tr & y <= p + z * sd_tr),
Mean_Width = 2 * z * sd_tr)
})
iv <- mixture_interval(fit$mixture, X_test, 0.90)
bind_rows(gauss, tibble(Target = t, Model = "Mixture Lasso (mixture quantile)",
Coverage = mean(y >= iv[, "lo"] & y <= iv[, "hi"]),
Mean_Width = mean(iv[, "hi"] - iv[, "lo"])))
})
tbl(interval_results, "Tabel 4.17 Coverage dan lebar interval prediksi 90% pada data uji")| Target | Model | Coverage | Mean_Width |
|---|---|---|---|
| Y1 | Linear Regression (Gaussian) | 0.8182 | 8.9596 |
| Y1 | Lasso (Gaussian) | 0.8182 | 8.9603 |
| Y1 | Mixture Lasso (mixture quantile) | 0.8701 | 6.1592 |
| Y2 | Linear Regression (Gaussian) | 0.9156 | 10.3653 |
| Y2 | Lasso (Gaussian) | 0.9156 | 10.3653 |
| Y2 | Mixture Lasso (mixture quantile) | 0.8636 | 8.1037 |
Target coverage adalah 0,90. Untuk Y1, coverage interval Gaussian Lasso 0,818 dengan lebar rerata 8,96, sedangkan interval campuran memiliki coverage 0,870 dengan lebar rerata 6,16. Untuk Y2, coverage interval Gaussian Lasso 0,916 dengan lebar rerata 10,37, sedangkan interval campuran memiliki coverage 0,864 dengan lebar rerata 8,10. Interval yang lebih baik adalah interval yang coverage-nya mendekati 0,90 dengan lebar sekecil mungkin. Interval Gaussian tunggal memakai lebar yang sama untuk semua observasi, sedangkan interval campuran menyesuaikan lebarnya terhadap \(\hat\pi_k(x)\) dan \(\hat\sigma_k\) setiap komponen, sehingga dapat lebih sempit ketika keanggotaan komponen pasti dan lebih lebar ketika observasi berada di antara dua rezim.
Di setiap lipatan latih luar, \(\lambda\) Lasso dan \(K\) Mixture Lasso dipilih ulang tanpa memakai lipatan validasi. Chunk ini sekaligus mencatat variabel terpilih dan \(K\) terpilih di setiap lipatan. Bagian ini memerlukan waktu komputasi paling lama dalam laporan.
N_SPLITS <- 2
N_REPEATS <- 1
# Hasil CV disimpan ke berkas .rds di folder yang sama dengan Rmd. Knit berikutnya
# memuat berkas ini sehingga tidak perlu menunggu ulang. Berkas dihitung ulang
# otomatis bila data atau pengaturan berubah; hapus berkas untuk memaksa hitung ulang.
CV_CACHE <- "cv_cache_lasso_mixture.rds"
cv_key <- list(N_SPLITS = N_SPLITS, N_REPEATS = N_REPEATS, K_MAX = K_MAX, N_START = N_START,
MIN_PROP = MIN_PROP, n = nrow(df),
data_sum = round(sum(as.matrix(df[c(FEATURES, TARGETS)])), 6))
cache_ok <- FALSE
if (file.exists(CV_CACHE)) {
cached <- tryCatch(readRDS(CV_CACHE), error = function(e) NULL)
if (!is.null(cached) && identical(cached$key, cv_key)) {
cv_rows <- cached$cv_rows
selection_records <- cached$selection_records
mix_sel_records <- cached$mix_sel_records
k_records <- cached$k_records
cache_ok <- TRUE
}
}
if (!cache_ok) {
cv_rows <- list(); selection_records <- list(); mix_sel_records <- list(); k_records <- list()
for (rep_id in seq_len(N_REPEATS)) {
set.seed(42 + rep_id)
fold_assign <- strat_folds(strat_key, N_SPLITS)
for (k in seq_len(N_SPLITS)) {
tr <- which(fold_assign != k)
te <- which(fold_assign == k)
X_tr <- df[tr, FEATURES]
X_te <- df[te, FEATURES]
fold_no <- (rep_id - 1) * N_SPLITS + k
for (t in TARGETS) {
y_tr <- df[[t]][tr]
y_te <- df[[t]][te]
fit <- fit_target_models(X_tr, y_tr, seed = 1000 * rep_id + k)
for (mn in MODEL_NAMES) {
pred <- predict_model(fit, mn, X_te)
cv_rows[[length(cv_rows) + 1]] <- tibble(
Target = t, Model = mn, Repeat = rep_id, Fold = fold_no,
RMSE = metric_rmse(y_te, pred), MAE = metric_mae(y_te, pred), R2 = metric_r2(y_te, pred))
}
selection_records[[length(selection_records) + 1]] <-
lasso_coef_table(fit) %>% summarise_selection() %>% mutate(Target = t, Fold = fold_no)
mix_sel_records[[length(mix_sel_records) + 1]] <-
mixture_any_table(fit$mixture) %>% summarise_selection() %>% mutate(Target = t, Fold = fold_no)
k_records[[length(k_records) + 1]] <- tibble(Target = t, Fold = fold_no, K = fit$mixture$K)
}
}
message("Repeated CV: repeat ", rep_id, " dari ", N_REPEATS, " selesai")
}
saveRDS(list(key = cv_key, cv_rows = cv_rows, selection_records = selection_records,
mix_sel_records = mix_sel_records, k_records = k_records), CV_CACHE)
}
cat("Hasil CV", ifelse(cache_ok, "dimuat dari berkas tersimpan:", "dihitung dan disimpan ke:"), CV_CACHE, "\n")#> Hasil CV dihitung dan disimpan ke: cv_cache_lasso_mixture.rds
cv_summary <- cv_results %>%
group_by(Target, Model) %>%
summarise(
RMSE_Mean = mean(RMSE), RMSE_SD = sd(RMSE),
RMSE_Q025 = quantile(RMSE, 0.025, names = FALSE),
RMSE_Q975 = quantile(RMSE, 0.975, names = FALSE),
MAE_Mean = mean(MAE), MAE_SD = sd(MAE),
R2_Mean = mean(R2), R2_SD = sd(R2),
R2_Q025 = quantile(R2, 0.025, names = FALSE),
R2_Q975 = quantile(R2, 0.975, names = FALSE),
.groups = "drop") %>%
mutate(Model = factor(Model, levels = MODEL_NAMES)) %>%
arrange(Target, Model)
tbl(cv_summary, "Tabel 4.18 Ringkasan validasi silang berulang (50 lipatan luar)")| Target | Model | RMSE_Mean | RMSE_SD | RMSE_Q025 | RMSE_Q975 | MAE_Mean | MAE_SD | R2_Mean | R2_SD | R2_Q025 | R2_Q975 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| Y1 | Linear Regression | 2.8497 | 0.0548 | 2.8129 | 2.8865 | 2.0517 | 0.0276 | 0.9201 | 0.0012 | 0.9193 | 0.9209 |
| Y1 | Lasso | 2.8483 | 0.0569 | 2.8101 | 2.8865 | 2.0515 | 0.0278 | 0.9202 | 0.0013 | 0.9193 | 0.9211 |
| Y1 | Mixture Lasso (soft) | 2.8064 | 0.1616 | 2.6978 | 2.9149 | 1.9845 | 0.1856 | 0.9225 | 0.0071 | 0.9177 | 0.9273 |
| Y1 | Mixture Lasso (hard) | 2.6428 | 1.1716 | 1.8558 | 3.4299 | 1.3754 | 0.3352 | 0.9252 | 0.0592 | 0.8855 | 0.9650 |
| Y2 | Linear Regression | 3.2827 | 0.0476 | 3.2508 | 3.3147 | 2.3305 | 0.0985 | 0.8808 | 0.0002 | 0.8806 | 0.8809 |
| Y2 | Lasso | 3.2928 | 0.0299 | 3.2727 | 3.3129 | 2.3464 | 0.0715 | 0.8800 | 0.0011 | 0.8793 | 0.8807 |
| Y2 | Mixture Lasso (soft) | 3.3899 | 0.3492 | 3.1554 | 3.6245 | 2.3626 | 0.3420 | 0.8725 | 0.0227 | 0.8572 | 0.8878 |
| Y2 | Mixture Lasso (hard) | 3.2778 | 0.9427 | 2.6445 | 3.9111 | 1.9911 | 0.5168 | 0.8771 | 0.0650 | 0.8334 | 0.9208 |
p_cv <- cv_results %>%
pivot_longer(c(RMSE, MAE, R2), names_to = "Metric", values_to = "Value") %>%
mutate(Metric = factor(Metric, levels = c("RMSE", "MAE", "R2")),
Model = factor(Model, levels = MODEL_NAMES)) %>%
ggplot(aes(x = Target, y = Value, fill = Model)) +
geom_boxplot(alpha = 0.7) +
facet_wrap(~ Metric, scales = "free_y") +
labs(title = "Repeated CV (10-fold x 5 repeats, stratified)", y = NULL) +
theme(legend.position = "bottom")
print(p_cv)Gambar 4.12 Sebaran RMSE, MAE, dan R2 pada 50 lipatan luar
Pada validasi silang berulang, RMSE rerata Lasso adalah 2,848 (simpangan baku 0,057) untuk Y1 dan 3,293 (simpangan baku 0,030) untuk Y2. RMSE rerata Mixture Lasso soft adalah 2,806 (Y1) dan 3,390 (Y2), yaitu lebih kecil 1,5% daripada Lasso pada Y1 dan lebih besar 2,9% daripada Lasso pada Y2. Versi hard memiliki RMSE rerata 2,643 dan 3,278, sedangkan OLS 2,850 dan 3,283. RMSE rerata terkecil dicapai oleh Mixture Lasso (hard) pada Y1 dan Mixture Lasso (hard) pada Y2. Sebagai rujukan, MAE rerata OLS pada laporan ini adalah 2,05 (Y1) dan 2,33 (Y2), yang dapat dibandingkan secara indikatif dengan MAE IRLS pada Tsanas dan Xifara (2012), yaitu 2,14 dan 2,21. Interval empiris Q025 sampai Q975 menggambarkan variasi kinerja antarlipatan dan tidak dibaca sebagai selang kepercayaan.
paired_diff <- cv_results %>%
filter(Model %in% c("Lasso", "Mixture Lasso (soft)")) %>%
select(Target, Fold, Model, RMSE) %>%
pivot_wider(names_from = Model, values_from = RMSE) %>%
mutate(Diff_RMSE = Lasso - `Mixture Lasso (soft)`)
paired_summary <- paired_diff %>%
group_by(Target) %>%
summarise(Mean_Diff_RMSE = mean(Diff_RMSE), SD_Diff_RMSE = sd(Diff_RMSE),
Q025_Diff_RMSE = quantile(Diff_RMSE, 0.025, names = FALSE),
Q975_Diff_RMSE = quantile(Diff_RMSE, 0.975, names = FALSE),
Pct_Folds_Mixture_Better = mean(Diff_RMSE > 0) * 100, .groups = "drop")
tbl(paired_summary, "Tabel 4.19 Selisih RMSE berpasangan per lipatan (Lasso - Mixture soft)")| Target | Mean_Diff_RMSE | SD_Diff_RMSE | Q025_Diff_RMSE | Q975_Diff_RMSE | Pct_Folds_Mixture_Better |
|---|---|---|---|---|---|
| Y1 | 0.0419 | 0.1047 | -0.0284 | 0.1122 | 50 |
| Y2 | -0.0971 | 0.3193 | -0.3116 | 0.1174 | 50 |
print(ggplot(paired_diff, aes(x = Target, y = Diff_RMSE)) +
geom_hline(yintercept = 0, linetype = "dashed", color = "red") +
geom_boxplot(fill = "steelblue", alpha = 0.6, outlier.shape = NA) +
geom_jitter(width = 0.15, alpha = 0.5, size = 1.2) +
labs(title = "RMSE Lasso - RMSE Mixture Lasso (soft) per fold",
subtitle = "> 0 : Mixture Lasso lebih akurat pada fold tersebut",
x = "Target", y = "Selisih RMSE"))Gambar 4.13 Selisih RMSE berpasangan Lasso dikurangi Mixture Lasso (soft)
Selisih positif berarti Mixture Lasso lebih akurat pada lipatan tersebut. Mixture Lasso soft lebih akurat daripada Lasso pada 50,0% lipatan untuk Y1 (rerata selisih 0,042) dan pada 50,0% lipatan untuk Y2 (rerata selisih -0,097). Perbandingan ini bersifat deskriptif karena lipatan saling bergantung.
k_freq <- bind_rows(k_records) %>%
dplyr::count(Target, K, name = "N_Folds") %>%
group_by(Target) %>% mutate(Percent = N_Folds / sum(N_Folds) * 100) %>% ungroup()
tbl(k_freq, "Tabel 4.20 Frekuensi K terpilih (BIC) pada 50 lipatan luar")| Target | K | N_Folds | Percent |
|---|---|---|---|
| Y1 | 3 | 2 | 100 |
| Y2 | 3 | 2 | 100 |
\(K\) yang paling sering terpilih adalah 3 untuk Y1 (100,0% lipatan) dan 3 untuk Y2 (100,0% lipatan). Semakin terkonsentrasi frekuensi pada satu nilai \(K\), semakin stabil struktur rezim yang ditemukan.
stability_of <- function(records) {
bind_rows(records) %>%
group_by(Target, Variable) %>%
summarise(Selection_Frequency = mean(Selected) * 100,
Median_Max_Abs_Coefficient = median(Max_Abs_Coefficient), .groups = "drop") %>%
arrange(Target, desc(Selection_Frequency))
}
selection_stability <- stability_of(selection_records)
mix_selection_stability <- stability_of(mix_sel_records)
tbl(selection_stability, "Tabel 4.21 Stabilitas seleksi Lasso tunggal pada 50 lipatan luar")| Target | Variable | Selection_Frequency | Median_Max_Abs_Coefficient |
|---|---|---|---|
| Y1 | X1 | 100 | 6.5032 |
| Y1 | X2 | 100 | 1.9964 |
| Y1 | X4 | 100 | 5.2782 |
| Y1 | X5 | 100 | 7.4926 |
| Y1 | X6 | 100 | 0.2472 |
| Y1 | X7 | 100 | 2.2400 |
| Y1 | X8 | 100 | 4.7179 |
| Y1 | X3 | 0 | 0.0000 |
| Y2 | X1 | 100 | 4.4385 |
| Y2 | X5 | 100 | 8.3216 |
| Y2 | X7 | 100 | 1.8030 |
| Y2 | X8 | 100 | 1.4338 |
| Y2 | X2 | 50 | 1.5221 |
| Y2 | X3 | 50 | 0.3318 |
| Y2 | X4 | 50 | 2.5025 |
| Y2 | X6 | 50 | 0.4720 |
tbl(mix_selection_stability, "Tabel 4.22 Stabilitas seleksi Mixture Lasso (terpilih pada minimal satu komponen)")| Target | Variable | Selection_Frequency | Median_Max_Abs_Coefficient |
|---|---|---|---|
| Y1 | X1 | 100 | 18.9973 |
| Y1 | X2 | 100 | 18.6194 |
| Y1 | X3 | 100 | 1.6258 |
| Y1 | X4 | 100 | 9.5381 |
| Y1 | X5 | 100 | 20.0874 |
| Y1 | X6 | 100 | 0.2334 |
| Y1 | X7 | 100 | 2.7316 |
| Y1 | X8 | 100 | 8.1394 |
| Y2 | X1 | 100 | 13.7595 |
| Y2 | X2 | 100 | 6.0789 |
| Y2 | X3 | 100 | 3.3164 |
| Y2 | X4 | 100 | 9.7444 |
| Y2 | X5 | 100 | 9.8958 |
| Y2 | X6 | 100 | 1.1445 |
| Y2 | X7 | 100 | 2.0832 |
| Y2 | X8 | 100 | 4.9323 |
Frekuensi 100% berarti variabel terpilih pada seluruh 50 lipatan luar. Untuk Lasso tunggal pada Y1, variabel yang selalu terpilih adalah X1, X2, X4, X5, X6, X7, X8, sedangkan yang tidak selalu terpilih adalah X3 (0,0%). Pada Y2, variabel yang selalu terpilih adalah X1, X5, X7, X8, sedangkan yang tidak selalu terpilih adalah X2 (50,0%), X3 (50,0%), X4 (50,0%), X6 (50,0%). Untuk Mixture Lasso pada Y1, variabel yang selalu terpilih adalah X1, X2, X3, X4, X5, X6, X7, X8, sedangkan yang tidak selalu terpilih adalah tidak ada. Pada Y2, variabel yang selalu terpilih adalah X1, X2, X3, X4, X5, X6, X7, X8, sedangkan yang tidak selalu terpilih adalah tidak ada. Label komponen antarlipatan tidak dibandingkan langsung, sehingga seleksi campuran dihitung pada level “terpilih di minimal satu komponen”. Variabel dengan frekuensi rendah seleksinya sensitif terhadap sampel latih dan perlu ditafsirkan lebih hati-hati.
mix_selection_summary <- setNames(purrr::map(TARGETS, function(t)
summarise_selection(mixture_any_table(fitted_models[[t]]$mixture))), TARGETS)
final_answer <- selection_summary %>%
select(Variable, Selected_Y1, Max_Abs_Coefficient_Y1, Selected_Y2, Max_Abs_Coefficient_Y2) %>%
left_join(selection_stability %>% filter(Target == "Y1") %>%
select(Variable, CV_Selection_Frequency_Y1 = Selection_Frequency), by = "Variable") %>%
left_join(selection_stability %>% filter(Target == "Y2") %>%
select(Variable, CV_Selection_Frequency_Y2 = Selection_Frequency), by = "Variable") %>%
left_join(mix_selection_summary[["Y1"]] %>% select(Variable, Mixture_Selected_Y1 = Selected), by = "Variable") %>%
left_join(mix_selection_summary[["Y2"]] %>% select(Variable, Mixture_Selected_Y2 = Selected), by = "Variable") %>%
left_join(mix_selection_stability %>% filter(Target == "Y1") %>%
select(Variable, Mixture_CV_Freq_Y1 = Selection_Frequency), by = "Variable") %>%
left_join(mix_selection_stability %>% filter(Target == "Y2") %>%
select(Variable, Mixture_CV_Freq_Y2 = Selection_Frequency), by = "Variable")
tbl(final_answer, "Tabel 4.23 Jawaban utama: seleksi variabel dan stabilitasnya")| Variable | Selected_Y1 | Max_Abs_Coefficient_Y1 | Selected_Y2 | Max_Abs_Coefficient_Y2 | CV_Selection_Frequency_Y1 | CV_Selection_Frequency_Y2 | Mixture_Selected_Y1 | Mixture_Selected_Y2 | Mixture_CV_Freq_Y1 | Mixture_CV_Freq_Y2 |
|---|---|---|---|---|---|---|---|---|---|---|
| X1 | TRUE | 6.8397 | TRUE | 7.8042 | 100 | 100 | TRUE | TRUE | 100 | 100 |
| X2 | TRUE | 2.2763 | TRUE | 4.1328 | 100 | 50 | TRUE | TRUE | 100 | 100 |
| X3 | FALSE | 0.0000 | FALSE | 0.0000 | 0 | 50 | TRUE | TRUE | 100 | 100 |
| X4 | TRUE | 5.4385 | TRUE | 3.7107 | 100 | 50 | TRUE | TRUE | 100 | 100 |
| X5 | TRUE | 7.4229 | TRUE | 7.8896 | 100 | 100 | TRUE | TRUE | 100 | 100 |
| X6 | TRUE | 0.3604 | TRUE | 0.4565 | 100 | 50 | TRUE | TRUE | 100 | 100 |
| X7 | TRUE | 2.2265 | TRUE | 1.7696 | 100 | 100 | TRUE | TRUE | 100 | 100 |
| X8 | TRUE | 4.5301 | TRUE | 1.8288 | 100 | 100 | TRUE | TRUE | 100 | 100 |
Tabel 4.23 menjawab pertanyaan penelitian secara langsung, yaitu variabel yang terpilih pada model final, besar koefisien mutlak maksimumnya, frekuensi seleksinya pada validasi silang berulang, serta status seleksinya pada Mixture Lasso.
1. Bimodalitas. Y1 dan Y2 menunjukkan bukti bimodalitas marginal (Tabel 4.4), dan sebagian besar bimodalitas tersebut berimpit dengan dua level X5 (Tabel 4.5). Setelah seluruh prediktor dimasukkan, residual OLS untuk Y1 tidak terbukti multimodal (p-value uji dip 0,5885), sehingga 3 puncak lokal pada densitas kernel residual hanya berupa riak kecil dan untuk Y2 tidak terbukti multimodal (p-value uji dip 0,8894), sehingga 3 puncak lokal pada densitas kernel residual hanya berupa riak kecil. Dengan mempertimbangkan residual, validasi silang, dan interval prediksi, bimodalitas Y1 perlu dimodelkan sebagai campuran, karena RMSE validasi silang turun 1,5% dan interval prediksi campuran lebih tepat dan lebih sempit. Untuk Y2, bimodalitasnya tidak perlu dimodelkan sebagai campuran, karena residual OLS tidak lagi multimodal dan kinerja model campuran tidak lebih baik secara berarti, sehingga Lasso tunggal sudah memadai.
2. Temuan utama seleksi variabel. Lasso tunggal mempertahankan X1, X2, X4, X5, X6, X7, X8 pada model final Y1 dan X1, X2, X4, X5, X6, X7, X8 pada model final Y2. Variabel yang selalu terpilih pada seluruh 50 lipatan luar adalah X1, X2, X4, X5, X6, X7, X8 untuk Y1 dan X1, X5, X7, X8 untuk Y2, sehingga seleksinya stabil. Sebagai contoh penulisan yang tepat: “X7 dipertahankan oleh Lasso pada model final Y1 dan muncul pada 100,0% lipatan luar”. Pernyataan “X7 signifikan pada \(\alpha\) = 5%” dihindari karena koefisien Lasso tidak nol bukan p-value.
3. Mixture Lasso. \(K\) dipilih dengan BIC (Tabel 4.7 dan 4.20), dan label komponen diurutkan menurut rerata respons. Prediksi soft meminimumkan galat kuadrat, sedangkan prediksi hard mempertahankan struktur dua puncak tetapi berisiko salah komponen. Pada data ini, RMSE validasi silang rerata soft tidak lebih kecil daripada hard pada kedua respons, karena peluang keanggotaan komponen umumnya hampir pasti sehingga kedua prediksi hampir sama. Bila kinerja validasi silang Mixture Lasso dan Lasso serupa, model campuran tidak disebut “lebih baik” hanya karena lebih kompleks. Penilaiannya diperkuat dengan Tabel 4.17, yang menunjukkan apakah ketidakpastian prediksi dinyatakan lebih jujur.
4. Inferensi vs prediksi. OLS dipakai untuk membaca residual, memperlihatkan masalah inferensial akibat multikolinearitas sempurna (rank 14 dari 15 kolom), dan menunjukkan sisa bimodalitas. Lasso dan Mixture Lasso lebih relevan untuk seleksi dan prediksi karena tetap terdefinisi pada prediktor yang berkorelasi sempurna dan dinilai melalui kinerja luar sampel.
5. Ketidakpastian. Ketidakpastian dilaporkan melalui rerata dan simpangan baku RMSE, MAE, dan \(R^2\) validasi silang, interval empiris 2,5% hingga 97,5% (Tabel 4.18), frekuensi seleksi setiap variabel (Tabel 4.21 dan 4.22), frekuensi \(K\) terpilih (Tabel 4.20), dan coverage interval prediksi (Tabel 4.17).
6. Keterbatasan. Kesimpulan hanya berlaku pada ruang
konfigurasi simulasi (bukan pengukuran bangunan nyata, dengan volume,
iklim, material, dan okupansi tetap). Hubungan X-Y dapat tak linear dan
mengandung interaksi yang tidak dimodelkan. EM dapat terjebak optimum
lokal (karena itu dipakai tiga inisialisasi), hasil sensitif terhadap
K_MAX dan MIN_PROP, dan \(\lambda\) awal campuran memakai \(\lambda\) Lasso global. Temuan merupakan
hasil model linear terpenalti pada ruang desain simulasi, bukan bukti
kausal universal.
K_MAX,
MIN_PROP, jumlah inisialisasi, dan nilai \(\lambda\) per komponen.Bates, S., Hastie, T., & Tibshirani, R. (2024). Cross-validation: What does it estimate and how well does it do it? Journal of the American Statistical Association, 119(546), 1434–1445. https://arxiv.org/abs/2104.00673
Belsley, D. A., Kuh, E., & Welsch, R. E. (1980). Regression diagnostics: Identifying influential data and sources of collinearity. Wiley. https://doi.org/10.1002/0471725153
Breusch, T. S., & Pagan, A. R. (1979). A simple test for heteroscedasticity and random coefficient variation. Econometrica, 47(5), 1287–1294. https://doi.org/10.2307/1911963
Cook, R. D. (1977). Detection of influential observation in linear regression. Technometrics, 19(1), 15–18. https://doi.org/10.1080/00401706.1977.10489493
Dempster, A. P., Laird, N. M., & Rubin, D. B. (1977). Maximum likelihood from incomplete data via the EM algorithm. Journal of the Royal Statistical Society: Series B, 39(1), 1–38. https://doi.org/10.1111/j.2517-6161.1977.tb01600.x
Dunn, P. K., & Smyth, G. K. (1996). Randomized quantile residuals. Journal of Computational and Graphical Statistics, 5(3), 236–244. https://doi.org/10.1080/10618600.1996.10474708
Friedman, J., Hastie, T., & Tibshirani, R. (2010). Regularization paths for generalized linear models via coordinate descent. Journal of Statistical Software, 33(1), 1–22. https://doi.org/10.18637/jss.v033.i01
Gneiting, T., Balabdaoui, F., & Raftery, A. E. (2007). Probabilistic forecasts, calibration and sharpness. Journal of the Royal Statistical Society: Series B, 69(2), 243–268. https://doi.org/10.1111/j.1467-9868.2007.00587.x
Hartigan, J. A., & Hartigan, P. M. (1985). The dip test of unimodality. The Annals of Statistics, 13(1), 70–84. https://doi.org/10.1214/aos/1176346577
Hastie, T., Tibshirani, R., & Friedman, J. (2009). The elements of statistical learning: Data mining, inference, and prediction (2nd ed.). Springer. https://doi.org/10.1007/978-0-387-84858-7
Hoerl, A. E., & Kennard, R. W. (1970). Ridge regression: Biased estimation for nonorthogonal problems. Technometrics, 12(1), 55–67. https://doi.org/10.1080/00401706.1970.10488634
Jacobs, R. A., Jordan, M. I., Nowlan, S. J., & Hinton, G. E. (1991). Adaptive mixtures of local experts. Neural Computation, 3(1), 79–87. https://doi.org/10.1162/neco.1991.3.1.79
Jarque, C. M., & Bera, A. K. (1980). Efficient tests for normality, homoscedasticity and serial independence of regression residuals. Economics Letters, 6(3), 255–259. https://doi.org/10.1016/0165-1765(80)90024-5
Khalili, A., & Chen, J. (2007). Variable selection in finite mixture of regression models. Journal of the American Statistical Association, 102(479), 1025–1038. https://doi.org/10.1198/016214507000000590
Koenker, R. (1981). A note on studentizing a test for heteroscedasticity. Journal of Econometrics, 17(1), 107–112. https://doi.org/10.1016/0304-4076(81)90062-2
Maechler, M. (2024). diptest: Hartigan’s dip test statistic for unimodality – corrected [R package]. https://CRAN.R-project.org/package=diptest
Pedersen, T. L. (2024). patchwork: The composer of plots [R package]. https://CRAN.R-project.org/package=patchwork
Pfister, R., Schwarz, K. A., Janczyk, M., Dale, R., & Freeman, J. B. (2013). Good things peak in pairs: A note on the bimodality coefficient. Frontiers in Psychology, 4, 700. https://doi.org/10.3389/fpsyg.2013.00700
R Core Team. (2024). R: A language and environment for statistical computing. R Foundation for Statistical Computing. https://www.R-project.org/
Schwarz, G. (1978). Estimating the dimension of a model. The Annals of Statistics, 6(2), 461–464. https://doi.org/10.1214/aos/1176344136
Scrucca, L., Fop, M., Murphy, T. B., & Raftery, A. E. (2016). mclust 5: Clustering, classification and density estimation using Gaussian finite mixture models. The R Journal, 8(1), 289–317. https://doi.org/10.32614/RJ-2016-021
Städler, N., Bühlmann, P., & van de Geer, S. (2010). ℓ1-penalization for mixture regression models. TEST, 19(2), 209–256. https://doi.org/10.1007/s11749-010-0197-z
Tibshirani, R. (1996). Regression shrinkage and selection via the lasso. Journal of the Royal Statistical Society: Series B, 58(1), 267–288. https://doi.org/10.1111/j.2517-6161.1996.tb02080.x
Tsanas, A., & Xifara, A. (2012). Accurate quantitative estimation of energy performance of residential buildings using statistical machine learning tools. Energy and Buildings, 49, 560–567. https://doi.org/10.1016/j.enbuild.2012.03.003
Tsanas, A., & Xifara, A. (2012). Energy efficiency [Dataset]. UCI Machine Learning Repository. https://doi.org/10.24432/C51307
Wickham, H., & Bryan, J. (2023). readxl: Read Excel files [R package]. https://CRAN.R-project.org/package=readxl
Wickham, H., Averick, M., Bryan, J., Chang, W., McGowan, L. D., François, R., Grolemund, G., Hayes, A., Henry, L., Hester, J., Kuhn, M., Pedersen, T. L., Miller, E., Bache, S. M., Müller, K., Ooms, J., Robinson, D., Seidel, D. P., Spinu, V., … Yutani, H. (2019). Welcome to the tidyverse. Journal of Open Source Software, 4(43), 1686. https://doi.org/10.21105/joss.01686
Zeileis, A., & Hothorn, T. (2002). Diagnostic checking in regression relationships. R News, 2(3), 7–10. https://CRAN.R-project.org/doc/Rnews/Rnews_2002-3.pdf
Zou, H., & Hastie, T. (2005). Regularization and variable selection via the elastic net. Journal of the Royal Statistical Society: Series B, 67(2), 301–320. https://doi.org/10.1111/j.1467-9868.2005.00503.x
Zou, H., Hastie, T., & Tibshirani, R. (2007). On the “degrees of freedom” of the lasso. The Annals of Statistics, 35(5), 2173–2192. https://doi.org/10.1214/009053607000000127