BAB I PENDAHULUAN

1.1 Latar Belakang Masalah

   Perkembangan machine learning menyebabkan kebutuhan terhadap data yang berkualitas semakin penting. Data yang diperoleh dari berbagai sumber tidak selalu dapat langsung digunakan dalam pemodelan karena dapat memiliki perbedaan skala, bentuk data yang beragam, variabel kategorik, maupun fitur yang kurang relevan. Oleh karena itu, diperlukan proses pengolahan fitur agar data dapat memberikan informasi yang lebih sesuai bagi model machine learning. Salah satu proses penting dalam tahap tersebut adalah feature engineering, yaitu proses mengubah, membuat, atau memilih fitur dari data yang tersedia sehingga dapat meningkatkan kualitas representasi data untuk pemodelan (Vasques, 2024).
  Feature engineering mencakup berbagai teknik, seperti transformasi variabel, encoding data kategorik, pembuatan fitur baru, serta pemilihan fitur yang relevan. Penerapan teknik tersebut dapat membantu menyederhanakan data dan menghasilkan fitur yang lebih informatif bagi algoritma machine learning (Mumuni & Mumuni, 2025). RStudio sebagai lingkungan pengembangan untuk bahasa pemrograman R dapat digunakan untuk melakukan berbagai teknik feature engineering. Dengan memanfaatkan fungsi-fungsi dalam R maupun berbagai paket pendukung, proses transformasi dan pembentukan fitur dapat dilakukan secara sistematis dan dapat direproduksi. Pemahaman terhadap feature engineering di RStudio penting bagi mahasiswa karena memungkinkan proses pengolahan data dilakukan secara langsung sebelum data digunakan untuk membangun model statistik maupun machine learning.

1.2 Rumusan Masalah

   Berdasarkan latar belakang, rumusan masalah yang dapat disimpulkan adalah:
   1.   Bagaimana cara memahami konsep dari berbagai jenis feature engineering pada RStudio?
   2.   Bagaimana cara melakukan teknik feature engineering di program RStudio?

1.3 Tujuan Penelitian

   Berdasarkan rumusan masalah, tujuan yang dapat disimpulkan adalah:
   1.   Mahasiswa memahami konsep dari berbagai jenis feature engineering pada RStudio.
   2.   Mahasiswa dapat melakukan teknik feature engineering di program RStudio.

1.4 Manfaat Penelitian

   Adapun manfaat pada penelitian ini, yaitu: 
   1.   Bagi penulis
      a.    Mengembangkan dan menerapkan wawasan ilmu yang diperoleh dalam bidang statistika khususnya tentang feature                   engineering pada RStudio.
      b.    Dapat memberikan informasi tentang feature engineering pada RStudio.
   2.   Bagi pembaca
     a. Menambah ilmu pengetahuan dan pemahaman yang berkaitan tentang feature engineering pada RStudio.
     b. Menjadi rujukan bagi peneliti selanjutnya

1.5 Batasan Masalah

   Adapun batasan masalah pada laporan praktikum pertemuan ini adalah Gunakan dataset data curah hujan.xlsx untuk melakukan preprocessing data. Menggunakan mode imputation, stratify splitting, dan Standard Scaler. Melakukan interpolasi pada Keadaan_Cuaca, capping pada data numerik, serta penanganan imbalance data menggunakan SMOTE. Tampilkan dan interpretasikan setiap output!

BAB II TINJAUAN PUSTAKA

2.1 Feature Engineering

   Feature engineering merupakan proses mengolah fitur yang terdapat dalam suatu dataset agar menghasilkan representasi data yang lebih sesuai untuk digunakan dalam proses machine learning. Proses ini dapat dilakukan dengan melakukan transformasi terhadap fitur yang sudah tersedia, membuat fitur baru, mengekstraksi informasi dari fitur, maupun memilih fitur yang dianggap penting. Feature engineering merupakan bagian dari tahapan persiapan data yang dapat dilakukan sebelum data digunakan dalam pemodelan (Mumuni & Mumuni, 2025).
  Beberapa proses yang termasuk dalam feature engineering antara lain feature transformation, feature construction, feature extraction, dan feature selection. Pemilihan teknik bergantung pada karakteristik data serta tujuan pemodelan yang dilakukan. Feature engineering yang tepat dapat membantu menghasilkan data yang lebih informatif dan sesuai dengan algoritma yang digunakan (Rachmatullah, 2025; Mumuni & Mumuni, 2025).

