Nama: Tengku Alfaisal Hardian
NIM: 2403112815
Prodi: Statistika
Kelas: A
Setelah menyelesaikan praktikum ini, mahasiswa diharapkan mampu:
Sebuah toko daring sedang bersiap melakukan analisis pelanggan. Data yang tersedia berasal dari dua sumber yang terpisah, yaitu data pelanggan yang memuat identitas, usia, kota, pendapatan, dan status keanggotaan, serta data transaksi yang memuat jumlah transaksi dan total nilai pembelian setiap pelanggan.
Kedua sumber data ini belum dapat langsung dianalisis karena masih memuat berbagai masalah, seperti nilai yang hilang, penulisan kategori yang tidak seragam, baris yang tercatat berulang, nilai yang secara logika tidak masuk akal, nilai ekstrem yang mencolok, serta perbedaan nama kolom identifier antar tabel. Tugas mahasiswa adalah menyiapkan kedua data tersebut hingga menjadi satu dataset analisis yang bersih dan siap dipakai.
Prinsip yang perlu dipegang selama praktikum adalah jangan membersihkan data hanya karena sebuah nilai terlihat aneh. Setiap perubahan yang dilakukan harus memiliki alasan yang jelas, mengikuti aturan tertentu, dan dicatat supaya bisa ditelusuri kembali.
getwd()
## [1] "D:/PERKULIAHAN/SEMESTER 5/Data Mining sem 5"
R.version.string
## [1] "R version 4.4.1 (2024-06-14 ucrt)"
Modul ini hanya memakai fungsi bawaan R sehingga dapat langsung dijalankan tanpa perlu memasang paket tambahan apa pun.
Dataset pada latihan ini sengaja dibuat kotor supaya seluruh tahap preprocessing dapat benar benar dipraktikkan. Data pelanggan berjumlah 32 baris dan data transaksi berjumlah 31 baris. Sebagian besar baris awal tetap dipertahankan sama persis seperti modul aslinya, kemudian ditambahkan baris baris baru yang juga memuat berbagai masalah data agar latihan menjadi lebih lengkap.
pelanggan_raw <- data.frame(
customer_id = c("C001","C002","C003","C004","C005","C006","C007","C008",
"C009","C010","C010","C011","C013","C014","C015","C016",
"C017","C018","C019","C020","C021","C022","C023","C024",
"C025","C026","C027","C028","C029","C030","C031","C032"),
nama = c("Ani","Budi","Citra","Dodi","Eka","Fani","Gilang","Hana","Indra",
"Joko","Joko","Kiki","Lestari","Made","Nita","Oscar","Putri","Qori",
"Rian","Sinta","Tono","Umi","Vino","Wulan","Xena","Yoga","Zaki",
"Aditya","Bella","Cahyo","Dewi","Eko"),
usia = c(21,25,23,150,27,NA,31,29,22,35,35,28,24,26,33,19,41,NA,45,30,5,27,
38,29,34,24,31,26,NA,37,28,33),
pendapatan = c(4500000,NA,5200000,4800000,4900000,5100000,500000000,4700000,
4600000,5300000,5300000,NA,4750000,NA,5000000,4650000,5400000,
4850000,5600000,200000000,4700000,4900000,NA,5100000,4800000,
4950000,5050000,4700000,4800000,5150000,4900000,5200000),
kota = c("Pekanbaru"," PKU","PEKANBARU","Dumai","pekanbaru","DUMAI",
"Pekanbaru ","Siak","PKU","Dumai","Dumai",NA,"Pekanbaru"," pekanbaru",
"DUMAI","Siak","Pekanbaru ","dumai","PKU","Pekanbaru","Siak",
"PEKANBARU","Dumai"," Siak","pku","Dumai","Pekanbaru","DUMAI","Siak",
"pekanbaru",NA,"Dumai"),
status = c("Aktif","aktif","ACTIVE","A","Tidak Aktif","nonaktif","Aktif",
"AKTIF","A","Tidak aktif","Tidak aktif","Aktif","Aktif","aktif",
"AKTIF","A","Tidak aktif","nonaktif","Aktif","Aktif","Tidak Aktif",
"ACTIVE","A","aktif","Tidak aktif","Aktif","nonaktif","AKTIF","A",
"Tidak Aktif","Aktif","aktif"),
stringsAsFactors = FALSE
)
pelanggan_raw
## customer_id nama usia pendapatan kota status
## 1 C001 Ani 21 4.50e+06 Pekanbaru Aktif
## 2 C002 Budi 25 NA PKU aktif
## 3 C003 Citra 23 5.20e+06 PEKANBARU ACTIVE
## 4 C004 Dodi 150 4.80e+06 Dumai A
## 5 C005 Eka 27 4.90e+06 pekanbaru Tidak Aktif
## 6 C006 Fani NA 5.10e+06 DUMAI nonaktif
## 7 C007 Gilang 31 5.00e+08 Pekanbaru Aktif
## 8 C008 Hana 29 4.70e+06 Siak AKTIF
## 9 C009 Indra 22 4.60e+06 PKU A
## 10 C010 Joko 35 5.30e+06 Dumai Tidak aktif
## 11 C010 Joko 35 5.30e+06 Dumai Tidak aktif
## 12 C011 Kiki 28 NA <NA> Aktif
## 13 C013 Lestari 24 4.75e+06 Pekanbaru Aktif
## 14 C014 Made 26 NA pekanbaru aktif
## 15 C015 Nita 33 5.00e+06 DUMAI AKTIF
## 16 C016 Oscar 19 4.65e+06 Siak A
## 17 C017 Putri 41 5.40e+06 Pekanbaru Tidak aktif
## 18 C018 Qori NA 4.85e+06 dumai nonaktif
## 19 C019 Rian 45 5.60e+06 PKU Aktif
## 20 C020 Sinta 30 2.00e+08 Pekanbaru Aktif
## 21 C021 Tono 5 4.70e+06 Siak Tidak Aktif
## 22 C022 Umi 27 4.90e+06 PEKANBARU ACTIVE
## 23 C023 Vino 38 NA Dumai A
## 24 C024 Wulan 29 5.10e+06 Siak aktif
## 25 C025 Xena 34 4.80e+06 pku Tidak aktif
## 26 C026 Yoga 24 4.95e+06 Dumai Aktif
## 27 C027 Zaki 31 5.05e+06 Pekanbaru nonaktif
## 28 C028 Aditya 26 4.70e+06 DUMAI AKTIF
## 29 C029 Bella NA 4.80e+06 Siak A
## 30 C030 Cahyo 37 5.15e+06 pekanbaru Tidak Aktif
## 31 C031 Dewi 28 4.90e+06 <NA> Aktif
## 32 C032 Eko 33 5.20e+06 Dumai aktif
transaksi_raw <- data.frame(
cust_id = c("C001","C002","C003","C004","C005","C006","C007","C008","C009",
"C010","C012","C013","C014","C015","C016","C017","C018","C019",
"C020","C021","C022","C023","C024","C025","C026","C027","C028",
"C029","C030","C015","C034"),
jumlah_transaksi = c(5,3,7,2,6,4,20,5,3,8,1,4,6,9,2,11,3,12,30,1,5,7,4,6,3,
8,5,2,9,9,4),
total_purchase = c(1500000,900000,2700000,600000,2100000,1300000,25000000,
1700000,800000,3200000,250000,1350000,2400000,3600000,
650000,4600000,950000,5000000,60000000,300000,1650000,
2500000,1300000,2050000,900000,3100000,1600000,600000,
3400000,3600000,1250000),
stringsAsFactors = FALSE
)
transaksi_raw
## cust_id jumlah_transaksi total_purchase
## 1 C001 5 1500000
## 2 C002 3 900000
## 3 C003 7 2700000
## 4 C004 2 600000
## 5 C005 6 2100000
## 6 C006 4 1300000
## 7 C007 20 25000000
## 8 C008 5 1700000
## 9 C009 3 800000
## 10 C010 8 3200000
## 11 C012 1 250000
## 12 C013 4 1350000
## 13 C014 6 2400000
## 14 C015 9 3600000
## 15 C016 2 650000
## 16 C017 11 4600000
## 17 C018 3 950000
## 18 C019 12 5000000
## 19 C020 30 60000000
## 20 C021 1 300000
## 21 C022 5 1650000
## 22 C023 7 2500000
## 23 C024 4 1300000
## 24 C025 6 2050000
## 25 C026 3 900000
## 26 C027 8 3100000
## 27 C028 5 1600000
## 28 C029 2 600000
## 29 C030 9 3400000
## 30 C015 9 3600000
## 31 C034 4 1250000
Beberapa masalah yang sengaja disisipkan pada kedua tabel di atas antara lain nilai usia yang tidak masuk akal (150 tahun dan 5 tahun), nilai pendapatan yang jauh lebih besar dari pelanggan lain, penulisan nama kota dan status keanggotaan yang tidak konsisten, baris pelanggan yang tercatat dua kali, baris transaksi yang tercatat dua kali, pelanggan yang tidak memiliki catatan transaksi (C011, C031, dan C032), serta transaksi yang identifier pelanggannya tidak ditemukan pada tabel pelanggan (C012 dan C034).
Sebelum melakukan perubahan apa pun, periksa terlebih dahulu struktur, ukuran, tipe atribut, dan beberapa baris awal dari data.
dim(pelanggan_raw)
## [1] 32 6
names(pelanggan_raw)
## [1] "customer_id" "nama" "usia" "pendapatan" "kota"
## [6] "status"
str(pelanggan_raw)
## 'data.frame': 32 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:32 Length:32 Min. : 5.00 Min. : 4500000
## Class :character Class :character 1st Qu.: 25.00 1st Qu.: 4787500
## Mode :character Mode :character Median : 29.00 Median : 4925000
## Mean : 32.97 Mean : 29603571
## 3rd Qu.: 34.00 3rd Qu.: 5200000
## Max. :150.00 Max. :500000000
## NA's :3 NA's :4
## kota status
## Length:32 Length:32
## Class :character Class :character
## Mode :character Mode :character
##
##
##
##
Han, Kamber, dan Pei menyebutkan enam dimensi kualitas data, yaitu akurasi, kelengkapan, konsistensi, ketepatan waktu, keterpercayaan, dan kemudahan diinterpretasikan. Tidak semua dimensi tersebut bisa dihitung langsung dari isi tabel. Akurasi misalnya memerlukan pembanding terhadap kondisi yang sebenarnya, sedangkan ketepatan waktu membutuhkan informasi kapan data terakhir diperbarui. Pemeriksaan berikut hanya berfokus pada masalah yang memang bisa dideteksi langsung dari dataset yang tersedia.
colSums(is.na(pelanggan_raw))
## customer_id nama usia pendapatan kota status
## 0 0 3 4 2 0
round(colMeans(is.na(pelanggan_raw)) * 100, 2)
## customer_id nama usia pendapatan kota status
## 0.00 0.00 9.38 12.50 6.25 0.00
sum(duplicated(pelanggan_raw))
## [1] 1
sum(duplicated(pelanggan_raw$customer_id))
## [1] 1
sort(unique(pelanggan_raw$kota))
## [1] " pekanbaru" " PKU" " Siak" "dumai" "Dumai"
## [6] "DUMAI" "pekanbaru" "Pekanbaru" "PEKANBARU" "Pekanbaru "
## [11] "pku" "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] 5 150
range(pelanggan_raw$pendapatan, na.rm = TRUE)
## [1] 4.5e+06 5.0e+08
Supaya pemeriksaan kualitas data dapat dipakai berulang kali pada tahap tahap berikutnya, sebaiknya dibuat satu fungsi khusus yang merangkum kondisi setiap atribut.
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 31
## 2 nama character 0 0.00 31
## 3 usia numeric 3 9.38 22
## 4 pendapatan numeric 4 12.50 20
## 5 kota character 2 6.25 14
## 6 status character 0 0.00 8
Perlu diingat bahwa hasil audit ini bukan keputusan pembersihan data itu sendiri. Audit hanya memberi tanda atribut mana yang perlu diperiksa lebih lanjut, keputusan penanganannya tetap harus dipikirkan satu per satu.
Data mentah sebaiknya tetap dipertahankan apa adanya, sehingga setiap perubahan yang dilakukan pada salinannya masih bisa ditelusuri kembali ke sumber aslinya.
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"
Penyeragaman kategori sebaiknya mengikuti aturan yang sudah disepakati bersama pemilik data. Pada latihan ini aturan tersebut dianggap sudah dikonfirmasi sebelumnya.
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), ]
## customer_id nama usia pendapatan kota status
## 10 C010 Joko 35 5300000 Dumai Tidak Aktif
## 11 C010 Joko 35 5300000 Dumai Tidak Aktif
pelanggan <- pelanggan[!duplicated(pelanggan$customer_id), ]
rownames(pelanggan) <- NULL
dim(pelanggan)
## [1] 31 6
Perlu dicatat bahwa menghapus baris duplikat semacam ini hanya tepat dilakukan apabila memang setiap customer_id seharusnya hanya mewakili satu pelanggan saja. Apabila satu pelanggan memang boleh memiliki banyak baris riwayat, maka tindakan penghapusan ini justru keliru.
Nilai yang hilang (NA) perlu dikecualikan terlebih dahulu pada saat pemeriksaan ini, sebab tanpa pengecualian tersebut baris dengan usia atau pendapatan yang kosong akan ikut tercetak sebagai baris kosong yang membingungkan, padahal bukan itu yang sedang diperiksa.
pelanggan[!is.na(pelanggan$usia) & (pelanggan$usia < 15 | pelanggan$usia > 100), ]
## customer_id nama usia pendapatan kota status
## 4 C004 Dodi 150 4800000 Dumai Aktif
## 20 C021 Tono 5 4700000 Siak Tidak Aktif
pelanggan[!is.na(pelanggan$pendapatan) & pelanggan$pendapatan < 0, ]
## [1] customer_id nama usia pendapatan kota status
## <0 rows> (or 0-length row.names)
Pelanggan dengan kode C004 tercatat berusia 150 tahun, sedangkan pelanggan dengan kode C021 tercatat berusia 5 tahun. Kedua nilai ini melanggar aturan domain usia pelanggan yang wajar, misalnya rentang 15 sampai 100 tahun. Anggaplah setelah dicek ke formulir pendaftaran aslinya, usia C004 yang benar adalah 50 tahun, sedangkan usia C021 yang benar adalah 25 tahun.
pelanggan$usia[pelanggan$customer_id == "C004"] <- 50
pelanggan$usia[pelanggan$customer_id == "C021"] <- 25
Setiap tahap pembersihan sebaiknya dicatat, supaya siapa pun yang membaca ulang laporan ini dapat memahami alasan di balik setiap perubahan.
log_perubahan <- data.frame(
tahap = c("Standardisasi","Standardisasi","Deduplikasi","Koreksi domain","Koreksi domain"),
atribut = c("kota","status","customer_id","usia","usia"),
tindakan = c(
"Variasi penulisan PKU dan huruf besar kecil diseragamkan menjadi Pekanbaru",
"Variasi ACTIVE, A, dan nonaktif diseragamkan menjadi Aktif atau Tidak Aktif",
"Baris kedua dari customer_id yang sama dihapus",
"Usia C004 dikoreksi dari 150 menjadi 50 berdasarkan formulir asli",
"Usia C021 dikoreksi dari 5 menjadi 25 berdasarkan formulir asli"
),
stringsAsFactors = FALSE
)
log_perubahan
## tahap atribut
## 1 Standardisasi kota
## 2 Standardisasi status
## 3 Deduplikasi customer_id
## 4 Koreksi domain usia
## 5 Koreksi domain usia
## tindakan
## 1 Variasi penulisan PKU dan huruf besar kecil diseragamkan menjadi Pekanbaru
## 2 Variasi ACTIVE, A, dan nonaktif diseragamkan menjadi Aktif atau Tidak Aktif
## 3 Baris kedua dari customer_id yang sama dihapus
## 4 Usia C004 dikoreksi dari 150 menjadi 50 berdasarkan formulir asli
## 5 Usia C021 dikoreksi dari 5 menjadi 25 berdasarkan formulir asli
colSums(is.na(pelanggan))
## customer_id nama usia pendapatan kota status
## 0 0 3 4 2 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
## 13 C014 Made 26 NA Pekanbaru Aktif
## 17 C018 Qori NA 4850000 Dumai Tidak Aktif
## 22 C023 Vino 38 NA Dumai Aktif
## 28 C029 Bella NA 4800000 Siak Aktif
## 30 C031 Dewi 28 4900000 <NA> Aktif
Cara ini hanya diperagakan sebagai perbandingan, objek pelanggan yang sebenarnya belum diganti.
pelanggan_complete <- pelanggan[complete.cases(pelanggan), ]
nrow(pelanggan)
## [1] 31
nrow(pelanggan_complete)
## [1] 23
round((1 - nrow(pelanggan_complete) / nrow(pelanggan)) * 100, 2)
## [1] 25.81
Coba pikirkan sendiri, apakah proporsi data yang hilang tersebut cukup besar sehingga menghapus baris bukan pilihan yang bijak.
Pendapatan pada dataset ini memiliki nilai yang sangat ekstrem, sehingga sebaiknya nilai rata rata dan nilai tengah dibandingkan lebih dahulu sebelum menentukan cara pengisian yang dipakai.
mean_pendapatan <- mean(pelanggan$pendapatan, na.rm = TRUE)
median_pendapatan <- median(pelanggan$pendapatan, na.rm = TRUE)
mean_pendapatan
## [1] 30503704
median_pendapatan
## [1] 4900000
Karena sebaran pendapatan menjadi sangat miring akibat adanya nilai ratusan juta, nilai tengah atau median dipilih sebagai dasar pengisian karena lebih tahan terhadap pengaruh nilai ekstrem tersebut.
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.50e+06 4.50e+06
## 2 C002 25 25.0 NA 4.90e+06
## 3 C003 23 23.0 5.20e+06 5.20e+06
## 4 C004 50 50.0 4.80e+06 4.80e+06
## 5 C005 27 27.0 4.90e+06 4.90e+06
## 6 C006 NA 28.5 5.10e+06 5.10e+06
## 7 C007 31 31.0 5.00e+08 5.00e+08
## 8 C008 29 29.0 4.70e+06 4.70e+06
## 9 C009 22 22.0 4.60e+06 4.60e+06
## 10 C010 35 35.0 5.30e+06 5.30e+06
## 11 C011 28 28.0 NA 4.90e+06
## 12 C013 24 24.0 4.75e+06 4.75e+06
## 13 C014 26 26.0 NA 4.90e+06
## 14 C015 33 33.0 5.00e+06 5.00e+06
## 15 C016 19 19.0 4.65e+06 4.65e+06
## 16 C017 41 41.0 5.40e+06 5.40e+06
## 17 C018 NA 28.5 4.85e+06 4.85e+06
## 18 C019 45 45.0 5.60e+06 5.60e+06
## 19 C020 30 30.0 2.00e+08 2.00e+08
## 20 C021 25 25.0 4.70e+06 4.70e+06
## 21 C022 27 27.0 4.90e+06 4.90e+06
## 22 C023 38 38.0 NA 4.90e+06
## 23 C024 29 29.0 5.10e+06 5.10e+06
## 24 C025 34 34.0 4.80e+06 4.80e+06
## 25 C026 24 24.0 4.95e+06 4.95e+06
## 26 C027 31 31.0 5.05e+06 5.05e+06
## 27 C028 26 26.0 4.70e+06 4.70e+06
## 28 C029 NA 28.5 4.80e+06 4.80e+06
## 29 C030 37 37.0 5.15e+06 5.15e+06
## 30 C031 28 28.0 4.90e+06 4.90e+06
## 31 C032 33 33.0 5.20e+06 5.20e+06
Nilai kota yang hilang tidak selalu tepat diisi dengan kategori yang paling sering muncul. Pada latihan ini digunakan label “Tidak diketahui” supaya ketidakpastian data tetap terlihat, bukan malah disembunyikan seolah olah datanya lengkap.
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
## 9 15 5 2
pelanggan$pendapatan_missing <- as.integer(is.na(pelanggan$pendapatan))
table(pelanggan$pendapatan_missing)
##
## 0 1
## 27 4
par(mfrow = c(1, 2))
hist(pelanggan$pendapatan, main = "Sebelum Pengisian", xlab = "Pendapatan", col = "skyblue", breaks = 8)
hist(pelanggan$pendapatan_imputasi, main = "Sesudah Pengisian dengan Median", xlab = "Pendapatan", col = "lightgreen", breaks = 8)
par(mfrow = c(1, 1))
Perlu diingat bahwa proses pengisian nilai yang hilang dapat mengubah bentuk sebaran data dan mengecilkan variasinya, sehingga evaluasi tidak boleh berhenti begitu saja setelah seluruh nilai kosong sudah terisi.
boxplot(pelanggan$pendapatan_imputasi, horizontal = TRUE, col = "lightblue",
main = "Sebaran Pendapatan Pelanggan", 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%
## 4800000 5125000 325000 4312500 5612500
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
## 19 C020 Sinta 2e+08
Nilai pendapatan yang jauh lebih tinggi dari pelanggan lain tidak boleh langsung dihapus begitu saja. Ada tiga kemungkinan yang perlu dipertimbangkan terlebih dahulu, yaitu kemungkinan kesalahan input yang bisa dikoreksi apabila nilai sebenarnya dapat diverifikasi, kemungkinan nilai tersebut memang benar terjadi walaupun ekstrem sehingga sebaiknya dipertahankan atau ditangani dengan metode yang lebih tahan terhadap nilai ekstrem, dan kemungkinan populasi yang berbeda misalnya pelanggan korporasi yang tercampur dengan pelanggan individu sehingga perlu dipisahkan segmennya.
Pada latihan ini nilai aslinya tetap dipertahankan, kemudian dibuat satu variabel tambahan hasil winsorizing hanya untuk membandingkan dampaknya terhadap analisis selanjutnya.
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 5612500
## 19 C020 2e+08 5612500
Winsorizing pada contoh ini hanya sebagai demonstrasi tambahan. Penerapannya di dunia nyata sebaiknya disesuaikan dengan tujuan analisis dan tetap didokumentasikan bahwa nilai ekstrem tersebut sudah dibatasi rentangnya.
Rumus untuk mengubah nilai ke rentang nol sampai satu adalah selisih antara nilai dengan nilai minimum, dibagi dengan selisih antara nilai maksimum dan nilai minimum.
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.06451613 4.50e+06 0.0000000000
## 2 C002 25 0.19354839 4.90e+06 0.0008072654
## 3 C003 23 0.12903226 5.20e+06 0.0014127144
## 4 C004 50 1.00000000 4.80e+06 0.0006054490
## 5 C005 27 0.25806452 4.90e+06 0.0008072654
## 6 C006 NA 0.30645161 5.10e+06 0.0012108981
## 7 C007 31 0.38709677 5.00e+08 1.0000000000
## 8 C008 29 0.32258065 4.70e+06 0.0004036327
## 9 C009 22 0.09677419 4.60e+06 0.0002018163
## 10 C010 35 0.51612903 5.30e+06 0.0016145308
## 11 C011 28 0.29032258 4.90e+06 0.0008072654
## 12 C013 24 0.16129032 4.75e+06 0.0005045409
## 13 C014 26 0.22580645 4.90e+06 0.0008072654
## 14 C015 33 0.45161290 5.00e+06 0.0010090817
## 15 C016 19 0.00000000 4.65e+06 0.0003027245
## 16 C017 41 0.70967742 5.40e+06 0.0018163471
## 17 C018 NA 0.30645161 4.85e+06 0.0007063572
## 18 C019 45 0.83870968 5.60e+06 0.0022199798
## 19 C020 30 0.35483871 2.00e+08 0.3945509586
## 20 C021 25 0.19354839 4.70e+06 0.0004036327
## 21 C022 27 0.25806452 4.90e+06 0.0008072654
## 22 C023 38 0.61290323 4.90e+06 0.0008072654
## 23 C024 29 0.32258065 5.10e+06 0.0012108981
## 24 C025 34 0.48387097 4.80e+06 0.0006054490
## 25 C026 24 0.16129032 4.95e+06 0.0009081736
## 26 C027 31 0.38709677 5.05e+06 0.0011099899
## 27 C028 26 0.22580645 4.70e+06 0.0004036327
## 28 C029 NA 0.30645161 4.80e+06 0.0006054490
## 29 C030 37 0.58064516 5.15e+06 0.0013118063
## 30 C031 28 0.29032258 4.90e+06 0.0008072654
## 31 C032 33 0.45161290 5.20e+06 0.0014127144
Standardisasi ini mengubah nilai berdasarkan seberapa jauh sebuah data menyimpang dari rata rata, dinyatakan dalam satuan simpangan baku.
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 -1.290 -0.240
## 2 -0.710 -0.236
## 3 -1.000 -0.233
## 4 2.920 -0.237
## 5 -0.419 -0.236
## 6 -0.201 -0.234
## 7 0.162 5.004
## 8 -0.129 -0.238
## 9 -1.145 -0.239
## 10 0.742 -0.232
## 11 -0.274 -0.236
## 12 -0.855 -0.238
## 13 -0.564 -0.236
## 14 0.452 -0.235
## 15 -1.581 -0.239
## 16 1.613 -0.231
## 17 -0.201 -0.237
## 18 2.194 -0.229
## 19 0.016 1.829
## 20 -0.710 -0.238
## 21 -0.419 -0.236
## 22 1.178 -0.236
## 23 -0.129 -0.234
## 24 0.597 -0.237
## 25 -0.855 -0.236
## 26 0.162 -0.234
## 27 -0.564 -0.238
## 28 -0.201 -0.237
## 29 1.033 -0.233
## 30 -0.274 -0.236
## 31 0.452 -0.233
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")]
transformasi
## customer_id pendapatan_imputasi pendapatan_minmax pendapatan_z
## 1 C001 4.50e+06 0.0000000000 -0.2402716
## 2 C002 4.90e+06 0.0008072654 -0.2360378
## 3 C003 5.20e+06 0.0014127144 -0.2328624
## 4 C004 4.80e+06 0.0006054490 -0.2370962
## 5 C005 4.90e+06 0.0008072654 -0.2360378
## 6 C006 5.10e+06 0.0012108981 -0.2339208
## 7 C007 5.00e+08 1.0000000000 5.0044237
## 8 C008 4.70e+06 0.0004036327 -0.2381547
## 9 C009 4.60e+06 0.0002018163 -0.2392131
## 10 C010 5.30e+06 0.0016145308 -0.2318039
## 11 C011 4.90e+06 0.0008072654 -0.2360378
## 12 C013 4.75e+06 0.0005045409 -0.2376254
## 13 C014 4.90e+06 0.0008072654 -0.2360378
## 14 C015 5.00e+06 0.0010090817 -0.2349793
## 15 C016 4.65e+06 0.0003027245 -0.2386839
## 16 C017 5.40e+06 0.0018163471 -0.2307454
## 17 C018 4.85e+06 0.0007063572 -0.2365670
## 18 C019 5.60e+06 0.0022199798 -0.2286285
## 19 C020 2.00e+08 0.3945509586 1.8290280
## 20 C021 4.70e+06 0.0004036327 -0.2381547
## 21 C022 4.90e+06 0.0008072654 -0.2360378
## 22 C023 4.90e+06 0.0008072654 -0.2360378
## 23 C024 5.10e+06 0.0012108981 -0.2339208
## 24 C025 4.80e+06 0.0006054490 -0.2370962
## 25 C026 4.95e+06 0.0009081736 -0.2355085
## 26 C027 5.05e+06 0.0011099899 -0.2344501
## 27 C028 4.70e+06 0.0004036327 -0.2381547
## 28 C029 4.80e+06 0.0006054490 -0.2370962
## 29 C030 5.15e+06 0.0013118063 -0.2333916
## 30 C031 4.90e+06 0.0008072654 -0.2360378
## 31 C032 5.20e+06 0.0014127144 -0.2328624
## pendapatan_decimal
## 1 0.00450
## 2 0.00490
## 3 0.00520
## 4 0.00480
## 5 0.00490
## 6 0.00510
## 7 0.50000
## 8 0.00470
## 9 0.00460
## 10 0.00530
## 11 0.00490
## 12 0.00475
## 13 0.00490
## 14 0.00500
## 15 0.00465
## 16 0.00540
## 17 0.00485
## 18 0.00560
## 19 0.20000
## 20 0.00470
## 21 0.00490
## 22 0.00490
## 23 0.00510
## 24 0.00480
## 25 0.00495
## 26 0.00505
## 27 0.00470
## 28 0.00480
## 29 0.00515
## 30 0.00490
## 31 0.00520
pelanggan$pendapatan_winsor_minmax <- minmax(pelanggan$pendapatan_winsor)
plot(pelanggan$pendapatan_minmax, pelanggan$pendapatan_winsor_minmax, pch = 19, col = "navy",
xlab = "Hasil Minmaks dari Data Asli", ylab = "Hasil Minmaks dari Data Winsorized",
main = "Perbandingan Dampak Penanganan Outlier terhadap Normalisasi")
abline(0, 1, col = "red", lty = 2)
Nilai ekstrem yang tidak ditangani dapat menekan hampir seluruh nilai hasil normalisasi ke rentang yang sangat sempit. Inilah sebabnya deteksi outlier perlu dilakukan lebih dahulu sebelum menentukan metode transformasi yang paling sesuai.
sum(duplicated(pelanggan$customer_id))
## [1] 0
sum(duplicated(transaksi_raw$cust_id))
## [1] 1
setdiff(pelanggan$customer_id, transaksi_raw$cust_id)
## [1] "C011" "C031" "C032"
setdiff(transaksi_raw$cust_id, pelanggan$customer_id)
## [1] "C012" "C034"
Hasil dari fungsi setdiff di atas menunjukkan pelanggan yang tidak
memiliki catatan transaksi sama sekali, dan sebaliknya, transaksi yang
identifier pelanggannya tidak ditemukan pada tabel pelanggan. Selain
itu, hasil sum(duplicated(transaksi_raw$cust_id))
menunjukkan bahwa ada satu cust_id pada data transaksi yang tercatat
lebih dari sekali. Duplikasi ini perlu ditangani lebih dahulu, sebab
apabila dibiarkan maka proses penggabungan akan menghasilkan baris ganda
pada pelanggan yang bersangkutan.
transaksi_raw[duplicated(transaksi_raw$cust_id) | duplicated(transaksi_raw$cust_id, fromLast = TRUE), ]
## cust_id jumlah_transaksi total_purchase
## 14 C015 9 3600000
## 30 C015 9 3600000
transaksi_raw <- transaksi_raw[!duplicated(transaksi_raw$cust_id), ]
rownames(transaksi_raw) <- NULL
dim(transaksi_raw)
## [1] 30 3
log_perubahan <- rbind(
log_perubahan,
data.frame(
tahap = "Deduplikasi",
atribut = "cust_id",
tindakan = "Baris kedua dari cust_id yang sama pada data transaksi dihapus sebelum penggabungan",
stringsAsFactors = FALSE
)
)
log_perubahan
## tahap atribut
## 1 Standardisasi kota
## 2 Standardisasi status
## 3 Deduplikasi customer_id
## 4 Koreksi domain usia
## 5 Koreksi domain usia
## 6 Deduplikasi cust_id
## tindakan
## 1 Variasi penulisan PKU dan huruf besar kecil diseragamkan menjadi Pekanbaru
## 2 Variasi ACTIVE, A, dan nonaktif diseragamkan menjadi Aktif atau Tidak Aktif
## 3 Baris kedua dari customer_id yang sama dihapus
## 4 Usia C004 dikoreksi dari 150 menjadi 50 berdasarkan formulir asli
## 5 Usia C021 dikoreksi dari 5 menjadi 25 berdasarkan formulir asli
## 6 Baris kedua dari cust_id yang sama pada data transaksi dihapus sebelum penggabungan
Sama seperti penanganan duplikasi pada data pelanggan, penghapusan ini hanya tepat dilakukan apabila memang satu cust_id seharusnya hanya mewakili satu transaksi teragregasi. Perubahan ini juga ditambahkan ke dalam log_perubahan supaya seluruh riwayat pembersihan data tetap tercatat pada satu tempat yang sama.
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")]
## customer_id nama jumlah_transaksi total_purchase
## 1 C001 Ani 5 1500000
## 2 C002 Budi 3 900000
## 3 C003 Citra 7 2700000
## 4 C004 Dodi 2 600000
## 5 C005 Eka 6 2100000
## 6 C006 Fani 4 1300000
## 7 C007 Gilang 20 25000000
## 8 C008 Hana 5 1700000
## 9 C009 Indra 3 800000
## 10 C010 Joko 8 3200000
## 11 C011 Kiki NA NA
## 12 C013 Lestari 4 1350000
## 13 C014 Made 6 2400000
## 14 C015 Nita 9 3600000
## 15 C016 Oscar 2 650000
## 16 C017 Putri 11 4600000
## 17 C018 Qori 3 950000
## 18 C019 Rian 12 5000000
## 19 C020 Sinta 30 60000000
## 20 C021 Tono 1 300000
## 21 C022 Umi 5 1650000
## 22 C023 Vino 7 2500000
## 23 C024 Wulan 4 1300000
## 24 C025 Xena 6 2050000
## 25 C026 Yoga 3 900000
## 26 C027 Zaki 8 3100000
## 27 C028 Aditya 5 1600000
## 28 C029 Bella 2 600000
## 29 C030 Cahyo 9 3400000
## 30 C031 Dewi NA NA
## 31 C032 Eko NA NA
Penggunaan all.x sama dengan TRUE membuat seluruh pelanggan tetap dipertahankan pada hasil penggabungan, meskipun sebagian dari mereka tidak memiliki pasangan data transaksi.
c(sebelum = nrow(pelanggan), sesudah = nrow(data_terintegrasi))
## sebelum sesudah
## 31 31
sum(duplicated(data_terintegrasi$customer_id))
## [1] 0
colSums(is.na(data_terintegrasi[, c("jumlah_transaksi","total_purchase")]))
## jumlah_transaksi total_purchase
## 3 3
data_terintegrasi[is.na(data_terintegrasi$jumlah_transaksi), c("customer_id","nama")]
## customer_id nama
## 11 C011 Kiki
## 30 C031 Dewi
## 31 C032 Eko
Nilai kosong pada kolom transaksi setelah penggabungan bisa berarti dua hal yang berbeda, yaitu pelanggan tersebut memang belum pernah melakukan transaksi sama sekali, atau data transaksinya tersedia namun gagal dipadankan dengan tabel pelanggan. Nilai kosong tersebut hanya boleh diganti menjadi nol apabila makna sebenarnya sudah dipastikan lebih dahulu.
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
## customer_id nama usia kota status pendapatan_imputasi
## 1 C001 Ani 21.0 Pekanbaru Aktif 4.50e+06
## 2 C002 Budi 25.0 Pekanbaru Aktif 4.90e+06
## 3 C003 Citra 23.0 Pekanbaru Aktif 5.20e+06
## 4 C004 Dodi 50.0 Dumai Aktif 4.80e+06
## 5 C005 Eka 27.0 Pekanbaru Tidak Aktif 4.90e+06
## 6 C006 Fani 28.5 Dumai Tidak Aktif 5.10e+06
## 7 C007 Gilang 31.0 Pekanbaru Aktif 5.00e+08
## 8 C008 Hana 29.0 Siak Aktif 4.70e+06
## 9 C009 Indra 22.0 Pekanbaru Aktif 4.60e+06
## 10 C010 Joko 35.0 Dumai Tidak Aktif 5.30e+06
## 11 C011 Kiki 28.0 Tidak diketahui Aktif 4.90e+06
## 12 C013 Lestari 24.0 Pekanbaru Aktif 4.75e+06
## 13 C014 Made 26.0 Pekanbaru Aktif 4.90e+06
## 14 C015 Nita 33.0 Dumai Aktif 5.00e+06
## 15 C016 Oscar 19.0 Siak Aktif 4.65e+06
## 16 C017 Putri 41.0 Pekanbaru Tidak Aktif 5.40e+06
## 17 C018 Qori 28.5 Dumai Tidak Aktif 4.85e+06
## 18 C019 Rian 45.0 Pekanbaru Aktif 5.60e+06
## 19 C020 Sinta 30.0 Pekanbaru Aktif 2.00e+08
## 20 C021 Tono 25.0 Siak Tidak Aktif 4.70e+06
## 21 C022 Umi 27.0 Pekanbaru Aktif 4.90e+06
## 22 C023 Vino 38.0 Dumai Aktif 4.90e+06
## 23 C024 Wulan 29.0 Siak Aktif 5.10e+06
## 24 C025 Xena 34.0 Pekanbaru Tidak Aktif 4.80e+06
## 25 C026 Yoga 24.0 Dumai Aktif 4.95e+06
## 26 C027 Zaki 31.0 Pekanbaru Tidak Aktif 5.05e+06
## 27 C028 Aditya 26.0 Dumai Aktif 4.70e+06
## 28 C029 Bella 28.5 Siak Aktif 4.80e+06
## 29 C030 Cahyo 37.0 Pekanbaru Tidak Aktif 5.15e+06
## 30 C031 Dewi 28.0 Tidak diketahui Aktif 4.90e+06
## 31 C032 Eko 33.0 Dumai Aktif 5.20e+06
## pendapatan_missing outlier_pendapatan usia_minmax pendapatan_minmax
## 1 0 FALSE 0.06451613 0.0000000000
## 2 1 FALSE 0.19354839 0.0008072654
## 3 0 FALSE 0.12903226 0.0014127144
## 4 0 FALSE 1.00000000 0.0006054490
## 5 0 FALSE 0.25806452 0.0008072654
## 6 0 FALSE 0.30645161 0.0012108981
## 7 0 TRUE 0.38709677 1.0000000000
## 8 0 FALSE 0.32258065 0.0004036327
## 9 0 FALSE 0.09677419 0.0002018163
## 10 0 FALSE 0.51612903 0.0016145308
## 11 1 FALSE 0.29032258 0.0008072654
## 12 0 FALSE 0.16129032 0.0005045409
## 13 1 FALSE 0.22580645 0.0008072654
## 14 0 FALSE 0.45161290 0.0010090817
## 15 0 FALSE 0.00000000 0.0003027245
## 16 0 FALSE 0.70967742 0.0018163471
## 17 0 FALSE 0.30645161 0.0007063572
## 18 0 FALSE 0.83870968 0.0022199798
## 19 0 TRUE 0.35483871 0.3945509586
## 20 0 FALSE 0.19354839 0.0004036327
## 21 0 FALSE 0.25806452 0.0008072654
## 22 1 FALSE 0.61290323 0.0008072654
## 23 0 FALSE 0.32258065 0.0012108981
## 24 0 FALSE 0.48387097 0.0006054490
## 25 0 FALSE 0.16129032 0.0009081736
## 26 0 FALSE 0.38709677 0.0011099899
## 27 0 FALSE 0.22580645 0.0004036327
## 28 0 FALSE 0.30645161 0.0006054490
## 29 0 FALSE 0.58064516 0.0013118063
## 30 0 FALSE 0.29032258 0.0008072654
## 31 0 FALSE 0.45161290 0.0014127144
## jumlah_transaksi_final total_purchase_final
## 1 5 1500000
## 2 3 900000
## 3 7 2700000
## 4 2 600000
## 5 6 2100000
## 6 4 1300000
## 7 20 25000000
## 8 5 1700000
## 9 3 800000
## 10 8 3200000
## 11 0 0
## 12 4 1350000
## 13 6 2400000
## 14 9 3600000
## 15 2 650000
## 16 11 4600000
## 17 3 950000
## 18 12 5000000
## 19 30 60000000
## 20 1 300000
## 21 5 1650000
## 22 7 2500000
## 23 4 1300000
## 24 6 2050000
## 25 3 900000
## 26 8 3100000
## 27 5 1600000
## 28 2 600000
## 29 9 3400000
## 30 0 0
## 31 0 0
audit_akhir <- audit_data(data_final)
audit_akhir
## atribut tipe jumlah_missing persen_missing jumlah_unik
## 1 customer_id character 0 0 31
## 2 nama character 0 0 31
## 3 usia numeric 0 0 21
## 4 kota character 0 0 4
## 5 status character 0 0 2
## 6 pendapatan_imputasi numeric 0 0 19
## 7 pendapatan_missing integer 0 0 2
## 8 outlier_pendapatan logical 0 0 2
## 9 usia_minmax numeric 0 0 21
## 10 pendapatan_minmax numeric 0 0 19
## 11 jumlah_transaksi_final numeric 0 0 14
## 12 total_purchase_final numeric 0 0 26
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 nilai hilang",
"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 32 31
## 2 Duplikasi customer_id 1 0
## 3 Total nilai hilang 9 0
## 4 Kategori kota unik 14 4
## 5 Kategori status unik 8 2
write.csv(data_final, "data_pelanggan_setelah_preprocessing.csv", row.names = FALSE)
write.csv(log_perubahan, "log_perubahan_preprocessing.csv", row.names = FALSE)
Alur kerja yang telah dilalui pada praktikum ini meliputi memahami struktur serta kualitas awal data, membersihkan kategori yang tidak seragam, duplikasi, dan pelanggaran aturan domain, menangani nilai yang hilang tanpa menyembunyikan ketidakpastian yang ada, mendeteksi serta mengevaluasi outlier, mentransformasikan atribut numerik ke skala yang lebih sesuai, dan menggabungkan dua sumber data sekaligus memvalidasi hasil penggabungannya.
Data preprocessing pada dasarnya adalah proses pengambilan keputusan. Kode R yang dijalankan hanya menjalankan keputusan tersebut, sementara kualitas hasil akhirnya tetap bergantung pada pemahaman terhadap data itu sendiri, tujuan analisis yang ingin dicapai, serta kelengkapan dokumentasi dari setiap perubahan yang dilakukan.
Han, J., Kamber, M., dan Pei, J. (2012). Data Mining: Concepts and Techniques (edisi ketiga). Morgan Kaufmann. Bab 3, Data Preprocessing.
Sebelum dokumen ini dipublikasikan, sebaiknya setiap mahasiswa melakukan penyesuaian berikut agar hasilnya benar benar mencerminkan pemahaman masing masing: