BAB I PENDAHULUAN

1.1 Latar Belakang Masalah

Feature engineering merupakan tahapan penting dalam machine learning yang bertujuan mengolah data mentah menjadi fitur yang sesuai untuk proses pemodelan. Tahapan ini mencakup penanganan missing value, analisis kardinalitas, pembagian data (data splitting), penanganan outlier, penskalaan data (scaling), encoding, serta penanganan data tidak seimbang (imbalanced dataset) (Fransiska, 2026). Pemilihan dan pengolahan fitur yang tepat diperlukan karena kualitas fitur dapat memengaruhi kemampuan model dalam mengenali pola data (Mumuni & Mumuni, 2025). Dalam praktiknya, data yang digunakan untuk pemodelan dapat mengandung nilai yang hilang, pencilan, perbedaan skala, dan variabel kategoris yang belum berbentuk numerik. Jika tidak ditangani dengan tepat, kondisi tersebut dapat menghambat proses analisis dan memengaruhi hasil pemodelan. Oleh karena itu, teknik prapemrosesan seperti imputasi nilai yang hilang, penanganan outlier, scaling, dan encoding perlu disesuaikan dengan karakteristik data serta algoritma yang digunakan (Koukaras & Tjortjis, 2025). Selain itu, pembagian data menjadi training dan testing diperlukan untuk mengevaluasi kemampuan model dalam memprediksi data yang belum pernah digunakan selama pelatihan. Praktikum ini bertujuan memahami konsep dan menerapkan berbagai teknik feature engineering menggunakan RStudio. Melalui kegiatan ini, mahasiswa mempelajari cara mengidentifikasi permasalahan pada data dan menentukan teknik pengolahan yang sesuai sebelum data digunakan dalam pemodelan machine learning. Penerapan tahapan tersebut diharapkan dapat menghasilkan data yang lebih siap dianalisis serta mendukung pembentukan model yang lebih baik dan dapat diandalkan (Santos & Ferreira, 2023).

1.2 Rumusan Masalah

Berdasarkan latar belakang di atas adapun rumusan masalah penelitian ini yaitu, sebagai berikut: 1. Bagaimana konsep dari berbagai jenis feature engineering pada RStudio? 2. Bagaimana cara melakukan teknik feature engineering di program RStudio?

1.3 Tujuan

Berdasarkan Rumusan masalah di atas adapun tujuan penelitian ini yaitu, sebagai berikut: 1. Mahasiswa memahami konsep dari berbagai jenis feature engineering pada RStudio. 2. Mahasiswa dapat melakukan teknik feature engineering di program RStudio.

BAB II TINJAUAN PUSTAKA

2.1 Feature Engineering

Feature engineering adalah rangkaian proses untuk mengubah data mentah menjadi fitur-fitur yang dapat digunakan oleh algoritma machine learning. Dalam proses ini, fitur yang paling berpengaruh dipilih dan direkayasa agar sesuai untuk pemodelan, dan kualitas fitur dinilai lebih menentukan daripada kecanggihan algoritma yang dipilih (Fransiska, 2026). Pandangan ini sejalan dengan Koukaras dan Tjortjis (2025) yang menegaskan bahwa preprocessing dan feature engineering merupakan tahapan fundamental dalam penambangan data, serta dengan Mumuni dan Mumuni (2025) yang menyatakan bahwa pemrosesan data dan rekayasa fitur berperan besar terhadap kinerja pemodelan. Alshdaifat dkk. (2021) menunjukkan bahwa pengaruh suatu teknik preprocessing dapat berbeda dari satu algoritma klasifikasi ke algoritma lainnya. Dengan demikian, pemilihan teknik perlu disesuaikan dengan karakteristik data dan algoritma yang akan digunakan. Secara umum, tahapan feature engineering meliputi penanganan missing value, analisis kardinalitas, pembagian data (splitting), penanganan outlier, scaling, encoding, dan penanganan data tidak seimbang (imbalanced dataset). Setiap tahapan dibahas pada subbab berikut.

2.2 Handling Missing Value

Missing value terjadi ketika tidak ada nilai yang tersimpan untuk suatu observasi pada suatu variabel. Kondisi ini umum dijumpai dan dapat berdampak signifikan pada pemodelan machine learning (Fransiska, 2026). Penanganan data hilang perlu mempertimbangkan mekanisme terjadinya data hilang dan dampaknya terhadap inferensi, karena pendekatan yang terlalu sederhana dapat menghasilkan estimasi yang bias atau menurunkan variansi data. Beberapa pendekatan yang umum digunakan adalah sebagai berikut.

2.2.1 Penghapusan Data (Drop)

Penghapusan dilakukan dengan membuang baris (atau kolom) yang mengandung nilai hilang. Cara ini paling sederhana dan tidak memasukkan nilai buatan, tetapi mengurangi ukuran sampel. Jika proporsi data hilang besar, banyak informasi akan terbuang dan data yang tersisa bisa tidak lagi mewakili populasi.

2.2.2 Imputasi Statistik (Mean, Median, dan Mode)

Imputasi statistik mengganti nilai yang hilang dengan ukuran pemusatan data yang tersedia (Zeileis & Grothendieck, 2005):

  • Mean imputation, menggunakan rata-rata \(\bar{x}\), sesuai untuk data numerik yang berdistribusi simetris dan tanpa outlier.
  • Median imputation, menggunakan nilai tengah, lebih tahan terhadap outlier sehingga lebih sesuai untuk data yang menjulur (skewed).
  • Mode imputation, menggunakan nilai yang paling sering muncul, \(\hat{x} = \arg\max_v f(v)\) dengan \(f(v)\) adalah frekuensi nilai \(v\). Metode ini dapat digunakan pada variabel kategorik maupun diskrit.

Ketiga metode ini mudah diterapkan dan mempertahankan ukuran sampel. Kelemahannya, nilai pengganti yang sama untuk semua data hilang akan mengecilkan variansi variabel dan dapat mendistorsi distribusi, terutama bila proporsi data hilang besar.

2.2.3 Forward Fill dan Interpolasi

Untuk data yang berurutan, nilai hilang dapat diisi dengan memanfaatkan observasi di sekitarnya. Forward fill menyalin nilai valid sebelumnya ke posisi yang kosong. Interpolasi linear memperkirakan nilai yang hilang dari dua nilai terdekat yang diketahui; jika nilai pada posisi \(t_a\) dan \(t_b\) diketahui, nilai pada posisi \(t\) di antara keduanya adalah:

\[ \hat{y}_t = y_a + \frac{t - t_a}{t_b - t_a}\,(y_b - y_a) \]

