Data preprocessing merupakan tahap penting dalam data mining karena data mentah sering kali belum siap langsung digunakan untuk analisis. Data dapat mengandung missing value, duplikasi, kategori yang tidak konsisten, kesalahan domain, dan nilai ekstrem (outlier).
Praktikum ini mengikuti alur preprocessing mulai dari memahami kondisi awal data, melakukan data cleaning, menangani missing values, mengevaluasi outlier, melakukan transformasi atribut numerik, mengintegrasikan dua sumber data, dan melakukan validasi akhir.
Setelah menyelesaikan praktikum, diharapkan dapat:
getwd()
## [1] "C:/Users/Hp/Downloads"
R.version.string
## [1] "R version 4.5.1 (2025-06-13 ucrt)"
Dataset berikut terdiri dari 20 data pelanggan. Data
sengaja dibuat memiliki beberapa masalah agar seluruh tahapan
preprocessing dapat dipraktikkan, seperti missing value,
kategori tidak konsisten, kesalahan nilai usia, duplikasi
customer_id, dan nilai pendapatan ekstrem.
pelanggan_raw <- data.frame(
customer_id = c("C001", "C002", "C003", "C004", "C005", "C006",
"C007", "C008", "C009", "C010", "C010", "C011",
"C012", "C013", "C014", "C015", "C016", "C017",
"C018", "C019"),
nama = c("Andi", "Bella", "Cahyo", "Dina", "Eko", "Fira",
"Galih", "Hani", "Iqbal", "Jihan", "Jihan", "Kevin",
"Lina", "Mira", "Nadia", "Omar", "Putri", "Raka",
"Salsa", "Taufik"),
usia = c(22, 26, 24, 145, 28, NA, 32, 30, 23, 36,
36, 29, 25, 27, 31, 20, NA, 34, 28, 42),
pendapatan = c(4200000, NA, 5500000, 4800000, 5100000, 5300000,
75000000, 4600000, 4400000, 5800000, 5800000, NA,
4300000, 5600000, 4900000, 4000000, 5200000, 6000000,
5100000, 6500000),
kota = c("Pekanbaru", " PKU", "PEKANBARU", "Dumai", "pekanbaru",
"DUMAI", "Pekanbaru ", "Siak", "PKU", "Dumai", "Dumai",
NA, "Pekanbaru", "pekanbaru", "DUMAI", "Siak",
"PEKANBARU", "Pekanbaru", "PKU", "Dumai"),
status = c("Aktif", "aktif", "ACTIVE", "A", "Tidak Aktif", "nonaktif",
"Aktif", "AKTIF", "A", "Tidak aktif", "Tidak aktif", "Aktif",
"active", "A", "Aktif", "tidak aktif", "AKTIF", "aktif",
"nonaktif", "Aktif"),
stringsAsFactors = FALSE
)
transaksi_raw <- data.frame(
cust_id = c("C001", "C002", "C003", "C004", "C005", "C006",
"C007", "C008", "C009", "C010", "C011", "C012",
"C013", "C014", "C015", "C016", "C017", "C018",
"C019", "C999"),
jumlah_transaksi = c(4, 6, 8, 3, 5, 4, 18, 7, 2, 9,
2, 5, 7, 10, 4, NA, 6, 3, 11, 5),
total_purchase = c(1200000, 1800000, 3000000, 900000, 2400000, 1500000,
18000000, 2100000, 700000, 3600000, 400000, 1600000,
2500000, 5200000, 1300000, NA, 2700000, 850000,
4800000, 1400000),
stringsAsFactors = FALSE
)
pelanggan_raw
transaksi_raw
dim(pelanggan_raw)
## [1] 20 6
names(pelanggan_raw)
## [1] "customer_id" "nama" "usia" "pendapatan" "kota"
## [6] "status"
str(pelanggan_raw)
## 'data.frame': 20 obs. of 6 variables:
## $ customer_id: chr "C001" "C002" "C003" "C004" ...
## $ nama : chr "Andi" "Bella" "Cahyo" "Dina" ...
## $ usia : num 22 26 24 145 28 NA 32 30 23 36 ...
## $ pendapatan : num 4.2e+06 NA 5.5e+06 4.8e+06 5.1e+06 5.3e+06 7.5e+07 4.6e+06 4.4e+06 5.8e+06 ...
## $ kota : chr "Pekanbaru" " PKU" "PEKANBARU" "Dumai" ...
## $ status : chr "Aktif" "aktif" "ACTIVE" "A" ...
head(pelanggan_raw)
summary(pelanggan_raw)
## customer_id nama usia pendapatan
## Length:20 Length:20 Min. : 20.00 Min. : 4000000
## Class :character Class :character 1st Qu.: 25.25 1st Qu.: 4650000
## Mode :character Mode :character Median : 28.50 Median : 5150000
## Mean : 35.44 Mean : 9005556
## 3rd Qu.: 33.50 3rd Qu.: 5750000
## Max. :145.00 Max. :75000000
## NA's :2 NA's :2
## kota status
## Length:20 Length:20
## Class :character Class :character
## Mode :character Mode :character
##
##
##
##
colSums(is.na(pelanggan_raw))
## customer_id nama usia pendapatan kota status
## 0 0 2 2 1 0
round(colMeans(is.na(pelanggan_raw)) * 100, 2)
## customer_id nama usia pendapatan kota status
## 0 0 10 10 5 0
sum(duplicated(pelanggan_raw))
## [1] 1
sum(duplicated(pelanggan_raw$customer_id))
## [1] 1
sort(unique(pelanggan_raw$kota))
## [1] " PKU" "Dumai" "DUMAI" "pekanbaru" "Pekanbaru"
## [6] "PEKANBARU" "Pekanbaru " "PKU" "Siak"
sort(unique(pelanggan_raw$status))
## [1] "A" "active" "ACTIVE" "aktif" "Aktif"
## [6] "AKTIF" "nonaktif" "tidak aktif" "Tidak aktif" "Tidak Aktif"
range(pelanggan_raw$usia, na.rm = TRUE)
## [1] 20 145
range(pelanggan_raw$pendapatan, na.rm = TRUE)
## [1] 4.0e+06 7.5e+07
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_awal
pelanggan <- pelanggan_raw
pelanggan$kota <- trimws(pelanggan$kota)
pelanggan$status <- trimws(pelanggan$status)
pelanggan$kota <- tolower(pelanggan$kota)
pelanggan$status <- tolower(pelanggan$status)
sort(unique(pelanggan$kota))
## [1] "dumai" "pekanbaru" "pku" "siak"
sort(unique(pelanggan$status))
## [1] "a" "active" "aktif" "nonaktif" "tidak aktif"
Selanjutnya kategori diseragamkan menjadi label yang konsisten.
pelanggan$kota[pelanggan$kota %in% c("pku", "pekanbaru")] <- "Pekanbaru"
pelanggan$kota[pelanggan$kota == "dumai"] <- "Dumai"
pelanggan$kota[pelanggan$kota == "siak"] <- "Siak"
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"
sort(unique(pelanggan$status))
## [1] "Aktif" "Tidak Aktif"
pelanggan[duplicated(pelanggan$customer_id) |
duplicated(pelanggan$customer_id, fromLast = TRUE), ]
Kemunculan kedua untuk C010 dihapus.
pelanggan <- pelanggan[!duplicated(pelanggan$customer_id), ]
rownames(pelanggan) <- NULL
dim(pelanggan)
## [1] 19 6
Usia yang dianggap masuk akal adalah 15–100 tahun.
pelanggan[pelanggan$usia < 15 | pelanggan$usia > 100, ]
Pendapatan negatif juga diperiksa.
pelanggan[pelanggan$pendapatan < 0, ]
Nilai usia C004 sebesar 145 diperbaiki menjadi 45 berdasarkan sumber asli.
pelanggan$usia[pelanggan$customer_id == "C004"] <- 45
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 145 menjadi 45"
),
stringsAsFactors = FALSE
)
log_perubahan
colSums(is.na(pelanggan))
## customer_id nama usia pendapatan kota status
## 0 0 2 2 1 0
pelanggan[!complete.cases(pelanggan), ]
pelanggan_complete <- pelanggan[complete.cases(pelanggan), ]
nrow(pelanggan)
## [1] 19
nrow(pelanggan_complete)
## [1] 15
round((1 - nrow(pelanggan_complete) / nrow(pelanggan)) * 100, 2)
## [1] 21.05
mean_pendapatan <- mean(pelanggan$pendapatan, na.rm = TRUE)
median_pendapatan <- median(pelanggan$pendapatan, na.rm = TRUE)
mean_pendapatan
## [1] 9194118
median_pendapatan
## [1] 5100000
pelanggan$pendapatan_imputasi <- pelanggan$pendapatan
pelanggan$pendapatan_imputasi[is.na(pelanggan$pendapatan_imputasi)] <-
median_pendapatan
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")]
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
## 5 11 2 1
pelanggan$pendapatan_missing <- as.integer(is.na(pelanggan$pendapatan))
table(pelanggan$pendapatan_missing)
##
## 0 1
## 17 2
par(mfrow = c(1, 2))
hist(pelanggan$pendapatan,
main = "Sebelum Imputasi",
xlab = "Pendapatan",
breaks = 8)
hist(pelanggan$pendapatan_imputasi,
main = "Sesudah Imputasi Median",
xlab = "Pendapatan",
breaks = 8)
par(mfrow = c(1, 1))
boxplot(pelanggan$pendapatan_imputasi,
horizontal = TRUE,
main = "Boxplot Pendapatan",
xlab = "Pendapatan")
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%
## 4700000 5550000 850000 3425000 6825000
pelanggan$outlier_pendapatan <-
pelanggan$pendapatan_imputasi < batas_bawah |
pelanggan$pendapatan_imputasi > batas_atas
pelanggan[pelanggan$outlier_pendapatan,
c("customer_id", "nama", "pendapatan_imputasi")]
pelanggan$pendapatan_winsor <- pmin(
pmax(pelanggan$pendapatan_imputasi, batas_bawah),
batas_atas
)
pelanggan[pelanggan$outlier_pendapatan,
c("customer_id", "pendapatan_imputasi", "pendapatan_winsor")]
Tiga metode transformasi yang digunakan adalah min–maks, z-score, dan decimal scaling.
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")]
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)
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.04 0.75
transformasi <- pelanggan[, c(
"customer_id", "pendapatan_imputasi", "pendapatan_minmax",
"pendapatan_z", "pendapatan_decimal"
)]
transformasi
pelanggan$pendapatan_winsor_minmax <- minmax(pelanggan$pendapatan_winsor)
plot(pelanggan$pendapatan_minmax,
pelanggan$pendapatan_winsor_minmax,
pch = 19,
xlab = "Min–maks Data Asli",
ylab = "Min–maks Data Winsorized",
main = "Dampak Penanganan Outlier terhadap Normalisasi")
abline(0, 1, lty = 2)
sum(duplicated(pelanggan$customer_id))
## [1] 0
sum(duplicated(transaksi_raw$cust_id))
## [1] 0
setdiff(pelanggan$customer_id, transaksi_raw$cust_id)
## character(0)
setdiff(transaksi_raw$cust_id, pelanggan$customer_id)
## [1] "C999"
transaksi <- transaksi_raw
names(transaksi)[names(transaksi) == "cust_id"] <- "customer_id"
data_terintegrasi <- merge(
pelanggan,
transaksi,
by = "customer_id",
all.x = TRUE
)
data_terintegrasi[, c(
"customer_id", "nama", "jumlah_transaksi", "total_purchase"
)]
c(sebelum = nrow(pelanggan), sesudah = nrow(data_terintegrasi))
## sebelum sesudah
## 19 19
sum(duplicated(data_terintegrasi$customer_id))
## [1] 0
colSums(is.na(data_terintegrasi[, c("jumlah_transaksi", "total_purchase")]))
## jumlah_transaksi total_purchase
## 1 1
Pelanggan yang tidak memiliki pasangan transaksi ditampilkan untuk diperiksa lebih lanjut.
data_terintegrasi[is.na(data_terintegrasi$jumlah_transaksi),
c("customer_id", "nama")]
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)
] <- 0
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
audit_akhir <- audit_data(data_final)
audit_akhir
sum(duplicated(data_final$customer_id))
## [1] 0
colSums(is.na(data_final))
## 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
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))
)
)
perbandingan
write.csv(data_final,
"data_pelanggan_20_data_setelah_preprocessing.csv",
row.names = FALSE)
write.csv(log_perubahan,
"log_perubahan_preprocessing_20_data.csv",
row.names = FALSE)
Berdasarkan seluruh tahapan praktikum, preprocessing dilakukan secara bertahap mulai dari memahami struktur dan kualitas awal data, membersihkan kategori, menghapus duplikasi, memperbaiki pelanggaran domain, menangani missing values, mendeteksi dan mengevaluasi outlier, melakukan transformasi numerik, mengintegrasikan data transaksi, hingga melakukan audit akhir.
Pada dataset 20 data pelanggan ini, proses preprocessing menunjukkan bahwa data mentah perlu diperiksa terlebih dahulu sebelum digunakan untuk analisis. Setiap perubahan terhadap data dilakukan berdasarkan alasan yang jelas, sehingga data akhir menjadi lebih konsisten dan siap digunakan untuk tahap analisis atau data mining.
Data mentah 20 data
↓
Eksplorasi dan audit awal
↓
Standardisasi kategori
↓
Deduplikasi dan pemeriksaan domain
↓
Penanganan missing value
↓
Deteksi dan evaluasi outlier
↓
Transformasi data
↓
Integrasi data
↓
Audit akhir
↓
Data siap digunakan untuk analisis/data mining