Data Preprocessing Menggunakan RStudio
Identitas Praktikum
| Komponen | Keterangan |
|---|---|
| Mata kuliah | Data Mining |
| Pertemuan | 03 |
| Topik | Data Preprocessing |
| Perangkat lunak | R dan RStudio |
| Alokasi waktu | 2 SKS / 100 menit |
| Acuan utama | Han, Kamber, dan Pei (2012), Bab 3 |
Capaian Praktikum
Setelah mengikuti praktikum ini, mahasiswa diharapkan mampu:
- menjelaskan kondisi-kondisi data yang membuat preprocessing menjadi diperlukan;
- mengidentifikasi masalah-masalah kualitas data dengan menggunakan R;
- menerapkan data cleaning secara sistematis dan terarah;
- memilih serta menerapkan strategi yang tepat untuk menangani missing values;
- mendeteksi dan mengevaluasi keberadaan outlier;
- melakukan transformasi terhadap atribut numerik; dan
- mengintegrasikan dua sumber data yang berbeda serta memvalidasi hasil integrasinya.
Skenario Praktikum
Ada sebuah perusahaan e-commerce yang ingin menganalisis pelanggannya. Datanya berasal dari dua sumber:
- data pelanggan, berisi identitas, usia, kota, pendapatan, dan status dari tiap pelanggan;
- data transaksi, berisi jumlah transaksi dan total pembelian yang dilakukan.
Data ini belum bisa langsung dipakai karena masih mengandung banyak masalah, mulai dari nilai yang hilang, penulisan kategori yang tidak konsisten, data duplikat, nilai yang terlalu ekstrem, sampai perbedaan nama identifier antar sumber. Karena itu, mahasiswa perlu menyiapkan data tersebut lebih dulu hingga menjadi satu dataset analisis yang sudah bersih.
Prinsip utama: jangan asal membersihkan data hanya karena sebuah nilai terlihat aneh. Setiap perubahan yang dilakukan harus memiliki alasan yang jelas, mengikuti aturan tertentu, dan tetap dicatat.
Persiapan RStudio
Membuat proyek
- Buka aplikasi RStudio.
- Pilih menu File → New Project → New Directory → New Project.
- Beri nama proyeknya, misalnya
praktikum_preprocessing. - Simpan file ini ke dalam folder proyek yang sudah dibuat.
- Klik tombol Knit untuk menghasilkan file HTML-nya.
Membangun Dataset Praktikum
Jalankan kode berikut ini. Dataset yang digunakan memang sengaja dibuat “kotor” agar seluruh tahap preprocessing bisa benar-benar dipraktikkan.
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 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
## 7 C007 Gilang 31 500000000 Pekanbaru Aktif
## 8 C008 Hana 29 4700000 Siak AKTIF
## 9 C009 Indra 22 4600000 PKU A
## 10 C010 Joko 35 5300000 Dumai Tidak aktif
## 11 C010 Joko 35 5300000 Dumai Tidak aktif
## 12 C011 Kiki 28 NA <NA> Aktif
## 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
Bagian I — Konsep Data Preprocessing
Memahami struktur data
Sebelum melakukan perubahan apa pun, ada baiknya kita periksa dulu strukturnya, dimensi datanya, tipe tiap atribut, serta beberapa observasi di awal.
## [1] 12 6
## [1] "customer_id" "nama" "usia" "pendapatan" "kota"
## [6] "status"
## 'data.frame': 12 obs. of 6 variables:
## $ customer_id: chr "C001" "C002" "C003" "C004" ...
## $ nama : chr "Ani" "Budi" "Citra" "Dodi" ...
## $ usia : num 21 25 23 150 27 NA 31 29 22 35 ...
## $ pendapatan : num 4500000 NA 5200000 4800000 4900000 5100000 500000000 4700000 4600000 5300000 ...
## $ kota : chr "Pekanbaru" " PKU" "PEKANBARU" "Dumai" ...
## $ status : chr "Aktif" "aktif" "ACTIVE" "A" ...
## 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
## 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
##
##
##
##
Mengukur kualitas awal
Han, Kamber, dan Pei menyebutkan ada enam dimensi kualitas data, yaitu accuracy, completeness, consistency, timeliness, believability, dan interpretability. Namun tidak semua dimensi ini bisa langsung dihitung hanya dari isi tabel saja. Sebagai contoh, accuracy membutuhkan pembanding terhadap kondisi yang sebenarnya, sedangkan timeliness membutuhkan informasi kapan data terakhir diperbarui.
Pemeriksaan awal berikut ini difokuskan pada masalah-masalah yang memang bisa dideteksi langsung dari dataset.
## customer_id nama usia pendapatan kota status
## 0 0 1 2 1 0
## customer_id nama usia pendapatan kota status
## 0.00 0.00 8.33 16.67 8.33 0.00
## [1] 1
## [1] 1
## [1] " PKU" "Dumai" "DUMAI" "pekanbaru" "Pekanbaru"
## [6] "PEKANBARU" "Pekanbaru " "PKU" "Siak"
## [1] "A" "ACTIVE" "aktif" "Aktif" "AKTIF"
## [6] "nonaktif" "Tidak aktif" "Tidak Aktif"
## [1] 21 150
## [1] 4500000 500000000
Membuat fungsi ringkasan kualitas
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
Interpretasi: hasil audit ini bukan berarti langsung menjadi keputusan cleaning. Audit hanya memberi sinyal atribut mana saja yang perlu diperiksa lebih lanjut.
Bagian II — Implementasi Data Cleaning
Membuat salinan kerja
Data mentah tetap disimpan supaya setiap perubahan yang dilakukan nanti masih bisa dilacak kembali.
Membersihkan spasi dan kapitalisasi
# Menghapus spasi di awal dan akhir
pelanggan$kota <- trimws(pelanggan$kota)
pelanggan$status <- trimws(pelanggan$status)
# Menyeragamkan huruf menjadi kecil untuk proses pencocokan
pelanggan$kota <- tolower(pelanggan$kota)
pelanggan$status <- tolower(pelanggan$status)
sort(unique(pelanggan$kota))## [1] "dumai" "pekanbaru" "pku" "siak"
## [1] "a" "active" "aktif" "nonaktif" "tidak aktif"
Menyeragamkan kategori
Standardisasi ini dilakukan dengan mengikuti aturan domain. Dalam praktik yang sesungguhnya, aturan semacam ini sebaiknya dikonfirmasi lebih dulu melalui kamus data atau langsung ke pemilik data.
# Standardisasi kota
pelanggan$kota[pelanggan$kota %in% c("pku", "pekanbaru")] <- "Pekanbaru"
pelanggan$kota[pelanggan$kota == "dumai"] <- "Dumai"
pelanggan$kota[pelanggan$kota == "siak"] <- "Siak"
# Standardisasi status
pelanggan$status[pelanggan$status %in% c("aktif", "active", "a")] <- "Aktif"
pelanggan$status[pelanggan$status %in% c("tidak aktif", "nonaktif")] <- "Tidak Aktif"
sort(unique(pelanggan$kota))## [1] "Dumai" "Pekanbaru" "Siak"
## [1] "Aktif" "Tidak Aktif"
Mendeteksi dan menghapus duplikasi
# Menampilkan seluruh baris dengan customer_id yang berulang
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
# Mempertahankan kemunculan pertama untuk customer_id yang identik
pelanggan <- pelanggan[!duplicated(pelanggan$customer_id), ]
rownames(pelanggan) <- NULL
dim(pelanggan)## [1] 11 6
Catatan: menghapus data duplikat hanya aman dilakukan apabila setiap
customer_idmemang seharusnya mewakili satu pelanggan saja. Jika ternyata satu pelanggan boleh memiliki lebih dari satu baris data, maka menghapusnya justru menjadi kesalahan.
Memeriksa aturan domain
# Kandidat usia tidak valid berdasarkan aturan bisnis 15–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>
## customer_id nama usia pendapatan kota status
## NA <NA> <NA> NA NA <NA> <NA>
## NA.1 <NA> <NA> NA NA <NA> <NA>
Ada nilai usia 150 yang jelas melanggar aturan domain. Setelah diperiksa ke formulir aslinya, ternyata nilai yang benar seharusnya adalah 50.
Membuat log perubahan
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
Bagian III — Penanganan Missing Values
Mengidentifikasi lokasi nilai hilang
## customer_id nama usia pendapatan kota status
## 0 0 1 2 1 0
## 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
Strategi 1: menghapus baris
Kode berikut ini hanya untuk mendemonstrasikan strateginya saja, objek pelanggan yang asli belum langsung diubah.
## [1] 11
## [1] 8
Pertanyaan: kira-kira berapa persen data yang akan hilang apabila seluruh baris yang tidak lengkap langsung dihapus?
## [1] 27.27
Strategi 2: imputasi mean dan median
Atribut pendapatan ternyata memiliki nilai yang ekstrem. Sebelum menentukan cara imputasinya, kita bandingkan dulu nilai mean dan median-nya.
mean_pendapatan <- mean(pelanggan$pendapatan, na.rm = TRUE)
median_pendapatan <- median(pelanggan$pendapatan, na.rm = TRUE)
mean_pendapatan## [1] 59900000
## [1] 4900000
Karena distribusi pendapatan menjadi sangat miring akibat adanya nilai 500 juta, median dipilih karena lebih stabil untuk kasus ini.
pelanggan$pendapatan_imputasi <- pelanggan$pendapatan
pelanggan$pendapatan_imputasi[is.na(pelanggan$pendapatan_imputasi)] <-
median_pendapatan
# Imputasi usia dengan median
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 4500000 4500000
## 2 C002 25 25.0 NA 4900000
## 3 C003 23 23.0 5200000 5200000
## 4 C004 50 50.0 4800000 4800000
## 5 C005 27 27.0 4900000 4900000
## 6 C006 NA 27.5 5100000 5100000
## 7 C007 31 31.0 500000000 500000000
## 8 C008 29 29.0 4700000 4700000
## 9 C009 22 22.0 4600000 4600000
## 10 C010 35 35.0 5300000 5300000
## 11 C011 28 28.0 NA 4900000
Strategi 3: imputasi nilai kategorik
Nilai kota yang hilang tidak selalu tepat jika diisi dengan modus. Pada latihan ini kita gunakan kategori eksplisit Tidak diketahui agar ketidakpastian datanya tidak disembunyikan.
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
Menambahkan indikator missing
Indikator ini berguna untuk tetap menyimpan informasi bahwa suatu nilai sebenarnya awalnya hilang.
pelanggan$pendapatan_missing <- as.integer(is.na(pelanggan$pendapatan))
table(pelanggan$pendapatan_missing)##
## 0 1
## 9 2
Membandingkan distribusi sebelum dan sesudah imputasi
par(mfrow = c(1, 2))
hist(pelanggan$pendapatan,
main = "Sebelum Imputasi",
xlab = "Pendapatan", col = "skyblue", breaks = 8)
hist(pelanggan$pendapatan_imputasi,
main = "Sesudah Imputasi Median",
xlab = "Pendapatan", col = "lightgreen", breaks = 8)Proses imputasi bisa mengubah distribusi data dan membuat variasinya menjadi lebih kecil. Karena itu, evaluasi tidak boleh berhenti begitu saja setelah semua nilai NA hilang.
Bagian IV — Penanganan Outlier
Visualisasi dengan boxplot
boxplot(pelanggan$pendapatan_imputasi,
horizontal = TRUE,
col = "lightblue",
main = "Boxplot Pendapatan",
xlab = "Pendapatan")Menghitung batas 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
Menandai 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 500000000
Mengevaluasi tindakan
Nilai 500 juta ini tidak langsung dihapus begitu saja. Ada tiga kemungkinan yang perlu dipertimbangkan:
- Kesalahan input: nilainya bisa dikoreksi apabila nilai yang benar memang dapat diverifikasi.
- Observasi valid tetapi ekstrem: nilai tetap dipertahankan, atau bisa ditangani dengan metode yang lebih robust.
- Populasi berbeda: segmen datanya perlu dipisah apabila pelanggan korporasi ternyata tercampur dengan pelanggan ritel.
Pada modul ini, nilai tersebut tetap dipertahankan, dan hanya dibuatkan variabel versi winsorized untuk membandingkan dampak dari metode yang digunakan.
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 500000000 5750000
Winsorizing di sini hanya sebagai demonstrasi tambahan. Metode ini sebaiknya diterapkan hanya jika memang sesuai dengan tujuan analisis, dan jangan lupa didokumentasikan bahwa nilai ekstremnya sudah dibatasi.
Bagian V — Transformasi Data
Normalisasi min–maks
Rumus untuk mengubah nilai ke rentang \([0,1]\) adalah sebagai berikut.
\[ 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 4500000 0.0000000000
## 2 C002 25 0.13793103 4900000 0.0008072654
## 3 C003 23 0.06896552 5200000 0.0014127144
## 4 C004 50 1.00000000 4800000 0.0006054490
## 5 C005 27 0.20689655 4900000 0.0008072654
## 6 C006 NA 0.22413793 5100000 0.0012108981
## 7 C007 31 0.34482759 500000000 1.0000000000
## 8 C008 29 0.27586207 4700000 0.0004036327
## 9 C009 22 0.03448276 4600000 0.0002018163
## 10 C010 35 0.48275862 5300000 0.0016145308
## 11 C011 28 0.24137931 4900000 0.0008072654
Normalisasi z-score
\[ 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
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
Membandingkan metode transformasi
transformasi <- pelanggan[, c(
"customer_id", "pendapatan_imputasi", "pendapatan_minmax",
"pendapatan_z", "pendapatan_decimal"
)]
transformasi## customer_id pendapatan_imputasi pendapatan_minmax pendapatan_z
## 1 C001 4500000 0.0000000000 -0.3041235
## 2 C002 4900000 0.0008072654 -0.3014440
## 3 C003 5200000 0.0014127144 -0.2994343
## 4 C004 4800000 0.0006054490 -0.3021138
## 5 C005 4900000 0.0008072654 -0.3014440
## 6 C006 5100000 0.0012108981 -0.3001042
## 7 C007 500000000 1.0000000000 3.0151095
## 8 C008 4700000 0.0004036327 -0.3027837
## 9 C009 4600000 0.0002018163 -0.3034536
## 10 C010 5300000 0.0016145308 -0.2987645
## 11 C011 4900000 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
Dampak outlier terhadap normalisasi
pelanggan$pendapatan_winsor_minmax <- minmax(pelanggan$pendapatan_winsor)
plot(pelanggan$pendapatan_minmax,
pelanggan$pendapatan_winsor_minmax,
pch = 19, col = "navy",
xlab = "Min–maks Data Asli",
ylab = "Min–maks Data Winsorized",
main = "Dampak Penanganan Outlier terhadap Normalisasi")
abline(0, 1, col = "red", lty = 2)Interpretasi: adanya outlier bisa membuat sebagian besar nilai hasil min–maks scaling malah terjepit ke rentang yang sangat sempit. Inilah salah satu alasan kenapa deteksi outlier perlu dilakukan lebih dulu sebelum menentukan jenis transformasi yang akan dipakai.
Bagian VI — Integrasi Data
Memeriksa kunci pada kedua sumber
## [1] 0
## [1] 0
## [1] "C011"
## [1] "C012"
Hasil dari setdiff() ini memperlihatkan pelanggan yang ternyata tidak memiliki data transaksi, sekaligus transaksi yang tidak memiliki pasangan data pelanggan.
Menyelaraskan nama identifier
Melakukan left join dengan merge()
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
Penggunaan all.x = TRUE membuat seluruh data pelanggan tetap dipertahankan, meskipun ada yang tidak memiliki pasangan data transaksi.
Memvalidasi hasil integrasi
# Apakah jumlah baris berubah secara tidak wajar?
c(sebelum = nrow(pelanggan), sesudah = nrow(data_terintegrasi))## sebelum sesudah
## 11 11
## [1] 0
# Missing value baru akibat tidak ditemukan pasangan transaksi
colSums(is.na(data_terintegrasi[, c("jumlah_transaksi", "total_purchase")]))## jumlah_transaksi total_purchase
## 1 1
# Pelanggan tanpa pasangan transaksi
data_terintegrasi[is.na(data_terintegrasi$jumlah_transaksi),
c("customer_id", "nama")]## customer_id nama
## 11 C011 Kiki
Mengisi nol atau mempertahankan NA?
Nilai NA pada data transaksi ini sebenarnya bisa memiliki dua arti yang berbeda:
- pelanggan tersebut memang belum pernah melakukan transaksi sama sekali; atau
- data transaksi pelanggan tersebut tidak tersedia, atau gagal dipadankan dengan data pelanggan.
Nilai ini baru boleh diubah menjadi 0 apabila definisinya memang sudah dipastikan lebih dulu.
# Contoh jika telah dikonfirmasi bahwa NA berarti belum pernah bertransaksi
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)
] <- 0Dataset Akhir dan Evaluasi
Memilih atribut 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 4500000
## 2 C002 Budi 25.0 Pekanbaru Aktif 4900000
## 3 C003 Citra 23.0 Pekanbaru Aktif 5200000
## 4 C004 Dodi 50.0 Dumai Aktif 4800000
## 5 C005 Eka 27.0 Pekanbaru Tidak Aktif 4900000
## 6 C006 Fani 27.5 Dumai Tidak Aktif 5100000
## 7 C007 Gilang 31.0 Pekanbaru Aktif 500000000
## 8 C008 Hana 29.0 Siak Aktif 4700000
## 9 C009 Indra 22.0 Pekanbaru Aktif 4600000
## 10 C010 Joko 35.0 Dumai Tidak Aktif 5300000
## 11 C011 Kiki 28.0 Tidak diketahui Aktif 4900000
## 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 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
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
## [1] 0
## customer_id nama usia
## 0 0 0
## kota status pendapatan_imputasi
## 0 0 0
## pendapatan_missing outlier_pendapatan usia_minmax
## 0 0 0
## pendapatan_minmax jumlah_transaksi_final total_purchase_final
## 0 0 0
Membandingkan kondisi sebelum dan sesudah
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
Pertanyaan Refleksi
- Apakah dataset yang sudah tidak memiliki missing value otomatis berarti lebih berkualitas?
- Mengapa outlier tidak boleh langsung dihapus begitu saja?
- Bagaimana proses preprocessing bisa menimbulkan bias pada data?
- Mengapa parameter imputasi dan transformasi sebaiknya dihitung dari data pelatihan saja?
- Apa risiko yang muncul saat integrasi data dilakukan jika identifier-nya ternyata tidak unik?
Ringkasan
Berikut rangkuman alur yang sudah dikerjakan:
- memahami struktur data serta kualitas awalnya;
- membersihkan kategori yang tidak konsisten, data duplikat, dan pelanggaran aturan domain;
- menangani missing values tanpa harus menyembunyikan ketidakpastian yang ada;
- mendeteksi dan mengevaluasi keberadaan outlier;
- mentransformasikan atribut-atribut numerik; dan
- mengintegrasikan seluruh data serta memvalidasi hasil akhirnya.
Pada dasarnya, preprocessing merupakan proses pengambilan keputusan. Kode R di sini hanya menjalankan keputusan-keputusan tersebut, sementara kualitas hasil akhirnya tetap bergantung pada seberapa paham kita terhadap data, tujuan analisis yang ingin dicapai, dan kelengkapan dokumentasi dari setiap perubahan yang dilakukan.
Daftar Pustaka
Han, J., Kamber, M., & Pei, J. (2012). Data Mining: Concepts and Techniques (3rd ed.). Morgan Kaufmann. Chapter 3: Data Preprocessing.