Metode-metode ini sesuai bila antarobservasi yang berdekatan saling berkaitan, misalnya pada data deret waktu. Pada R, tersedia fungsi fill() dari paket tidyr dan na.approx() dari paket zoo (Zeileis & Grothendieck, 2005).

2.3 Kardinalitas

Kardinalitas adalah jumlah nilai unik atau label yang terdapat dalam suatu variabel kategorik, dan umumnya dibedakan menjadi kardinalitas rendah dan tinggi (Fransiska, 2026). Kardinalitas tinggi dapat menimbulkan curse of dimensionality ketika variabel ditransformasi dengan One-Hot Encoding, karena setiap label akan menjadi satu kolom biner baru sehingga data menjadi sangat jarang (sparse) dan beban komputasi meningkat. Selain itu, label yang hanya muncul beberapa kali meningkatkan risiko overfitting karena model cenderung menghafal pola spesifik label langka tersebut (Fransiska, 2026). Micci-Barreca (2001) juga mencatat bahwa atribut kategorik dengan kardinalitas tinggi merupakan tantangan tersendiri dalam pemodelan karena pengkodean standar menjadi tidak efisien. Kardinalitas dapat diturunkan, misalnya dengan menggabungkan label yang jarang muncul ke dalam satu kategori (misalnya “lainnya”) atau, untuk variabel bernilai angka, dengan pengelompokan ke dalam interval (binning) sehingga banyak nilai unik dipadatkan menjadi beberapa kategori saja.

2.4 Splitting Data

Splitting data adalah pembagian himpunan data menjadi data latih (training) untuk membangun model dan data uji (testing) untuk menguji kemampuan generalisasi model pada data baru. Proporsi pembagian bersifat fleksibel, dan rasio yang paling umum digunakan adalah 80% untuk data latih dan 20% untuk data uji (Muraina, 2022; Woschnagg & Cipan, 2004, dalam Fransiska, 2026). Gholamy dkk. (2018) menjelaskan bahwa proporsi seperti 70:30 atau 80:20 dapat dibenarkan secara teoretis. Strategi pembagian yang umum adalah sebagai berikut.

  • Non-shuffle splitting, membagi data sesuai urutan aslinya (bagian awal menjadi data latih dan bagian akhir menjadi data uji). Strategi ini sesuai untuk data deret waktu karena urutan kronologis harus dipertahankan.
  • Shuffle splitting, mengacak data terlebih dahulu sebelum dibagi, sehingga setiap observasi memiliki peluang yang sama masuk ke data latih maupun data uji.
  • Stratified splitting, membagi data secara acak di dalam setiap kelas (strata) sehingga proporsi kelas pada data latih dan data uji sama dengan data asli. Strategi ini penting pada data dengan kelas tidak seimbang, karena pengambilan sampel acak biasa berisiko menghasilkan data uji yang kurang mewakili kelas minoritas.

2.5 Handling Outlier

2.5.1 Pengertian Outlier

Outlier adalah observasi yang menunjukkan deviasi ekstrem dan berbeda secara signifikan dari sebagian besar data lain, serta berpotensi memberikan pengaruh yang tidak proporsional terhadap hasil analisis (Fransiska, 2026). Aguinis dkk. (2013) membedakan outlier menjadi tiga jenis, yaitu error outliers (akibat kesalahan pencatatan atau pengukuran), interesting outliers (nilai ekstrem yang sah dan informatif), dan influential outliers (nilai yang sangat memengaruhi hasil analisis). Perbedaan jenis ini penting karena menentukan cara penanganan yang tepat.

2.5.2 Deteksi Outlier

Salah satu metode deteksi yang paling umum adalah metode IQR (Interquartile Range) yang diperkenalkan Tukey (1977). Metode ini menetapkan batas data normal berdasarkan kuartil pertama (\(Q_1\)) dan kuartil ketiga (\(Q_3\)); observasi di luar batas berikut dianggap outlier:

\[ IQR = Q_3 - Q_1,\qquad BB = Q_1 - 1{,}5 \times IQR,\qquad BA = Q_3 + 1{,}5 \times IQR \]

dengan \(BB\) adalah batas bawah dan \(BA\) adalah batas atas. Selain IQR, deteksi juga dapat dilakukan secara visual menggunakan boxplot dan histogram, atau dengan skor-z, yaitu menandai observasi yang berjarak jauh dari rata-rata dalam satuan simpangan baku. Karena metode berbasis rata-rata sensitif terhadap nilai ekstrem itu sendiri, IQR yang berbasis kuartil lebih tahan terhadap pencilan.

2.5.3 Penanganan Outlier

Setelah terdeteksi, outlier dapat ditangani dengan beberapa cara:

  • Trimming, yaitu menghapus observasi yang tergolong outlier. Cara ini sederhana, tetapi mengurangi ukuran sampel dan dapat membuang informasi yang sah.
  • Capping (winsorizing), yaitu mengganti nilai di luar batas menjadi nilai batas terdekat sehingga seluruh observasi tetap dipertahankan: \(x_{capped} = \min\big(\max(x,\; BB),\; BA\big)\).
  • Transformasi atau pendekatan robust, misalnya transformasi logaritma atau penggunaan metode yang tahan terhadap outlier.

Aguinis dkk. (2013) menekankan bahwa keputusan penanganan sebaiknya mempertimbangkan jenis outlier, karena nilai ekstrem yang sah tidak selalu layak dihapus.

2.6 Scaling Data

Scaling data adalah transformasi variabel ke dalam rentang skala yang sebanding, sehingga tidak ada variabel yang mendominasi proses pembelajaran hanya karena memiliki rentang nilai yang lebih besar (Fransiska, 2026). Singh dan Singh (2020) menunjukkan bahwa normalisasi data dapat meningkatkan kinerja klasifikasi, terutama pada algoritma yang sensitif terhadap skala input seperti algoritma berbasis jarak. Beberapa metode yang umum digunakan adalah sebagai berikut.

  • Standard Scaler (standarisasi z-score) menghasilkan data dengan rata-rata 0 dan simpangan baku 1: \(z = \dfrac{x - \bar{x}}{s}\). Metode ini cocok untuk data yang berdistribusi mendekati simetris, tetapi sensitif terhadap outlier karena memakai rata-rata dan simpangan baku.
  • Robust Scaler menggunakan median dan IQR: \(x' = \dfrac{x - \text{median}}{IQR}\). Metode ini lebih tahan terhadap outlier sehingga sesuai untuk data yang menjulur.
  • Min-Max Scaler mengubah data ke rentang tertentu, umumnya 0 sampai 1: \(x' = \dfrac{x - x_{min}}{x_{max} - x_{min}}\). Metode ini mempertahankan bentuk distribusi, tetapi sangat dipengaruhi oleh nilai minimum dan maksimum.

Pada ketiga metode, parameter (rata-rata, simpangan baku, median, IQR, minimum, maksimum) dihitung dari data latih lalu diterapkan pada data latih maupun data uji.

2.7 Encoding

Feature encoding adalah proses mengubah fitur kategorik atau non-numerik menjadi format numerik karena banyak algoritma machine learning membutuhkan input numerik (Fransiska, 2026). Dua teknik yang umum digunakan adalah:

  • One-Hot Encoding, yang mengubah setiap kategori menjadi kolom biner tersendiri bernilai 1 jika observasi termasuk kategori tersebut dan 0 jika tidak. Teknik ini sesuai untuk variabel nominal karena tidak mengasumsikan urutan antarkategori, tetapi jumlah kolom bertambah seiring banyaknya kategori (Potdar dkk., 2017).
  • Ordinal Encoding, yang memberi angka berurutan pada kategori yang memang memiliki tingkatan (misalnya rendah, sedang, tinggi). Teknik ini tidak menambah jumlah kolom, tetapi hanya tepat bila urutan kategori bermakna.
Tabel 1. Ilustrasi One-Hot Encoding
Warna Merah Kuning Hijau
Merah 1 0 0
Merah 1 0 0
Kuning 0 1 0
Hijau 0 0 1
Kuning 0 1 0

2.8 Imbalanced Dataset

Imbalanced dataset adalah kondisi ketika distribusi kelas tidak setara, di mana kelas dengan proporsi besar disebut kelas mayoritas dan kelas dengan proporsi kecil disebut kelas minoritas (Fransiska, 2026). Pendekatan penanganannya pada tingkat data adalah resampling, antara lain:

  • Random under-sampling, yaitu mengurangi observasi kelas mayoritas secara acak. Cara ini sederhana, tetapi dapat membuang informasi yang berguna.
  • Random over-sampling, yaitu menggandakan observasi kelas minoritas secara acak. Cara ini mempertahankan seluruh data, tetapi duplikasi dapat meningkatkan risiko overfitting.
  • SMOTE (Synthetic Minority Over-sampling Technique), yang diusulkan Chawla dkk. (2002), membangkitkan sampel sintetis pada kelas minoritas, bukan sekadar menduplikasi data yang ada. Untuk setiap sampel minoritas \(x_i\), dipilih salah satu dari \(k\) tetangga terdekatnya (\(x_{nn}\)) pada kelas yang sama, lalu sampel baru dibentuk pada garis penghubung keduanya:

\[ x_{baru} = x_i + \lambda\,(x_{nn} - x_i),\qquad \lambda \sim U(0,1) \]

Resampling hanya diterapkan pada data latih, sedangkan data uji dibiarkan pada distribusi aslinya agar evaluasi model tetap mencerminkan kondisi sebenarnya.

BAB III METODE PENELITIAN

3.1 Sumber Data

Jenis data yang digunakan dalam penelitian ini adalah data kuantitatif. Data kuantitatif merupakan jenis data yang berbentuk angka. Data ini dapat diukur maupun dihitung berdasarkan perhitungan statistik atau matematis. Sumber data yang diperoleh dalam penelitian ini adalah data sekunder. Data sekunder merupakan data penelitian yang diperoleh atau dicatat oleh pihak lain. Dalam penelitian ini, data sekunder yang digunakan adalah data curah hujan yang berasal dari Asisten Praktikum Machine Learning and Modern Prediction.

3.2 Variabel Penelitian

Penelitian ini menggunakan lima variabel yang mengambarkan kondisi curah hujan. Dimana variabel yang digunakan yaitu Hujan, Suhu, Kelembapan, Keadaan_Cuaca, dan Kecepatan_Angin. Variabel inilah yang akan dilakukan Feature engineering menggunakan mode imputation, stratify splitting, dan Standard Scaler. Kemudian dilakukan interpolasi pada variabel Keadaan_Cuaca, capping pada data numerik, serta penanganan imbalance data menggunakan SMOTE

3.3 Langkah-langkah Analisis

Adapun langkah-langkah feature engineering pada penelitian ini, yaitu sebagai berikut: 1. Menginputkan data yang terdiri atas variabel Hujan, Suhu, Kelembapan, Keadaan Cuaca, dan Kecepatan Angin. 2. Melakukan penanganan data hilang (imputation). 3. Membagi data menjadi data latih dan data uji menggunakan metode stratified splitting. 4. Melakukan standardisasi pada data numerik menggunakan Standard Scaler. 5. Melakukan interpolasi pada variabel Keadaan_Cuaca. 6. Melakukan capping pada data numerik untuk menangani nilai pencilan (outlier). 7. Menangani ketidakseimbangan kelas pada data latih menggunakan metode SMOTE. 8. Memperoleh data yang telah melalui tahapan feature engineering untuk digunakan pada proses analisis selanjutnya.

BAB IV HASIL DAN PEMBAHASAN

4.1 Library dan Eksplorasi Data

Tahap pertama adalah memanggil seluruh library yang dibutuhkan dan mengimpor data curah hujan. Pada laporan ini, kombinasi perlakuan yang digunakan mengikuti ketentuan NPM genap, yaitu mode imputation, stratified splitting, dan Standard Scaler*, serta interpolasi pada Keadaan_Cuaca, capping pada data numerik, dan penanganan imbalance menggunakan SMOTE.

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/data curah hujan.xlsx")

knitr::kable(head(data), align = "c", caption = "Enam baris pertama data curah hujan")
Enam baris pertama data curah hujan
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
str(data)
## 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 ...
dim(data)
## [1] 743   5

Data terdiri dari 743 observasi dan 5 variabel, yaitu Hujan, Suhu, Kelembapan, Keadaan_Cuaca, dan Kecepatan_Angin. Seluruh variabel terbaca bertipe numerik (num/dbl). Namun, Hujan sebenarnya merupakan variabel kategorik biner (kelas 1 dan 2) dan Keadaan_Cuaca merupakan kode kondisi cuaca (bukan ukuran kontinu), sehingga keduanya perlu diperlakukan sebagai kategori pada tahap berikutnya. Pada enam baris pertama sudah terlihat adanya nilai NA pada Kecepatan_Angin.

Statistik Deskriptif

summary(data)
##      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
tabel_hujan <- data.frame(
  Kelas  = names(table(data$Hujan)),
  Jumlah = as.vector(table(data$Hujan)),
  Persen = round(as.vector(prop.table(table(data$Hujan))) * 100, 2)
)
knitr::kable(tabel_hujan, align = "c", caption = "Distribusi kelas variabel Hujan")
Distribusi kelas variabel Hujan
Kelas Jumlah Persen
1 164 22.07
2 579 77.93

Statistik deskriptif memberikan gambaran awal tentang pusat dan sebaran data sebelum diolah: - Suhu berkisar 22,6 hingga 32,0 dengan rata-rata sekitar 26,54, dan sebarannya relatif simetris (rata-rata dan median berdekatan). - Kelembapan berkisar 52 hingga 100 dengan rata-rata sekitar 83,88. Nilainya cenderung terkumpul di angka tinggi (median lebih besar dari rata-rata), yang wajar untuk wilayah tropis. - Keadaan_Cuaca memiliki rata-rata (sekitar 15,11) yang jauh lebih besar daripada median (2) dengan nilai maksimum 97, menandakan sebaran sangat menjulur ke kanan. Hal ini terjadi karena variabel ini adalah kode kategori, bukan pengukuran kontinu. - Kecepatan_Angin berkisar 2 hingga 21 dengan rata-rata sekitar 6,66 (dihitung dari data yang tersedia saja) dan memiliki banyak NA. - Variabel target Hujan tidak seimbang: kelas 1 hanya 164 observasi (22,07%), sedangkan kelas 2 sebanyak 579 observasi (77,93%).

Berdasarkan hasil statistika deskriptif dapat diketahui perlunya dilakukan preprocessing pada data curah hujan dimana yang teridentifikasi adalah: (1) menangani missing value pada Keadaan_Cuaca dan Kecepatan_Angin, (2) mereduksi kardinalitas Keadaan_Cuaca, (3) membagi data dengan proporsi kelas yang terjaga, (4) menangani outlier dan perbedaan skala antarvariabel numerik, (5) encoding variabel kategorik, dan (6) menyeimbangkan kelas Hujan.

4.2 Handling Missing Value

Identifikasi Missing Value

tabel_missing <- data.frame(
  Variabel   = names(data),
  Jumlah_NA  = as.vector(colSums(is.na(data))),
  Persen_NA  = round(as.vector(colMeans(is.na(data))) * 100, 2)
)
knitr::kable(tabel_missing, align = "c", caption = "Jumlah dan persentase missing value per variabel")
Jumlah dan persentase missing value per variabel
Variabel Jumlah_NA Persen_NA
Hujan 0 0.00
Suhu 0 0.00
Kelembapan 0 0.00
Keadaan_Cuaca 9 1.21
Kecepatan_Angin 314 42.26
# Baris dengan missing value pada Keadaan_Cuaca
knitr::kable(data %>% filter(is.na(Keadaan_Cuaca)), align = "c",
             caption = "Baris dengan Keadaan_Cuaca yang kosong")
Baris dengan Keadaan_Cuaca yang kosong
Hujan Suhu Kelembapan Keadaan_Cuaca Kecepatan_Angin
2 26.0 92 NA NA
1 23.8 98 NA 5
1 23.3 97 NA NA
2 28.8 79 NA 12
2 24.6 92 NA NA
1 22.8 98 NA 4
2 25.5 96 NA NA
2 27.0 80 NA 4
2 31.0 57 NA 6

Dapat dilihat missing value hanya terdapat pada dua variabel yaitu Keadaan_Cuaca dan Kecepatan_Angin. Keadaan_Cuaca memiliki 9 nilai hilang (1,21%), sedangkan Kecepatan_Angin memiliki 314 nilai hilang (42,26%). Variabel Hujan, Suhu, dan Kelembapan lengkap. Proporsi data hilang pada Kecepatan_Angin sangat besar, sehingga penghapusan baris berpotensi membuang banyak informasi. Oleh karena itu, hal ini diperiksa pada bagian berikut.

Pembanding: Menghapus Baris Bermissing Value

data_dropna <- data %>% drop_na()
cat("Jumlah baris sebelum drop_na :", nrow(data), "\n")
## Jumlah baris sebelum drop_na : 743
cat("Jumlah baris sesudah drop_na :", nrow(data_dropna), "\n")
## Jumlah baris sesudah drop_na : 424
cat("Persentase data yang hilang  :", round((1 - nrow(data_dropna) / nrow(data)) * 100, 2), "%\n")
## Persentase data yang hilang  : 42.93 %

Jika seluruh baris yang mengandung NA dihapus, jumlah data berkurang dari 743 menjadi 424 baris atau hilang sekitar 42.9%. Kehilangan data sebesar ini terlalu besar, sehingga metode imputasi lebih tepat digunakan.

Mode Imputation pada Kecepatan_Angin

Sesuai ketentuan NPM genap (02), Variabel Kecepatan_Angin diimputasi menggunakan modus (nilai yang paling sering muncul).

df_imp <- data

frek_angin <- sort(table(df_imp$Kecepatan_Angin), decreasing = TRUE)
knitr::kable(
  data.frame(Kecepatan_Angin = names(frek_angin)[1:5],
             Frekuensi = as.vector(frek_angin)[1:5]),
  align = "c", caption = "Lima nilai Kecepatan_Angin dengan frekuensi tertinggi"
)
Lima nilai Kecepatan_Angin dengan frekuensi tertinggi
Kecepatan_Angin Frekuensi
3 57
5 55
8 47
6 40
4 39
modus <- as.numeric(names(frek_angin)[1])
cat("Nilai modus Kecepatan_Angin :", modus, "\n")
## Nilai modus Kecepatan_Angin : 3
df_imp$Kecepatan_Angin[is.na(df_imp$Kecepatan_Angin)] <- modus
colSums(is.na(df_imp))
##           Hujan            Suhu      Kelembapan   Keadaan_Cuaca Kecepatan_Angin 
##               0               0               0               9               0

Nilai modus Kecepatan_Angin adalah 3 karena muncul sebanyak 57 kali. Oleh karena itu, seluruh 314 nilai yang hilang kemudian diisi dengan angka 3 sehingga Kecepatan_Angin tidak lagi memiliki NA. Namun, karena proporsi data hilang sangat besar (42,26%), imputasi modus membuat nilai 3 menjadi sangat dominan. Akibatnya, variansi variabel mengecil dan sebaran data menjadi terkonsentrasi di sekitar nilai tersebut.

Interpolasi pada Keadaan_Cuaca

baris_na <- which(is.na(data$Keadaan_Cuaca))

df_imp$Keadaan_Cuaca <- na.approx(df_imp$Keadaan_Cuaca, na.rm = FALSE)

knitr::kable(
  data.frame(Baris = baris_na,
             Sebelum = data$Keadaan_Cuaca[baris_na],
             Sesudah_Interpolasi = df_imp$Keadaan_Cuaca[baris_na]),
  align = "c", caption = "Hasil interpolasi linear pada Keadaan_Cuaca"
)
Hasil interpolasi linear pada Keadaan_Cuaca
Baris Sebelum Sesudah_Interpolasi
42 NA 2.0
207 NA 61.0
310 NA 62.0
512 NA 49.0
598 NA 21.0
639 NA 56.0
675 NA 2.0
723 NA 2.0
725 NA 1.5
colSums(is.na(df_imp))
##           Hujan            Suhu      Kelembapan   Keadaan_Cuaca Kecepatan_Angin 
##               0               0               0               0               0

Kemudian dilakukan Interpolasi linear pada Keadaan_Cuaca. Interpolasi linear mengisi sembilan nilai kosong pada Keadaan_Cuaca berdasarkan nilai di baris sebelum dan sesudahnya. Setelah proses ini, tidak ada lagi missing value pada seluruh variabel. Sebagian besar hasil interpolasi bernilai sama dengan kode yang sudah ada (misalnya 2 dan 61), tetapi ada beberapa nilai yang tidak ada pada kode aslinya (misalnya 49, 56, dan 1,5). Hal ini dapat terjadi karena Keadaan_Cuaca sebenarnya adalah kode kategori.

4.3 Kardinalitas

Kardinalitas adalah banyaknya nilai unik pada suatu variabel. Kardinalitas yang tinggi pada variabel kategorik akan menghasilkan terlalu banyak kolom saat encoding. Oleh karena itu, Keadaan_Cuaca dikelompokkan ke dalam 10 interval (bin) dengan lebar 10.

sort(unique(df_imp$Keadaan_Cuaca))
##  [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_imp$Keadaan_Cuaca_reduced <- cut(
  df_imp$Keadaan_Cuaca,
  breaks = bins,
  labels = labels,
  right = TRUE,
  include.lowest = TRUE
)

cat("--- Hasil Perbandingan (10 baris acak) ---\n")
## --- Hasil Perbandingan (10 baris acak) ---
set.seed(123)
knitr::kable(df_imp[sample(nrow(df_imp), 10), ], align = "c")
Hujan Suhu Kelembapan Keadaan_Cuaca Kecepatan_Angin Keadaan_Cuaca_reduced
2 31.6 60 2 10 0
2 28.9 80 15 6 1
1 25.1 94 61 10 6
2 24.5 89 2 3 0
2 24.6 91 2 2 0
2 24.4 93 1 3 0
2 29.3 76 3 8 0
2 27.4 76 1 6 0
2 28.9 65 3 10 0
1 26.5 84 3 3 0
cat("\n--- Pengecekan Kardinalitas ---\n")
## 
## --- Pengecekan Kardinalitas ---
cat('Jumlah kategori di "Keadaan_Cuaca" asli    :', length(unique(df_imp$Keadaan_Cuaca)), "\n")
## Jumlah kategori di "Keadaan_Cuaca" asli    : 23
cat('Jumlah kategori di "Keadaan_Cuaca_reduced" :', length(unique(df_imp$Keadaan_Cuaca_reduced)), "\n")
## Jumlah kategori di "Keadaan_Cuaca_reduced" : 7
cat("\nFrekuensi tiap kategori (reduced):\n")
## 
## Frekuensi tiap kategori (reduced):
table(df_imp$Keadaan_Cuaca_reduced)
## 
##   0   1   2   3   4   5   6   7   8   9 
## 523  48  46   0   1  28  71   0   0  26

Sebelum dilakukan direduksi, Keadaan_Cuaca memiliki 23 nilai unik. Setelah dikelompokkan, kardinalitas turun menjadi 7 kategori yang terisi. Dimana kategori 0 (kode 0–10) mendominasi dengan 523 observasi, sedangkan kategori 3, 7, dan 8 tidak memiliki observasi sama sekali (tetap tercatat sebagai level tetapi frekuensinya nol), dan kategori 4 hanya berisi 1 observasi. Artinya, mayoritas kondisi cuaca berada pada kode rendah, dan kode tinggi (60-an dan 90-an) muncul lebih jarang. Reduksi ini membuat jumlah kolom hasil encoding menjadi jauh lebih ringkas dibanding menggunakan 23 nilai unik asli.

4.4 Splitting Data

Sesuai ketentuan NPM genap, pembagian data dilakukan menggunakan stratified splitting dengan proporsi 80% data latih dan 20% data uji. Stratifikasi dilakukan berdasarkan Hujan agar proporsi kelas pada data latih dan data uji tetap sama dengan data asli. Variabel Hujan diubah menjadi factor terlebih dahulu karena merupakan variabel kelas. Variabel Keadaan_Cuaca asli dikeluarkan dari fitur karena sudah digantikan oleh Keadaan_Cuaca_reduced.

df_imp$Hujan <- factor(df_imp$Hujan)

set.seed(200)
split_stratify <- initial_split(df_imp, prop = 0.8, strata = Hujan)

X_train <- training(split_stratify) %>% select(-Hujan, -Keadaan_Cuaca)
X_test  <- testing(split_stratify)  %>% select(-Hujan, -Keadaan_Cuaca)
y_train <- training(split_stratify)$Hujan
y_test  <- testing(split_stratify)$Hujan

cat("Dimensi X_train :", dim(X_train), "\n")
## Dimensi X_train : 594 4
cat("Dimensi X_test  :", dim(X_test), "\n")
## Dimensi X_test  : 149 4
perbandingan <- rbind(
  Data_Asli  = round(prop.table(table(df_imp$Hujan)) * 100, 2),
  Data_Latih = round(prop.table(table(y_train)) * 100, 2),
  Data_Uji   = round(prop.table(table(y_test)) * 100, 2)
)
knitr::kable(perbandingan, align = "c", caption = "Persentase kelas Hujan (%) pada tiap himpunan data")
Persentase kelas Hujan (%) pada tiap himpunan data
1 2
Data_Asli 22.07 77.93
Data_Latih 22.05 77.95
Data_Uji 22.15 77.85

Data terbagi menjadi 594 observasi data latih dan 149 observasi data uji (sekitar 80:20). Tabel perbandingan menunjukkan bahwa proporsi kelas Hujan pada data latih dan data uji hampir identik dengan data asli (sekitar 22% kelas 1 dan 78% kelas 2). Dengan demikian, metode stratified splitting berhasil menjaga distribusi kelas, sehingga data uji merepresentasikan kondisi data sebenarnya, hal ini penting karena data bersifat tidak seimbang. Sehingga untuk menghindari terjadinya kebocoran informasi (data leakage), seluruh parameter pada tahap berikutnya (batas outlier, rata-rata, dan simpangan baku) dihitung hanya dari data latih, lalu diterapkan pada data uji.

4.5 Handling Outlier

Outlier dideteksi menggunakan metode IQR pada seluruh variabel numerik (Suhu, Kelembapan, Kecepatan_Angin). Data dikatakan outlier apabila berada di luar rentang \([Q_1 - 1{,}5 \times IQR,\; Q_3 + 1{,}5 \times IQR]\).

list_num <- c("Suhu", "Kelembapan", "Kecepatan_Angin")

outliers <- do.call(rbind, lapply(list_num, function(i) {
  Q1  <- quantile(X_train[[i]], 0.25, na.rm = TRUE)
  Q3  <- 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
  data.frame(
    Kolom          = i,
    Q1             = as.numeric(Q1),
    Q3             = as.numeric(Q3),
    Lower_Bound    = as.numeric(lower_bound),
    Upper_Bound    = as.numeric(upper_bound),
    Jumlah_Outlier = sum(X_train[[i]] < lower_bound | X_train[[i]] > upper_bound, na.rm = TRUE)
  )
}))

knitr::kable(outliers, digits = 2, align = "c", caption = "Hasil deteksi outlier (IQR) pada data latih")
Hasil deteksi outlier (IQR) pada data latih
Kolom Q1 Q3 Lower_Bound Upper_Bound Jumlah_Outlier
Suhu 24.33 28.9 17.46 35.76 0
Kelembapan 75.00 94.0 46.50 122.50 0
Kecepatan_Angin 3.00 7.0 -3.00 13.00 12

Berdasarkan metode IQR, Suhu dan Kelembapan tidak memiliki outlier karena seluruh nilainya berada di dalam batas bawah dan batas atas. Sebaliknya, Kecepatan_Angin memiliki 12 observasi outlier pada data latih, seluruhnya berada di atas batas atas (13). Banyaknya outlier ini sebagian besar disebabkan oleh mode imputation pada subbab 4.2: karena 42,26% data diisi dengan angka yang sama (3), rentang antarkuartil (IQR) menyempit sehingga nilai angin yang tinggi (misalnya di atas 11) dianggap menyimpang.

Pemeriksaan Skewness

skew_awal <- data.frame(
  Kolom    = list_num,
  Skewness = sapply(list_num, function(i) skewness(X_train[[i]], na.rm = TRUE))
)
skew_awal$Interpretasi <- ifelse(abs(skew_awal$Skewness) <= 0.5, "Mendekati simetris", "Miring (skewed)")
rownames(skew_awal) <- NULL
knitr::kable(skew_awal, digits = 3, align = "c", caption = "Nilai skewness data latih sebelum capping")
Nilai skewness data latih sebelum capping
Kolom Skewness Interpretasi
Suhu 0.288 Mendekati simetris
Kelembapan -0.552 Miring (skewed)
Kecepatan_Angin 1.237 Miring (skewed)

Nilai skewness antara −0,5 hingga 0,5 menunjukkan distribusi yang mendekati simetris. Kecepatan_Angin memiliki skewness positif yang paling besar (menjulur ke kanan), konsisten dengan keberadaan outlier di sisi atas. Informasi ini juga mempertegas perlunya penanganan outlier sebelum penskalaan.

Capping (Winsorizing)

Capping dilakukan dengan mengganti nilai di luar batas IQR menjadi nilai batas terdekat, tanpa menghapus observasi. Batas yang digunakan dihitung dari data latih dan diterapkan pada data latih maupun data uji.

X_train_capped <- X_train
X_test_capped  <- X_test

for (i in list_num) {
  lb <- outliers$Lower_Bound[outliers$Kolom == i]
  ub <- outliers$Upper_Bound[outliers$Kolom == i]
  X_train_capped[[i]] <- Winsorize(X_train[[i]], val = c(lb, ub))
  X_test_capped[[i]]  <- Winsorize(X_test[[i]],  val = c(lb, ub))
}

ringkasan_cap <- data.frame(
  Kolom             = list_num,
  Min_Sebelum       = sapply(list_num, function(i) min(X_train[[i]])),
  Max_Sebelum       = sapply(list_num, function(i) max(X_train[[i]])),
  Min_Sesudah       = sapply(list_num, function(i) min(X_train_capped[[i]])),
  Max_Sesudah       = sapply(list_num, function(i) max(X_train_capped[[i]])),
  Jumlah_Dicapping  = sapply(list_num, function(i) sum(X_train[[i]] != X_train_capped[[i]]))
)
rownames(ringkasan_cap) <- NULL
knitr::kable(ringkasan_cap, digits = 2, align = "c", caption = "Perbandingan rentang data latih sebelum dan sesudah capping")
Perbandingan rentang data latih sebelum dan sesudah capping
Kolom Min_Sebelum Max_Sebelum Min_Sesudah Max_Sesudah Jumlah_Dicapping
Suhu 22.6 32 22.6 32 0
Kelembapan 52.0 100 52.0 100 0
Kecepatan_Angin 2.0 15 2.0 13 12

Nilai maksimum Kecepatan_Angin pada data latih turun dari 15 menjadi 13, sesuai batas atas IQR. Sebanyak 12 observasi diubah nilainya, sedangkan Suhu dan Kelembapan tidak berubah karena memang tidak memiliki outlier. Jumlah baris tetap sama sehingga tidak ada informasi yang hilang.

Visualisasi Sebelum dan Sesudah Capping

diagnostic_plots <- function(df, variable) {
  p1 <- ggplot(df, aes(x = .data[[variable]])) +
    geom_histogram(bins = 30, fill = "#008080", color = "black") +
    ggtitle("Histogram") +
    theme_minimal()

  p2 <- ggplot(df, aes(y = .data[[variable]])) +
    geom_boxplot(fill = "#008080") +
    ggtitle("Boxplot") +
    theme_minimal()

  grid.arrange(p1, p2, ncol = 2)
}

for (col in list_num) {
  cat(col, "- Sebelum Capping\n")
  diagnostic_plots(X_train, col)

  cat("\n", col, "- Sesudah Capping\n")
  diagnostic_plots(X_train_capped, col)
}
## Suhu - Sebelum Capping

## 
##  Suhu - Sesudah Capping

## Kelembapan - Sebelum Capping

## 
##  Kelembapan - Sesudah Capping

## Kecepatan_Angin - Sebelum Capping

## 
##  Kecepatan_Angin - Sesudah Capping

Pada Suhu dan Kelembapan, grafik sebelum dan sesudah capping identik karena tidak ada nilai yang dipotong. Pada histogram Kelembapan tampak menjulur ke kiri dengan konsentrasi nilai di angka tinggi, tetapi tidak ada titik di luar whisker boxplot. Pada Kecepatan_Angin, sebelum capping histogram menunjukkan tumpukan nilai di sekitar 3 efek dari mode imputation yang dilakukan dan boxplot menampilkan titik-titik outlier di bagian atas. Setelah capping, ekor kanan histogram terpotong pada batas atas dan titik outlier pada boxplot hilang, sehingga distribusi menjadi lebih terkendali.

4.6 Scaling Data

Sesuai ketentuan NPM genap, seluruh variabel numerik diskalakan menggunakan Standard Scaler (standarisasi z-score):

\[ z = \frac{x - \bar{x}}{s} \]

dengan \(\bar{x}\) dan \(s\) masing-masing adalah rata-rata dan simpangan baku data latih (setelah capping).

# Parameter 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)

knitr::kable(data.frame(Kolom = list_num, Mean_Latih = mean_val, SD_Latih = sd_val, row.names = NULL),
             digits = 3, align = "c", caption = "Parameter Standard Scaler (data latih)")
Parameter Standard Scaler (data latih)
Kolom Mean_Latih SD_Latih
Suhu 26.558 2.557
Kelembapan 83.758 11.551
Kecepatan_Angin 5.069 2.987
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]] - mean_val[col]) / sd_val[col]
  X_test_scale[[col]]  <- (X_test_capped[[col]]  - mean_val[col]) / sd_val[col]
}

hasil_scale <- rbind(
  Latih_Mean = sapply(X_train_scale[list_num], mean),
  Latih_SD   = sapply(X_train_scale[list_num], sd),
  Uji_Mean   = sapply(X_test_scale[list_num], mean),
  Uji_SD     = sapply(X_test_scale[list_num], sd)
)
knitr::kable(hasil_scale, digits = 3, align = "c", caption = "Rata-rata dan simpangan baku setelah scaling")
Rata-rata dan simpangan baku setelah scaling
Suhu Kelembapan Kecepatan_Angin
Latih_Mean 0.000 0.000 0.000
Latih_SD 1.000 1.000 1.000
Uji_Mean -0.033 0.054 0.008
Uji_SD 1.003 1.007 1.037
knitr::kable(head(X_train_scale), digits = 3, align = "c", caption = "Enam baris pertama data latih setelah scaling")
Enam baris pertama data latih setelah scaling
Suhu Kelembapan Kecepatan_Angin Keadaan_Cuaca_reduced
-1.391 0.973 -0.693 0
-1.000 0.540 -0.693 0
0.095 -0.585 -0.693 0
1.190 -1.884 -1.027 0
1.659 -2.403 0.646 0
1.502 -2.316 1.316 0

Setelah standarisasi, seluruh variabel numerik pada data latih memiliki rata-rata 0 dan simpangan baku 1, sehingga Suhu, Kelembapan, dan Kecepatan_Angin berada pada skala yang sebanding dan tidak ada variabel yang mendominasi hanya karena satuannya lebih besar. Pada data uji, rata-rata dan simpangan baku tidak tepat 0 dan 1. Dimana hal ini wajar dan memang diharapkan, karena data uji ditransformasi menggunakan parameter dari data latih, dan nilainya yang tetap mendekati 0 dan 1 menunjukkan distribusi kedua himpunan data serupa.

4.7 Encoding

Variabel Keadaan_Cuaca_reduced merupakan variabel kategorik sehingga harus diubah menjadi bentuk numerik. Encoding yang digunakan adalah One-Hot Encoding, karena kategorinya bersifat nominal (kode kondisi cuaca) dan tidak diasumsikan memiliki urutan.

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)

cat("Dimensi X_train_encoded :", dim(X_train_encoded), "\n")
## Dimensi X_train_encoded : 594 10
cat("Dimensi X_test_encoded  :", dim(X_test_encoded), "\n")
## Dimensi X_test_encoded  : 149 10
knitr::kable(head(X_train_encoded), align = "c", caption = "Enam baris pertama data latih hasil one-hot encoding")
Enam baris pertama data latih hasil one-hot encoding
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
1 0 0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0 0 0

Berdasarkan di atas satu kolom kategorik Keadaan_Cuaca_reduced berubah menjadi 10 kolom biner (Keadaan_Cuaca_reduced_X0 sampai Keadaan_Cuaca_reduced_X9), masing-masing bernilai 1 jika observasi termasuk kategori tersebut dan 0 jika tidak. Kolom untuk kategori 3, 7, dan 8 berisi 0 seluruhnya karena tidak ada observasi pada kategori itu. Kolom seperti ini tidak informatif, tetapi dipertahankan agar struktur kolom data latih dan data uji konsisten. Karena level faktor pada data uji telah disamakan dengan data asli, jumlah kolom pada data latih dan data uji sama.

4.8 Balancing Data (SMOTE)

Selajutkan di lakukan imbalance data. Dimana distribusi kelas Hujan yang tidak seimbang dapat membuat model cenderung memprediksi kelas mayoritas. Synthetic Minority Over-sampling Technique (SMOTE) mengatasi hal ini dengan membangkitkan data sintetis kelas minoritas berdasarkan tetangga terdekatnya (k-nearest neighbors). SMOTE hanya diterapkan pada data latih, sedangkan data uji dibiarkan apa adanya. Sebelum SMOTE, fitur numerik yang telah diskalakan digabung dengan hasil one-hot encoding sehingga seluruh fitur ikut digunakan.

cat("Distribusi kelas data latih SEBELUM SMOTE:\n")
## Distribusi kelas data latih SEBELUM SMOTE:
print(table(y_train))
## y_train
##   1   2 
## 131 463
train_full <- bind_cols(X_train_scale[list_num], X_train_encoded) %>%
  mutate(Hujan = y_train)

set.seed(42)
resep_smote <- recipe(Hujan ~ ., data = train_full) %>%
  step_smote(Hujan, over_ratio = 1, neighbors = 5)

resep_smote_prep <- prep(resep_smote, training = train_full)
train_balanced   <- bake(resep_smote_prep, new_data = NULL)