2.2 Jenis-jenis Feature engineering

   Feature engineering memerlukan pemahaman yang baik karena melibatkan kombinasi analisis data, pengetahuan tentang data, dan sedikit intuisi. Tujuan dari feature engineering adalah menyederhanakan dan mempercepat transformasi data sekaligus meningkatkan akurasi model. kualitas fitur jauh lebih penting daripada seberapa canggih algoritma  yang dipilih. Berikut merupakan beberapa jenis feature engineering yang harus dilakukan (Fransiska, 2026): 
  1. Handling Missing Value Missing values atau missing data, terjadi ketika tidak ada data / tidak ada nilai yang tersimpan untuk observasi tertentu dalam suatu variabel. Missing data adalah kejadian yang umum dan dapat berdampak signifikan pada pemodelan machine learning. Data yang tidak lengkap adalah masalah yang tidak terhindarkan dalam menangani sebagian besar sumber data.
  2. Kardinalitas Kardinalitas merujuk pada jumlah nilai unik, atau label, yang terdapat dalam suatu variabel kategoris. Konsep ini digunakan untuk mengukur 1 granularitas atau keunikan data dalam sebuah fitur. Kardinalitas dapat diklasifikasikan dalam spektrum, namun umumnya dibedakan menjadi kardinalitas rendah (low cardinality) dan kardinalitas tinggi (high cardinality). Pengaruh kardinalitas, sangat krusial terhadap performa model dan efisiensi komputasi. Pertama, variabel high cardinality dapat menyebabkan curse of dimensionality jika ditransformasi menggunakan metode encoding standar seperti One-Hot Encoding. Metode ini akan menciptakan banyak fitur biner baru (sesuai jumlah label), membuat data menjadi sangat jarang dan secara drastis meningkatkan kompleksitas komputasi. Kedua, kardinalitas secara signifikan meningkatkan risiko overfitting.
  3. Splitting Data Splitting data merupakan strategi fundamental dalam machine learning untuk membagi himpunan data menjadi bagian terpisah: data training dan data testing. Pembagian ini krusial untuk membangun model yang robust dan dapat digeneralisasi dengan baik pada data baru (Muraina, 2022). Proporsi pembagian data bersifat subjektif dan fleksibel. Rasio yang umum digunakan adalah 80% untuk training dan 20% untuk testing (Woschnagg dan Cipan, 2004). Namun, seperti yang ditekankan oleh Géron (2019), rasio ini dapat disesuaikan tergantung pada ukuran dataset.
  4. Handling Outlier Outlier, atau dikenal sebagai pencilan, merupakan observasi atau titik data yang menunjukkan deviasi ekstrem dan berbeda secara signifikan dari sebagian besar data lain dalam suatu set data. Keberadaan outlier dapat diatribusikan ke berbagai sumber, mulai dari kesalahan pengukuran, kesalahan entri data (misinput), kontaminasi data, hingga representasi sah dari kejadian langka atau variabilitas inheren dalam populasi. Dalam analisis statistik dan pemodelan machine learning, outlier menuntut perhatian khusus karena memiliki potensi untuk memberikan pengaruh yang tidak proporsional (disproportionate influence) terhadap hasil.
  5. Scaling Data Scaling data adalah salah satu langkah pra-pemrosesan dalam analisis data untuk mentransformasi variabel data ke dalam rentang skala yang sama. Tujuannya adalah untuk memastikan bahwa tidak ada satu variabel independen pun yang mendominasi proses pembelajaran hanya karena memiliki rentang nilai yang lebih besar dibandingkan variabel independen lainnya. Sebagaimana dinyatakan oleh Singh dan Singh (2020), normalisasi data sering kali dapat meningkatkan performa model prediktif secara signifikan, terutama untuk algoritma yang sensitif terhadap skala input.
  6. Encoding Feature encoding adalah proses mengubah fitur kategoris atau nonnumerik menjadi format numerik yang dapat digunakan sebagai input untuk algoritma machine learning. Banyak algoritma machine learning membutuhkan input numerik. Ada beberapa teknik umum untuk feature encoding, seperti OneHot Encoding dan Ordinal Encoding.
  7. Imbalanced Dataset Imbalanced Dataset adalah sebuah dataset di mana distribusi kelas atau kategorinya tidak setara. Kelas-kelas yang mencakup proporsi besar dari set data disebut kelas mayoritas, sedangkan kelas-kelas yang mencakup proporsi lebih kecil disebut kelas minoritas. Dalam praktiknya, akan lebih sering menjumpai data yang tidak seimbang daripada yang seimbang. Hal ini tidak serta merta menjadi masalah jika variabel target hanya memiliki sedikit ketidakseimbangan. Sayangnya, kenyataannya tidak selalu demikian dan variabel target Anda mungkin sangat tidak seimbang, sebagai contoh, dengan rasio 10:1.

BAB III METODE PENELITIAN

3.1 Sumber Data

   Jenis data yang digunakan di dalam penelitian ini berupa data kuantitatif. Data kuantitatif merupakan data atau informasi yang didapatkan dalam bentuk angka atau numerik. Data yang digunakan dalam penelitian ini merupakan data sekunder yang diperoleh dari Asisten Praktikum Machine Learning and Modern Prediction.

3.2 Variabel Penelitian

   Variabel penelitian adalah karakter atau segala sesuatu yang menjadi perhatian dalam suatu penelitian. Pada penelitian ini terdapat 5 variabel. Variabel yang digunakan dalam praktikum ini meliputi hujan, suhu, kelembapan, keadaan cuaca dan kecepatan angin.

3.3 Langkah-langkah Analisis

   Berikut adalah algoritma untuk menyelesaiankan batasan masalah:
   1.Mulai.
   2.Menghitung distribusi kelas pada variabel target y_train.
   3.Menggabungkan data prediktor hasil encoding dengan variabel target Hujan.
   4.Mengubah variabel target Hujan menjadi tipe factor agar dapat digunakan dalam proses SMOTE.
   5.Menentukan seed untuk menjaga hasil proses tetap konsisten.
   6.Membuat recipe SMOTE dengan variabel Hujan sebagai target.
   7.Menentukan over_ratio = 1 dan jumlah tetangga (neighbors) sebanyak 5.
   8.Melakukan prep() berdasarkan data training.
   9.Menerapkan SMOTE menggunakan bake() untuk menghasilkan data training yang lebih seimbang.
  10.Menghasilkan train_balanced sebagai dataset hasil penanganan imbalanced dataset.
  11.Selesai.

