Pada kesempatan kali ini, saya ingin melakukan data preprocessing menggunakan R dan RStudio.
Disini saya berperan sebagai analis data di sebuah perusahaan e-commerce fiktif dengan dua sumber data:
Kedua data tersebut masih “kotor”. Ada nilai yang hilang, penulisan kategori yang tidak konsisten, data yang terduplikasi, angka yang tampak tidak wajar (outlier), sampai nama kolom identifier yang berbeda antara satu tabel dengan tabel lainnya. Sehingga, data tersebut harus dilakukan data preprocessing terlebih dahulu sebelum data layak dianalisis.
Prinsip dalam data preprocessing Perubahan atau penghapusan data tidak pernah dilakukan hanya karena “kelihatannya aneh”. Setiap perubahan harus memiliki alasan, aturan yang jelas, dan tercatat.
Berikut langkah-langkah dalam melakukan data preprocessing pada aplikasi RStudio:
Sebelum mulai menulis kode, proyek disiapkan terlebih dahulu di RStudio dengan langkah berikut:
praktikum_preprocessing.Direktori kerja dan versi R yang digunakan juga selalu diperiksa terlebih dahulu, supaya apabila terjadi kendala di kemudian hari, kondisi awal lingkungan kerja dapat ditelusuri dengan pasti.
## [1] "D:/UR Z/SEMS 5 bismillah chan/Data Mining"
## [1] "R version 4.4.2 (2024-10-31 ucrt)"
Catatan Seluruh teknik dalam tulisan ini menggunakan fungsi dasar R (base R) saja, sehingga tidak diperlukan pemasangan paket tambahan apa pun untuk mengikutinya.
Karena ini merupakan latihan, data dibuat secara mandiri dan sengaja “dikotorkan” agar seluruh teknik pembersihan data dapat dipraktikkan dari awal hingga akhir.
Berikut data yang digunakan dalam proses data preprocessing ini :
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_rawPerhatikan data di atas dengan saksama. Beberapa hal sengaja disalahkan agar terlihat jelas mengapa data ini belum siap dianalisis :
150 tahun — 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 memiliki transaksi, sedangkan
C012 pada data transaksi tidak memiliki data
pelanggan.Sebelum melakukan perubahan apa pun, bentuk data diperiksa terlebih dahulu: berapa baris, berapa kolom, tipe data 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
##
##
##
##
Berdasarkan Han, Kamber, dan Pei (2012), kualitas data dapat dilihat dari enam dimensi : accuracy, completeness, consistency, timeliness, believability, dan interpretability. Tidak semua dimensi tersebut dapat diukur langsung dari tabel, misalnya accuracy membutuhkan pembanding dengan kondisi aslinya, dan timeliness membutuhkan informasi kapan data terakhir diperbarui.
Dimensi yang dapat diperiksa langsung dari tabel ini adalah kelengkapan data, konsistensi kategori, dan duplikasi. Berikut pemeriksaannya :
## 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 hasil ini, dapat diidentifikasi beberapa hal : terdapat nilai
hilang pada kolom usia, pendapatan, dan
kota. Kategori kota dan status masih berantakan, serta
terdapat satu baris duplikat.
Karena pemeriksaan kualitas data akan sering dilakukan, baik sebelum maupun sesudah dibersihkan, satu fungsi khusus dibuat agar tidak perlu menulis ulang kode yang sama, yaitu :
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_awalCatatan penting Hasil audit ini bukan keputusan pembersihan data. Audit hanya menunjukkan kolom mana saja yang perlu diperhatikan lebih lanjut, keputusan tetap diambil setelah memahami konteksnya.
Perlu diingat bahwa : jangan pernah mengubah data mentah secara langsung. Salinan data dibuat terlebih dahulu, sehingga apabila terjadi kesalahan, data asli tetap tersedia sebagai pembanding.
Langkah pertama yang dilakukan adalah hal paling sederhana yaitu : menghapus spasi berlebih di awal/akhir teks, kemudian menyeragamkan semuanya menjadi huruf kecil agar 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"
Variasi kategorinya sudah jauh berkurang setelah langkah ini.
Langkah berikutnya adalah menyatukan kategori-kategori yang sebenarnya merujuk pada hal yang sama. Aturan yang digunakan di sini ditentukan berdasarkan pemahaman terhadap data, dalam pekerjaan nyata, aturan semacam ini sebaiknya dikonfirmasikan terlebih dahulu kepada pemilik data atau tim pengelola data resmi.
# 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"
Kolom kota kini hanya memiliki 3 kategori dan
status hanya memiliki 2 kategori, jauh lebih rapi
dibandingkan sebelumnya.
Sebelumnya sempat teridentifikasi ada customer_id yang
muncul dua kali. Baris-baris yang terlibat diperiksa terlebih dahulu
sebelum dihapus:
Karena baris C010 benar-benar identik di semua kolomnya,
diputuskan untuk menyimpan kemunculan pertamanya saja dan membuang
duplikatnya:
pelanggan <- pelanggan[!duplicated(pelanggan$customer_id), ]
rownames(pelanggan) <- NULL
dim(pelanggan)## [1] 11 6
Perlu diperhatikan Penghapusan
duplikasi hanya aman dilakukan apabila setiap customer_id
memang seharusnya mewakili satu pelanggan. Apabila satu pelanggan boleh
memiliki banyak baris (misalnya untuk mencatat banyak transaksi),
penghapusan seperti ini justru menjadi langkah yang keliru.
Selanjutnya, diperiksa apakah terdapat nilai yang melanggar aturan bisnis yang wajar. Sebagai contoh, rentang usia pelanggan yang dianggap wajar ditetapkan antara 15–100 tahun, dan pendapatan tidak boleh bernilai negatif.
# Usia yang di luar rentang wajar (15–100 tahun)
pelanggan[pelanggan$usia < 15 | pelanggan$usia > 100, ]Ternyata terdapat satu pelanggan dengan usia 150 tahun,
jelas melanggar aturan tersebut. Pada kondisi nyata, hal ini akan
dikonfirmasi ulang ke formulir aslinya. Untuk latihan ini, diasumsikan
setelah dikonfirmasi, nilai yang benar adalah 50 tahun,
sehingga dilakukan koreksi:
Kebiasaan yang tergolong penting namun sering terlewat adalah mencatat log perubahan. Dengan catatan ini, siapa pun yang membaca laporan ini dapat mengetahui secara pasti apa saja yang telah diubah, dan alasannya.
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
Terdapat tiga pilihan strategi untuk menangani nilai hilang ini, dan ketiganya akan dicoba satu per satu agar dapat dilihat perbandingannya.
Strategi ini paling sederhana, namun hanya didemonstrasikan di sini, tidak langsung diterapkan pada data utama.
## [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 dengan strategi ini, lebih dari seperempat data akan hilang. Jumlah tersebut terlalu besar untuk dataset sekecil ini, sehingga strategi lain dipilih.
Untuk kolom numerik seperti pendapatan, nilai mean dan median dibandingkan terlebih dahulu:
mean_pendapatan <- mean(pelanggan$pendapatan, na.rm = TRUE)
median_pendapatan <- median(pelanggan$pendapatan, na.rm = TRUE)
mean_pendapatan## [1] 59900000
## [1] 4900000
Perbedaannya cukup jauh. Hal ini terjadi karena terdapat satu nilai pendapatan yang sangat ekstrem (500 juta) yang menarik rata-rata ke atas. Oleh karena itu, median dipilih untuk mengisi nilai yang hilang pada kolom pendapatan, karena median lebih tahan terhadap nilai ekstrem semacam ini.
pelanggan$pendapatan_imputasi <- pelanggan$pendapatan
pelanggan$pendapatan_imputasi[is.na(pelanggan$pendapatan_imputasi)] <-
median_pendapatanUntuk kolom usia, pendekatan yang sama digunakan:
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, nilai yang hilang
sengaja tidak diisi dengan modus (kategori yang paling
sering muncul), karena hal itu dapat menyembunyikan fakta bahwa datanya
memang tidak diketahui. Label eksplisit "Tidak diketahui"
digunakan agar lebih jujur menggambarkan kondisi data:
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
Satu trik yang digunakan adalah menambahkan kolom penanda (indikator) agar tetap dapat diketahui baris mana saja yang nilai aslinya hilang, meskipun sudah diisi.
pelanggan$pendapatan_missing <- as.integer(is.na(pelanggan$pendapatan))
table(pelanggan$pendapatan_missing)##
## 0 1
## 9 2
Perlu dipastikan pula bahwa proses imputasi tadi tidak mengubah bentuk sebaran data secara drastis. Perbandingan dilakukan melalui 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)Insight Imputasi dapat mengubah
sebaran data dan mengecilkan variasinya. Proses pembersihan data belum
tentu selesai hanya karena seluruh NA telah hilang,
dampaknya tetap perlu dievaluasi.
boxplot(pelanggan$pendapatan_imputasi,
horizontal = TRUE,
col = "lightblue",
main = "Boxplot Pendapatan",
xlab = "Pendapatan")Terlihat jelas ada satu titik yang jauh terpisah dari titik-titik lainnya.
Metode Interquartile Range (IQR) digunakan 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 tersebut terkonfirmasi sebagai outlier.
Pada tahap ini, keputusan perlu dipertimbangkan dengan cermat, outlier tidak boleh langsung dihapus begitu saja. Terdapat tiga kemungkinan yang perlu dipertimbangkan:
Untuk latihan ini, diputuskan untuk mempertahankan nilai aslinya, namun versi winsorized (nilai ekstrem “dipangkas” ke batas wajar) juga dibuat 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")]Catatan Winsorizing di sini hanya digunakan sebagai demonstrasi tambahan. Apabila hendak diterapkan pada data asli, pastikan pendekatan ini memang sesuai dengan tujuan analisis, dan jangan lupa mendokumentasikan bahwa nilai ekstremnya telah dibatasi.
Rumus normalisasi min–maks yang digunakan untuk membawa nilai ke rentang \([0, 1]\) adalah:
\[ x' = \frac{x-\min(x)}{\max(x)-\min(x)} \]
Fungsi berikut dibuat agar dapat digunakan secara 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 digunakan:
\[ 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)Insight Inilah alasan pentingnya menangani outlier sebelum melakukan normalisasi: satu nilai ekstrem saja dapat menekan hampir seluruh nilai lain ke rentang yang sangat sempit setelah dinormalisasi dengan metode min–maks.
Sebelum menggabungkan dua tabel, kolom kunci
(customer_id dan cust_id) dipastikan terlebih
dahulu keunikannya, sekaligus diperiksa siapa saja yang “tidak memiliki
pasangan”:
## [1] 0
## [1] 0
## [1] "C011"
## [1] "C012"
Ternyata pelanggan C011 belum pernah bertransaksi,
sementara transaksi milik C012 tidak memiliki data
pelanggan yang cocok.
Karena nama kolomnya berbeda (customer_id vs
cust_id), penyamaan nama dilakukan terlebih dahulu agar
kedua tabel dapat digabungkan:
Fungsi merge() dengan all.x = TRUE
digunakan agar seluruh pelanggan tetap ada di hasil akhir, meskipun
sebagian di antaranya tidak memiliki 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, pemeriksaan ulang selalu dilakukan untuk memastikan proses berjalan sesuai yang diharapkan:
# 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")]Pertanyaan penting yang perlu diperhatikan di sini adalah: nilai
NA pada kolom transaksi dapat berarti dua hal yang
berbeda:
NA hanya boleh diubah menjadi 0 apabila
telah dipastikan bahwa yang terjadi adalah kemungkinan pertama. Untuk
latihan ini, diasumsikan hal tersebut sudah dikonfirmasi:
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 seluruh tahap di atas, data dirapikan kembali dan hanya kolom-kolom yang relevan untuk analisis lanjutan yang disimpan:
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
Seluruh nilai hilang telah tertangani, dan tidak ada lagi
customer_id yang terduplikasi.
Agar terlihat jelas seberapa besar perubahan yang telah dilakukan, perbandingannya diringkas sebagai berikut:
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))
)
)
perbandinganPerbedaannya cukup signifikan, jumlah kategori kota turun dari 10 menjadi 4, kategori status turun dari 8 menjadi 2, dan seluruh nilai hilang telah tertangani.
Sebagai langkah terakhir, dataset yang sudah bersih beserta log perubahannya disimpan ke dalam file CSV, agar dapat digunakan kembali pada analisis selanjutnya:
write.csv(data_final,
"data_pelanggan_setelah_preprocessing.csv",
row.names = FALSE)
write.csv(log_perubahan,
"log_perubahan_preprocessing.csv",
row.names = FALSE)Sebagai bahan renungan, berikut beberapa pertanyaan yang relevan untuk diajukan setiap kali proses preprocessing selesai dilakukan:
Secara ringkas, berikut alur yang telah dilalui dari awal hingga akhir:
Poin utama Preprocessing pada dasarnya merupakan proses pengambilan keputusan. Kode R hanya menjalankan keputusan yang telah dibuat, kualitas hasil akhirnya tetap bergantung pada seberapa dalam pemahaman terhadap data itu sendiri, tujuan analisis yang hendak dicapai, dan seberapa rapi setiap perubahan didokumentasikan.
Semoga langkah-langkah ini dapat dengan mudah dipahami bagi sedang mempelajari data preprocessing di RStudio.
Han, J., Kamber, M., & Pei, J. (2012). Data Mining: Concepts and Techniques (3rd ed.). Morgan Kaufmann. Bab 3: Data Preprocessing.