X_train_balanced <- train_balanced %>% select(-Hujan)
y_train_balanced <- train_balanced$Hujan

cat("\nDistribusi kelas data latih SETELAH SMOTE:\n")
## 
## Distribusi kelas data latih SETELAH SMOTE:
print(table(y_train_balanced))
## y_train_balanced
##   1   2 
## 463 463
cat("\nDimensi X_train_balanced :", dim(X_train_balanced), "\n")
## 
## Dimensi X_train_balanced : 926 13
cat("Distribusi kelas data uji (tidak di-SMOTE):\n")
## Distribusi kelas data uji (tidak di-SMOTE):
print(table(y_test))
## y_test
##   1   2 
##  33 116
df_plot <- data.frame(
  Kondisi = factor(rep(c("Sebelum SMOTE", "Sesudah SMOTE"), each = 2),
                   levels = c("Sebelum SMOTE", "Sesudah SMOTE")),
  Kelas   = c(names(table(y_train)), names(table(y_train_balanced))),
  Jumlah  = c(as.vector(table(y_train)), as.vector(table(y_train_balanced)))
)

ggplot(df_plot, aes(x = Kelas, y = Jumlah, fill = Kelas)) +
  geom_col(color = "black", width = 0.6) +
  geom_text(aes(label = Jumlah), vjust = -0.4) +
  facet_wrap(~ Kondisi) +
  scale_fill_manual(values = c("#008080", "#E69F00")) +
  labs(title = "Distribusi Kelas Hujan pada Data Latih", x = "Kelas Hujan", y = "Jumlah") +
  theme_minimal() +
  theme(legend.position = "none")

