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).
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?
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.
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.
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.
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.
Imputasi statistik mengganti nilai yang hilang dengan ukuran pemusatan data yang tersedia (Zeileis & Grothendieck, 2005):
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.
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).
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.
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.
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.
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.
Setelah terdeteksi, outlier dapat ditangani dengan beberapa cara:
Aguinis dkk. (2013) menekankan bahwa keputusan penanganan sebaiknya mempertimbangkan jenis outlier, karena nilai ekstrem yang sah tidak selalu layak dihapus.
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.
Pada ketiga metode, parameter (rata-rata, simpangan baku, median, IQR, minimum, maksimum) dihitung dari data latih lalu diterapkan pada data latih maupun data uji.
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:
| Warna | Merah | Kuning | Hijau |
|---|---|---|---|
| Merah | 1 | 0 | 0 |
| Merah | 1 | 0 | 0 |
| Kuning | 0 | 1 | 0 |
| Hijau | 0 | 0 | 1 |
| Kuning | 0 | 1 | 0 |
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:
\[ 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.
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.
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
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.
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")| 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 |
## 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 ...
## [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.
## 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")| 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.
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")| 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")| 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.
## Jumlah baris sebelum drop_na : 743
## Jumlah baris sesudah drop_na : 424
## 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.
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"
)| Kecepatan_Angin | Frekuensi |
|---|---|
| 3 | 57 |
| 5 | 55 |
| 8 | 47 |
| 6 | 40 |
| 4 | 39 |
## Nilai modus Kecepatan_Angin : 3
## 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.
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"
)| 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 |
## 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.
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.
## [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) ---
| 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 |
##
## --- Pengecekan Kardinalitas ---
## 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
##
## Frekuensi tiap kategori (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.
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
## 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")| 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.
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")| 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.
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")| 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 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")| 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.
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.
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)")| 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")| 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")| 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.
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
## Dimensi X_test_encoded : 149 10
knitr::kable(head(X_train_encoded), align = "c", caption = "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.
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.
## Distribusi kelas data latih SEBELUM SMOTE:
## 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:
## y_train_balanced
## 1 2
## 463 463
##
## Dimensi X_train_balanced : 926 13
## Distribusi kelas data uji (tidak di-SMOTE):
## 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.
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.
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