Data pelanggan dan data transaksi dapat memberikan gambaran mengenai profil pelanggan sekaligus aktivitas pembelian yang berlangsung. Namun, data yang dikumpulkan belum tentu langsung berada dalam kondisi yang siap dianalisis. Karena itu, kualitas dan struktur data perlu ditinjau terlebih dahulu agar hasil analisis berikutnya lebih dapat diandalkan.
Pengolahan ini memanfaatkan dua dataset, yaitu dataset pelanggan dan dataset transaksi. Dataset pelanggan memuat informasi identitas, usia, pendapatan, kota, serta status pelanggan. Sementara itu, dataset transaksi menggambarkan aktivitas pembelian melalui jumlah transaksi dan nilai total pembelian masing-masing pelanggan.
Pada pemeriksaan awal ditemukan beberapa kondisi yang perlu diperbaiki, antara lain nilai yang belum tersedia, variasi penulisan kategori, identifier pelanggan yang muncul lebih dari sekali, nilai numerik yang tidak sesuai dengan batas domain, dan nilai pendapatan yang jauh lebih besar daripada observasi lainnya. Kedua dataset juga menggunakan nama identifier yang berbeda sehingga perlu diselaraskan sebelum proses penggabungan.
Berdasarkan kondisi tersebut, preprocessing dilakukan secara bertahap agar data menjadi lebih terstruktur dan konsisten. Prosesnya mencakup audit awal, pembersihan data, penanganan missing value, pemeriksaan outlier, transformasi variabel, integrasi dataset, hingga evaluasi kondisi data setelah seluruh tahapan selesai.
Kasus yang digunakan menggambarkan sebuah perusahaan e-commerce yang ingin mempelajari karakteristik pelanggan beserta aktivitas transaksi mereka. Informasi yang tersedia berasal dari dua sumber, yaitu data pelanggan dan data transaksi.
Data pelanggan berfungsi sebagai sumber informasi mengenai profil pelanggan, sedangkan data transaksi digunakan untuk melihat aktivitas pembelian. Kedua sumber tersebut perlu dihubungkan melalui identifier pelanggan agar informasi yang berasal dari keduanya dapat dianalisis secara bersama.
Preprocessing dalam skenario ini tidak hanya berfokus pada penghapusan data bermasalah. Setiap tindakan terhadap data perlu didasarkan pada alasan yang dapat dipertanggungjawabkan sehingga perubahan yang dilakukan tetap dapat ditelusuri. Dengan pendekatan tersebut, dataset akhir diharapkan memiliki struktur yang lebih konsisten dan siap digunakan untuk analisis lanjutan.
getwd()
## [1] "C:/Users/LenovoX390/Downloads"
R.version.string
## [1] "R version 4.5.0 (2025-04-11 ucrt)"
Data pelanggan dan transaksi yang digunakan dalam pengolahan ini dibuat dalam bentuk dataset yang memiliki beberapa kondisi yang perlu ditangani melalui preprocessing.
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_raw
## customer_id nama usia pendapatan kota status
## 1 C001 Ani 21 4.5e+06 Pekanbaru Aktif
## 2 C002 Budi 25 NA PKU aktif
## 3 C003 Citra 23 5.2e+06 PEKANBARU ACTIVE
## 4 C004 Dodi 150 4.8e+06 Dumai A
## 5 C005 Eka 27 4.9e+06 pekanbaru Tidak Aktif
## 6 C006 Fani NA 5.1e+06 DUMAI nonaktif
## 7 C007 Gilang 31 5.0e+08 Pekanbaru Aktif
## 8 C008 Hana 29 4.7e+06 Siak AKTIF
## 9 C009 Indra 22 4.6e+06 PKU A
## 10 C010 Joko 35 5.3e+06 Dumai Tidak aktif
## 11 C010 Joko 35 5.3e+06 Dumai Tidak aktif
## 12 C011 Kiki 28 NA <NA> Aktif
transaksi_raw
## cust_id jumlah_transaksi total_purchase
## 1 C001 5 1.5e+06
## 2 C002 3 9.0e+05
## 3 C003 7 2.7e+06
## 4 C004 2 6.0e+05
## 5 C005 6 2.1e+06
## 6 C006 4 1.3e+06
## 7 C007 20 2.5e+07
## 8 C008 5 1.7e+06
## 9 C009 3 8.0e+05
## 10 C010 8 3.2e+06
## 11 C012 1 2.5e+05
Pemeriksaan terhadap data mentah memperlihatkan bahwa dataset
pelanggan terdiri dari 12 observasi dan 6 variabel. Beberapa nilai belum
tersedia pada variabel usia, pendapatan, dan kota. Selain itu,
identifier C010 muncul sebanyak dua kali. Pada sisi lain, dataset
transaksi memiliki 11 observasi dan menggunakan cust_id
sebagai identifier pelanggan.
Pemeriksaan awal dilakukan untuk mengetahui ukuran dataset, nama variabel, tipe data, beberapa observasi pertama, dan ringkasan statistik. Tahap ini membantu mengetahui kondisi data sebelum dilakukan perubahan.
dim(pelanggan_raw)
## [1] 12 6
names(pelanggan_raw)
## [1] "customer_id" "nama" "usia" "pendapatan" "kota"
## [6] "status"
str(pelanggan_raw)
## '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" ...
head(pelanggan_raw)
## customer_id nama usia pendapatan kota status
## 1 C001 Ani 21 4500000 Pekanbaru Aktif
## 2 C002 Budi 25 NA PKU aktif
## 3 C003 Citra 23 5200000 PEKANBARU ACTIVE
## 4 C004 Dodi 150 4800000 Dumai A
## 5 C005 Eka 27 4900000 pekanbaru Tidak Aktif
## 6 C006 Fani NA 5100000 DUMAI nonaktif
summary(pelanggan_raw)
## 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
##
##
##
##
Dari pemeriksaan struktur dataset dapat diketahui bahwa terdapat 12
observasi dengan 6 variabel pada data pelanggan.
customer_id, nama, kota, dan
status tersimpan sebagai karakter, sementara
usia serta pendapatan termasuk variabel
numerik.
Pemeriksaan kualitas dilakukan untuk mengetahui permasalahan yang dapat ditemukan secara langsung dari dataset. Fokus pemeriksaan meliputi missing value, duplikasi, konsistensi kategori, serta rentang variabel numerik.
colSums(is.na(pelanggan_raw))
## customer_id nama usia pendapatan kota status
## 0 0 1 2 1 0
round(colMeans(is.na(pelanggan_raw)) * 100, 2)
## customer_id nama usia pendapatan kota status
## 0.00 0.00 8.33 16.67 8.33 0.00
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" "aktif" "Aktif" "AKTIF"
## [6] "nonaktif" "Tidak aktif" "Tidak Aktif"
range(pelanggan_raw$usia, na.rm = TRUE)
## [1] 21 150
range(pelanggan_raw$pendapatan, na.rm = TRUE)
## [1] 4.5e+06 5.0e+08
Audit awal memperlihatkan adanya satu nilai kosong pada usia, dua pada pendapatan, dan satu pada kota. Pemeriksaan identifier juga menemukan satu pelanggan yang tercatat lebih dari sekali. Selain itu, kategori kota dan status ditulis dalam beberapa bentuk, padahal sebagian besar bentuk tersebut merujuk pada kategori yang sama.
Pada variabel usia terdapat nilai maksimum 150 tahun. Sementara itu, pendapatan mencapai 500 juta. Kedua nilai tersebut perlu ditinjau berdasarkan aturan domain dan karakteristik data sebelum masuk ke tahap analisis berikutnya.
Untuk mempermudah pemeriksaan, dibuat fungsi yang memberikan ringkasan tipe data, jumlah missing value, persentase missing value, dan jumlah kategori unik.
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
## atribut tipe jumlah_missing persen_missing jumlah_unik
## 1 customer_id character 0 0.00 11
## 2 nama character 0 0.00 11
## 3 usia numeric 1 8.33 11
## 4 pendapatan numeric 2 16.67 10
## 5 kota character 1 8.33 10
## 6 status character 0 0.00 8
Audit ini menjadi titik awal untuk mengetahui kualitas dataset sebelum perubahan dilakukan. Informasi yang diperoleh kemudian digunakan sebagai dasar dalam menentukan bagian data yang memerlukan tindakan preprocessing.
Data mentah tetap dipertahankan agar perubahan yang dilakukan selama preprocessing dapat dilacak dan dibandingkan dengan kondisi awal.
pelanggan <- pelanggan_raw
Pada data pelanggan terdapat spasi tambahan pada beberapa nilai kota serta perbedaan penggunaan huruf besar dan kecil. Kondisi tersebut dapat menyebabkan kategori yang sebenarnya sama terbaca sebagai kategori berbeda.
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"
Penyeragaman spasi dan penggunaan huruf membuat variasi kategori lebih mudah dikenali. Dengan bentuk penulisan yang sudah konsisten, proses standardisasi kategori dapat dilakukan dengan lebih terarah.
Kategori kota dan status kemudian disesuaikan berdasarkan aturan yang digunakan pada data.
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"
Setelah standardisasi diterapkan, variabel kota memiliki tiga kelompok utama, yaitu Pekanbaru, Dumai, dan Siak. Variabel status juga berhasil diringkas menjadi dua kategori, yakni Aktif dan Tidak Aktif.
Duplikasi customer_id perlu diperiksa karena dalam data
pelanggan satu identifier seharusnya mewakili satu pelanggan.
pelanggan[
duplicated(pelanggan$customer_id) |
duplicated(pelanggan$customer_id, fromLast = TRUE),
]
## customer_id nama usia pendapatan kota status
## 10 C010 Joko 35 5300000 Dumai Tidak Aktif
## 11 C010 Joko 35 5300000 Dumai Tidak Aktif
Pemeriksaan duplikasi menemukan identifier C010 muncul dua kali
dengan informasi yang identik. Karena setiap customer_id
seharusnya merepresentasikan satu pelanggan, salah satu baris duplikat
kemudian dikeluarkan dari dataset.
pelanggan <- pelanggan[
!duplicated(pelanggan$customer_id),
]
rownames(pelanggan) <- NULL
dim(pelanggan)
## [1] 11 6
Deduplikasi tersebut menghasilkan 11 pelanggan yang berbeda pada dataset kerja.
Nilai numerik juga diperiksa berdasarkan aturan yang sesuai dengan karakteristik data. Untuk usia digunakan rentang 15 sampai 100 tahun.
pelanggan[
pelanggan$usia < 15 |
pelanggan$usia > 100,
]
## customer_id nama usia pendapatan kota status
## 4 C004 Dodi 150 4800000 Dumai Aktif
## NA <NA> <NA> NA NA <NA> <NA>
Pada C004 ditemukan nilai usia sebesar 150 tahun, yang berada di luar rentang usia yang ditetapkan dalam skenario. Oleh sebab itu, berdasarkan informasi sumber asli yang digunakan pada data, nilai tersebut disesuaikan menjadi 50 tahun.
pelanggan$usia[
pelanggan$customer_id == "C004"
] <- 50
Pendapatan juga diperiksa untuk memastikan tidak terdapat nilai negatif.
pelanggan[
pelanggan$pendapatan < 0,
]
## customer_id nama usia pendapatan kota status
## NA <NA> <NA> NA NA <NA> <NA>
## NA.1 <NA> <NA> NA NA <NA> <NA>
Hasil pengecekan tidak menemukan nilai pendapatan negatif. Dengan demikian, tidak ada perubahan yang diperlukan untuk aturan tersebut.
Pencatatan perubahan dilakukan untuk menjaga keterlacakan proses, sehingga setiap keputusan preprocessing dapat diketahui kembali pada tahap evaluasi.
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
## tahap atribut
## 1 Standardisasi kota
## 2 Standardisasi status
## 3 Deduplikasi customer_id
## 4 Koreksi domain usia
## tindakan
## 1 PKU dan variasi kapital menjadi Pekanbaru
## 2 ACTIVE/A menjadi Aktif; nonaktif menjadi Tidak Aktif
## 3 Menghapus kemunculan kedua C010
## 4 Usia C004 dikoreksi dari 150 menjadi 50 berdasarkan sumber asli
Setelah cleaning, missing value diperiksa kembali untuk mengetahui bagian data yang masih belum lengkap.
colSums(is.na(pelanggan))
## customer_id nama usia pendapatan kota status
## 0 0 1 2 1 0
pelanggan[
!complete.cases(pelanggan),
]
## customer_id nama usia pendapatan kota status
## 2 C002 Budi 25 NA Pekanbaru Aktif
## 6 C006 Fani NA 5100000 Dumai Tidak Aktif
## 11 C011 Kiki 28 NA <NA> Aktif
Setelah tahap cleaning, beberapa nilai kosong masih ditemukan pada usia, pendapatan, dan kota. Kondisi tersebut selanjutnya digunakan sebagai dasar untuk menentukan strategi penanganan missing value yang sesuai.
Sebagai perbandingan, dilakukan simulasi apabila seluruh baris yang memiliki missing value dihapus.
pelanggan_complete <- pelanggan[
complete.cases(pelanggan),
]
nrow(pelanggan)
## [1] 11
nrow(pelanggan_complete)
## [1] 8
round(
(1 - nrow(pelanggan_complete) /
nrow(pelanggan)) * 100,
2
)
## [1] 27.27
Jika seluruh observasi yang mengandung nilai kosong dihapus, ukuran dataset akan berkurang cukup banyak. Karena pertimbangan tersebut, penghapusan baris hanya digunakan sebagai pembanding dan bukan sebagai hasil preprocessing utama.
Pendapatan memiliki satu nilai yang sangat ekstrem, yaitu 500 juta. Karena nilai ekstrem dapat memengaruhi nilai rata-rata, 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
median_pendapatan
## [1] 4900000
Nilai mean dan median pendapatan menunjukkan perbedaan yang cukup besar. Hal ini mengindikasikan bahwa observasi ekstrem memberikan pengaruh kuat terhadap rata-rata. Oleh karena itu, median dipilih sebagai nilai pengganti untuk missing value pada pendapatan.
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"
)
]
## customer_id usia usia_imputasi pendapatan pendapatan_imputasi
## 1 C001 21 21.0 4.5e+06 4.5e+06
## 2 C002 25 25.0 NA 4.9e+06
## 3 C003 23 23.0 5.2e+06 5.2e+06
## 4 C004 50 50.0 4.8e+06 4.8e+06
## 5 C005 27 27.0 4.9e+06 4.9e+06
## 6 C006 NA 27.5 5.1e+06 5.1e+06
## 7 C007 31 31.0 5.0e+08 5.0e+08
## 8 C008 29 29.0 4.7e+06 4.7e+06
## 9 C009 22 22.0 4.6e+06 4.6e+06
## 10 C010 35 35.0 5.3e+06 5.3e+06
## 11 C011 28 28.0 NA 4.9e+06
Pendekatan ini mempertahankan nilai asli sekaligus menyediakan kolom baru yang berisi hasil imputasi, sehingga perubahan terhadap data awal tetap dapat dilacak.
Nilai kosong pada kota tidak langsung digantikan dengan modus. Cara
tersebut berpotensi membuat data terlihat seolah-olah memiliki informasi
yang pasti. Sebagai alternatif, missing value diberi label eksplisit
Tidak diketahui agar kondisi data aslinya tetap
tercermin.
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
Sebuah variabel indikator juga dibuat untuk menandai observasi yang pada data awal memiliki nilai pendapatan yang kosong. Dengan demikian, informasi mengenai kondisi awal data tidak hilang setelah imputasi.
pelanggan$pendapatan_missing <-
as.integer(
is.na(pelanggan$pendapatan)
)
table(
pelanggan$pendapatan_missing
)
##
## 0 1
## 9 2
Perbandingan visual dilakukan untuk melihat perubahan distribusi pendapatan akibat imputasi.
par(mfrow = c(1, 2))
hist(
pelanggan$pendapatan,
main = "Sebelum Imputasi",
xlab = "Pendapatan",
col = "gray80",
breaks = 8
)
hist(
pelanggan$pendapatan_imputasi,
main = "Sesudah Imputasi Median",
xlab = "Pendapatan",
col = "gray55",
breaks = 8
)
par(mfrow = c(1, 1))
Setelah imputasi diterapkan, jumlah nilai yang tersedia menjadi lebih lengkap. Namun, imputasi tetap dapat memengaruhi bentuk distribusi dan tingkat variasi data. Karena itu, metode imputasi yang digunakan perlu diperhatikan ketika hasil dataset tersebut digunakan dalam analisis lanjutan.
Outlier pada pendapatan diperiksa secara visual menggunakan boxplot.
boxplot(
pelanggan$pendapatan_imputasi,
horizontal = TRUE,
col = "gray75",
main = "Boxplot Pendapatan",
xlab = "Pendapatan"
)
Visualisasi boxplot memperlihatkan satu observasi pendapatan yang posisinya sangat jauh dibandingkan sebagian besar nilai lainnya. Kondisi ini menjadi indikasi awal adanya kandidat outlier.
Deteksi outlier dilakukan menggunakan metode Interquartile Range (IQR).
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
Perhitungan berbasis IQR menghasilkan batas bawah sebesar 4.150.000 dan batas atas sebesar 5.750.000. Observasi pendapatan yang berada di luar interval tersebut kemudian ditandai sebagai kandidat outlier.
pelanggan$outlier_pendapatan <-
pelanggan$pendapatan_imputasi <
batas_bawah |
pelanggan$pendapatan_imputasi >
batas_atas
pelanggan[
pelanggan$outlier_pendapatan,
c(
"customer_id",
"nama",
"pendapatan_imputasi"
)
]
## customer_id nama pendapatan_imputasi
## 7 C007 Gilang 5e+08
Dari hasil deteksi, C007 dengan pendapatan 500.000.000 menjadi observasi yang teridentifikasi sebagai kandidat outlier.
Kandidat outlier tidak serta-merta dihilangkan karena nilai ekstrem belum tentu menunjukkan kesalahan pencatatan. Pendapatan 500 juta masih mungkin merupakan nilai yang benar dan dapat mencerminkan pelanggan dengan karakteristik ekonomi yang berbeda. Karena tidak ditemukan bukti bahwa nilai tersebut merupakan kesalahan input, observasi asli tetap dipertahankan.
Sebagai pembanding, dibuat versi winsorized dengan membatasi nilai tersebut pada batas atas.
pelanggan$pendapatan_winsor <- pmin(
pmax(
pelanggan$pendapatan_imputasi,
batas_bawah
),
batas_atas
)
pelanggan[
pelanggan$outlier_pendapatan,
c(
"customer_id",
"pendapatan_imputasi",
"pendapatan_winsor"
)
]
## customer_id pendapatan_imputasi pendapatan_winsor
## 7 C007 5e+08 5750000
Winsorizing digunakan sebagai skenario pembanding untuk mengetahui perubahan yang terjadi apabila nilai ekstrem dibatasi. Metode ini tidak ditetapkan sebagai pengganti nilai asli secara otomatis.
Transformasi digunakan untuk mengubah skala data numerik agar lebih sesuai dengan kebutuhan analisis.
Normalisasi min–maks menggunakan rumus:
\[ x' = \frac{x-\min(x)}{\max(x)-\min(x)} \]
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"
)
]
## customer_id usia usia_minmax pendapatan_imputasi pendapatan_minmax
## 1 C001 21 0.00000000 4.5e+06 0.0000000000
## 2 C002 25 0.13793103 4.9e+06 0.0008072654
## 3 C003 23 0.06896552 5.2e+06 0.0014127144
## 4 C004 50 1.00000000 4.8e+06 0.0006054490
## 5 C005 27 0.20689655 4.9e+06 0.0008072654
## 6 C006 NA 0.22413793 5.1e+06 0.0012108981
## 7 C007 31 0.34482759 5.0e+08 1.0000000000
## 8 C008 29 0.27586207 4.7e+06 0.0004036327
## 9 C009 22 0.03448276 4.6e+06 0.0002018163
## 10 C010 35 0.48275862 5.3e+06 0.0016145308
## 11 C011 28 0.24137931 4.9e+06 0.0008072654
Transformasi Min-Max menghasilkan nilai dalam interval 0 hingga 1. Pada variabel pendapatan, nilai ekstrem membuat sebagian besar observasi lainnya terkonsentrasi pada bagian bawah rentang tersebut.
Standardisasi Z-score mengubah nilai berdasarkan posisi observasi terhadap rata-rata dan simpangan bakunya.
\[ 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
)
## usia_z pendapatan_z
## 1 -0.984 -0.304
## 2 -0.489 -0.301
## 3 -0.737 -0.299
## 4 2.604 -0.302
## 5 -0.242 -0.301
## 6 -0.180 -0.300
## 7 0.253 3.015
## 8 0.006 -0.303
## 9 -0.860 -0.303
## 10 0.748 -0.299
## 11 -0.118 -0.301
Decimal scaling dilakukan dengan membagi nilai menggunakan pangkat sepuluh tertentu.
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
Ketiga hasil transformasi pendapatan dibandingkan untuk melihat perbedaan skala yang dihasilkan.
transformasi <- pelanggan[
,
c(
"customer_id",
"pendapatan_imputasi",
"pendapatan_minmax",
"pendapatan_z",
"pendapatan_decimal"
)
]
transformasi
## customer_id pendapatan_imputasi pendapatan_minmax pendapatan_z
## 1 C001 4.5e+06 0.0000000000 -0.3041235
## 2 C002 4.9e+06 0.0008072654 -0.3014440
## 3 C003 5.2e+06 0.0014127144 -0.2994343
## 4 C004 4.8e+06 0.0006054490 -0.3021138
## 5 C005 4.9e+06 0.0008072654 -0.3014440
## 6 C006 5.1e+06 0.0012108981 -0.3001042
## 7 C007 5.0e+08 1.0000000000 3.0151095
## 8 C008 4.7e+06 0.0004036327 -0.3027837
## 9 C009 4.6e+06 0.0002018163 -0.3034536
## 10 C010 5.3e+06 0.0016145308 -0.2987645
## 11 C011 4.9e+06 0.0008072654 -0.3014440
## pendapatan_decimal
## 1 0.0045
## 2 0.0049
## 3 0.0052
## 4 0.0048
## 5 0.0049
## 6 0.0051
## 7 0.5000
## 8 0.0047
## 9 0.0046
## 10 0.0053
## 11 0.0049
Ketiga metode menghasilkan skala yang berbeda karena masing-masing memiliki mekanisme transformasi tersendiri. Oleh karena itu, metode yang dipilih pada tahap analisis selanjutnya sebaiknya disesuaikan dengan kebutuhan dan karakteristik model yang akan digunakan.
Untuk melihat pengaruh outlier, dilakukan normalisasi terhadap data pendapatan yang telah di-winsorize.
pelanggan$pendapatan_winsor_minmax <-
minmax(
pelanggan$pendapatan_winsor
)
plot(
pelanggan$pendapatan_minmax,
pelanggan$pendapatan_winsor_minmax,
pch = 19,
col = "gray35",
xlab = "Min–maks Data Asli",
ylab = "Min–maks Data Winsorized",
main = "Dampak Penanganan Outlier terhadap Normalisasi"
)
abline(
0,
1,
col = "red",
lty = 2
)
Perbandingan tersebut memperlihatkan bahwa keberadaan nilai ekstrem dapat mempersempit rentang relatif observasi lainnya pada normalisasi Min-Max. Hal ini menunjukkan pentingnya mengevaluasi outlier sebelum menentukan teknik transformasi yang akan digunakan.
Sebelum kedua dataset digabungkan, identifier diperiksa untuk memastikan tidak terdapat duplikasi dan untuk mengetahui perbedaan pelanggan antara kedua sumber data.
sum(
duplicated(
pelanggan$customer_id
)
)
## [1] 0
sum(
duplicated(
transaksi_raw$cust_id
)
)
## [1] 0
setdiff(
pelanggan$customer_id,
transaksi_raw$cust_id
)
## [1] "C011"
setdiff(
transaksi_raw$cust_id,
pelanggan$customer_id
)
## [1] "C012"
Pencocokan identifier memperlihatkan adanya perbedaan antara kedua sumber. C011 tercatat pada data pelanggan tetapi tidak memiliki data transaksi, sedangkan C012 muncul pada data transaksi tanpa pasangan pada data pelanggan.
Data transaksi menggunakan nama cust_id, sedangkan data
pelanggan menggunakan customer_id. Nama identifier
disesuaikan agar kedua data dapat digabungkan.
transaksi <- transaksi_raw
names(transaksi)[
names(transaksi) == "cust_id"
] <- "customer_id"
Proses merge dirancang untuk mempertahankan seluruh observasi dari data pelanggan. Dengan demikian, pelanggan yang belum memiliki pasangan transaksi tetap tercantum pada dataset hasil penggabungan.
data_terintegrasi <- merge(
pelanggan,
transaksi,
by = "customer_id",
all.x = TRUE
)
data_terintegrasi[
,
c(
"customer_id",
"nama",
"jumlah_transaksi",
"total_purchase"
)
]
## customer_id nama jumlah_transaksi total_purchase
## 1 C001 Ani 5 1.5e+06
## 2 C002 Budi 3 9.0e+05
## 3 C003 Citra 7 2.7e+06
## 4 C004 Dodi 2 6.0e+05
## 5 C005 Eka 6 2.1e+06
## 6 C006 Fani 4 1.3e+06
## 7 C007 Gilang 20 2.5e+07
## 8 C008 Hana 5 1.7e+06
## 9 C009 Indra 3 8.0e+05
## 10 C010 Joko 8 3.2e+06
## 11 C011 Kiki NA NA
Setelah penggabungan dilakukan, hasil integrasi diperiksa kembali.
c(
sebelum = nrow(pelanggan),
sesudah = nrow(data_terintegrasi)
)
## sebelum sesudah
## 11 11
sum(
duplicated(
data_terintegrasi$customer_id
)
)
## [1] 0
colSums(
is.na(
data_terintegrasi[
,
c(
"jumlah_transaksi",
"total_purchase"
)
]
)
)
## jumlah_transaksi total_purchase
## 1 1
data_terintegrasi[
is.na(
data_terintegrasi$jumlah_transaksi
),
c(
"customer_id",
"nama"
)
]
## customer_id nama
## 11 C011 Kiki
Setelah integrasi, jumlah pelanggan tetap sebanyak 11. C011 memiliki nilai kosong pada atribut transaksi karena tidak ditemukan record transaksi yang sesuai pada sumber kedua.
Nilai NA pada transaksi perlu dipahami terlebih dahulu.
Nilai tersebut dapat berarti pelanggan memang belum pernah bertransaksi
atau data transaksi pelanggan tidak tersedia. Oleh karena itu, perubahan
NA menjadi nol hanya dilakukan apabila telah diketahui
bahwa NA memang berarti tidak ada transaksi.
Dalam skenario ini diasumsikan bahwa tidak adanya record transaksi untuk C011 menunjukkan bahwa pelanggan tersebut belum melakukan transaksi. Atas dasar asumsi tersebut, nilai transaksi direpresentasikan sebagai nol.
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
Setelah seluruh proses preprocessing selesai, dipilih atribut yang dibutuhkan untuk membentuk dataset akhir.
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
## customer_id nama usia kota status pendapatan_imputasi
## 1 C001 Ani 21.0 Pekanbaru Aktif 4.5e+06
## 2 C002 Budi 25.0 Pekanbaru Aktif 4.9e+06
## 3 C003 Citra 23.0 Pekanbaru Aktif 5.2e+06
## 4 C004 Dodi 50.0 Dumai Aktif 4.8e+06
## 5 C005 Eka 27.0 Pekanbaru Tidak Aktif 4.9e+06
## 6 C006 Fani 27.5 Dumai Tidak Aktif 5.1e+06
## 7 C007 Gilang 31.0 Pekanbaru Aktif 5.0e+08
## 8 C008 Hana 29.0 Siak Aktif 4.7e+06
## 9 C009 Indra 22.0 Pekanbaru Aktif 4.6e+06
## 10 C010 Joko 35.0 Dumai Tidak Aktif 5.3e+06
## 11 C011 Kiki 28.0 Tidak diketahui Aktif 4.9e+06
## pendapatan_missing outlier_pendapatan usia_minmax pendapatan_minmax
## 1 0 FALSE 0.00000000 0.0000000000
## 2 1 FALSE 0.13793103 0.0008072654
## 3 0 FALSE 0.06896552 0.0014127144
## 4 0 FALSE 1.00000000 0.0006054490
## 5 0 FALSE 0.20689655 0.0008072654
## 6 0 FALSE 0.22413793 0.0012108981
## 7 0 TRUE 0.34482759 1.0000000000
## 8 0 FALSE 0.27586207 0.0004036327
## 9 0 FALSE 0.03448276 0.0002018163
## 10 0 FALSE 0.48275862 0.0016145308
## 11 1 FALSE 0.24137931 0.0008072654
## jumlah_transaksi_final total_purchase_final
## 1 5 1.5e+06
## 2 3 9.0e+05
## 3 7 2.7e+06
## 4 2 6.0e+05
## 5 6 2.1e+06
## 6 4 1.3e+06
## 7 20 2.5e+07
## 8 5 1.7e+06
## 9 3 8.0e+05
## 10 8 3.2e+06
## 11 0 0.0e+00
Dataset akhir menggabungkan profil pelanggan yang telah melalui proses cleaning dengan informasi transaksi. Beberapa atribut hasil imputasi dan transformasi tetap disertakan agar dapat dimanfaatkan pada analisis berikutnya.
Audit akhir dilakukan untuk memastikan tidak terdapat missing value yang tidak ditangani dan tidak terdapat duplikasi identifier.
audit_akhir <- audit_data(
data_final
)
audit_akhir
## atribut tipe jumlah_missing persen_missing jumlah_unik
## 1 customer_id character 0 0 11
## 2 nama character 0 0 11
## 3 usia numeric 0 0 11
## 4 kota character 0 0 4
## 5 status character 0 0 2
## 6 pendapatan_imputasi numeric 0 0 9
## 7 pendapatan_missing integer 0 0 2
## 8 outlier_pendapatan logical 0 0 2
## 9 usia_minmax numeric 0 0 11
## 10 pendapatan_minmax numeric 0 0 9
## 11 jumlah_transaksi_final numeric 0 0 9
## 12 total_purchase_final numeric 0 0 11
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
Audit akhir menunjukkan bahwa seluruh nilai kosong telah ditangani dan tidak ditemukan lagi identifier pelanggan yang berulang. Kondisi ini menunjukkan adanya peningkatan konsistensi dataset dibandingkan keadaan sebelum preprocessing.
Perbandingan dilakukan untuk melihat perubahan kualitas data setelah preprocessing.
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
## indikator sebelum sesudah
## 1 Jumlah baris 12 11
## 2 Duplikasi customer_id 1 0
## 3 Total missing value 4 0
## 4 Kategori kota unik 10 4
## 5 Kategori status unik 8 2
Jika dibandingkan dengan kondisi awal, jumlah observasi berubah dari
12 menjadi 11 karena satu baris duplikat dikeluarkan. Duplikasi
customer_id tidak lagi ditemukan, dan nilai kosong telah
ditangani. Selain itu, variasi kategori pada kota dan status berhasil
diringkas menjadi bentuk yang lebih seragam.
Dataset akhir dan catatan perubahan disimpan agar dapat digunakan kembali pada tahap 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
)
Secara keseluruhan, pemeriksaan awal memperlihatkan bahwa data pelanggan dan transaksi masih membutuhkan beberapa perbaikan kualitas. Masalah yang ditemukan meliputi nilai kosong pada usia, pendapatan, dan kota, identifier pelanggan yang berulang, serta ketidakseragaman penulisan pada kategori kota dan status.
Pembersihan dimulai dengan menghilangkan spasi yang tidak diperlukan
serta menyamakan penggunaan huruf. Setelah itu, berbagai bentuk
penulisan kota, seperti PKU, PEKANBARU, dan
pekanbaru, dipetakan ke kategori Pekanbaru.
Hal serupa dilakukan pada status, sehingga ACTIVE,
A, dan aktif menjadi Aktif.
Identifier C010 yang terduplikasi kemudian ditangani dengan
mempertahankan satu record.
Pada pemeriksaan domain, nilai usia 150 tahun pada C004 teridentifikasi sebagai nilai yang tidak wajar menurut batas yang digunakan. Nilai ini kemudian diperbaiki menjadi 50 berdasarkan sumber asli. Sementara itu, tidak ada pendapatan negatif, tetapi terdapat satu nilai pendapatan 500 juta yang jauh lebih tinggi daripada sebagian besar observasi.
Untuk variabel numerik, missing value pada usia dan pendapatan diisi
menggunakan median. Median dipilih terutama karena distribusi pendapatan
dipengaruhi oleh nilai ekstrem. Pada variabel kota, nilai kosong diberi
kategori Tidak diketahui sehingga ketidaklengkapan
informasi tetap dapat dikenali.
Metode IQR digunakan untuk menilai keberadaan outlier pada pendapatan. C007 dengan nilai 500 juta teridentifikasi sebagai kandidat outlier. Namun, observasi tersebut tetap dipertahankan karena belum ada dasar yang cukup untuk menyatakan bahwa nilainya salah. Winsorizing kemudian digunakan sebagai alternatif pembanding untuk melihat dampak pembatasan nilai ekstrem terhadap transformasi.
Pada tahap transformasi diterapkan tiga pendekatan, yaitu Min-Max, Z-Score, dan Decimal Scaling. Perbandingan hasil menunjukkan bahwa nilai ekstrem dapat memberikan pengaruh besar terhadap skala, terutama pada Min-Max. Oleh sebab itu, evaluasi outlier perlu dilakukan sebelum menentukan transformasi yang paling sesuai.
Integrasi kedua sumber dilakukan melalui customer_id.
Pemeriksaan kunci menunjukkan bahwa C011 hanya terdapat pada data
pelanggan, sedangkan C012 hanya muncul pada data transaksi. Karena
seluruh pelanggan perlu dipertahankan, penggabungan dilakukan dengan
merge() menggunakan all.x = TRUE.
Setelah seluruh tahapan selesai, dataset akhir tidak lagi mengandung nilai kosong dan tidak memiliki identifier pelanggan yang duplikat. Kategori kota dan status juga telah diseragamkan. Dengan kondisi tersebut, dataset menjadi lebih rapi, konsisten, dan lebih siap digunakan sebagai dasar analisis lanjutan.
Dataset awal masih memiliki beberapa persoalan kualitas yang perlu diselesaikan sebelum analisis dilakukan. Persoalan tersebut mencakup missing value, variasi kategori, identifier ganda, nilai usia di luar batas domain, serta satu nilai pendapatan yang sangat ekstrem.
Tahapan preprocessing mencakup audit awal, cleaning, penanganan
missing value, pemeriksaan outlier, transformasi, serta integrasi.
Missing value numerik diisi dengan median, sedangkan kota yang tidak
memiliki informasi diberi label Tidak diketahui. Duplikasi
C010 dihilangkan dan usia C004 disesuaikan berdasarkan aturan domain
yang digunakan.
Penerapan IQR mengidentifikasi pendapatan C007 sebagai kandidat outlier. Karena belum ada bukti bahwa nilai tersebut merupakan kesalahan, nilai asli tetap disimpan dan hasil winsorizing digunakan sebagai pembanding. Selanjutnya, variabel numerik ditransformasikan dengan Min-Max, Z-Score, serta Decimal Scaling.
Pada bagian akhir, kedua sumber data berhasil digabungkan melalui
customer_id. Dataset hasil preprocessing menunjukkan
struktur yang lebih seragam, seluruh missing value telah ditangani, dan
tidak ditemukan lagi identifier yang berulang. Oleh karena itu, dataset
ini dapat dijadikan dasar untuk analisis karakteristik pelanggan maupun
aktivitas transaksi pada tahap berikutnya.
Secara ringkas, proses yang diterapkan dimulai dari audit kondisi data, dilanjutkan dengan cleaning, penanganan missing value, evaluasi outlier, transformasi numerik, penggabungan data pelanggan dan transaksi, serta audit akhir.
Preprocessing tidak dilakukan hanya dengan menghapus observasi yang bermasalah. Setiap tindakan mempertimbangkan karakteristik data dan tujuan pengolahan. Dengan demikian, informasi penting dari dataset awal tetap dipertahankan sambil meningkatkan konsistensi data untuk analisis berikutnya.
Batini, C., & Scannapieco, M. (2016). Data and Information Quality: Dimensions, Principles and Techniques. Springer.
Kuhn, M., & Johnson, K. (2013). Applied Predictive Modeling. Springer.
Wickham, H., & Grolemund, G. (2017). R for Data Science: Import, Tidy, Transform, Visualize, and Model Data. O’Reilly Media.