Data pelanggan dan transaksi dapat digunakan untuk memahami karakteristik pelanggan serta aktivitas pembelian. Sebelum digunakan untuk analisis, data perlu diperiksa dan dipersiapkan karena masih terdapat beberapa masalah kualitas data.
Pada kasus ini digunakan dua sumber data, yaitu data pelanggan dan data transaksi. Tujuan preprocessing adalah menghasilkan dataset yang lebih konsisten dan siap digunakan untuk analisis clustering.
Alur preprocessing: Understand → Clean → Transform → Integrate → Evaluate
Unit observasi adalah pelanggan e-commerce. Atribut
yang akan digunakan untuk analisis meliputi usia, pendapatan, jumlah
transaksi, dan total pembelian. customer_id digunakan
sebagai identifier, bukan sebagai variabel clustering.
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
)
datatable(pelanggan_raw, filter="top", rownames=FALSE,
options=list(pageLength=10, scrollX=TRUE))
datatable(transaksi_raw, filter="top", rownames=FALSE,
options=list(pageLength=10, scrollX=TRUE))
Data pelanggan terdiri dari 12 baris dan 6 variabel. Data transaksi
terdiri dari 11 baris dan menggunakan identifier
cust_id.
Grafik berikut digunakan untuk melihat sebaran usia pelanggan. Nilai usia 150 tahun pada C004 terlihat sangat jauh dari observasi lainnya dan menjadi indikasi adanya kesalahan domain.
ggplot(pelanggan_raw, aes(x = usia)) +
geom_histogram(
bins = 8,
fill = "#f8a5c2",
color = "#c44569",
na.rm = TRUE
) +
labs(
title = "Distribusi Usia Pelanggan",
x = "Usia",
y = "Jumlah Pelanggan"
) +
theme_minimal(base_size = 12)
Grafik berikut memperlihatkan perbedaan skala pendapatan. Nilai C007 sebesar Rp500.000.000 terlihat sangat ekstrem dibandingkan observasi lainnya sehingga perlu diperiksa sebagai kandidat outlier.
ggplot(pelanggan_raw, aes(x = pendapatan)) +
geom_histogram(
bins = 8,
fill = "#f8a5c2",
color = "#c44569",
na.rm = TRUE
) +
scale_x_continuous(labels = comma) +
labs(
title = "Distribusi Pendapatan Pelanggan",
x = "Pendapatan (Rp)",
y = "Jumlah Pelanggan"
) +
theme_minimal(base_size = 12)
Karena nilai C007 sangat besar, visualisasi tambahan berikut menggunakan skala logaritmik agar pola pendapatan pelanggan lainnya tetap terlihat.
ggplot(
pelanggan_raw,
aes(
x = reorder(customer_id, pendapatan),
y = pendapatan
)
) +
geom_col(
fill = "#c44569",
na.rm = TRUE
) +
scale_y_log10(labels = comma) +
labs(
title = "Pendapatan Pelanggan (Skala Logaritmik)",
x = "Customer ID",
y = "Pendapatan (Rp)"
) +
theme_minimal(base_size = 12)
Visualisasi transaksi digunakan untuk melihat perbedaan aktivitas pembelian antar pelanggan.
ggplot(
transaksi_raw,
aes(
x = reorder(cust_id, jumlah_transaksi),
y = jumlah_transaksi
)
) +
geom_col(
fill = "#d96c8a"
) +
labs(
title = "Jumlah Transaksi per Pelanggan",
x = "Customer ID",
y = "Jumlah Transaksi"
) +
theme_minimal(base_size = 12)
Masalah yang ditemukan pada data awal adalah:
customer_id C010 tercatat dua kali;cust_id, sedangkan
data pelanggan menggunakan customer_id.missing_awal <- data.frame(
atribut = names(pelanggan_raw),
jumlah_missing = sapply(pelanggan_raw, function(x) sum(is.na(x))),
persen_missing = round(sapply(pelanggan_raw, function(x) mean(is.na(x))*100),2)
)
datatable(missing_awal, filter="top", rownames=FALSE,
options=list(pageLength=10))
pelanggan <- pelanggan_raw
pelanggan$kota <- tolower(trimws(pelanggan$kota))
pelanggan$status <- tolower(trimws(pelanggan$status))
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"
Setelah standardisasi, kategori kota menjadi Pekanbaru, Dumai, dan Siak, sedangkan status menjadi Aktif dan Tidak Aktif.
# C010 muncul dua kali, sehingga salah satu baris dihapus
pelanggan <- pelanggan[!duplicated(pelanggan$customer_id), ]
rownames(pelanggan) <- NULL
Jumlah pelanggan setelah deduplikasi menjadi 11 pelanggan.
pelanggan$usia[pelanggan$customer_id == "C004"] <- 50
Usia C004 dikoreksi dari 150 menjadi 50 berdasarkan skenario data yang digunakan. Pendapatan tidak memiliki nilai negatif.
Untuk variabel numerik digunakan median karena terdapat nilai
pendapatan yang sangat ekstrem. Missing value pada kota diberi kategori
eksplisit Tidak diketahui.
median_usia <- median(pelanggan$usia, na.rm=TRUE)
median_pendapatan <- median(pelanggan$pendapatan, na.rm=TRUE)
pelanggan$usia_imputasi <- pelanggan$usia
pelanggan$usia_imputasi[is.na(pelanggan$usia_imputasi)] <- median_usia
pelanggan$pendapatan_imputasi <- pelanggan$pendapatan
pelanggan$pendapatan_imputasi[is.na(pelanggan$pendapatan_imputasi)] <- median_pendapatan
pelanggan$kota_imputasi <- pelanggan$kota
pelanggan$kota_imputasi[is.na(pelanggan$kota_imputasi)] <- "Tidak diketahui"
pelanggan$pendapatan_missing <- as.integer(is.na(pelanggan$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
pelanggan$outlier_pendapatan <- pelanggan$pendapatan_imputasi < batas_bawah | pelanggan$pendapatan_imputasi > batas_atas
hasil_outlier <- pelanggan[pelanggan$outlier_pendapatan,
c("customer_id","nama","pendapatan_imputasi")]
hasil_outlier
## customer_id nama pendapatan_imputasi
## 7 C007 Gilang 5e+08
Pendapatan C007 sebesar Rp500.000.000 merupakan kandidat outlier. Nilai tersebut tidak langsung dihapus karena belum ada bukti bahwa nilai tersebut merupakan kesalahan pencatatan.
Variabel numerik memiliki skala berbeda sehingga perlu ditransformasikan sebelum analisis berbasis jarak. Pada bagian ini digunakan normalisasi Min-Max sebagai hasil transformasi utama.
Rumus Min-Max:
nilai baru = (nilai - minimum) / (maksimum - minimum)
minmax <- function(x) {
r <- max(x, na.rm=TRUE) - min(x, na.rm=TRUE)
if(r == 0) return(rep(0, length(x)))
(x - min(x, na.rm=TRUE)) / r
}
pelanggan$usia_minmax <- minmax(pelanggan$usia_imputasi)
pelanggan$pendapatan_minmax <- minmax(pelanggan$pendapatan_imputasi)
transformasi <- pelanggan[, c("customer_id","usia_imputasi","usia_minmax",
"pendapatan_imputasi","pendapatan_minmax")]
datatable(transformasi, filter="top", rownames=FALSE,
options=list(pageLength=10, scrollX=TRUE))
Hasil normalisasi berada pada rentang 0 sampai 1 sehingga skala variabel numerik menjadi lebih sebanding untuk analisis selanjutnya.
Data transaksi menggunakan cust_id, sedangkan data
pelanggan menggunakan customer_id. Nama identifier
disamakan sebelum penggabungan.
transaksi <- transaksi_raw
names(transaksi)[names(transaksi) == "cust_id"] <- "customer_id"
pelanggan_tanpa_transaksi <- setdiff(pelanggan$customer_id, transaksi$customer_id)
transaksi_tanpa_pelanggan <- setdiff(transaksi$customer_id, pelanggan$customer_id)
list(
pelanggan_tanpa_transaksi = pelanggan_tanpa_transaksi,
transaksi_tanpa_pelanggan = transaksi_tanpa_pelanggan
)
## $pelanggan_tanpa_transaksi
## [1] "C011"
##
## $transaksi_tanpa_pelanggan
## [1] "C012"
C011 terdapat pada data pelanggan tetapi tidak memiliki pasangan transaksi. Sebaliknya, C012 terdapat pada data transaksi tetapi tidak terdapat pada data pelanggan.
Karena unit observasi adalah pelanggan, seluruh pelanggan
dipertahankan menggunakan merge() dengan
all.x = TRUE.
data_terintegrasi <- merge(
pelanggan,
transaksi,
by = "customer_id",
all.x = TRUE
)
Setelah data pelanggan dan transaksi digabungkan, total pembelian dapat divisualisasikan untuk melihat karakteristik aktivitas pembelian setiap pelanggan.
ggplot(
data_terintegrasi,
aes(
x = reorder(customer_id, total_purchase),
y = total_purchase
)
) +
geom_col(
fill = "#c44569",
na.rm = TRUE
) +
scale_y_continuous(labels = comma) +
labs(
title = "Total Pembelian per Pelanggan",
x = "Customer ID",
y = "Total Pembelian (Rp)"
) +
theme_minimal(base_size = 12)
Pada kasus ini diasumsikan bahwa pelanggan tanpa data transaksi berarti belum melakukan transaksi. Oleh karena itu, nilai transaksi yang kosong diubah menjadi 0.
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) == "usia_imputasi"] <- "usia"
names(data_final)[names(data_final) == "kota_imputasi"] <- "kota"
datatable(data_final, filter="top", rownames=FALSE,
options=list(pageLength=10, scrollX=TRUE, autoWidth=TRUE))
Tabel di atas bersifat interaktif. Kolom dapat dicari, difilter, dan diurutkan. Tabel juga dapat digeser secara horizontal apabila layar tidak cukup lebar.
perbandingan <- data.frame(
indikator = c("Jumlah baris", "Duplikasi customer_id", "Total missing value"),
sebelum = c(
nrow(pelanggan_raw),
sum(duplicated(pelanggan_raw$customer_id)),
sum(is.na(pelanggan_raw))
),
sesudah = c(
nrow(data_final),
sum(duplicated(data_final$customer_id)),
sum(is.na(data_final))
)
)
datatable(perbandingan, rownames=FALSE, options=list(dom="t"))
Hasil preprocessing menunjukkan bahwa jumlah pelanggan berkurang dari 12 menjadi 11 karena satu duplikasi C010 dihapus. Duplikasi identifier menjadi 0. Missing value pada variabel utama pelanggan telah ditangani melalui imputasi, sedangkan data transaksi pelanggan tanpa transaksi direpresentasikan sebagai 0 sesuai asumsi kasus.
Kategori kota dan status juga telah diseragamkan. Usia C004 diperbaiki dan pendapatan C007 ditandai sebagai kandidat outlier tanpa menghapus nilai aslinya.
Apakah dataset lebih siap untuk clustering?
Ya. Dataset sudah lebih konsisten karena duplikasi telah ditangani,
kategori telah distandardisasi, missing value telah ditangani, outlier
telah diperiksa, variabel numerik telah ditransformasi, dan data
pelanggan telah diintegrasikan dengan data transaksi.
Preprocessing dilakukan melalui lima tahapan utama, yaitu Understand, Clean, Transform, Integrate, dan Evaluate. Data awal memiliki beberapa permasalahan berupa missing value, ketidakkonsistenan kategori, duplikasi identifier, kesalahan nilai usia, dan nilai pendapatan ekstrem.
Setelah proses preprocessing, data menjadi lebih terstruktur dan konsisten. Dataset akhir dapat digunakan sebagai dasar untuk analisis clustering karena variabel numerik telah ditransformasikan dan informasi transaksi telah digabungkan dengan data pelanggan.