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 mode imputation, reduksi kardinalitas, stratified splitting, penanganan outlier dengan capping, scaling menggunakan Standard Scaler, encoding, serta penanganan data tidak seimbang menggunakan SMOTE.
Hujan?Adapun manfaat pada penelitian ini yaitu:
Bagi penulis
Bagi pembaca
Menambah ilmu pengetahuan dan pemahaman yang berkaitan tentang Feature Engineering dengan RStudio.
Adapun batasan masalah pada praktikum ini yaitu menggunakan dataset
data curah hujan.xlsx untuk melakukan
preprocessing data. Karena NPM penulis genap (F1F023044), maka
missing value ditangani dengan mode imputation,
pembagian data menggunakan stratify splitting, dan penskalaan
menggunakan Standard Scaler. Selain itu, dilakukan interpolasi
pada keadaan cuaca, capping pada data numerik, serta penanganan
imbalance data menggunakan SMOTE. Setiap output
ditampilkan dan diinterpretasikan.
Adapun sistematika penulisan dari praktikum ini adalah sebagai berikut.
BAB IPENDAHULUAN
Bab ini terdiri dari latar belakang, rumusan masalah, batasan masalah, tujuan penelitian, manfaat penelitian, dan sistematika penulisan.
BAB IITINJAUAN PUSTAKA
Bab ini berisi kajian pustaka yang terdiri dari jurnal, buku, dan informasi pendukung mengenai feature engineering pada program RStudio.
BAB IIIMETODE PENELITIAN
Bab ini terdiri dari jenis dan sumber data, variabel penelitian, algoritma penelitian, dan diagram alir.
BAB IVHASIL DAN PEMBAHASAN
Bab ini berisi hasil yang diperoleh dari analisis data beserta penjelasannya pada bagian pembahasan.
BAB VKESIMPULAN DAN SARAN
Bab ini merupakan bagian penutup laporan yang berisi kesimpulan dari hasil analisis serta saran dari penulis.
DAFTAR PUSTAKA
LAMPIRAN
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) \]
\[ \text{Mo}(x) = \arg\max_{v}\; f(v) \]
dengan \(f(v)\) adalah frekuensi kemunculan nilai \(v\). Teknik ini sederhana dan biasa dipakai pada variabel kategorik, 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 membagi data secara acak di dalam setiap kelas sehingga proporsi kelas target pada data latih dan data uji tetap sama dengan proporsi pada data asli. Teknik ini penting ketika kelas target tidak seimbang.
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.
Standard Scaler. Scaling menyamakan skala antarvariabel. Standard Scaler (standardisasi z-score) mengubah data sehingga memiliki rata-rata 0 dan simpangan baku 1 (Pedregosa et al., 2011):
\[ z = \frac{x - \bar{x}}{s} \]
dengan \(\bar{x}\) adalah rata-rata dan \(s\) adalah simpangan baku. Parameter rata-rata dan simpangan baku dihitung dari data latih saja, kemudian diterapkan pada data latih dan data uji untuk menghindari kebocoran data (data leakage). Karena memakai rata-rata dan simpangan baku, Standard Scaler sensitif terhadap outlier, sehingga capping dilakukan terlebih dahulu.
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 mode imputation pada
Kecepatan_Angin.Keadaan_Cuaca menjadi kategori
Keadaan_Cuaca_reduced.Hujan.Diagram alir pada penelitian ini menggunakan simbol standar: oval untuk terminator (mulai dan selesai), jajargenjang untuk input dan output, serta persegi panjang untuk proses.
Diagram Alir Penelitian
Interpretasi. Diagram alir memperlihatkan urutan
tahapan preprocessing yang dilakukan. Proses diawali dari
terminator Mulai, lalu input berupa impor data
data curah hujan.xlsx. Data kemudian melalui proses
statistik deskriptif, penanganan missing value, reduksi
kardinalitas, pembagian data secara stratifikasi, capping,
scaling, encoding, dan diakhiri SMOTE yang hanya
diterapkan pada data latih. Output berupa data latih dan data
uji yang siap dimodelkan, kemudian proses berakhir pada terminator
Selesai. Urutan ini menjaga agar seluruh parameter transformasi
berasal dari data latih sehingga tidak terjadi kebocoran data.
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:/SEMESTER 7/PRAK SPASIAL/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. Data berhasil diimpor ke dalam R dan
berisi 743 observasi dengan 5 variabel, yaitu Hujan, Suhu, Kelembapan,
Keadaan_Cuaca, Kecepatan_Angin. Enam baris pertama (head)
memberikan gambaran awal bahwa setiap baris mewakili satu pengamatan
cuaca, sedangkan fungsi str menunjukkan tipe data setiap
kolom. Seluruh variabel terbaca bertipe numerik (Hujan
bertipe integer, sedangkan variabel lainnya bertipe
double/integer). Hal ini penting diperhatikan karena variabel
Hujan sebenarnya adalah kode kelas (1 dan 2), bukan ukuran
kuantitatif, sehingga nantinya harus diubah menjadi faktor agar
diperlakukan sebagai target klasifikasi. Demikian pula
Keadaan_Cuaca yang berupa kode keadaan cuaca perlu
dipertimbangkan untuk diperlakukan sebagai kategori, bukan angka
kontinu. Pada tahap ini juga terlihat bahwa sebagian sel pada kolom
tertentu kosong (NA), yang akan dibahas pada sub-bab
berikutnya.
## 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 memberikan gambaran awal tentang pemusatan dan penyebaran setiap variabel sebelum dilakukan preprocessing. Informasi ini dipakai untuk menentukan perlakuan yang tepat pada tahap-tahap selanjutnya:
Suhu berkisar dari 22.6 sampai 32
dengan rata-rata 26.54 dan median 26. Kedekatan nilai rata-rata dan
median menjadi petunjuk awal tentang apakah sebaran data cenderung
simetris atau menjulur ke salah satu sisi.Kelembapan berkisar dari 52 sampai 100
dengan rata-rata 83.88 dan median 86. Perbedaan rentang nilai antara
Suhu dan Kelembapan menunjukkan bahwa kedua
variabel memiliki skala yang berbeda, sehingga scaling
diperlukan.Kecepatan_Angin (tanpa memperhitungkan
NA) berkisar dari 2 sampai 21 dengan rata-rata 6.66 dan
median 6.Keadaan_Cuaca memiliki mean
(15.11) yang jauh lebih besar daripada median (2) dengan nilai maksimum
97. Selisih yang besar ini menunjukkan sebaran yang menjulur ke kanan,
yaitu sebagian besar nilai kecil dan hanya sebagian kecil bernilai
sangat besar. Karena variabel ini berupa kode keadaan cuaca (bukan
ukuran kontinu), lebih tepat jika diperlakukan sebagai kategori dan
dikelompokkan terlebih dahulu.NA pada
Keadaan_Cuaca dan Kecepatan_Angin, sehingga
penanganan missing value wajib dilakukan sebelum pemodelan
karena sebagian besar algoritma tidak dapat memproses nilai kosong.Hujan tidak seimbang:
kelas 2 sebanyak 579 observasi (77.93%) dan kelas 1 sebanyak 164
observasi (22.07%). Perbandingan kelas mayoritas terhadap minoritas
adalah sekitar 3.53 : 1. Jika data dibiarkan demikian, model berisiko
lebih sering memprediksi kelas mayoritas dan kurang mampu mengenali
kelas minoritas, sehingga 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. Pengecekan missing value
menunjukkan bahwa Keadaan_Cuaca memiliki 9 data hilang
(1.21%) dan Kecepatan_Angin memiliki 314 data hilang
(42.26%), sedangkan Hujan, Suhu, dan
Kelembapan lengkap tanpa NA. Proporsi data
hilang pada Kecepatan_Angin jauh lebih besar dibandingkan
Keadaan_Cuaca, sehingga dampak teknik imputasi yang dipilih
akan lebih terasa pada Kecepatan_Angin. Tabel terakhir
menampilkan baris-baris yang Keadaan_Cuaca-nya hilang;
baris-baris ini diperlukan untuk memeriksa nilai di sekitarnya, karena
interpolasi bergantung pada nilai sebelum dan sesudah baris yang
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, rule = 2)
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) Mode imputation pada Kecepatan_Angin
# Fungsi modus: nilai yang paling sering muncul (NA diabaikan)
hitung_modus <- function(x) {
x <- x[!is.na(x)]
ux <- unique(x)
ux[which.max(tabulate(match(x, ux)))]
}
# Tabel frekuensi nilai yang paling sering muncul
head(sort(table(data$Kecepatan_Angin), decreasing = TRUE), 5)##
## 3 5 8 6 4
## 57 55 47 40 39
## [1] 3
## Hujan Suhu Kelembapan Keadaan_Cuaca Kecepatan_Angin
## 0 0 0 0 0
# Dampak mode 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)),
Median = c(median(data$Kecepatan_Angin, na.rm = TRUE), median(df_imp2$Kecepatan_Angin)),
SD = c(sd(data$Kecepatan_Angin, na.rm = TRUE), sd(df_imp2$Kecepatan_Angin))
)## Kondisi Mean Median SD
## 1 Sebelum imputasi 6.655012 6 3.352688
## 2 Sesudah imputasi 5.110363 3 3.122165
Interpretasi.
drop_na) sebagai
pembanding. Jika seluruh baris yang mengandung NA
dihapus, data tersisa hanya 424 dari 743 baris, atau kehilangan 42.93%
informasi. Kehilangan data sebesar ini tergolong besar dan dapat membuat
model kehilangan banyak pola penting serta memperkecil ukuran sampel.
Karena itu penghapusan baris tidak dipilih dan digantikan dengan
imputasi.Keadaan_Cuaca.
Sebanyak 9 data hilang berhasil diisi berdasarkan nilai sebelum dan
sesudahnya, sehingga jumlah NA pada
Keadaan_Cuaca menjadi 0. Tabel hasil interpolasi
memperlihatkan nilai pengisi pada setiap baris yang hilang. Nilai ini
dapat berupa bilangan desimal karena merupakan titik di antara dua kode
cuaca di sekitarnya. Hal ini bukan masalah, sebab pada tahap berikutnya
variabel ini akan dikelompokkan ke dalam interval (binning)
sehingga nilai desimal akan masuk ke kategori yang sesuai.Kecepatan_Angin.
Nilai yang paling sering muncul pada Kecepatan_Angin adalah
3, dan nilai inilah yang dipakai untuk mengisi 314 data hilang. Tabel
frekuensi menampilkan lima nilai yang paling sering muncul sebagai dasar
penentuan modus. Setelah imputasi, seluruh kolom tidak lagi memiliki
NA, sehingga data siap diproses pada tahap
selanjutnya.Kecepatan_Angin berubah dari 6.655 menjadi 5.11, median
dari 6 menjadi 3, dan simpangan baku dari 3.353 menjadi 3.122. Berbeda
dengan mean imputation yang menjaga rata-rata tetap, mode
imputation dapat menggeser rata-rata ke arah nilai modus, dan
karena sebagian besar data hilang diisi dengan satu nilai yang sama,
variasi data berkurang. Hal ini merupakan konsekuensi dari teknik modus
yang perlu disadari, terutama karena persentase data hilang pada
Kecepatan_Angin cukup besar.## # A tibble: 10 × 5
## Hujan Suhu Kelembapan Keadaan_Cuaca Kecepatan_Angin
## <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 1 23 95 5 3
## 2 1 24 90 1 3
## 3 1 26.8 77 1 3
## 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 3 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 3 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.
Keadaan_Cuaca
memiliki 23 nilai unik, yang berarti kardinalitasnya tinggi. Jika
langsung di-one-hot encoding, akan terbentuk 23 kolom biner
yang sebagian besar jarang bernilai 1, sehingga dimensi data membengkak
dan model mudah mengalami overfitting.Keadaan_Cuaca dengan kategori
barunya, sehingga terlihat bahwa nilai-nilai yang berdekatan dipetakan
ke kategori yang sama.df_clean$Hujan <- factor(df_clean$Hujan)
set.seed(200)
split_strat <- initial_split(df_clean, prop = 0.8, strata = Hujan) # strata = stratified splitting
X_train <- training(split_strat) %>% dplyr::select(-Hujan)
X_test <- testing(split_strat) %>% dplyr::select(-Hujan)
y_train <- training(split_strat)$Hujan
y_test <- testing(split_strat)$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 asli:\n")##
## Distribusi kelas data asli:
##
## 1 2
## 164 579
##
## 1 2
## 22.07 77.93
##
## Distribusi kelas data latih:
## y_train
## 1 2
## 131 463
## y_train
## 1 2
## 22.05 77.95
##
## Distribusi kelas data uji:
## y_test
## 1 2
## 33 116
## y_test
## 1 2
## 22.15 77.85
Interpretasi.
Hujan
(set.seed(200)) menjadi 594 observasi data latih (79.9%)
dan 149 observasi data uji (20.1%). Dimensi X_train dan
X_test memiliki jumlah kolom yang sama, yaitu 5 variabel
prediktor (target Hujan dipisahkan sebagai
y_train dan y_test).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.4625 35.7625
## 2 Kelembapan 0 46.5000 122.5000
## 3 Kecepatan_Angin 12 -3.0000 13.0000
# Skewness sebelum capping
skew_sebelum <- sapply(X_train[list_num], skewness, na.rm = TRUE)
round(skew_sebelum, 3)## Suhu Kelembapan Kecepatan_Angin
## 0.288 -0.552 1.237
# 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 12 0
## Suhu Kelembapan Kecepatan_Angin
## Min. :22.60 Min. : 52.00 Min. : 2.000
## 1st Qu.:24.32 1st Qu.: 75.00 1st Qu.: 3.000
## Median :26.00 Median : 86.00 Median : 3.000
## Mean :26.56 Mean : 83.76 Mean : 5.098
## 3rd Qu.:28.90 3rd Qu.: 94.00 3rd Qu.: 7.000
## Max. :32.00 Max. :100.00 Max. :15.000
## Suhu Kelembapan Kecepatan_Angin
## Min. :22.60 Min. : 52.00 Min. : 2.000
## 1st Qu.:24.32 1st Qu.: 75.00 1st Qu.: 3.000
## Median :26.00 Median : 86.00 Median : 3.000
## Mean :26.56 Mean : 83.76 Mean : 5.069
## 3rd Qu.:28.90 3rd Qu.: 94.00 3rd Qu.: 7.000
## Max. :32.00 Max. :100.00 Max. :13.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.288 -0.552 1.126
# 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 (0%),
Kelembapan 0 (0%), dan Kecepatan_Angin 12
(2.02%). Variabel dengan outlier terbanyak adalah
Kecepatan_Angin, sehingga variabel inilah yang paling terdampak oleh
capping. Batas bawah dan batas atas masing-masing variabel
adalah: Suhu (17.46 sampai 35.76), Kelembapan
(46.5 sampai 122.5), dan Kecepatan_Angin (-3 sampai
13).Kecepatan_Angin. Sebanyak 42.26% data
Kecepatan_Angin diisi dengan nilai modus yang sama.
Penumpukan nilai pada satu titik cenderung menyempitkan rentang
antarkuartil (IQR), sehingga batas atas menjadi rendah (13) dan nilai
kecepatan angin yang relatif tinggi lebih mudah terdeteksi sebagai
outlier. Dengan kata lain, sebagian outlier yang
terdeteksi merupakan akibat dari teknik imputasi, bukan semata-mata
nilai ekstrem alami dari pengamatan.Kecepatan_Angin berubah dari 15 menjadi 13. Batas yang
dihitung dari data latih juga dipakai pada data uji agar tidak ada
kebocoran data.Suhu memiliki skewness 0.288 (mendekati simetris),
Kelembapan -0.552 (agak miring menjulur ke kiri), dan
Kecepatan_Angin 1.237 (sangat miring menjulur ke kanan).
Setelah capping, nilainya menjadi 0.288 untuk Suhu
(mendekati simetris), -0.552 untuk Kelembapan (agak miring
menjulur ke kiri), dan 1.126 untuk Kecepatan_Angin (sangat
miring menjulur ke kanan). Penurunan nilai mutlak skewness
berarti distribusi menjadi lebih simetris karena ekor ekstrem terpotong;
perubahan terbesar terjadi pada variabel yang paling banyak
di-capping.# Parameter Standard Scaler dihitung dari data latih
mean_val <- sapply(X_train_capped[list_num], mean, na.rm = TRUE)
sd_val <- sapply(X_train_capped[list_num], sd, na.rm = TRUE)
rbind(Mean = mean_val, SD = sd_val)## Suhu Kelembapan Kecepatan_Angin
## Mean 26.557912 83.75758 5.069024
## SD 2.557246 11.55098 2.987374
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(mean_val[col])) / unname(sd_val[col])
X_test_scale[[col]] <- (X_test_capped[[col]] - unname(mean_val[col])) / unname(sd_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 -1.39 0.973 5 -0.693 0
## 2 -1.00 0.540 1 -0.693 0
## 3 0.0947 -0.585 1 -0.693 0
## 4 1.19 -1.88 2 -1.03 0
## 5 1.66 -2.40 1 0.646 0
## 6 1.50 -2.32 3 1.32 0
##
## Mean & SD data latih setelah scaling:
round(rbind(Mean = sapply(X_train_scale[list_num], mean),
SD = sapply(X_train_scale[list_num], sd)), 3)## Suhu Kelembapan Kecepatan_Angin
## Mean 0 0 0
## SD 1 1 1
##
## Mean & SD data uji setelah scaling:
round(rbind(Mean = sapply(X_test_scale[list_num], mean),
SD = sapply(X_test_scale[list_num], sd)), 3)## Suhu Kelembapan Kecepatan_Angin
## Mean -0.033 0.054 0.008
## SD 1.003 1.007 1.037
##
## Ringkasan data latih setelah scaling:
## Suhu Kelembapan Kecepatan_Angin
## Min. :-1.5477 Min. :-2.7493 Min. :-1.0273
## 1st Qu.:-0.8732 1st Qu.:-0.7582 1st Qu.:-0.6926
## Median :-0.2182 Median : 0.1941 Median :-0.6926
## Mean : 0.0000 Mean : 0.0000 Mean : 0.0000
## 3rd Qu.: 0.9159 3rd Qu.: 0.8867 3rd Qu.: 0.6464
## Max. : 2.1281 Max. : 1.4062 Max. : 2.6548
##
## Ringkasan data uji setelah scaling:
## Suhu Kelembapan Kecepatan_Angin
## Min. :-1.54772 Min. :-2.48962 Min. :-1.027332
## 1st Qu.:-0.88295 1st Qu.:-0.75817 1st Qu.:-0.692589
## Median :-0.21817 Median : 0.28071 Median :-0.692589
## Mean :-0.03314 Mean : 0.05352 Mean : 0.008347
## 3rd Qu.: 0.75944 3rd Qu.: 0.88671 3rd Qu.: 0.646379
## Max. : 1.97169 Max. : 1.40615 Max. : 2.654832
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 Standard Scaler") +
theme_minimal() +
theme(legend.position = "none")Interpretasi.
Suhu,
Kelembapan, dan Kecepatan_Angin berturut-turut
26.558, 83.758, dan 5.069, dengan simpangan baku 2.557, 11.551, dan
2.987. Perbedaan yang mencolok antarvariabel pada rata-rata dan
simpangan baku ini menegaskan bahwa ketiga variabel semula berada pada
skala yang berbeda.Suhu), 0.054
(Kelembapan), dan 0.008 (Kecepatan_Angin),
dengan simpangan baku 1.003, 1.007, dan 1.037. Nilainya mendekati,
tetapi tidak harus persis, 0 dan 1 karena data uji hanya ditransformasi
menggunakan parameter data latih. Inilah praktik yang benar untuk
mencegah kebocoran data.## 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 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
## # ℹ 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 0 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.
Keadaan_Cuaca_reduced menjadi 10 kolom biner, satu kolom
untuk setiap kategori (0 sampai 9). Pada setiap baris tepat satu kolom
bernilai 1 dan sisanya 0, sehingga model tidak mengira ada urutan atau
jarak numerik antarkategori, yang akan terjadi jika label 0 sampai 9
dipakai langsung sebagai angka.# 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
## 131 463
##
## 1 2
## 22.05 77.95
# 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
## 463 463
##
## 1 2
## 50 50
##
## Dimensi data latih sebelum & sesudah SMOTE:
## Sebelum: 594 14 | Sesudah: 926 14
##
## Distribusi kelas data uji (tidak diubah):
##
## 1 2
## 33 116
## # A tibble: 6 × 14
## Suhu Kelembapan Kecepatan_Angin Keadaan_Cuaca_reduced_X0
## <dbl> <dbl> <dbl> <dbl>
## 1 -1.39 0.973 -0.693 1
## 2 -1.00 0.540 -0.693 1
## 3 0.0947 -0.585 -0.693 1
## 4 1.19 -1.88 -1.03 1
## 5 1.66 -2.40 0.646 1
## 6 1.50 -2.32 1.32 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): 926 14
## Data uji : 149 14
Interpretasi.
head(train_smote) menunjukkan data akhir berisi tiga
variabel numerik yang sudah di-scaling, 10 kolom
one-hot Keadaan_Cuaca_reduced, dan target
Hujan (total 14 kolom). Kolom one-hot pada
observasi sintetis dapat bernilai pecahan karena dibentuk dari
interpolasi, bukan dari kategori asli; hal ini wajar dalam SMOTE standar
dan perlu diingat saat menginterpretasikan data sintetis.Keadaan_Cuaca (9 data atau 1.21%) dan
Kecepatan_Angin (314 data atau 42.26%), skala antarvariabel
yang berbeda, kardinalitas tinggi pada Keadaan_Cuaca (23
nilai unik), serta kelas target yang tidak seimbang (77.93% kelas 2 dan
22.07% kelas 1). Kondisi ini menegaskan bahwa data belum layak langsung
dipakai untuk pemodelan.NA hanya menyisakan 424 dari
743 baris, sehingga imputasi dipilih. Interpolasi mengisi data hilang
pada Keadaan_Cuaca, dan mode imputation mengisi
data hilang pada Kecepatan_Angin dengan nilai 3. Seluruh
observasi dipertahankan, tetapi mode imputation menggeser
rata-rata (6.655 menjadi 5.11) dan mengecilkan simpangan baku (3.353
menjadi 3.122), sehingga variasi Kecepatan_Angin
berkurang.Keadaan_Cuaca berhasil direduksi dari 23 nilai
unik menjadi 7 kategori terisi (dari 10 kategori yang tersedia), lalu
di-encode dengan one-hot encoding menjadi 10 kolom
biner dengan struktur yang sama pada data latih dan data uji.Hujan
yang terjaga (kelas 2 sebesar 77.95% pada data latih dan 77.85% pada
data uji, dibandingkan 77.93% pada data asli), sehingga evaluasi model
kelak lebih adil dan representatif.Hujan pada data latih menjadi 463 :
463 (50% : 50%) dengan menambahkan 332 observasi sintetis, sementara
data uji tetap utuh. Hasil akhirnya adalah data latih berukuran 926
baris dan data uji berukuran 149 baris dengan 14 kolom, yang siap
digunakan pada tahap pemodelan klasifikasi.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.
Lampiran 1. Dataset
data curah hujan.xlsx (20 baris pertama)
| Hujan | Suhu | Kelembapan | Keadaan_Cuaca | Kecepatan_Angin |
|---|---|---|---|---|
| 1 | 23.0 | 95 | 5 | NA |
| 1 | 24.0 | 90 | 1 | NA |
| 1 | 26.8 | 77 | 1 | NA |
| 1 | 29.6 | 62 | 2 | 2 |
| 1 | 30.8 | 56 | 1 | 7 |
| 1 | 31.0 | 55 | 1 | 7 |
| 1 | 30.4 | 57 | 3 | 9 |
| 2 | 30.9 | 58 | 2 | 10 |
| 2 | 30.2 | 62 | 2 | 8 |
| 2 | 29.7 | 62 | 2 | 7 |
| 2 | 29.2 | 68 | 3 | 5 |
| 2 | 28.6 | 73 | 1 | 5 |
| 2 | 27.6 | 79 | 1 | 2 |
| 1 | 26.5 | 84 | 3 | 3 |
| 1 | 26.2 | 83 | 2 | 3 |
| 1 | 25.7 | 86 | 2 | 2 |
| 1 | 25.0 | 89 | 2 | NA |
| 1 | 24.6 | 90 | 2 | NA |
| 2 | 24.2 | 90 | 2 | NA |
| 2 | 23.9 | 90 | 2 | NA |
Lampiran 2. Data latih akhir setelah SMOTE (10 baris pertama)
| Suhu | Kelembapan | Kecepatan_Angin | Keadaan_Cuaca_reduced_X0 | Keadaan_Cuaca_reduced_X1 | Keadaan_Cuaca_reduced_X2 | Keadaan_Cuaca_reduced_X3 | Keadaan_Cuaca_reduced_X4 | Keadaan_Cuaca_reduced_X5 | Keadaan_Cuaca_reduced_X6 | Keadaan_Cuaca_reduced_X7 | Keadaan_Cuaca_reduced_X8 | Keadaan_Cuaca_reduced_X9 | Hujan |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| -1.391 | 0.973 | -0.693 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
| -1.000 | 0.540 | -0.693 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
| 0.095 | -0.585 | -0.693 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
| 1.190 | -1.884 | -1.027 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
| 1.659 | -2.403 | 0.646 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
| 1.502 | -2.316 | 1.316 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
| -0.023 | 0.021 | -0.693 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
| -0.140 | -0.066 | -0.693 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
| -0.335 | 0.194 | -1.027 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
| -0.609 | 0.454 | -0.693 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |