Perkembangan teknologi informasi menghasilkan data dalam jumlah besar dan beragam, sehingga machine learning semakin banyak digunakan untuk prediksi dan pengambilan keputusan di berbagai bidang. Namun, kinerja model tidak hanya ditentukan oleh algoritma yang dipilih, tetapi juga oleh kualitas data yang digunakan. Data mentah umumnya belum siap dimodelkan karena sering mengandung nilai hilang, pencilan, skala antarvariabel yang berbeda, serta variabel kategorik yang tidak dapat langsung diproses oleh algoritma. Jika kondisi ini diabaikan, model berpotensi menghasilkan prediksi yang kurang akurat dan sulit diinterpretasikan.
Feature engineering hadir sebagai tahapan untuk mengatasi masalah tersebut. Proses ini berfokus pada transformasi data mentah menjadi masukan yang sesuai bagi algoritma machine learning (Teixeira & Cavique, 2023). Secara umum, feature engineering dapat dibagi menjadi empat proses, yaitu pembentukan ide fitur, pembangkitan fitur, transformasi fitur, dan seleksi fitur (Chatzimparmpas et al., 2022). Pada praktiknya, tahapan ini mencakup penanganan data hilang, pengkodean variabel kategorik, normalisasi atau standardisasi, pembuatan fitur baru dari variabel yang ada, dan pemilihan fitur yang paling relevan.
Penerapan feature engineering yang tepat dapat meningkatkan akurasi model, mengurangi dimensi data, mempercepat komputasi, dan mengurangi risiko overfitting. Fitur yang informatif juga membuat hasil model lebih mudah dijelaskan. Oleh karena itu, feature engineering menjadi bagian penting dalam alur kerja analisis prediktif, dan pemahaman tentang konsep serta tekniknya diperlukan sebelum membangun model machine learning.
Missing value adalah kondisi ketika suatu observasi tidak memiliki nilai pada variabel tertentu. Penanganannya dapat dilakukan dengan menghapus baris atau mengisi nilai yang hilang (imputasi). Pada praktikum ini digunakan dua teknik:
Interpolasi linear, yaitu mengisi nilai hilang berdasarkan garis lurus antara nilai sebelum dan sesudahnya. Teknik ini cocok untuk data yang berurutan (misalnya deret waktu). Jika nilai hilang berada pada posisi \(t\) di antara titik \((t_a, y_a)\) dan \((t_b, y_b)\), maka:
\[ \hat{y}_t = y_a + \frac{t - t_a}{t_b - t_a}\,(y_b - y_a) \]
Mean imputation, yaitu mengganti nilai hilang dengan rata-rata variabel tersebut, \(\bar{x} = \frac{1}{n}\sum_{i=1}^{n} x_i\). Teknik ini sederhana, tetapi dapat mengecilkan variasi data karena banyak observasi diisi dengan nilai yang sama (Han et al., 2012).
Kardinalitas adalah banyaknya kategori unik pada suatu variabel. Kardinalitas yang tinggi dapat membuat encoding menghasilkan terlalu banyak kolom, sehingga kategori dapat dikelompokkan (binning) menjadi lebih sedikit (Kuhn & Johnson, 2019).
Splitting data membagi data menjadi data latih (train) dan data uji (test). Shuffle splitting membagi data secara acak tanpa mempertimbangkan proporsi kelas, sedangkan stratified splitting menjaga proporsi kelas pada kedua subset.
Outlier dan capping. Salah satu aturan deteksi outlier* adalah aturan Interquartile Range (IQR) (Tukey, 1977). Dengan \(IQR = Q_3 - Q_1\), nilai dianggap outlier jika berada di luar batas:
\[ \text{Batas bawah} = Q_1 - 1{,}5 \times IQR \qquad \text{Batas atas} = Q_3 + 1{,}5 \times IQR \]
Capping (winsorizing) mengganti nilai yang melewati batas dengan nilai batas tersebut sehingga jumlah observasi tidak berkurang.
Robust Scaler. Scaling menyamakan skala antarvariabel. Robust Scaler menggunakan median dan IQR sehingga tidak mudah terpengaruh outlier (Pedregosa et al., 2011):
\[ x' = \frac{x - \mathrm{median}(x)}{IQR(x)} \]
Parameter median dan IQR dihitung dari data latih saja, kemudian diterapkan pada data latih dan data uji untuk menghindari kebocoran data (data leakage).
One-hot encoding mengubah variabel kategorik menjadi beberapa kolom biner (0/1), satu kolom untuk setiap kategori.
SMOTE (Synthetic Minority Over-sampling Technique) menangani data tidak seimbang dengan membuat observasi sintetis pada kelas minoritas. Sebuah titik baru dibentuk di antara satu observasi minoritas \(x_i\) dan salah satu tetangga terdekatnya \(x_{nn}\) (Chawla et al., 2002):
\[ x_{new} = x_i + \lambda\,(x_{nn} - x_i), \qquad \lambda \in [0,1] \]
Jenis penelitian ini adalah penelitian terapan yang mengaplikasikan materi yang telah diberikan untuk mengatasi permasalahan pendekatan kuantitatif, yaitu menggunakan data numerik yang akan dianalisis secara statistik menggunakan program R. Data yang digunakan dalam penelitian ini yaitu sekunder data curah hujan data tersebut diperoleh dari Asisten Praktikum
| Variabel | Jenis | Keterangan |
|---|---|---|
| Hujan | Kategorik biner (target) | Kode kelas kejadian hujan (nilai 1 dan 2) |
| Suhu | Numerik kontinu | Suhu udara hasil pengamatan |
| Kelembapan | Numerik | Kelembapan udara hasil pengamatan |
| Keadaan_Cuaca | Numerik berupa kode | Kode keadaan cuaca; dikelompokkan menjadi kategori Keadaan_Cuaca_reduced |
| Kecepatan_Angin | Numerik | Kecepatan angin hasil pengamatan |
library(zoo)
library(readxl)
library(tidyverse)
library(rsample)
library(e1071)
library(DescTools)
library(dplyr)
library(ggplot2)
library(gridExtra)
library(recipes)
library(themis)
library(caret)
data <- read_excel("C:/Users/amand/Downloads/data curah hujan.xlsx")
head(data)## # A tibble: 6 × 5
## Hujan Suhu Kelembapan Keadaan_Cuaca Kecepatan_Angin
## <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 1 23 95 5 NA
## 2 1 24 90 1 NA
## 3 1 26.8 77 1 NA
## 4 1 29.6 62 2 2
## 5 1 30.8 56 1 7
## 6 1 31 55 1 7
## tibble [743 × 5] (S3: tbl_df/tbl/data.frame)
## $ Hujan : num [1:743] 1 1 1 1 1 1 1 2 2 2 ...
## $ Suhu : num [1:743] 23 24 26.8 29.6 30.8 31 30.4 30.9 30.2 29.7 ...
## $ Kelembapan : num [1:743] 95 90 77 62 56 55 57 58 62 62 ...
## $ Keadaan_Cuaca : num [1:743] 5 1 1 2 1 1 3 2 2 2 ...
## $ Kecepatan_Angin: num [1:743] NA NA NA 2 7 7 9 10 8 7 ...
Interpretasi: Berdasarkan hasil dataset berisi 743
observasi dan 5 variabel. Seluruh variabel terbaca dan bertipe numerik
(Hujan bertipe integer, sedangkan variabel lainnya bertipe
double/integer), sehingga variabel Hujan akan
diubah menjadi faktor karena berperan sebagai target klasifikasi.
## Hujan Suhu Kelembapan Keadaan_Cuaca
## Min. :1.000 Min. :22.60 Min. : 52.00 Min. : 1.00
## 1st Qu.:2.000 1st Qu.:24.30 1st Qu.: 75.00 1st Qu.: 2.00
## Median :2.000 Median :26.00 Median : 86.00 Median : 2.00
## Mean :1.779 Mean :26.54 Mean : 83.88 Mean :15.11
## 3rd Qu.:2.000 3rd Qu.:28.90 3rd Qu.: 94.00 3rd Qu.:15.00
## Max. :2.000 Max. :32.00 Max. :100.00 Max. :97.00
## NA's :9
## Kecepatan_Angin
## Min. : 2.000
## 1st Qu.: 4.000
## Median : 6.000
## Mean : 6.655
## 3rd Qu.: 9.000
## Max. :21.000
## NA's :314
##
## Distribusi kelas Hujan:
##
## 1 2
## 164 579
##
## 1 2
## 22.07 77.93
Interpretasi: Ringkasan statistik deskriptif
memperlihatkan gambaran umum tiap variabel, yaitu nilai tengah
(rata-rata dan median), sebaran data (kuartil, nilai minimum, dan nilai
maksimum), serta banyaknya NA pada variabel yang memiliki data hilang.
Gambaran ini menjadi dasar untuk menentukan langkah preprocessing yang
perlu dilakukan sebelum data dimodelkan. Variabel
Keadaan_Cuaca memiliki mean (15.11) yang jauh
lebih besar daripada median (2) dengan nilai maksimum 97. Hal ini
menunjukkan sebaran yang menjulur ke kanan dan variabel ini berupa kode,
bukan ukuran kontinu, sehingga lebih tepat diperlakukan sebagai
kategori. Terdapat NA pada Keadaan_Cuaca dan
Kecepatan_Angin, sehingga diperlukan penanganan missing
value. Kelas target Hujan tidak seimbang: kelas 2
sebanyak 579 observasi (77.93%) dan kelas 1 sebanyak 164 observasi
(22.07%). Karena itu diperlukan penanganan imbalance dengan
SMOTE.
## Hujan Suhu Kelembapan Keadaan_Cuaca Kecepatan_Angin
## 0 0 0 9 314
## Hujan Suhu Kelembapan Keadaan_Cuaca Kecepatan_Angin
## 0.00 0.00 0.00 1.21 42.26
## # A tibble: 9 × 5
## Hujan Suhu Kelembapan Keadaan_Cuaca Kecepatan_Angin
## <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 2 26 92 NA NA
## 2 1 23.8 98 NA 5
## 3 1 23.3 97 NA NA
## 4 2 28.8 79 NA 12
## 5 2 24.6 92 NA NA
## 6 1 22.8 98 NA 4
## 7 2 25.5 96 NA NA
## 8 2 27 80 NA 4
## 9 2 31 57 NA 6
Interpretasi: Berdasarkan hasil Keadaan_Cuaca memiliki 9 data hilang (1.21%) dan Kecepatan_Angin memiliki 314 data hilang (42.26%). Variabel Hujan, Suhu, dan Kelembapan lengkap. Tabel terakhir menampilkan baris-baris yang Keadaan_Cuaca nya hilang.
# (a) Hapus baris NA (hanya sebagai pembanding, tidak dipakai)
data_dropna <- data %>% drop_na()
cat("Jumlah baris sebelum drop_na :", nrow(data), "\n")## Jumlah baris sebelum drop_na : 743
## Jumlah baris sesudah drop_na : 424
# (b) Interpolasi pada Keadaan_Cuaca
df_imp1 <- data
df_imp1$Keadaan_Cuaca <- na.approx(df_imp1$Keadaan_Cuaca, na.rm = FALSE)
idx_na <- which(is.na(data$Keadaan_Cuaca))
data.frame(Baris = idx_na,
Hasil_Interpolasi = df_imp1$Keadaan_Cuaca[idx_na])## Baris Hasil_Interpolasi
## 1 42 2.0
## 2 207 61.0
## 3 310 62.0
## 4 512 49.0
## 5 598 21.0
## 6 639 56.0
## 7 675 2.0
## 8 723 2.0
## 9 725 1.5
## Hujan Suhu Kelembapan Keadaan_Cuaca Kecepatan_Angin
## 0 0 0 0 314
# (c) Mean imputation pada Kecepatan_Angin
df_imp2 <- df_imp1
mean_angin <- mean(df_imp2$Kecepatan_Angin, na.rm = TRUE)
mean_angin## [1] 6.655012
## Hujan Suhu Kelembapan Keadaan_Cuaca Kecepatan_Angin
## 0 0 0 0 0
# Dampak mean imputation terhadap Kecepatan_Angin
data.frame(
Kondisi = c("Sebelum imputasi", "Sesudah imputasi"),
Mean = c(mean(data$Kecepatan_Angin, na.rm = TRUE), mean(df_imp2$Kecepatan_Angin)),
SD = c(sd(data$Kecepatan_Angin, na.rm = TRUE), sd(df_imp2$Kecepatan_Angin))
)## Kondisi Mean SD
## 1 Sebelum imputasi 6.655012 3.352688
## 2 Sesudah imputasi 6.655012 2.546321
Interpretasi:
NA
dihapus (drop_na), data tersisa hanya 424 dari 743 baris
atau kehilangan 42.93% informasi. Karena itu penghapusan baris tidak
dipilih.Keadaan_Cuaca berdasarkan nilai sebelum dan sesudahnya.
Setelah interpolasi, jumlah NA pada
Keadaan_Cuaca menjadi 0. Hasil interpolasi dapat berupa
bilangan desimal karena merupakan nilai di antara dua kode cuaca.Kecepatan_Angin dengan rata-rata 6.655. Setelah imputasi,
seluruh kolom tidak lagi memiliki NA.Kecepatan_Angin tidak berubah, tetapi
simpangan baku turun dari 3.353 menjadi 2.546. Ini adalah konsekuensi
mean imputation: banyak data diisi dengan nilai yang sama
sehingga variasi data mengecil.## # A tibble: 10 × 5
## Hujan Suhu Kelembapan Keadaan_Cuaca Kecepatan_Angin
## <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 1 23 95 5 6.66
## 2 1 24 90 1 6.66
## 3 1 26.8 77 1 6.66
## 4 1 29.6 62 2 2
## 5 1 30.8 56 1 7
## 6 1 31 55 1 7
## 7 1 30.4 57 3 9
## 8 2 30.9 58 2 10
## 9 2 30.2 62 2 8
## 10 2 29.7 62 2 7
## [1] 1.0 1.5 2.0 3.0 5.0 10.0 13.0 14.0 15.0 16.0 17.0 21.0 29.0 49.0 56.0
## [16] 60.0 61.0 62.0 63.0 65.0 91.0 95.0 97.0
bins <- c(0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100)
labels <- 0:9
df_clean$Keadaan_Cuaca_reduced <- cut(
df_clean$Keadaan_Cuaca,
breaks = bins,
labels = labels,
right = TRUE,
include.lowest = TRUE
)
cat("--- Hasil Perbandingan ---\n")## --- Hasil Perbandingan ---
## # A tibble: 10 × 6
## Hujan Suhu Kelembapan Keadaan_Cuaca Kecepatan_Angin Keadaan_Cuaca_reduced
## <dbl> <dbl> <dbl> <dbl> <dbl> <fct>
## 1 1 23.7 97 61 6.66 6
## 2 2 31 68 2 10 0
## 3 2 26.4 81 3 5 0
## 4 2 31.3 57 2 9 0
## 5 2 29.1 78 2 6.66 0
## 6 2 27.8 79 2 3 0
## 7 1 23.7 97 61 4 6
## 8 1 27.2 83 60 7 5
## 9 2 27.2 86 2 6 0
## 10 2 25.7 90 2 4 0
##
## --- Pengecekan Kardinalitas ---
## Jumlah kategori di "Keadaan_Cuaca" asli : 23
cat('Jumlah kategori di "Keadaan_Cuaca_reduced" :', length(unique(df_clean$Keadaan_Cuaca_reduced)), "\n")## Jumlah kategori di "Keadaan_Cuaca_reduced" : 7
##
## Kategori unik yang baru (reduced):
## [1] 0 1 2 4 5 6 9
## Levels: 0 1 2 3 4 5 6 7 8 9
##
## Frekuensi tiap kategori (reduced):
##
## 0 1 2 3 4 5 6 7 8 9
## 523 48 46 0 1 28 71 0 0 26
Interpretasi: Berdasarkan hasil variabel Keadaan_Cuaca awalnya memiliki 23 nilai unik (kardinalitas tinggi). Nilai tersebut dikelompokkan ke dalam interval lebar 10 (0-10, 10-20, …, 90-100) menjadi kategori berlabel 0 hingga 9, sehingga kategori yang benar-benar terisi tinggal 7. Tabel frekuensi menunjukkan sebagian besar observasi berada pada kategori 0 (kode cuaca kecil), sedangkan beberapa kategori (misalnya 3, 7, dan 8) tidak memiliki observasi. Kategori kosong ini tetap tercatat sebagai level faktor agar kolom one-hot pada data latih dan data uji konsisten.
df_clean$Hujan <- factor(df_clean$Hujan)
set.seed(200)
split_shuffle <- initial_split(df_clean, prop = 0.8) # tanpa strata = shuffle (acak)
X_train <- training(split_shuffle) %>% dplyr::select(-Hujan)
X_test <- testing(split_shuffle) %>% dplyr::select(-Hujan)
y_train <- training(split_shuffle)$Hujan
y_test <- testing(split_shuffle)$Hujan
cat("Dimensi X_train :", dim(X_train), "\n")## Dimensi X_train : 594 5
## Dimensi X_test : 149 5
## samakan level Keadaan_Cuaca_reduced
all_levels <- levels(df_clean$Keadaan_Cuaca_reduced)
X_train$Keadaan_Cuaca_reduced <- factor(X_train$Keadaan_Cuaca_reduced, levels = all_levels)
X_test$Keadaan_Cuaca_reduced <- factor(X_test$Keadaan_Cuaca_reduced, levels = all_levels)
cat("\nDistribusi kelas data latih:\n")##
## Distribusi kelas data latih:
## y_train
## 1 2
## 128 466
## y_train
## 1 2
## 21.55 78.45
##
## Distribusi kelas data uji:
## y_test
## 1 2
## 36 113
## y_test
## 1 2
## 24.16 75.84
Interpretasi: Berdasarkan hasil data dibagi secara
acak (shuffle splitting, set.seed(200)) menjadi
594 observasi data latih (80%) dan 149 observasi data uji (20%). Karena
tidak memakai stratifikasi, proporsi kelas pada data latih (kelas 2:
78.45%) dan data uji (kelas 2: 75.84%) hanya mendekati proporsi data
asli (77.93%) dan dapat sedikit berbeda antar subset. Kedua subset
sama-sama didominasi kelas 2, sehingga ketidakseimbangan kelas tetap ada
dan akan ditangani dengan SMOTE. Seluruh tahap berikutnya menggunakan
parameter yang dihitung dari data latih saja.
list_num <- c("Suhu", "Kelembapan", "Kecepatan_Angin")
list_outlier <- c()
list_lower_bound <- c()
list_upper_bound <- c()
for (i in list_num) {
Q1 <- unname(quantile(X_train[[i]], 0.25, na.rm = TRUE))
Q3 <- unname(quantile(X_train[[i]], 0.75, na.rm = TRUE))
IQR_val <- Q3 - Q1
lower_bound <- Q1 - 1.5 * IQR_val
upper_bound <- Q3 + 1.5 * IQR_val
list_lower_bound <- c(list_lower_bound, lower_bound)
list_upper_bound <- c(list_upper_bound, upper_bound)
num_outliers_lower <- sum(X_train[[i]] < lower_bound, na.rm = TRUE)
num_outliers_upper <- sum(X_train[[i]] > upper_bound, na.rm = TRUE)
list_outlier <- c(list_outlier, num_outliers_lower + num_outliers_upper)
}
outliers <- data.frame(
Kolom = list_num,
Jumlah_Outlier = list_outlier,
Lower_Bound = list_lower_bound,
Upper_Bound = list_upper_bound
)
outliers## Kolom Jumlah_Outlier Lower_Bound Upper_Bound
## 1 Suhu 0 17.4 35.8
## 2 Kelembapan 0 46.5 122.5
## 3 Kecepatan_Angin 50 2.0 10.0
# Skewness sebelum capping
skew_sebelum <- sapply(X_train[list_num], skewness, na.rm = TRUE)
round(skew_sebelum, 3)## Suhu Kelembapan Kecepatan_Angin
## 0.279 -0.558 0.837
# Capping (batas dari data latih, diterapkan ke data latih dan data uji)
X_train_capped <- X_train
X_test_capped <- X_test
for (k in seq_along(list_num)) {
kol <- list_num[k]
batas <- c(list_lower_bound[k], list_upper_bound[k])
X_train_capped[[kol]] <- Winsorize(X_train[[kol]], val = batas)
X_test_capped[[kol]] <- Winsorize(X_test[[kol]], val = batas)
}
# Cek jumlah outlier setelah capping (data latih)
outlier_sesudah <- sapply(seq_along(list_num), function(k) {
x <- X_train_capped[[list_num[k]]]
sum(x < list_lower_bound[k] | x > list_upper_bound[k])
})
data.frame(Kolom = list_num, Outlier_Sebelum = list_outlier, Outlier_Sesudah = outlier_sesudah)## Kolom Outlier_Sebelum Outlier_Sesudah
## 1 Suhu 0 0
## 2 Kelembapan 0 0
## 3 Kecepatan_Angin 50 0
## Suhu Kelembapan Kecepatan_Angin
## Min. :22.60 Min. : 52 Min. : 2.000
## 1st Qu.:24.30 1st Qu.: 75 1st Qu.: 5.000
## Median :26.00 Median : 86 Median : 6.655
## Mean :26.54 Mean : 84 Mean : 6.642
## 3rd Qu.:28.90 3rd Qu.: 94 3rd Qu.: 7.000
## Max. :31.60 Max. :100 Max. :21.000
## Suhu Kelembapan Kecepatan_Angin
## Min. :22.60 Min. : 52 Min. : 2.000
## 1st Qu.:24.30 1st Qu.: 75 1st Qu.: 5.000
## Median :26.00 Median : 86 Median : 6.655
## Mean :26.54 Mean : 84 Mean : 6.447
## 3rd Qu.:28.90 3rd Qu.: 94 3rd Qu.: 7.000
## Max. :31.60 Max. :100 Max. :10.000
# Skewness sesudah capping
skew_sesudah <- sapply(X_train_capped[list_num], skewness, na.rm = TRUE)
round(skew_sesudah, 3)## Suhu Kelembapan Kecepatan_Angin
## 0.279 -0.558 -0.163
# Visualisasi sebelum dan sesudah capping
diagnostic_plots <- function(df, variable, judul) {
p1 <- ggplot(df, aes(x = .data[[variable]])) +
geom_histogram(bins = 30, fill = "#008080", color = "black") +
ggtitle(paste("Histogram -", judul)) +
theme_minimal()
p2 <- ggplot(df, aes(y = .data[[variable]])) +
geom_boxplot(fill = "#008080") +
ggtitle(paste("Boxplot -", judul)) +
theme_minimal()
grid.arrange(p1, p2, ncol = 2)
}
for (col in list_num) {
diagnostic_plots(X_train, col, paste(col, "(Sebelum Capping)"))
diagnostic_plots(X_train_capped, col, paste(col, "(Sesudah Capping)"))
}Interpretasi:
Suhu adalah 0,
Kelembapan 0, dan Kecepatan_Angin 50. Artinya
Suhu dan Kelembapan tidak bermasalah,
sedangkan outlier terkonsentrasi pada
Kecepatan_Angin.Kecepatan_Angin. Karena sekitar 42% data
Kecepatan_Angin diisi dengan nilai rata-rata yang sama,
rentang antarkuartil (IQR) menjadi sempit, yaitu batas atas hanya 10.
Akibatnya nilai kecepatan angin yang tinggi (di atas batas tersebut)
terdeteksi sebagai outlier.Kecepatan_Angin turun dari 21
menjadi 10. Batas yang sama dipakai pada data uji agar tidak ada
kebocoran data.Suhu, Kelembapan, dan
Kecepatan_Angin sebelum capping berturut-turut
0.279, -0.558, dan 0.837; setelah capping menjadi 0.279,
-0.558, dan -0.163. Perubahan terbesar terjadi pada variabel yang
di-capping.Kecepatan_Angin terlihat titik-titik di luar
whisker; pada boxplot sesudah capping titik tersebut
sudah tidak ada. Histogram Suhu dan Kelembapan
tidak berubah karena tidak ada nilai yang di-capping.# Parameter Robust Scaler dihitung dari data latih
median_val <- sapply(X_train_capped[list_num], median, na.rm = TRUE)
iqr_val <- sapply(X_train_capped[list_num], IQR, na.rm = TRUE)
rbind(Median = median_val, IQR = iqr_val)## Suhu Kelembapan Kecepatan_Angin
## Median 26.0 86 6.655012
## IQR 4.6 19 2.000000
X_train_scale <- X_train_capped
X_test_scale <- X_test_capped
for (col in list_num) {
X_train_scale[[col]] <- (X_train_capped[[col]] - unname(median_val[col])) / unname(iqr_val[col])
X_test_scale[[col]] <- (X_test_capped[[col]] - unname(median_val[col])) / unname(iqr_val[col])
}
head(X_train_scale)## # A tibble: 6 × 5
## Suhu Kelembapan Keadaan_Cuaca Kecepatan_Angin Keadaan_Cuaca_reduced
## <dbl> <dbl> <dbl> <dbl> <fct>
## 1 -0.500 0.579 61 0 6
## 2 1.09 -0.947 2 1.67 0
## 3 0.0870 -0.263 3 -0.828 0
## 4 1.15 -1.53 2 1.17 0
## 5 0.674 -0.421 2 0 0
## 6 0.391 -0.368 2 -1.83 0
##
## Median & IQR data latih setelah scaling:
## Suhu Kelembapan Kecepatan_Angin
## Median 0 0 0
## IQR 1 1 1
##
## Ringkasan data latih setelah scaling:
## Suhu Kelembapan Kecepatan_Angin
## Min. :-0.7391 Min. :-1.7895 Min. :-2.3275
## 1st Qu.:-0.3696 1st Qu.:-0.5789 1st Qu.:-0.8275
## Median : 0.0000 Median : 0.0000 Median : 0.0000
## Mean : 0.1177 Mean :-0.1053 Mean :-0.1041
## 3rd Qu.: 0.6304 3rd Qu.: 0.4211 3rd Qu.: 0.1725
## Max. : 1.2174 Max. : 0.7368 Max. : 1.6725
##
## Ringkasan data uji setelah scaling:
## Suhu Kelembapan Kecepatan_Angin
## Min. :-0.69565 Min. :-1.6316 Min. :-2.32751
## 1st Qu.:-0.34783 1st Qu.:-0.6842 1st Qu.:-0.32751
## Median :-0.04348 Median : 0.0000 Median : 0.00000
## Mean : 0.11716 Mean :-0.1363 Mean :-0.05804
## 3rd Qu.: 0.65217 3rd Qu.: 0.4211 3rd Qu.: 0.67249
## Max. : 1.30435 Max. : 0.7368 Max. : 1.67249
X_train_scale %>%
dplyr::select(all_of(list_num)) %>%
pivot_longer(everything(), names_to = "Variabel", values_to = "Nilai") %>%
ggplot(aes(x = Variabel, y = Nilai, fill = Variabel)) +
geom_boxplot() +
labs(title = "Boxplot Data Latih Setelah Robust Scaler") +
theme_minimal() +
theme(legend.position = "none")Interpretasi: Berdasarkan hasil Robust
Scaler mengubah setiap nilai menjadi \((x
- \text{median}) / IQR\). Median data training sebelum
scaling untuk Suhu, Kelembapan, dan
Kecepatan_Angin berturut-turut 26, 86, dan 6.655, dengan
IQR 4.6, 19, dan 2. Setelah scaling, median setiap variabel
pada data training menjadi 0 dan IQR menjadi 1, sehingga ketiga variabel
berada pada skala yang sebanding. Nilai negatif berarti di bawah median
dan nilai positif berarti di atas median. Pada data uji, median dan IQR
tidak harus persis 0 dan 1 karena data uji hanya ditransformasi memakai
parameter data latih. Robust Scaler dipilih karena memakai
median dan IQR yang tahan terhadap outlier, dan boxplot di atas
memperlihatkan ketiga variabel sudah berada pada rentang yang
sebanding.
## Encoding - One Hot Encoder
list_cat <- c("Keadaan_Cuaca_reduced")
resep_encode <- recipe(~ Keadaan_Cuaca_reduced, data = X_train_scale) %>%
step_dummy(all_of(list_cat), one_hot = TRUE) %>%
prep(training = X_train_scale)
X_train_encoded <- bake(resep_encode, new_data = NULL)
X_test_encoded <- bake(resep_encode, new_data = X_test_scale)
X_train_encoded## # A tibble: 594 × 10
## Keadaan_Cuaca_reduced_X0 Keadaan_Cuaca_reduced_X1 Keadaan_Cuaca_reduced_X2
## <dbl> <dbl> <dbl>
## 1 0 0 0
## 2 1 0 0
## 3 1 0 0
## 4 1 0 0
## 5 1 0 0
## 6 1 0 0
## 7 0 0 0
## 8 0 0 0
## 9 1 0 0
## 10 1 0 0
## # ℹ 584 more rows
## # ℹ 7 more variables: Keadaan_Cuaca_reduced_X3 <dbl>,
## # Keadaan_Cuaca_reduced_X4 <dbl>, Keadaan_Cuaca_reduced_X5 <dbl>,
## # Keadaan_Cuaca_reduced_X6 <dbl>, Keadaan_Cuaca_reduced_X7 <dbl>,
## # Keadaan_Cuaca_reduced_X8 <dbl>, Keadaan_Cuaca_reduced_X9 <dbl>
## # A tibble: 149 × 10
## Keadaan_Cuaca_reduced_X0 Keadaan_Cuaca_reduced_X1 Keadaan_Cuaca_reduced_X2
## <dbl> <dbl> <dbl>
## 1 1 0 0
## 2 1 0 0
## 3 1 0 0
## 4 1 0 0
## 5 1 0 0
## 6 1 0 0
## 7 1 0 0
## 8 1 0 0
## 9 1 0 0
## 10 1 0 0
## # ℹ 139 more rows
## # ℹ 7 more variables: Keadaan_Cuaca_reduced_X3 <dbl>,
## # Keadaan_Cuaca_reduced_X4 <dbl>, Keadaan_Cuaca_reduced_X5 <dbl>,
## # Keadaan_Cuaca_reduced_X6 <dbl>, Keadaan_Cuaca_reduced_X7 <dbl>,
## # Keadaan_Cuaca_reduced_X8 <dbl>, Keadaan_Cuaca_reduced_X9 <dbl>
## Dimensi X_train_encoded : 594 10
## Dimensi X_test_encoded : 149 10
## [1] "Keadaan_Cuaca_reduced_X0" "Keadaan_Cuaca_reduced_X1"
## [3] "Keadaan_Cuaca_reduced_X2" "Keadaan_Cuaca_reduced_X3"
## [5] "Keadaan_Cuaca_reduced_X4" "Keadaan_Cuaca_reduced_X5"
## [7] "Keadaan_Cuaca_reduced_X6" "Keadaan_Cuaca_reduced_X7"
## [9] "Keadaan_Cuaca_reduced_X8" "Keadaan_Cuaca_reduced_X9"
Interpretasi: Berdasarkan hasil One-hot
encoding mengubah Keadaan_Cuaca_reduced menjadi 10
kolom biner (satu kolom per kategori 0 sampai 9); pada setiap baris
tepat satu kolom bernilai 1 dan lainnya 0. Data latih menghasilkan 594
baris dan data uji 149 baris dengan jumlah kolom yang sama. Kolom untuk
kategori yang tidak muncul di data (misalnya kategori 3, 7, dan 8)
berisi 0 seluruhnya; kolom ini tidak membawa informasi, tetapi dibiarkan
agar struktur data latih dan data uji tetap sama.
# Gabungkan fitur numerik (sudah di-scaling) + fitur hasil encoding + target
# (variabel Keadaan_Cuaca asli tidak ikut karena sudah diwakili hasil encoding)
X_train_final <- bind_cols(X_train_scale[list_num], X_train_encoded) %>%
mutate(Hujan = y_train)
X_test_final <- bind_cols(X_test_scale[list_num], X_test_encoded) %>%
mutate(Hujan = y_test)
cat("Distribusi kelas data latih SEBELUM SMOTE:\n")## Distribusi kelas data latih SEBELUM SMOTE:
##
## 1 2
## 128 466
##
## 1 2
## 21.55 78.45
# SMOTE hanya pada data latih
set.seed(200)
resep_smote <- recipe(Hujan ~ ., data = X_train_final) %>%
step_smote(Hujan, over_ratio = 1, neighbors = 5) %>%
prep(training = X_train_final)
train_smote <- bake(resep_smote, new_data = NULL)
cat("\nDistribusi kelas data latih SESUDAH SMOTE:\n")##
## Distribusi kelas data latih SESUDAH SMOTE:
##
## 1 2
## 466 466
##
## 1 2
## 50 50
##
## Dimensi data latih sebelum & sesudah SMOTE:
## Sebelum: 594 14 | Sesudah: 932 14
##
## Distribusi kelas data uji (tidak diubah):
##
## 1 2
## 36 113
## # A tibble: 6 × 14
## Suhu Kelembapan Kecepatan_Angin Keadaan_Cuaca_reduced_X0
## <dbl> <dbl> <dbl> <dbl>
## 1 -0.500 0.579 0 0
## 2 1.09 -0.947 1.67 1
## 3 0.0870 -0.263 -0.828 1
## 4 1.15 -1.53 1.17 1
## 5 0.674 -0.421 0 1
## 6 0.391 -0.368 -1.83 1
## # ℹ 10 more variables: Keadaan_Cuaca_reduced_X1 <dbl>,
## # Keadaan_Cuaca_reduced_X2 <dbl>, Keadaan_Cuaca_reduced_X3 <dbl>,
## # Keadaan_Cuaca_reduced_X4 <dbl>, Keadaan_Cuaca_reduced_X5 <dbl>,
## # Keadaan_Cuaca_reduced_X6 <dbl>, Keadaan_Cuaca_reduced_X7 <dbl>,
## # Keadaan_Cuaca_reduced_X8 <dbl>, Keadaan_Cuaca_reduced_X9 <dbl>, Hujan <fct>
# Visualisasi
dist_kelas <- bind_rows(
data.frame(Tahap = "Sebelum SMOTE", Hujan = X_train_final$Hujan),
data.frame(Tahap = "Sesudah SMOTE", Hujan = train_smote$Hujan)
)
dist_kelas$Tahap <- factor(dist_kelas$Tahap, levels = c("Sebelum SMOTE", "Sesudah SMOTE"))
ggplot(dist_kelas, aes(x = Hujan, fill = Hujan)) +
geom_bar() +
geom_text(stat = "count", aes(label = after_stat(count)), vjust = -0.3) +
facet_wrap(~ Tahap) +
labs(title = "Distribusi Kelas Hujan pada Data Latih", x = "Hujan", y = "Frekuensi") +
theme_minimal() +
theme(legend.position = "none")##
## Dimensi akhir:
## Data latih (SMOTE): 932 14
## Data uji : 149 14
Interpretasi:
head(train_smote) menunjukkan data akhir berisi
tiga variabel numerik yang sudah di-scaling, kolom
one-hot Keadaan_Cuaca_reduced, dan target
Hujan. Kolom one-hot pada observasi sintetis dapat
bernilai pecahan karena dibentuk dari interpolasi, bukan dari kategori
asli.Konsep berbagai jenis feature engineering dapat dipahami di RStudio dengan mempraktikkannya langsung pada data, mulai dari eksplorasi data melalui statistik deskriptif dan visualisasi untuk mengenali data hilang, pencilan, kemencengan, dan perbedaan skala. Temuan tersebut kemudian dihubungkan dengan teknik yang sesuai, yaitu imputasi untuk data hilang, encoding untuk variabel kategorik, penskalaan dan transformasi untuk skala serta sebaran data, pembangkitan fitur untuk variabel baru, dan seleksi fitur untuk memilih variabel yang relevan. Membandingkan data sebelum dan sesudah tiap teknik, misalnya melalui histogram, membuat fungsi dan dampak masing-masing teknik lebih mudah dipahami.
Teknik feature engineering di RStudio dilakukan secara bertahap sesuai alur pengolahan data. Imputasi dilakukan dengan is.na() dan median(), one-hot encoding dengan model.matrix(), normalisasi min-max dan standardisasi z-score dengan perhitungan langsung atau fungsi scale(), transformasi logaritma dengan log1p(), fitur baru dengan operasi antarvariabel, dan seleksi fitur dengan cor(). Seluruh langkah ini ditulis dalam satu dokumen RMarkdown sehingga prosesnya terdokumentasi, dapat diulang, dan dapat dipublikasikan melalui RPubs.
Berdasarkan batasan masalah dataset curah hujan berisi 743 observasi
dengan missing value pada Keadaan_Cuaca (9 data)
dan Kecepatan_Angin (314 data), serta kelas target yang
tidak seimbang (77.93% kelas 2 dan 22.07% kelas 1). Interpolasi berhasil
mengisi data hilang pada Keadaan_Cuaca dan mean
imputation mengisi data hilang pada Kecepatan_Angin
tanpa menghilangkan observasi, tetapi mean imputation
mengecilkan variasi Kecepatan_Angin. Kardinalitas
Keadaan_Cuaca berhasil direduksi dari 23 nilai unik menjadi
7 kategori terisi, lalu di-encode dengan one-hot
encoding. Shuffle splitting menghasilkan 594 data latih
dan 149 data uji. Capping berbasis IQR menghilangkan
outlier terutama pada Kecepatan_Angin tanpa
mengurangi jumlah data, dan Robust Scaler menyamakan skala
variabel numerik (median 0 dan IQR 1 pada data latih). SMOTE
menyeimbangkan kelas Hujan pada data latih menjadi 466 :
466, sehingga data latih dan data uji siap digunakan pada tahap
pemodelan.
Chatzimparmpas, A., Martins, R. M., Kucher, K., & Kerren, A. (2022). FeatureEnVi: Visual analytics for feature engineering using stepwise selection and semi-automatic extraction approaches. IEEE Transactions on Visualization and Computer Graphics.
Chawla, N. V., Bowyer, K. W., Hall, L. O., & Kegelmeyer, W. P. (2002). SMOTE: Synthetic Minority Over-sampling Technique. Journal of Artificial Intelligence Research*, 16, 321-357.
Han, J., Kamber, M., & Pei, J. (2012). Data Mining: Concepts and Techniques* (3rd ed.). Morgan Kaufmann.
Kuhn, M., & Johnson, K. (2019). feature Engineering and Selection: A Practical Approach for Predictive Models. CRC Press.
Pedregosa, F., et al. (2011). Scikit-learn: Machine Learning in Python. Journal of Machine Learning Research, 12, 2825-2830.
Teixeira, M., & Cavique, L. (2023). Feature engineering: Techniques and applications. Repositório Aberto, Universidade Aberta.
Tukey, J. W. (1977). Exploratory Data Analysis. Addison-Wesley.