Curah hujan merupakan salah satu unsur iklim yang sangat memengaruhi kegiatan manusia, seperti pertanian, transportasi, dan mitigasi bencana banjir. Prediksi kejadian hujan dapat dilakukan dengan memanfaatkan data pengamatan cuaca, misalnya suhu, kelembapan, keadaan cuaca, dan kecepatan angin, melalui pendekatan machine learning.
Namun, data mentah hasil pengamatan jarang langsung siap digunakan untuk pemodelan. Data sering mengandung nilai hilang (missing value), nilai pencilan (outlier), variabel dengan skala yang berbeda, variabel kategorik berkardinalitas tinggi, serta distribusi kelas target yang tidak seimbang (imbalanced). Kondisi tersebut dapat menurunkan kinerja model sehingga diperlukan tahap data preprocessing sebelum pemodelan (Han et al., 2012; Kuhn & Johnson, 2019).
Pada praktikum ini dilakukan preprocessing pada dataset curah hujan yang meliputi penanganan missing value dengan interpolasi dan mean imputation, reduksi kardinalitas, shuffle splitting, penanganan outlier dengan capping, scaling menggunakan Robust Scaler, encoding, serta penanganan data tidak seimbang menggunakan SMOTE.
Hujan?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:
\[ \hat{y}_t = y_a + \frac{t - t_a}{t_b - t_a}\,(y_b - y_a) \]
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] \]
Data yang digunakan adalah dataset praktikum Machine
Learning berupa file data curah hujan.xlsx yang
terdiri dari 743 observasi dan 5 variabel.
| 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 |
Keadaan_Cuaca dan mean imputation pada
Kecepatan_Angin.Keadaan_Cuaca menjadi kategori
Keadaan_Cuaca_reduced.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("D:/Kuliah/Semester 7/Mata Kuliah Semester 7/Praktikum Machine Learning and Modern Prediction/Pertemuan 1/Teladan 1/data curah hujan (1).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. Dataset berisi 743 observasi dan 5
variabel. Seluruh variabel terbaca bertipe numerik (Hujan
bertipe integer, sedangkan variabel lainnya bertipe
double/integer), sehingga variabel Hujan nantinya
perlu 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
## NAs :9
## Kecepatan_Angin
## Min. : 2.000
## 1st Qu.: 4.000
## Median : 6.000
## Mean : 6.655
## 3rd Qu.: 9.000
## Max. :21.000
## NAs :314
##
## Distribusi kelas Hujan:
##
## 1 2
## 164 579
##
## 1 2
## 22.07 77.93
Interpretasi. Statistik deskriptif menunjukkan
ukuran pemusatan (mean, median) dan penyebaran (kuartil,
minimum, maksimum) setiap variabel, serta jumlah NA pada
variabel yang memiliki data hilang. Informasi ini dibutuhkan untuk
menentukan tahap preprocessing yang diperlukan:
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.NA pada Keadaan_Cuaca dan
Kecepatan_Angin, sehingga diperlukan penanganan missing
value.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. 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. 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 sampai 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. 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. Robust Scaler mengubah setiap
nilai menjadi \((x - \text{median}) /
IQR\). Median data latih 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 latih 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. 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.Keadaan_Cuaca (9 data) dan
Kecepatan_Angin (314 data), serta kelas target yang tidak
seimbang (77.93% kelas 2 dan 22.07% kelas 1).Keadaan_Cuaca dan mean imputation mengisi data
hilang pada Kecepatan_Angin tanpa menghilangkan observasi,
tetapi mean imputation mengecilkan variasi
Kecepatan_Angin.Keadaan_Cuaca berhasil direduksi dari 23
nilai unik menjadi 7 kategori terisi, lalu di-encode dengan
one-hot encoding.Kecepatan_Angin tanpa mengurangi jumlah data,
dan Robust Scaler menyamakan skala variabel numerik (median 0
dan IQR 1 pada data latih).Hujan pada data latih
menjadi 466 : 466, sehingga data latih dan data uji siap digunakan pada
tahap pemodelan.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.
Tukey, J. W. (1977). Exploratory Data Analysis. Addison-Wesley.