BAB IV HASIL DAN PEMBAHASAN

4.1 Library

library(zoo)
## 
## Attaching package: 'zoo'
## The following objects are masked from 'package:base':
## 
##     as.Date, as.Date.numeric
library(readxl)
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.2.1     ✔ readr     2.2.0
## ✔ forcats   1.0.1     ✔ stringr   1.6.0
## ✔ ggplot2   4.0.3     ✔ tibble    3.3.1
## ✔ lubridate 1.9.5     ✔ tidyr     1.3.2
## ✔ purrr     1.2.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(rsample)
library(e1071)
## 
## Attaching package: 'e1071'
## 
## The following object is masked from 'package:rsample':
## 
##     permutations
## 
## The following object is masked from 'package:ggplot2':
## 
##     element
library(DescTools)
library(dplyr)
library(ggplot2)
library(gridExtra)
## 
## Attaching package: 'gridExtra'
## 
## The following object is masked from 'package:dplyr':
## 
##     combine
library(recipes)
## 
## Attaching package: 'recipes'
## 
## The following object is masked from 'package:stringr':
## 
##     fixed
## 
## The following object is masked from 'package:stats':
## 
##     step
library(themis)
library(caret)
## Loading required package: lattice
## 
## Attaching package: 'caret'
## 
## The following objects are masked from 'package:DescTools':
## 
##     MAE, RMSE
## 
## The following object is masked from 'package:rsample':
## 
##     calibration
## 
## The following object is masked from 'package:purrr':
## 
##     lift
data <- read_excel("C:/Users/LENOVO/Downloads/data curah hujan (1).xlsx")
# Melihat 6 baris pertama
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
# Melihat struktur data
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 ...
# Melihat ukuran data
dim(data)
## [1] 743   5
# Melihat nama variabel
names(data)
## [1] "Hujan"           "Suhu"            "Kelembapan"      "Keadaan_Cuaca"  
## [5] "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

Data terdiri atas 743 observasi dan 5 variabel, yaitu Hujan, Suhu, Kelembapan, Keadaan_Cuaca, dan Kecepatan_Angin. Variabel Hujan digunakan sebagai target klasifikasi, sedangkan variabel lainnya digunakan sebagai prediktor. Statistik deskriptif menunjukkan bahwa Suhu memiliki rata-rata 26,54°C dan median 26°C, sedangkan Kelembapan memiliki rata-rata 83,88% dan median 86%. Kecepatan_Angin memiliki rata-rata 6,655 dan median 6. Pada variabel Hujan, rata-rata sebesar 1,779 menunjukkan bahwa kelas 2 lebih banyak ditemukan daripada kelas 1. Selain itu, Keadaan_Cuaca memiliki rentang nilai 1–97, sehingga makna dan pengkodean variabel tersebut perlu diperiksa.

4.2 Handling Missing Value

# 3. CEK MISSING VALUE
# Jumlah missing value setiap variabel
colSums(is.na(data))
##           Hujan            Suhu      Kelembapan   Keadaan_Cuaca Kecepatan_Angin 
##               0               0               0               9             314
# Proporsi missing value
colMeans(is.na(data))
##           Hujan            Suhu      Kelembapan   Keadaan_Cuaca Kecepatan_Angin 
##      0.00000000      0.00000000      0.00000000      0.01211306      0.42261104
# Persentase missing value
colMeans(is.na(data)) * 100
##           Hujan            Suhu      Kelembapan   Keadaan_Cuaca Kecepatan_Angin 
##        0.000000        0.000000        0.000000        1.211306       42.261104
# Melihat baris yang memiliki missing value pada
# variabel Keadaan_Cuaca
data %>%
  filter(is.na(Keadaan_Cuaca))
## # 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
# 4. PENANGANAN MISSING VALUE
# 4.1 Drop Missing Value
data_dropna <- data %>%
  drop_na()

cat("Jumlah baris sebelum drop NA :", nrow(data), "\n")
## Jumlah baris sebelum drop NA : 743
cat("Jumlah baris setelah drop NA :", nrow(data_dropna), "\n")
## Jumlah baris setelah drop NA : 424
colSums(is.na(data_dropna))
##           Hujan            Suhu      Kelembapan   Keadaan_Cuaca Kecepatan_Angin 
##               0               0               0               0               0
# 4.2 Forward Fill
df_imp1 <- data %>%
  fill(Keadaan_Cuaca, .direction = "down")

cat("\nMissing value setelah Forward Fill:\n")
## 
## Missing value setelah Forward Fill:
print(colSums(is.na(df_imp1)))
##           Hujan            Suhu      Kelembapan   Keadaan_Cuaca Kecepatan_Angin 
##               0               0               0               0             314
# 4.3 Mean Imputation
df_imp2 <- data

df_imp2$Kecepatan_Angin[
  is.na(df_imp2$Kecepatan_Angin)
] <- mean(
  df_imp2$Kecepatan_Angin,
  na.rm = TRUE
)

cat("\nMissing value setelah Mean Imputation:\n")
## 
## Missing value setelah Mean Imputation:
print(colSums(is.na(df_imp2)))
##           Hujan            Suhu      Kelembapan   Keadaan_Cuaca Kecepatan_Angin 
##               0               0               0               9               0
# 4.4 Median Imputation
df_imp3 <- data

df_imp3$Kecepatan_Angin[
  is.na(df_imp3$Kecepatan_Angin)
] <- median(
  df_imp3$Kecepatan_Angin,
  na.rm = TRUE
)

cat("\nMissing value setelah Median Imputation:\n")
## 
## Missing value setelah Median Imputation:
print(colSums(is.na(df_imp3)))
##           Hujan            Suhu      Kelembapan   Keadaan_Cuaca Kecepatan_Angin 
##               0               0               0               9               0
# 4.5 Mode Imputation
df_imp5 <- data

# Mencari modus
modus <- names(
  sort(
    table(df_imp5$Kecepatan_Angin),
    decreasing = TRUE
  )
)[1]

cat("\nModus Kecepatan_Angin :", modus, "\n")
## 
## Modus Kecepatan_Angin : 3
# Mengganti missing value dengan modus
df_imp5$Kecepatan_Angin[
  is.na(df_imp5$Kecepatan_Angin)
] <- as.numeric(modus)

cat("\nMissing value setelah Mode Imputation:\n")
## 
## Missing value setelah Mode Imputation:
print(colSums(is.na(df_imp5)))
##           Hujan            Suhu      Kelembapan   Keadaan_Cuaca Kecepatan_Angin 
##               0               0               0               9               0
# 5. INTERPOLASI
df_imp4 <- data

# Interpolasi Keadaan_Cuaca
df_imp4$Keadaan_Cuaca <- na.approx(
  df_imp4$Keadaan_Cuaca,
  na.rm = FALSE
)

cat("\nMissing value setelah interpolasi:\n")
## 
## Missing value setelah interpolasi:
print(colSums(is.na(df_imp4)))
##           Hujan            Suhu      Kelembapan   Keadaan_Cuaca Kecepatan_Angin 
##               0               0               0               0             314
# Median Imputation untuk Kecepatan_Angin

median_value <- median(
  df_imp4$Kecepatan_Angin,
  na.rm = TRUE
)

df_imp4$Kecepatan_Angin[
  is.na(df_imp4$Kecepatan_Angin)
] <- median_value

cat("\nMissing value setelah Median Imputation:\n")
## 
## Missing value setelah Median Imputation:
print(colSums(is.na(df_imp4)))
##           Hujan            Suhu      Kelembapan   Keadaan_Cuaca Kecepatan_Angin 
##               0               0               0               0               0
# Melihat data setelah preprocessing missing value
head(df_imp4, 10)
## # A tibble: 10 × 5
##    Hujan  Suhu Kelembapan Keadaan_Cuaca Kecepatan_Angin
##    <dbl> <dbl>      <dbl>         <dbl>           <dbl>
##  1     1  23           95             5               6
##  2     1  24           90             1               6
##  3     1  26.8         77             1               6
##  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

Pemeriksaan jumlah dan persentase missing value, kemudian dibandingkan beberapa metode penanganannya, yaitu penghapusan baris, forward fill, imputasi mean, median, modus, dan interpolasi. Hasil pemeriksaan menunjukkan bahwa Keadaan_Cuaca memiliki 9 nilai hilang (1,21%), sedangkan Kecepatan_Angin memiliki 314 nilai hilang (42,26%). Pada data akhir, nilai hilang Keadaan_Cuaca diisi menggunakan interpolasi, sedangkan Kecepatan_Angin diisi menggunakan median. Hasilnya, seluruh 743 observasi berhasil dipertahankan tanpa missing value. Namun, interpolasi pada Keadaan_Cuaca perlu dipastikan sesuai dengan makna variabelnya, terutama jika nilai tersebut merupakan kode kategori cuaca.

4.3 Kardinalitas

# 6.KARDINALITAS
# Melihat nilai unik Keadaan_Cuaca
cat("\nNilai unik Keadaan_Cuaca:\n")
## 
## Nilai unik Keadaan_Cuaca:
print(unique(df_imp4$Keadaan_Cuaca))
##  [1]  5.0  1.0  2.0  3.0 14.0 60.0 61.0 21.0 29.0 10.0 16.0 62.0 63.0 65.0 15.0
## [16] 17.0 13.0 95.0 91.0 49.0 97.0 56.0  1.5
# Menentukan batas bin
bins <- c(
  0, 10, 20, 30, 40,
  50, 60, 70, 80, 90, 100
)

# Menentukan label
labels <- 0:9

# Melakukan binning
df_imp4$Keadaan_Cuaca_reduced <- cut(
  df_imp4$Keadaan_Cuaca,
  breaks = bins,
  labels = labels,
  right = TRUE,
  include.lowest = TRUE
)

# Melihat hasil reduksi kardinalitas
cat("\n--- HASIL REDUKSI KARDINALITAS ---\n")
## 
## --- HASIL REDUKSI KARDINALITAS ---
print(
  df_imp4[
    sample(
      nrow(df_imp4),
      min(10, nrow(df_imp4))
    ),
  ]
)
## # A tibble: 10 × 6
##    Hujan  Suhu Kelembapan Keadaan_Cuaca Kecepatan_Angin Keadaan_Cuaca_reduced
##    <dbl> <dbl>      <dbl>         <dbl>           <dbl> <fct>                
##  1     1  23.2         97            61               4 6                    
##  2     2  24.6         91             2               2 0                    
##  3     2  29.2         72             2               8 0                    
##  4     2  29.2         74             2               9 0                    
##  5     1  23.7         98            60               6 5                    
##  6     2  23.6         96            61               6 6                    
##  7     2  23.6         96            17               6 1                    
##  8     2  29.3         70             3               9 0                    
##  9     1  23.8         97            65               9 6                    
## 10     2  24.2         94             2               6 0
# Jumlah kategori sebelum
cat(
  "\nJumlah kategori Keadaan_Cuaca asli :",
  length(unique(df_imp4$Keadaan_Cuaca)),
  "\n"
)
## 
## Jumlah kategori Keadaan_Cuaca asli : 23
# Jumlah kategori setelah
cat(
  "Jumlah kategori Keadaan_Cuaca_reduced :",
  length(unique(df_imp4$Keadaan_Cuaca_reduced)),
  "\n"
)
## Jumlah kategori Keadaan_Cuaca_reduced : 7
# Kategori baru
cat("\nKategori unik setelah reduksi:\n")
## 
## Kategori unik setelah reduksi:
print(unique(df_imp4$Keadaan_Cuaca_reduced))
## [1] 0 1 5 6 2 9 4
## Levels: 0 1 2 3 4 5 6 7 8 9

Reduksi kardinalitas dilakukan dengan mengelompokkan nilai Keadaan_Cuaca ke dalam interval 0–100 menggunakan metode binning. Hasilnya, terdapat 7 kategori yang teramati dari sebelumnya 23 nilai unik. Pengelompokan ini menyederhanakan variasi nilai, tetapi perlu disesuaikan dengan arti kode cuaca agar kategori yang berbeda tidak tergabung secara keliru.

4.4 Splitting Data

set.seed(200)
# Membagi data 80% training dan 20% testing
# dengan stratifikasi berdasarkan Hujan
split_stratify <- initial_split(
  df_imp4,
  prop = 0.8,
  strata = Hujan
)

# Data training
train_data <- training(split_stratify)

# Data testing
test_data <- testing(split_stratify)


# Menampilkan ukuran data
cat("\n--- UKURAN DATA ---\n")
## 
## --- UKURAN DATA ---
cat(
  "Jumlah data awal   :",
  nrow(df_imp4),
  "\n"
)
## Jumlah data awal   : 743
cat(
  "Jumlah data training :",
  nrow(train_data),
  "\n"
)
## Jumlah data training : 594
cat(
  "Jumlah data testing  :",
  nrow(test_data),
  "\n"
)
## Jumlah data testing  : 149
# Melihat proporsi target
cat("\n--- PROPORSI TARGET DATA AWAL ---\n")
## 
## --- PROPORSI TARGET DATA AWAL ---
print(prop.table(table(df_imp4$Hujan)))
## 
##         1         2 
## 0.2207268 0.7792732
cat("\n--- PROPORSI TARGET TRAINING ---\n")
## 
## --- PROPORSI TARGET TRAINING ---
print(prop.table(table(train_data$Hujan)))
## 
##         1         2 
## 0.2205387 0.7794613
cat("\n--- PROPORSI TARGET TESTING ---\n")
## 
## --- PROPORSI TARGET TESTING ---
print(prop.table(table(test_data$Hujan)))
## 
##         1         2 
## 0.2214765 0.7785235
# 8. MEMISAHKAN X DAN Y
X_train <- train_data %>%
  select(-Hujan)

X_test <- test_data %>%
  select(-Hujan)
y_train <- train_data$Hujan
y_test <- test_data$Hujan

# Melihat dimensi
cat("\nDimensi X_train :")
## 
## Dimensi X_train :
print(dim(X_train))
## [1] 594   5
cat("\nDimensi X_test :")
## 
## Dimensi X_test :
print(dim(X_test))
## [1] 149   5
# 9. IDENTIFIKASI DATA NUMERIK
list_num <- c(
  "Suhu",
  "Kelembapan",
  "Kecepatan_Angin"
)

cat("\nVariabel numerik yang digunakan:\n")
## 
## Variabel numerik yang digunakan:
print(list_num)
## [1] "Suhu"            "Kelembapan"      "Kecepatan_Angin"

Pembagian data dilakukan dengan proporsi 80% untuk training dan 20% untuk testing menggunakan stratifikasi berdasarkan Hujan. Hasilnya, diperoleh 594 observasi training dan 149 observasi testing. Proporsi kelas 1 dan 2 tetap mendekati distribusi data awal, sehingga pembagian data tidak mengubah keseimbangan relatif kedua kelas secara berarti.

4.5 Handling Outlier

## Handling Outlier
list_outlier <- c()
list_lower_bound <- c()
list_upper_bound <- c()

for (i in list_num) {
  
  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
  
  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
  )
  
  total_outliers <-
    num_outliers_lower +
    num_outliers_upper
  
  list_outlier <- c(
    list_outlier,
    total_outliers
  )
}


# Membuat tabel outlier
outliers <- data.frame(
  Kolom = list_num,
  Jumlah_Outlier = list_outlier,
  Lower_Bound = list_lower_bound,
  Upper_Bound = list_upper_bound
)

