Halo temen-temen, jadi dikesempatan kali ini aku mau berbagi pengalaman mempraktikkan data preprocessing memakai R dan RStudio.
Nah ceritanya begini: aku berperan sebagai analis data di sebuah perusahaan e-commerce fiktif dan aku punya dua sumber data:
Masalahnya, kedua data ini masih “kotor”. Ada nilai yang hilang, penulisan kategori yang tidak konsisten, data yang terduplikasi, angka yang kelihatan aneh (outlier), sampai nama kolom identifier yang berbeda antara satu tabel dengan tabel lainnya. Semua itu harus aku beresin dulu sebelum datanya layak dianalisis.
Okeh langsung, ikuti langkah-langkahnya bareng yah.
Sebelum mulai ngoding, aku menyiapkan dulu proyeknya di RStudio:
praktikum_preprocessing.## [1] "D:/SEMESTER 5/DATA MINING"
## [1] "R version 4.4.2 (2024-10-31 ucrt)"
Karena ini latihan, aku sengaja membuat datanya sendiri dan membuatnya kotor — supaya aku bisa mempraktikkan semua teknik pembersihan data dari awal sampai akhir.
pelanggan_raw <- data.frame(
customer_id = c("C001", "C002", "C003", "C004", "C005", "C006",
"C007", "C008", "C009", "C010", "C010", "C011"),
nama = c("Ani", "Budi", "Citra", "Dodi", "Eka", "Fani",
"Gilang", "Hana", "Indra", "Joko", "Joko", "Kiki"),
usia = c(21, 25, 23, 150, 27, NA, 31, 29, 22, 35, 35, 28),
pendapatan = c(4500000, NA, 5200000, 4800000, 4900000, 5100000,
500000000, 4700000, 4600000, 5300000, 5300000, NA),
kota = c("Pekanbaru", " PKU", "PEKANBARU", "Dumai", "pekanbaru",
"DUMAI", "Pekanbaru ", "Siak", "PKU", "Dumai", "Dumai", NA),
status = c("Aktif", "aktif", "ACTIVE", "A", "Tidak Aktif", "nonaktif",
"Aktif", "AKTIF", "A", "Tidak aktif", "Tidak aktif", "Aktif"),
stringsAsFactors = FALSE
)
transaksi_raw <- data.frame(
cust_id = c("C001", "C002", "C003", "C004", "C005", "C006",
"C007", "C008", "C009", "C010", "C012"),
jumlah_transaksi = c(5, 3, 7, 2, 6, 4, 20, 5, 3, 8, 1),
total_purchase = c(1500000, 900000, 2700000, 600000, 2100000, 1300000,
25000000, 1700000, 800000, 3200000, 250000),
stringsAsFactors = FALSE
)
pelanggan_rawCoba perhatikan baik-baik data di atas. Aku sengaja menyelipkan beberapa “jebakan” supaya temen-temen bisa lihat sendiri kenapa data ini belum siap dianalisis:
150 tahun yang jelas tidak masuk akal.500000000 yang jauh di atas pendapatan
pelanggan lain."PKU", " PKU", "pekanbaru",
"PEKANBARU", "Pekanbaru "."Aktif",
"aktif", "ACTIVE", "A".C010 muncul dua
kali).NA).C011 tidak punya transaksi, dan
C012 di data transaksi tidak punya data pelanggan.Sebelum aku ubah apa pun, aku selalu memeriksa dulu bentuk datanya: berapa baris, berapa kolom, tipe datanya apa saja, dan seperti apa isinya.
## [1] 12 6
## [1] "customer_id" "nama" "usia" "pendapatan" "kota"
## [6] "status"
## 'data.frame': 12 obs. of 6 variables:
## $ customer_id: chr "C001" "C002" "C003" "C004" ...
## $ nama : chr "Ani" "Budi" "Citra" "Dodi" ...
## $ usia : num 21 25 23 150 27 NA 31 29 22 35 ...
## $ pendapatan : num 4.5e+06 NA 5.2e+06 4.8e+06 4.9e+06 5.1e+06 5.0e+08 4.7e+06 4.6e+06 5.3e+06 ...
## $ kota : chr "Pekanbaru" " PKU" "PEKANBARU" "Dumai" ...
## $ status : chr "Aktif" "aktif" "ACTIVE" "A" ...
## customer_id nama usia pendapatan
## Length:12 Length:12 Min. : 21.00 Min. : 4500000
## Class :character Class :character 1st Qu.: 24.00 1st Qu.: 4725000
## Mode :character Mode :character Median : 28.00 Median : 5000000
## Mean : 38.73 Mean : 54440000
## 3rd Qu.: 33.00 3rd Qu.: 5275000
## Max. :150.00 Max. :500000000
## NA's :1 NA's :2
## kota status
## Length:12 Length:12
## Class :character Class :character
## Mode :character Mode :character
##
##
##
##
Menurut Han, Kamber, dan Pei (2012) kalau kualitas data itu bisa dilihat dari enam dimensi: accuracy, completeness, consistency, timeliness, believability, dan interpretability. Tapi tidak semuanya bisa aku ukur langsung dari tabel — misalnya accuracy butuh pembanding dengan kondisi aslinya, dan timeliness butuh informasi kapan data terakhir diperbarui.
Yang bisa aku cek langsung dari tabel ini adalah kelengkapan data, konsistensi kategori, dan duplikasi. Ini yang aku lakukan:
## customer_id nama usia pendapatan kota status
## 0 0 1 2 1 0
## customer_id nama usia pendapatan kota status
## 0.00 0.00 8.33 16.67 8.33 0.00
## [1] 1
## [1] 1
## [1] " PKU" "Dumai" "DUMAI" "pekanbaru" "Pekanbaru"
## [6] "PEKANBARU" "Pekanbaru " "PKU" "Siak"
## [1] "A" "ACTIVE" "aktif" "Aktif" "AKTIF"
## [6] "nonaktif" "Tidak aktif" "Tidak Aktif"
## [1] 21 150
## [1] 4.5e+06 5.0e+08
Dari sini aku sudah bisa menebak beberapa hal: ada nilai hilang di
kolom usia, pendapatan, dan kota;
kategori kota dan status berantakan; dan ada satu baris duplikat.
Karena aku akan sering mengecek kualitas data, baik sebelum maupun sesudah dibersihkan, aku membuat satu fungsi khusus supaya tidak perlu menulis ulang kode yang sama:
audit_data <- function(data) {
data.frame(
atribut = names(data),
tipe = sapply(data, function(x) class(x)[1]),
jumlah_missing = sapply(data, function(x) sum(is.na(x))),
persen_missing = round(sapply(data, function(x) mean(is.na(x)) * 100), 2),
jumlah_unik = sapply(data, function(x) length(unique(x))),
row.names = NULL
)
}
audit_awal <- audit_data(pelanggan_raw)
audit_awalPenting buat aku catat di sini: hasil audit ini bukan keputusan pembersihan data. Audit hanya memberi tahu aku kolom mana saja yang perlu aku perhatikan lebih lanjut, keputusan tetap aku ambil sendiri setelah memahami konteksnya.
Ini kebiasaan yang selalu aku terapkan: jangan pernah mengubah data mentah secara langsung. Aku selalu membuat salinannya dulu, supaya kalau ada yang salah, aku masih punya data aslinya untuk dibandingkan.
Langkah pertama yang aku lakukan adalah hal paling sederhana: menghapus spasi berlebih di awal/akhir teks, lalu menyeragamkan semuanya jadi huruf kecil supaya lebih mudah dicocokkan.
# Menghapus spasi di awal dan akhir
pelanggan$kota <- trimws(pelanggan$kota)
pelanggan$status <- trimws(pelanggan$status)
# Menyeragamkan huruf menjadi huruf kecil untuk proses pencocokan
pelanggan$kota <- tolower(pelanggan$kota)
pelanggan$status <- tolower(pelanggan$status)
sort(unique(pelanggan$kota))## [1] "dumai" "pekanbaru" "pku" "siak"
## [1] "a" "active" "aktif" "nonaktif" "tidak aktif"
Lihat, kategorinya jadi jauh lebih sedikit variasinya sekarang.
Langkah berikutnya, aku menyatukan kategori-kategori yang sebenarnya merujuk pada hal yang sama. Aturan yang aku pakai di sini aku tentukan sendiri berdasarkan pemahaman terhadap datanya dalam pekerjaan nyata, aturan semacam ini sebaiknya aku konfirmasikan dulu ke pemilik data.
# Menyeragamkan nama kota
pelanggan$kota[pelanggan$kota %in% c("pku", "pekanbaru")] <- "Pekanbaru"
pelanggan$kota[pelanggan$kota == "dumai"] <- "Dumai"
pelanggan$kota[pelanggan$kota == "siak"] <- "Siak"
# Menyeragamkan status keanggotaan
pelanggan$status[pelanggan$status %in% c("aktif", "active", "a")] <- "Aktif"
pelanggan$status[pelanggan$status %in% c("tidak aktif", "nonaktif")] <- "Tidak Aktif"
sort(unique(pelanggan$kota))## [1] "Dumai" "Pekanbaru" "Siak"
## [1] "Aktif" "Tidak Aktif"
Sekarang kolom kota hanya punya 3 kategori dan
status hanya punya 2 kategori jauh lebih rapi dibanding
sebelumnya.
Ingat tadi aku sempat lihat ada customer_id yang muncul
dua kali? Aku cek dulu baris mana saja yang terlibat sebelum aku
hapus:
Karena baris C010 benar-benar identik di semua kolomnya,
aku putuskan untuk menyimpan kemunculan pertamanya saja dan membuang
duplikatnya:
pelanggan <- pelanggan[!duplicated(pelanggan$customer_id), ]
rownames(pelanggan) <- NULL
dim(pelanggan)## [1] 11 6
Aku selalu ingat satu catatan penting di sini: menghapus
duplikasi hanya aman kalau setiap customer_id memang
seharusnya cuma mewakili satu pelanggan. Kalau ternyata satu
pelanggan boleh punya banyak baris (misalnya untuk mencatat banyak
transaksi), maka menghapus baris seperti ini justru salah langkah.
Selanjutnya aku memeriksa apakah ada nilai yang melanggar aturan bisnis yang wajar. Misalnya, aku tetapkan usia pelanggan yang masuk akal itu antara 15–100 tahun, dan pendapatan tidak boleh negatif.
# Usia yang di luar rentang wajar (15–100 tahun)
pelanggan[pelanggan$usia < 15 | pelanggan$usia > 100, ]Ternyata ada satu pelanggan dengan usia 150 tahun jelas
melanggar aturan. Di kondisi nyata, aku akan mengecek ulang ke formulir
aslinya. Untuk latihan ini, anggap saja setelah aku cek, nilai yang
benar adalah 50 tahun, jadi aku koreksi:
Ini kebiasaan yang menurutku sangat penting tapi sering dilewatkan orang: mencatat log perubahan. Dengan catatan ini, siapa pun yang membaca laporan ini (termasuk aku sendiri di kemudian hari) bisa tahu persis apa saja yang sudah diubah, dan kenapa.
log_perubahan <- data.frame(
tahap = c("Standardisasi", "Standardisasi", "Deduplikasi", "Koreksi domain"),
atribut = c("kota", "status", "customer_id", "usia"),
tindakan = c(
"PKU dan variasi kapital menjadi Pekanbaru",
"ACTIVE/A menjadi Aktif; nonaktif menjadi Tidak Aktif",
"Menghapus kemunculan kedua C010",
"Usia C004 dikoreksi dari 150 menjadi 50 berdasarkan sumber asli"
),
stringsAsFactors = FALSE
)
log_perubahan## customer_id nama usia pendapatan kota status
## 0 0 1 2 1 0
Aku punya tiga pilihan strategi untuk menangani nilai hilang ini, dan aku akan coba semuanya satu per satu supaya temen-temen bisa lihat perbandingannya.
Ini strategi paling sederhana, tapi aku hanya mendemonstrasikannya di sini aku tidak langsung menerapkannya ke data utamaku.
## [1] 11
## [1] 8
# Berapa persen data yang hilang kalau semua baris tidak lengkap dibuang?
round((1 - nrow(pelanggan_complete) / nrow(pelanggan)) * 100, 2)## [1] 27.27
Ternyata kalau aku pakai strategi ini, aku akan kehilangan lebih dari seperempat datanya. Itu terlalu banyak untuk dataset sekecil ini, jadi aku memilih strategi lain.
Untuk kolom numerik seperti pendapatan, aku bandingkan dulu nilai mean dan median-nya:
mean_pendapatan <- mean(pelanggan$pendapatan, na.rm = TRUE)
median_pendapatan <- median(pelanggan$pendapatan, na.rm = TRUE)
mean_pendapatan## [1] 59900000
## [1] 4900000
Perhatikan bedanya jauh sekali! Ini karena ada satu nilai pendapatan yang sangat ekstrem (500 juta) yang menarik rata-ratanya ke atas. Karena itu, aku memilih median untuk mengisi nilai yang hilang di kolom pendapatan, karena median lebih tahan terhadap nilai ekstrem seperti ini.
pelanggan$pendapatan_imputasi <- pelanggan$pendapatan
pelanggan$pendapatan_imputasi[is.na(pelanggan$pendapatan_imputasi)] <-
median_pendapatanUntuk kolom usia, aku pakai pendekatan yang sama:
median_usia <- median(pelanggan$usia, na.rm = TRUE)
pelanggan$usia_imputasi <- pelanggan$usia
pelanggan$usia_imputasi[is.na(pelanggan$usia_imputasi)] <- median_usia
pelanggan[, c("customer_id", "usia", "usia_imputasi",
"pendapatan", "pendapatan_imputasi")]Untuk kolom kategorik seperti kota, aku sengaja
tidak mengisinya dengan modus (kategori yang paling
sering muncul), karena itu bisa menyembunyikan fakta bahwa datanya
memang tidak diketahui. Aku lebih suka jujur dengan label eksplisit
"Tidak diketahui":
pelanggan$kota_imputasi <- pelanggan$kota
pelanggan$kota_imputasi[is.na(pelanggan$kota_imputasi)] <- "Tidak diketahui"
table(pelanggan$kota_imputasi, useNA = "ifany")##
## Dumai Pekanbaru Siak Tidak diketahui
## 3 6 1 1
Ada satu trik yang aku suka pakai: menambahkan kolom penanda (indikator) supaya aku tetap tahu baris mana saja yang nilai aslinya hilang, walaupun sudah diisi.
pelanggan$pendapatan_missing <- as.integer(is.na(pelanggan$pendapatan))
table(pelanggan$pendapatan_missing)##
## 0 1
## 9 2
Aku juga ingin memastikan proses imputasi tadi tidak mengubah bentuk sebaran datanya secara drastis. Makanya aku bandingkan lewat histogram:
par(mfrow = c(1, 2))
hist(pelanggan$pendapatan,
main = "Sebelum Imputasi",
xlab = "Pendapatan", col = "skyblue", breaks = 8)
hist(pelanggan$pendapatan_imputasi,
main = "Sesudah Imputasi Median",
xlab = "Pendapatan", col = "lightgreen", breaks = 8)Dari sini aku belajar satu hal penting: imputasi bisa
mengubah sebaran data dan mengecilkan variasinya. Jadi
pekerjaanku belum selesai hanya karena semua NA sudah
hilang aku tetap perlu mengevaluasi dampaknya.
boxplot(pelanggan$pendapatan_imputasi,
horizontal = TRUE,
col = "lightblue",
main = "Boxplot Pendapatan",
xlab = "Pendapatan")Kelihatan jelas ada satu titik yang jauh terpisah dari yang lain.
Aku pakai metode Interquartile Range (IQR) untuk menentukan batas bawah dan batas atas nilai yang masih dianggap wajar:
q1 <- quantile(pelanggan$pendapatan_imputasi, 0.25)
q3 <- quantile(pelanggan$pendapatan_imputasi, 0.75)
iqr <- IQR(pelanggan$pendapatan_imputasi)
batas_bawah <- q1 - 1.5 * iqr
batas_atas <- q3 + 1.5 * iqr
c(Q1 = q1, Q3 = q3, IQR = iqr,
batas_bawah = batas_bawah,
batas_atas = batas_atas)## Q1.25% Q3.75% IQR batas_bawah.25% batas_atas.75%
## 4750000 5150000 400000 4150000 5750000
pelanggan$outlier_pendapatan <-
pelanggan$pendapatan_imputasi < batas_bawah |
pelanggan$pendapatan_imputasi > batas_atas
pelanggan[pelanggan$outlier_pendapatan,
c("customer_id", "nama", "pendapatan_imputasi")]Benar saja, pelanggan dengan pendapatan 500 juta itu terkonfirmasi sebagai outlier.
Nah, di sinilah aku harus berpikir dulu outlier tidak boleh langsung aku hapus begitu saja. Aku mempertimbangkan tiga kemungkinan:
Untuk latihan ini, aku memutuskan mempertahankan nilai aslinya, tapi aku juga membuat versi winsorized (nilai ekstrem “dipangkas” ke batas wajar) hanya untuk membandingkan dampaknya:
pelanggan$pendapatan_winsor <- pmin(
pmax(pelanggan$pendapatan_imputasi, batas_bawah),
batas_atas
)
pelanggan[pelanggan$outlier_pendapatan,
c("customer_id", "pendapatan_imputasi", "pendapatan_winsor")]Winsorizing ini cuma aku pakai sebagai demonstrasi tambahan kalau temen-temen mau menerapkannya di data asli, pastikan itu memang sesuai dengan tujuan analisismu, dan jangan lupa dokumentasikan bahwa nilai ekstremnya sudah dibatasi.
Rumus normalisasi min–maks yang aku pakai untuk membawa nilai ke rentang \([0, 1]\) adalah:
\[ x' = \frac{x-\min(x)}{\max(x)-\min(x)} \]
Aku buat fungsinya sendiri supaya bisa dipakai berulang:
minmax <- function(x) {
if (all(is.na(x))) return(rep(NA_real_, length(x)))
rentang <- max(x, na.rm = TRUE) - min(x, na.rm = TRUE)
if (rentang == 0) return(rep(0, length(x)))
(x - min(x, na.rm = TRUE)) / rentang
}
pelanggan$usia_minmax <- minmax(pelanggan$usia_imputasi)
pelanggan$pendapatan_minmax <- minmax(pelanggan$pendapatan_imputasi)
pelanggan[, c("customer_id", "usia", "usia_minmax",
"pendapatan_imputasi", "pendapatan_minmax")]Rumus z-score yang aku pakai:
\[ z = \frac{x-\bar{x}}{s} \]
pelanggan$usia_z <- as.numeric(scale(pelanggan$usia_imputasi))
pelanggan$pendapatan_z <- as.numeric(scale(pelanggan$pendapatan_imputasi))
round(pelanggan[, c("usia_z", "pendapatan_z")], 3)Cara lain yang lebih sederhana adalah membagi nilai dengan pangkat 10 yang sesuai:
decimal_scale <- function(x) {
maks <- max(abs(x), na.rm = TRUE)
if (maks == 0) return(x)
j <- ceiling(log10(maks + 1))
x / (10 ^ j)
}
pelanggan$pendapatan_decimal <- decimal_scale(pelanggan$pendapatan_imputasi)
range(pelanggan$pendapatan_decimal, na.rm = TRUE)## [1] 0.0045 0.5000
transformasi <- pelanggan[, c(
"customer_id", "pendapatan_imputasi", "pendapatan_minmax",
"pendapatan_z", "pendapatan_decimal"
)]
transformasipelanggan$pendapatan_winsor_minmax <- minmax(pelanggan$pendapatan_winsor)
plot(pelanggan$pendapatan_minmax,
pelanggan$pendapatan_winsor_minmax,
pch = 19, col = "navy",
xlab = "Min–maks Data Asli",
ylab = "Min–maks Data Winsorized",
main = "Dampak Penanganan Outlier terhadap Normalisasi")
abline(0, 1, col = "red", lty = 2)Ini yang aku maksud tadi soal pentingnya menangani outlier sebelum melakukan normalisasi: satu nilai ekstrem saja bisa menekan hampir semua nilai lain ke rentang yang sangat sempit setelah dinormalisasi min–maks.
Sebelum menggabungkan dua tabel, aku pastikan dulu kolom kuncinya
(customer_id dan cust_id) unik dan aku cek
juga siapa saja yang “tidak punya pasangan”:
## [1] 0
## [1] 0
## [1] "C011"
## [1] "C012"
Ternyata pelanggan C011 belum pernah bertransaksi,
sementara transaksi milik C012 tidak punya data pelanggan
yang cocok.
Karena nama kolomnya berbeda (customer_id vs
cust_id), aku samakan dulu supaya bisa digabungkan:
Aku pakai merge() dengan all.x = TRUE
supaya semua pelanggan tetap ada di hasil akhir, walaupun sebagian di
antaranya tidak punya data transaksi:
data_terintegrasi <- merge(
pelanggan,
transaksi,
by = "customer_id",
all.x = TRUE
)
data_terintegrasi[, c(
"customer_id", "nama", "jumlah_transaksi", "total_purchase"
)]Setelah digabungkan, aku selalu mengecek ulang supaya yakin prosesnya berjalan seperti yang aku harapkan:
# Apakah jumlah baris berubah secara tidak wajar?
c(sebelum = nrow(pelanggan), sesudah = nrow(data_terintegrasi))## sebelum sesudah
## 11 11
## [1] 0
# Ada berapa nilai hilang baru akibat tidak ketemu pasangan transaksi?
colSums(is.na(data_terintegrasi[, c("jumlah_transaksi", "total_purchase")]))## jumlah_transaksi total_purchase
## 1 1
# Siapa saja pelanggan yang tidak punya pasangan transaksi?
data_terintegrasi[is.na(data_terintegrasi$jumlah_transaksi),
c("customer_id", "nama")]Ini pertanyaan yang menurutku penting untuk direnungkan: nilai
NA pada kolom transaksi itu bisa berarti dua hal yang
beda:
Aku hanya boleh mengubah NA menjadi 0 kalau
aku sudah yakin betul yang terjadi adalah kemungkinan pertama. Untuk
latihan ini, anggap saja aku sudah mengonfirmasinya:
data_terintegrasi$jumlah_transaksi_final <- data_terintegrasi$jumlah_transaksi
data_terintegrasi$total_purchase_final <- data_terintegrasi$total_purchase
data_terintegrasi$jumlah_transaksi_final[
is.na(data_terintegrasi$jumlah_transaksi_final)
] <- 0
data_terintegrasi$total_purchase_final[
is.na(data_terintegrasi$total_purchase_final)
] <- 0Setelah semua tahap di atas, aku rapikan lagi dan hanya menyimpan kolom-kolom yang relevan untuk analisis lanjutan:
data_final <- data_terintegrasi[, c(
"customer_id", "nama", "usia_imputasi", "kota_imputasi", "status",
"pendapatan_imputasi", "pendapatan_missing", "outlier_pendapatan",
"usia_minmax", "pendapatan_minmax", "jumlah_transaksi_final",
"total_purchase_final"
)]
names(data_final)[names(data_final) == "kota_imputasi"] <- "kota"
names(data_final)[names(data_final) == "usia_imputasi"] <- "usia"
data_final## [1] 0
## customer_id nama usia
## 0 0 0
## kota status pendapatan_imputasi
## 0 0 0
## pendapatan_missing outlier_pendapatan usia_minmax
## 0 0 0
## pendapatan_minmax jumlah_transaksi_final total_purchase_final
## 0 0 0
Semua nilai hilang sudah tertangani, dan tidak ada lagi
customer_id yang terduplikasi.
Supaya kelihatan jelas seberapa besar perubahan yang sudah aku lakukan, aku ringkas perbandingannya di sini:
perbandingan <- data.frame(
indikator = c(
"Jumlah baris",
"Duplikasi customer_id",
"Total missing value",
"Kategori kota unik",
"Kategori status unik"
),
sebelum = c(
nrow(pelanggan_raw),
sum(duplicated(pelanggan_raw$customer_id)),
sum(is.na(pelanggan_raw)),
length(unique(pelanggan_raw$kota)),
length(unique(pelanggan_raw$status))
),
sesudah = c(
nrow(data_final),
sum(duplicated(data_final$customer_id)),
sum(is.na(data_final)),
length(unique(data_final$kota)),
length(unique(data_final$status))
)
)
perbandinganLihat sendiri betapa jauh bedanya jumlah kategori kota turun dari 10 jadi 4, kategori status turun dari 8 jadi 2, dan semua nilai hilang sudah tertangani.
Terakhir, aku simpan dataset yang sudah bersih dan log perubahannya ke dalam file CSV, supaya bisa dipakai lagi di analisis selanjutnya:
Sebagai bahan renungan, ini beberapa pertanyaan yang selalu aku tanyakan ke diri sendiri setiap kali selesai melakukan preprocessing:
Kalau aku rangkum, ini alur yang sudah aku lalui dari awal sampai akhir:
Satu hal yang aku pegang sampai sekarang: preprocessing itu pada dasarnya adalah proses pengambilan keputusan. Kode R hanya menjalankan keputusan yang sudah aku buat kualitas hasil akhirnya tetap bergantung pada seberapa paham aku terhadap data itu sendiri, apa tujuan analisisnya, dan seberapa rapi aku mendokumentasikan setiap perubahan yang aku lakukan.
Semoga tulisan ini membantu temen-temen yang baru belajar data preprocessing di R yaa!
Han, J., Kamber, M., & Pei, J. (2012). Data Mining: Concepts and Techniques (3rd ed.). Morgan Kaufmann. Bab 3: Data Preprocessing.