Sebelum dilakukan SMOTE, data latih terdiri dari 131 observasi kelas 1 (minoritas) dan 463 observasi kelas 2 (mayoritas), dengan rasio sekitar 1 : 3,5. Setelah SMOTE dengan over_ratio = 1, kedua kelas menjadi seimbang masing-masing 463 observasi, dengan total 926 baris data latih. Peningkatan jumlah baris ini berasal dari data sintetis kelas 1 yang dibangkitkan, bukan dari duplikasi data asli. Dimana data uji tetap mempertahankan distribusi aslinya sehingga evaluasi model nantinya tetap mencerminkan kondisi nyata. Data hasil akhir (X_train_balanced, y_train_balanced, X_test_encoded, dan y_test) siap digunakan untuk pemodelan pada algoritma machine learning.

BAB V KESIMPULAN

5.1 Kesimpulan

Berdasarkan hasil preprocessing data curah hujan dengan perlakuan, dapat disimpulkan sebagai berikut: 1. Missing value terdapat pada Keadaan_Cuaca (9 data atau 1,21%) dan Kecepatan_Angin (314 data atau 42,26%). Penghapusan baris akan membuang banyak data, sehingga digunakan mode imputation pada Kecepatan_Angin (modus = 3) dan interpolasi linear pada Keadaan_Cuaca. Setelah itu seluruh variabel bebas dari missing value. Namun, tingginya persentase data yang diimputasi dengan satu nilai membuat variansi Kecepatan_Angin mengecil. 2. Kardinalitas Keadaan_Cuaca berhasil direduksi dari 23 nilai unik menjadi 7 kategori terisi melalui binning, sehingga encoding menjadi lebih ringkas. 3. Stratified splitting (80:20) menghasilkan 594 data latih dan 149 data uji dengan proporsi kelas Hujan yang tetap terjaga sama seperti data asli. 4. Outlier hanya ditemukan pada Kecepatan_Angin (12 observasi pada data latih), terutama akibat penyempitan IQR oleh mode imputation. Capping berhasil membatasi nilai ekstrem tanpa mengurangi jumlah observasi. 5. Standard Scaler menyamakan skala Suhu, Kelembapan, dan Kecepatan_Angin menjadi memiliki rata-rata 0 dan simpangan baku 1 pada data latih, dengan parameter yang diambil dari data latih saja untuk mencegah data leakage. 6. One-Hot Encoding mengubah Keadaan_Cuaca_reduced menjadi 10 kolom biner sehingga seluruh fitur berbentuk numerik. 7. SMOTE menyeimbangkan kelas Hujan pada data latih dari rasio sekitar 1 : 3,5 menjadi 1 : 1 (463 observasi per kelas), sementara data uji dibiarkan tidak berubah.

Dengan demikian, data telah melalui seluruh tahapan feature engineering yang diminta dan siap digunakan untuk tahap pemodelan machine learning.

DAFTAR PUSTAKA

Aguinis, H., Gottfredson, R. K., & Joo, H. (2013). Best-practice recommendations for defining, identifying, and handling outliers. Organizational Research Methods, 16(2), 270–301.

Alshdaifat, E., Alshdaifat, D., Alsarhan, A., Hussein, F., & El-Salhi, S. M. F. S. (2021). The effect of preprocessing techniques, applied to numeric features, on classification algorithms’ performance. Data, 6(2), 11. https://doi.org/10.3390/data6020011

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. https://doi.org/10.1613/jair.953

Fransiska, H. (2026). Modul Praktikum Machine Learning and Modern Prediction. Bengkulu: Universitas Bengkulu.

Gholamy, A., Kreinovich, V., & Kosheleva, O. (2018). Why 70/30 or 80/20 relation between training and testing sets: A pedagogical explanation (Technical Report UTEP-CS-18-09). University of Texas at El Paso.

Koukaras, P., & Tjortjis, C. (2025). Data preprocessing and feature engineering for data mining: Techniques, tools, and best practices. AI, 6(10), 257. https://doi.org/10.3390/ai6100257

Micci-Barreca, D. (2001). A preprocessing scheme for high-cardinality categorical attributes in classification and prediction problems. ACM SIGKDD Explorations Newsletter, 3(1), 27–32. https://doi.org/10.1145/507533.507538

Mumuni, A., & Mumuni, F. (2025). Automated data processing and feature engineering for deep learning and big data applications: A survey. Journal of Information and Intelligence, 3(2), 113–153. https://doi.org/10.1016/j.jiixd.2024.01.002

Potdar, K., Pardawala, T. S., & Pai, C. D. (2017). A comparative study of categorical variable encoding techniques for neural network classifiers. International Journal of Computer Applications, 175(4), 7–9. https://doi.org/10.5120/ijca2017915495

Santos, L., & Ferreira, L. (2023). Atlantic—Automated data preprocessing framework for supervised machine learning. Software Impacts, 17, 100532. https://doi.org/10.1016/j.simpa.2023.100532

Singh, D., & Singh, B. (2020). Investigating the impact of data normalization on classification performance. Applied Soft Computing, 97, 105524. https://doi.org/10.1016/j.asoc.2019.105524

Zeileis, A., & Grothendieck, G. (2005). zoo: S3 infrastructure for regular and irregular time series. Journal of Statistical Software, 14(6), 1–27. https://doi.org/10.18637/jss.v014.i06