cat("\n--- HASIL IDENTIFIKASI OUTLIER ---\n")
## 
## --- HASIL IDENTIFIKASI OUTLIER ---
print(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            106      2.6250      9.6250

handling outlier atau identifikasi pencilan dilakukan menggunakan metode Interquartile Range (IQR) dengan menentukan batas bawah dan batas atas pada setiap variabel numerik. Berdasarkan hasil analisis dan plot boxplot, variabel Suhu memiliki batas IQR sebesar 17,4625–35,7625 dan Kelembapan sebesar 46,5–122,5, tetapi keduanya tidak menunjukkan adanya outlier. Sementara itu, variabel Kecepatan_Angin memiliki 106 outlier dengan batas bawah 2,625 dan batas atas 9,625, yang terlihat sebagai titik-titik di luar whisker pada boxplot. Oleh karena itu, penanganan outlier dilakukan menggunakan metode capping atau Winsorization, yaitu membatasi nilai yang berada di luar batas IQR tanpa menghapus data. Hasilnya, nilai ekstrem pada Kecepatan_Angin dibatasi sehingga pengaruh outlier terhadap analisis selanjutnya dapat dikurangi, sedangkan data Suhu dan Kelembapan relatif tidak mengalami perubahan.

4.6 Caping

X_train_capped <- X_train
X_test_capped <- X_test


# Melakukan capping untuk setiap variabel numerik
for (i in seq_along(list_num)) {
  
  col <- list_num[i]
  
  lower <- list_lower_bound[i]
  upper <- list_upper_bound[i]
  
  # Capping training
  X_train_capped[[col]] <- Winsorize(
    X_train[[col]],
    val = c(lower, upper)
  )
  
  # Capping testing
  X_test_capped[[col]] <- Winsorize(
    X_test[[col]],
    val = c(lower, upper)
  )
}


# Melihat ringkasan data setelah capping
cat("\n--- DATA SETELAH CAPPING ---\n")
## 
## --- DATA SETELAH CAPPING ---
summary(X_train_capped[list_num])
##       Suhu         Kelembapan     Kecepatan_Angin
##  Min.   :22.60   Min.   : 52.00   Min.   :2.625  
##  1st Qu.:24.32   1st Qu.: 75.00   1st Qu.:5.250  
##  Median :26.00   Median : 86.00   Median :6.000  
##  Mean   :26.56   Mean   : 83.76   Mean   :6.170  
##  3rd Qu.:28.90   3rd Qu.: 94.00   3rd Qu.:7.000  
##  Max.   :32.00   Max.   :100.00   Max.   :9.625
# ============================================================
# 13. 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
  )
}


# Visualisasi setiap variabel
for (col in list_num) {
  
  cat(
    "\n",
    col,
    "- SEBELUM CAPPING\n"
  )
  
  diagnostic_plots(
    X_train,
    col,
    "Sebelum Capping"
  )
  
  cat(
    "\n",
    col,
    "- SETELAH CAPPING\n"
  )
  
  diagnostic_plots(
    X_train_capped,
    col,
    "Setelah Capping"
  )
}
## 
##  Suhu - SEBELUM CAPPING

## 
##  Suhu - SETELAH CAPPING

## 
##  Kelembapan - SEBELUM CAPPING

## 
##  Kelembapan - SETELAH CAPPING

## 
##  Kecepatan_Angin - SEBELUM CAPPING

## 
##  Kecepatan_Angin - SETELAH CAPPING

Berdasarkan hasil penanganan outlier menggunakan fungsi Winsorize() dengan membatasi nilai berdasarkan batas bawah dan batas atas IQR. Berdasarkan plot histogram dan boxplot sebelum capping, variabel Suhu menunjukkan sebaran data yang terkonsentrasi pada beberapa rentang suhu, sedangkan Kelembapan didominasi oleh nilai kelembapan yang relatif tinggi. Kedua variabel tersebut tidak memiliki outlier berdasarkan metode IQR sehingga bentuk distribusinya relatif tidak berubah setelah capping. Sementara itu, plot Kecepatan_Angin menunjukkan beberapa titik di luar whisker boxplot yang menandakan adanya outlier. Setelah capping, nilai Kecepatan_Angin dibatasi pada rentang 2,625–9,625 sehingga titik ekstrem tidak lagi berada di luar batas tersebut. Dengan demikian, capping terutama memengaruhi variabel Kecepatan_Angin dengan membatasi nilai ekstrem tanpa menghapus observasi dari data.

4.7 Scalling Data

nilai_skew <- c()
nilai_skew_normal <- c()

for (i in list_num) {
  
  skew_val <- skewness(
    X_train_capped[[i]],
    na.rm = TRUE
  )
  
  if (
    skew_val >= -0.5 &&
    skew_val <= 0.5
  ) {
    
    nilai_skew_normal <-
      c(nilai_skew_normal, i)
    
  } else {
    
    nilai_skew <-
      c(nilai_skew, i)
  }
}


cat(
  "\nKolom untuk Standard Scaler:\n"
)
## 
## Kolom untuk Standard Scaler:
print(nilai_skew_normal)
## [1] "Suhu"            "Kecepatan_Angin"
cat(
  "\nKolom dengan skewness tinggi:\n"
)
## 
## Kolom dengan skewness tinggi:
print(nilai_skew)
## [1] "Kelembapan"
# ------------------------------------------------------------
# Standard Scaler
# ------------------------------------------------------------

mean_val <- sapply(
  X_train_capped[nilai_skew_normal],
  mean,
  na.rm = TRUE
)

sd_val <- sapply(
  X_train_capped[nilai_skew_normal],
  sd,
  na.rm = TRUE
)


for (col in nilai_skew_normal) {
  
  X_train_capped[[col]] <-
    (
      X_train_capped[[col]] -
        mean_val[col]
    ) /
    sd_val[col]
  
  X_test_capped[[col]] <-
    (
      X_test_capped[[col]] -
        mean_val[col]
    ) /
    sd_val[col]
}


# ------------------------------------------------------------
# Robust Scaler untuk variabel skewed
# ------------------------------------------------------------

if (length(nilai_skew) > 0) {
  
  median_val <- sapply(
    X_train_capped[nilai_skew],
    median,
    na.rm = TRUE
  )
  
  iqr_val <- sapply(
    X_train_capped[nilai_skew],
    IQR,
    na.rm = TRUE
  )
  
  for (col in nilai_skew) {
    
    X_train_capped[[col]] <-
      (
        X_train_capped[[col]] -
          median_val[col]
      ) /
      iqr_val[col]
    
    X_test_capped[[col]] <-
      (
        X_test_capped[[col]] -
          median_val[col]
      ) /
      iqr_val[col]
  }
}


# Menyimpan hasil scaling
X_train_scale <- X_train_capped
X_test_scale <- X_test_capped


# Melihat hasil scaling
cat("\n--- HASIL STANDARD SCALER ---\n")
## 
## --- HASIL STANDARD SCALER ---
print(
  head(
    X_train_scale[list_num]
  )
)
## # A tibble: 6 × 3
##      Suhu Kelembapan Kecepatan_Angin
##     <dbl>      <dbl>           <dbl>
## 1 -1.39        0.474         -0.0874
## 2 -1.00        0.211         -0.0874
## 3  0.0947     -0.474         -0.0874
## 4  1.19       -1.26          -1.82  
## 5  1.66       -1.58           0.425 
## 6  1.50       -1.53           1.45
# Mengecek mean
cat("\nMean setelah scaling:\n")
## 
## Mean setelah scaling:
print(
  sapply(
    X_train_scale[nilai_skew_normal],
    mean,
    na.rm = TRUE
  )
)
##            Suhu Kecepatan_Angin 
##   -7.045562e-17   -2.388123e-16
# Mengecek standar deviasi
cat("\nStandar deviasi setelah scaling:\n")
## 
## Standar deviasi setelah scaling:
print(
  sapply(
    X_train_scale[nilai_skew_normal],
    sd,
    na.rm = TRUE
  )
)
##            Suhu Kecepatan_Angin 
##               1               1

Pada tahap scaling data, variabel Suhu dan Kecepatan_Angin ditransformasikan menggunakan metode Standard Scaling, sedangkan variabel Kelembapan menggunakan median dan Interquartile Range (IQR). Proses ini bertujuan menyamakan skala data agar perbedaan rentang nilai antarvariabel tidak terlalu memengaruhi pemodelan. Hasil scaling menunjukkan bahwa Suhu dan Kecepatan_Angin memiliki rata-rata mendekati nol dan standar deviasi mendekati satu. Parameter scaling dihitung dari data latih, kemudian diterapkan pada data uji agar transformasi tetap konsisten.

4.8 Encoding

X_train_scale$Keadaan_Cuaca_reduced <-
  as.factor(
    X_train_scale$Keadaan_Cuaca_reduced
  )

X_test_scale$Keadaan_Cuaca_reduced <-
  as.factor(
    X_test_scale$Keadaan_Cuaca_reduced
  )


# Variabel kategorik
list_cat <- c(
  "Keadaan_Cuaca_reduced"
)


# Membuat recipe One Hot Encoding
resep_encode <- recipe(
  ~ Keadaan_Cuaca_reduced,
  data = X_train_scale
) %>%
  step_dummy(
    all_of(list_cat),
    one_hot = TRUE
  ) %>%
  prep(
    training = X_train_scale
  )


# Melakukan encoding
X_train_encoded <- bake(
  resep_encode,
  new_data = X_train_scale
)

X_test_encoded <- bake(
  resep_encode,
  new_data = X_test_scale
)


# Melihat hasil encoding
cat("\n--- HASIL ONE HOT ENCODING TRAINING ---\n")
## 
## --- HASIL ONE HOT ENCODING TRAINING ---
print(
  head(X_train_encoded)
)
## # A tibble: 6 × 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 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>
cat("\n--- HASIL ONE HOT ENCODING TESTING ---\n")
## 
## --- HASIL ONE HOT ENCODING TESTING ---
print(
  head(X_test_encoded)
)
## # A tibble: 6 × 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 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>

Berdasarkan hasil, variabel kategorik Keadaan_Cuaca_reduced diubah menjadi beberapa variabel biner menggunakan metode One-Hot Encoding. Berdasarkan hasil pengolahan, terbentuk 10 variabel dummy, yaitu X0 hingga X9, yang mewakili kategori cuaca. Setiap variabel dummy menunjukkan ada atau tidaknya suatu kategori pada observasi, sehingga data kategorik dapat digunakan dalam proses pemodelan. Dengan demikian, encoding membantu mengubah informasi cuaca menjadi format numerik tanpa memberikan urutan tertentu pada setiap kategori.

4.9 Balancing data dengan SMOTE

# Melihat distribusi kelas sebelum SMOTE
cat("\n--- DISTRIBUSI KELAS SEBELUM SMOTE ---\n")
## 
## --- DISTRIBUSI KELAS SEBELUM SMOTE ---
print(table(y_train))
## y_train
##   1   2 
## 131 463
cat("\nProporsi kelas sebelum SMOTE:\n")
## 
## Proporsi kelas sebelum SMOTE:
print(prop.table(table(y_train)))
## y_train
##         1         2 
## 0.2205387 0.7794613
# Menggabungkan X dan y
train_full <- X_train_encoded %>%
  mutate(
    Hujan = y_train
  )

# Target harus berupa factor
train_full$Hujan <-
  factor(train_full$Hujan)

# SMOTE
set.seed(42)

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

# Prep recipe
resep_smote_prep <- prep(
  resep_smote,
  training = train_full
)

# Melakukan SMOTE
train_balanced <- bake(
  resep_smote_prep,
  new_data = NULL
)

# Memisahkan kembali X dan y
X_train_balanced <-
  train_balanced %>%
  select(-Hujan)

y_train_balanced <-
  train_balanced$Hujan

# HASIL SETELAH SMOTE
cat("\n--- DISTRIBUSI KELAS SETELAH SMOTE ---\n")
## 
## --- DISTRIBUSI KELAS SETELAH SMOTE ---
print(
  table(y_train_balanced)
)
## y_train_balanced
##   1   2 
## 463 463
cat("\nProporsi kelas setelah SMOTE:\n")
## 
## Proporsi kelas setelah SMOTE:
print(
  prop.table(
    table(y_train_balanced)
  )
)
## y_train_balanced
##   1   2 
## 0.5 0.5
# 18. VISUALISASI SEBELUM DAN SESUDAH SMOTE
par(mfrow = c(1, 2))
barplot(
  table(y_train),
  main = "Sebelum SMOTE",
  xlab = "Kelas Hujan",
  ylab = "Frekuensi"
)

barplot(
  table(y_train_balanced),
  main = "Setelah SMOTE",
  xlab = "Kelas Hujan",
  ylab = "Frekuensi"
)

par(mfrow = c(1, 1))

# 19. HASIL AKHIR
cat("\n============================================\n")
## 
## ============================================
cat("        HASIL AKHIR PREPROCESSING\n")
##         HASIL AKHIR PREPROCESSING
cat("============================================\n")
## ============================================
cat(
  "Jumlah data awal       :",
  nrow(data),
  "\n"
)
## Jumlah data awal       : 743
cat(
  "Data training awal     :",
  nrow(X_train),
  "\n"
)
## Data training awal     : 594
cat(
  "Data testing           :",
  nrow(X_test),
  "\n"
)
## Data testing           : 149
cat(
  "Data training setelah SMOTE :",
  nrow(X_train_balanced),
  "\n"
)
## Data training setelah SMOTE : 926
cat(
  "Jumlah fitur setelah encoding :",
  ncol(X_train_encoded),
  "\n"
)
## Jumlah fitur setelah encoding : 10
cat("\nDistribusi kelas sebelum SMOTE:\n")
## 
## Distribusi kelas sebelum SMOTE:
print(table(y_train))
## y_train
##   1   2 
## 131 463
cat("\nDistribusi kelas setelah SMOTE:\n")
## 
## Distribusi kelas setelah SMOTE:
print(table(y_train_balanced))
## y_train_balanced
##   1   2 
## 463 463

Dilakukan penyeimbangan kelas variabel target Hujan menggunakan metode Synthetic Minority Over-sampling Technique (SMOTE). Sebelum balancing, kelas 1 berjumlah 131 observasi, sedangkan kelas 2 berjumlah 463 observasi, sehingga distribusi kelas tidak seimbang. Setelah SMOTE diterapkan dengan over_ratio = 1 dan neighbors = 5, jumlah masing-masing kelas menjadi 463 observasi, dengan total 926 observasi pada data latih. Hal ini terlihat pada plot setelah balancing, ketika jumlah kedua kelas menjadi sama. Proses ini bertujuan mengurangi ketidakseimbangan kelas agar model dapat mempelajari kedua kelas dengan lebih baik. Balancing hanya diterapkan pada data latih, sedangkan data uji tetap dipertahankan agar evaluasi model lebih objektif.

BAB V KESIMPULAN

5.1 Kesimpulan

Berdasarkan hasil dapat disimpulkan bahwa data curah hujan yang terdiri dari 743 observasi dan 5 variabel telah melalui proses prapemrosesan, mulai dari analisis deskriptif, penanganan missing value, pengurangan kardinalitas, pembagian data, penanganan outlier, scaling, encoding, hingga balancing menggunakan SMOTE. Penanganan missing value dilakukan dengan metode yang dipilih untuk setiap variabel, sedangkan pengurangan kardinalitas bertujuan menyederhanakan kategori cuaca. Selanjutnya, analisis outlier menunjukkan bahwa variabel Kecepatan_Angin memiliki 106 pencilan yang ditangani menggunakan metode capping. Proses scaling dilakukan untuk menyesuaikan skala variabel numerik, sementara encoding mengubah kategori cuaca menjadi 10 variabel dummy. Pada tahap balancing, SMOTE menyeimbangkan jumlah kelas pada data latih dari 131 dan 463 observasi menjadi masing-masing 463 observasi. Secara keseluruhan, rangkaian prapemrosesan ini menghasilkan data yang lebih terstruktur dan siap digunakan untuk tahap pemodelan serta evaluasi kinerja model.

DAFTAR PUSTAKA

Fransiska, H. (2026). Modul Praktikum Machine Learning and Modern Prediction. Laboratorium Matematika Fakultas Matematika dan Ilmu Pengetahuan Alam Universitas Bengkulu. 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. Rachmatullah, M. I. C. (2025). Ekstraksi Fitur pada Machine Learning: Tinjauan Konsep, Metode, Tren Representasi Modern, dan Arah Riset. Jurnal Improve, 17(1). Vasques, X. (2024). Feature Engineering Techniques in Machine Learning. Wiley.