Data preprocessing (praproses data) adalah tahapan penting dalam analisis data dan machine learning yang bertujuan untuk mengubah data mentah (raw data) menjadi data yang bersih, konsisten, dan siap dianalisis. Data mentah sering kali memiliki masalah seperti nilai hilang (missing values), duplikasi, outlier, skala yang tidak seragam, maupun format yang tidak konsisten.
Tahapan umum dalam data preprocessing meliputi:
Dataset berikut sengaja dibuat memiliki beberapa masalah agar seluruh
tahapan preprocessing dapat dipraktikkan. Masalah tersebut meliputi
duplikasi customer_id, missing value, kategori
tidak konsisten, kesalahan nilai usia, dan nilai pendapatan yang sangat
besar.
# Dataset 1: Data Pelanggan Mentah
pelanggan_raw <- data.frame(
customer_id = c("C001", "C002", "C003", "C004", "C005", "C006",
"C007", "C008", "C009", "C010", "C010", "C011",
"C012", "C013", "C014", "C015", "C016", "C017",
"C018", "C019", "C020", "C021", "C022", "C023",
"C024", "C025", "C026", "C027", "C028", "C029"),
nama = c("Ani", "Budi", "Citra", "Dodi", "Eka", "Fani",
"Gilang", "Hana", "Indra", "Joko", "Joko", "Kiki",
"Lina", "Made", "Nita", "Oki", "Putri", "Qori",
"Rian", "Sari", "Tono", "Umi", "Vino", "Wati",
"Xaverius", "Yani", "Zaki", "Ayu", "Bayu", "Cinta"),
usia = c(21, 25, 23, 150, 27, NA, 31, 29, 22, 35, 35, 28,
24, 26, 30, 33, 19, 45, 38, 27, 29, 0, 34, 26,
41, 23, 37, NA, 29, 25),
pendapatan = c(4500000, NA, 5200000, 4800000, 4900000, 5100000,
500000000, 4700000, 4600000, 5300000, 5300000, NA,
4550000, 4650000, 4750000, NA, 4400000, 5400000,
4950000, -500000, 4850000, 4700000, 4900000, NA,
5250000, 4650000, 5050000, 4800000, 4700000, 5300000),
kota = c("Pekanbaru", " PKU", "PEKANBARU", "Dumai", "pekanbaru",
"DUMAI", "Pekanbaru ", "Siak", "PKU", "Dumai", "Dumai", NA,
"Pekanbaru", "PKU", "Dumai", "pekanbaru", "Siak", "DUMAI",
"Pekanbaru ", "PKU", "pekanbaru", "Dumai", "Siak", "PEKANBARU",
"Dumai", "pku", "Pekanbaru", "DUMAI", "Siak", "Pekanbaru "),
status = c("Aktif", "aktif", "ACTIVE", "A", "Tidak Aktif", "nonaktif",
"Aktif", "AKTIF", "A", "Tidak aktif", "Tidak aktif", "Aktif",
"aktif", "Aktif", "TIDAK AKTIF", "Aktif", "aktif", "A",
"nonaktif", "Aktif", "Tidak Aktif", "aktif", "AKTIF", "A",
"Tidak aktif", "aktif", "Aktif", "nonaktif", "A", "Tidak Aktif"),
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
## 13 C012 Lina 24 4550000 Pekanbaru aktif
## 14 C013 Made 26 4650000 PKU Aktif
## 15 C014 Nita 30 4750000 Dumai TIDAK AKTIF
## 16 C015 Oki 33 NA pekanbaru Aktif
## 17 C016 Putri 19 4400000 Siak aktif
## 18 C017 Qori 45 5400000 DUMAI A
## 19 C018 Rian 38 4950000 Pekanbaru nonaktif
## 20 C019 Sari 27 -500000 PKU Aktif
## 21 C020 Tono 29 4850000 pekanbaru Tidak Aktif
## 22 C021 Umi 0 4700000 Dumai aktif
## 23 C022 Vino 34 4900000 Siak AKTIF
## 24 C023 Wati 26 NA PEKANBARU A
## 25 C024 Xaverius 41 5250000 Dumai Tidak aktif
## 26 C025 Yani 23 4650000 pku aktif
## 27 C026 Zaki 37 5050000 Pekanbaru Aktif
## 28 C027 Ayu NA 4800000 DUMAI nonaktif
## 29 C028 Bayu 29 4700000 Siak A
## 30 C029 Cinta 25 5300000 Pekanbaru Tidak Aktif
# Dataset 2: Data Transaksi Mentah
transaksi_raw <- data.frame(
cust_id = c("C001", "C002", "C003", "C004", "C005", "C006",
"C007", "C008", "C009", "C010", "C011", "C012",
"C013", "C014", "C015", "C016", "C017", "C018",
"C019", "C999"),
jumlah_transaksi = c(5, 3, 7, 2, 6, 4, 20, 5, 3, 8,
1, 4, 6, 9, 3, NA, 7, 2, 10, 5),
total_purchase = c(1500000, 900000, 2700000, 600000, 2100000, 1300000,
25000000, 1700000, 800000, 3200000, 250000, 1400000,
2200000, 4500000, 1000000, NA, 2800000, 700000,
4200000, 1200000),
stringsAsFactors = FALSE
)
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 C011 1 2.5e+05
## 12 C012 4 1.4e+06
## 13 C013 6 2.2e+06
## 14 C014 9 4.5e+06
## 15 C015 3 1.0e+06
## 16 C016 NA NA
## 17 C017 7 2.8e+06
## 18 C018 2 7.0e+05
## 19 C019 10 4.2e+06
## 20 C999 5 1.2e+06
Tahap ini bertujuan memahami ukuran, nama variabel, tipe data, beberapa baris awal, dan ringkasan statistik.
## [1] 30 6
## [1] "customer_id" "nama" "usia" "pendapatan" "kota"
## [6] "status"
## 'data.frame': 30 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" ...
## 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:30 Length:30 Min. : 0.00 Min. : -500000
## Class :character Class :character 1st Qu.: 24.75 1st Qu.: 4662500
## Mode :character Mode :character Median : 28.50 Median : 4825000
## Mean : 32.57 Mean : 23723077
## 3rd Qu.: 34.25 3rd Qu.: 5175000
## Max. :150.00 Max. :500000000
## NA's :2 NA's :4
## kota status
## Length:30 Length:30
## Class :character Class :character
## Mode :character Mode :character
##
##
##
##
## customer_id nama usia pendapatan kota status
## 0 0 2 4 1 0
## customer_id nama usia pendapatan kota status
## 0.00 0.00 6.67 13.33 3.33 0.00
## [1] 1
## [1] 1
## [1] " PKU" "Dumai" "DUMAI" "pekanbaru" "Pekanbaru"
## [6] "PEKANBARU" "Pekanbaru " "pku" "PKU" "Siak"
## [1] "A" "ACTIVE" "aktif" "Aktif" "AKTIF"
## [6] "nonaktif" "Tidak aktif" "Tidak Aktif" "TIDAK AKTIF"
Fungsi berikut membuat ringkasan kualitas setiap atribut sehingga pemeriksaan dapat dilakukan secara lebih sistematis.
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 29
## 2 nama character 0 0.00 29
## 3 usia numeric 2 6.67 22
## 4 pendapatan numeric 4 13.33 20
## 5 kota character 1 3.33 11
## 6 status character 0 0.00 9
Pada data mentah terdapat perbedaan spasi dan penggunaan huruf besar-kecil. Tahap pertama adalah membersihkan spasi, kemudian menyeragamkan huruf menjadi kecil untuk memudahkan pencocokan kategori.
# 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"
Selanjutnya kategori diseragamkan menjadi label yang konsisten.
# 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"
customer_id digunakan sebagai identifier pelanggan.
Baris dengan identifier yang berulang diperiksa terlebih dahulu.
# 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
Kemunculan kedua untuk C010 dihapus.
# Mempertahankan kemunculan pertama untuk customer_id yang identik
pelanggan <- pelanggan[!duplicated(pelanggan$customer_id), ]
rownames(pelanggan) <- NULL
dim(pelanggan)## [1] 29 6
Aturan domain digunakan untuk menemukan nilai yang tidak masuk akal berdasarkan konteks data. Pada contoh ini usia yang dianggap masuk akal adalah 15–100 tahun.
# 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>
## 21 C021 Umi 0 4700000 Dumai Aktif
## NA.1 <NA> <NA> NA NA <NA> <NA>
Pendapatan negatif juga diperiksa sebagai kandidat pelanggaran domain.
## customer_id nama usia pendapatan kota status
## NA <NA> <NA> NA NA <NA> <NA>
## NA.1 <NA> <NA> NA NA <NA> <NA>
## NA.2 <NA> <NA> NA NA <NA> <NA>
## 19 C019 Sari 27 -5e+05 Pekanbaru Aktif
## NA.3 <NA> <NA> NA NA <NA> <NA>
Nilai usia C004 sebesar 150 diperbaiki menjadi 50 berdasarkan sumber asli.
Pencatatan perubahan penting agar proses preprocessing dapat ditelusuri.
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
## customer_id nama usia pendapatan kota status
## 0 0 2 4 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
## 15 C015 Oki 33 NA Pekanbaru Aktif
## 23 C023 Wati 26 NA Pekanbaru Aktif
## 27 C027 Ayu NA 4800000 Dumai Tidak Aktif
Sebagai ilustrasi, data tanpa missing value dapat diperoleh
dengan complete.cases(). Jumlah baris sebelum dan sesudah
dibandingkan.
## [1] 29
## [1] 23
## [1] 20.69
Untuk atribut numerik, median digunakan sebagai salah satu strategi imputasi. Mean juga dihitung sebagai pembanding.
mean_pendapatan <- mean(pelanggan$pendapatan, na.rm = TRUE)
median_pendapatan <- median(pelanggan$pendapatan, na.rm = TRUE)
mean_pendapatan## [1] 24460000
## [1] 4800000
Pendapatan dan usia kemudian dibuat versi imputasinya tanpa menghilangkan kolom asli.
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 4500000 4500000
## 2 C002 25 25 NA 4800000
## 3 C003 23 23 5200000 5200000
## 4 C004 50 50 4800000 4800000
## 5 C005 27 27 4900000 4900000
## 6 C006 NA 28 5100000 5100000
## 7 C007 31 31 500000000 500000000
## 8 C008 29 29 4700000 4700000
## 9 C009 22 22 4600000 4600000
## 10 C010 35 35 5300000 5300000
## 11 C011 28 28 NA 4800000
## 12 C012 24 24 4550000 4550000
## 13 C013 26 26 4650000 4650000
## 14 C014 30 30 4750000 4750000
## 15 C015 33 33 NA 4800000
## 16 C016 19 19 4400000 4400000
## 17 C017 45 45 5400000 5400000
## 18 C018 38 38 4950000 4950000
## 19 C019 27 27 -500000 -500000
## 20 C020 29 29 4850000 4850000
## 21 C021 0 0 4700000 4700000
## 22 C022 34 34 4900000 4900000
## 23 C023 26 26 NA 4800000
## 24 C024 41 41 5250000 5250000
## 25 C025 23 23 4650000 4650000
## 26 C026 37 37 5050000 5050000
## 27 C027 NA 28 4800000 4800000
## 28 C028 29 29 4700000 4700000
## 29 C029 25 25 5300000 5300000
Untuk kota yang tidak diketahui, digunakan label
Tidak diketahui agar ketidakpastian tidak disamarkan
sebagai kategori tertentu.
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
## 8 16 4 1
Indikator biner dapat digunakan untuk mempertahankan informasi bahwa nilai pendapatan pada awalnya merupakan missing value.
pelanggan$pendapatan_missing <- as.integer(is.na(pelanggan$pendapatan))
table(pelanggan$pendapatan_missing)##
## 0 1
## 25 4
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)Boxplot digunakan untuk membantu melihat kandidat nilai ekstrem pada pendapatan.
boxplot(pelanggan$pendapatan_imputasi,
horizontal = TRUE,
col = "lightblue",
main = "Boxplot Pendapatan",
xlab = "Pendapatan")Metode IQR menggunakan Q1, Q3, dan IQR untuk menentukan batas bawah dan batas atas kandidat outlier.
q1 <- quantile(pelanggan$pendapatan_imputasi, 0.25)
q3 <- quantile(pelanggan$pendapatan_imputasi, 0.75)
iqr <- IQR(pelanggan$pendapatan_imputasi)
batas_bawah <- q1 - 1.5 * iqr
batas_atas <- q3 + 1.5 * iqr
c(Q1 = q1, Q3 = q3, IQR = iqr,
batas_bawah = batas_bawah,
batas_atas = batas_atas)## Q1.25% Q3.75% IQR batas_bawah.25% batas_atas.75%
## 4700000 5050000 350000 4175000 5575000
Nilai yang berada di luar batas IQR ditandai sebagai kandidat outlier. Penandaan ini bukan berarti nilai langsung dihapus.
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 C019 Sari -5e+05
Sebagai contoh penanganan, nilai yang berada di luar batas digeser ke batas bawah atau batas atas menggunakan winsorization.
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 5575000
## 19 C019 -5e+05 4175000
Setelah dilakukan winsorization, boxplot kembali digunakan untuk melihat perubahan distribusi pendapatan. Visualisasi ini digunakan untuk membandingkan kondisi data sebelum dan sesudah penanganan outlier.
par(mfrow = c(1, 2))
boxplot(
pelanggan$pendapatan_imputasi,
horizontal = TRUE,
main = "Sebelum Winsorization",
xlab = "Pendapatan"
)
boxplot(
pelanggan$pendapatan_winsor,
horizontal = TRUE,
main = "Sesudah Winsorization",
xlab = "Pendapatan"
)Berdasarkan perbandingan boxplot, sebelum dilakukan winsorization terdapat nilai pendapatan yang sangat ekstrem dan berada jauh dari kelompok data utama. Setelah dilakukan winsorization, nilai ekstrem tersebut dibatasi berdasarkan batas yang diperoleh dari metode IQR. Dengan demikian, pengaruh nilai ekstrem terhadap distribusi pendapatan dapat dikurangi tanpa menghapus observasi dari dataset.
Transformasi digunakan untuk mengubah skala atribut numerik. Tiga metode yang digunakan dalam praktikum ini adalah min–maks, z-score, dan decimal scaling.
Rumus min–maks untuk rentang [0,1] adalah:
\[x' = \frac{x - x_{min}}{x_{max} - x_{min}}\]
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.42 4500000 0.00999001
## 2 C002 25 0.50 4800000 0.01058941
## 3 C003 23 0.46 5200000 0.01138861
## 4 C004 50 1.00 4800000 0.01058941
## 5 C005 27 0.54 4900000 0.01078921
## 6 C006 NA 0.56 5100000 0.01118881
## 7 C007 31 0.62 500000000 1.00000000
## 8 C008 29 0.58 4700000 0.01038961
## 9 C009 22 0.44 4600000 0.01018981
## 10 C010 35 0.70 5300000 0.01158841
## 11 C011 28 0.56 4800000 0.01058941
## 12 C012 24 0.48 4550000 0.01008991
## 13 C013 26 0.52 4650000 0.01028971
## 14 C014 30 0.60 4750000 0.01048951
## 15 C015 33 0.66 4800000 0.01058941
## 16 C016 19 0.38 4400000 0.00979021
## 17 C017 45 0.90 5400000 0.01178821
## 18 C018 38 0.76 4950000 0.01088911
## 19 C019 27 0.54 -500000 0.00000000
## 20 C020 29 0.58 4850000 0.01068931
## 21 C021 0 0.00 4700000 0.01038961
## 22 C022 34 0.68 4900000 0.01078921
## 23 C023 26 0.52 4800000 0.01058941
## 24 C024 41 0.82 5250000 0.01148851
## 25 C025 23 0.46 4650000 0.01028971
## 26 C026 37 0.74 5050000 0.01108891
## 27 C027 NA 0.56 4800000 0.01058941
## 28 C028 29 0.58 4700000 0.01038961
## 29 C029 25 0.50 5300000 0.01158841
Z-score mengubah nilai menjadi ukuran relatif terhadap mean dan 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 -0.856 -0.188
## 2 -0.413 -0.184
## 3 -0.635 -0.180
## 4 2.359 -0.184
## 5 -0.191 -0.183
## 6 -0.080 -0.181
## 7 0.252 5.199
## 8 0.031 -0.185
## 9 -0.746 -0.186
## 10 0.696 -0.179
## 11 -0.080 -0.184
## 12 -0.524 -0.187
## 13 -0.302 -0.186
## 14 0.141 -0.185
## 15 0.474 -0.184
## 16 -1.078 -0.189
## 17 1.805 -0.178
## 18 1.028 -0.183
## 19 -0.191 -0.242
## 20 0.031 -0.184
## 21 -3.185 -0.185
## 22 0.585 -0.183
## 23 -0.302 -0.184
## 24 1.361 -0.179
## 25 -0.635 -0.186
## 26 0.918 -0.182
## 27 -0.080 -0.184
## 28 0.031 -0.185
## 29 -0.413 -0.179
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] -5e-04 5e-01
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.00999001 -0.1875086
## 2 C002 4800000 0.01058941 -0.1842473
## 3 C003 5200000 0.01138861 -0.1798988
## 4 C004 4800000 0.01058941 -0.1842473
## 5 C005 4900000 0.01078921 -0.1831601
## 6 C006 5100000 0.01118881 -0.1809859
## 7 C007 500000000 1.00000000 5.1991463
## 8 C008 4700000 0.01038961 -0.1853344
## 9 C009 4600000 0.01018981 -0.1864215
## 10 C010 5300000 0.01158841 -0.1788117
## 11 C011 4800000 0.01058941 -0.1842473
## 12 C012 4550000 0.01008991 -0.1869650
## 13 C013 4650000 0.01028971 -0.1858779
## 14 C014 4750000 0.01048951 -0.1847908
## 15 C015 4800000 0.01058941 -0.1842473
## 16 C016 4400000 0.00979021 -0.1885957
## 17 C017 5400000 0.01178821 -0.1777246
## 18 C018 4950000 0.01088911 -0.1826166
## 19 C019 -500000 0.00000000 -0.2418643
## 20 C020 4850000 0.01068931 -0.1837037
## 21 C021 4700000 0.01038961 -0.1853344
## 22 C022 4900000 0.01078921 -0.1831601
## 23 C023 4800000 0.01058941 -0.1842473
## 24 C024 5250000 0.01148851 -0.1793552
## 25 C025 4650000 0.01028971 -0.1858779
## 26 C026 5050000 0.01108891 -0.1815295
## 27 C027 4800000 0.01058941 -0.1842473
## 28 C028 4700000 0.01038961 -0.1853344
## 29 C029 5300000 0.01158841 -0.1788117
## pendapatan_decimal
## 1 0.00450
## 2 0.00480
## 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.00480
## 12 0.00455
## 13 0.00465
## 14 0.00475
## 15 0.00480
## 16 0.00440
## 17 0.00540
## 18 0.00495
## 19 -0.00050
## 20 0.00485
## 21 0.00470
## 22 0.00490
## 23 0.00480
## 24 0.00525
## 25 0.00465
## 26 0.00505
## 27 0.00480
## 28 0.00470
## 29 0.00530
Perbandingan berikut menunjukkan bagaimana penanganan outlier dapat memengaruhi hasil normalisasi min–maks.
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)Sebelum melakukan integrasi, identifier harus diperiksa agar tidak menghasilkan penggandaan baris yang tidak diinginkan.
## [1] 0
## [1] 0
# Customer yang ada di pelanggan tetapi tidak ada di transaksi
setdiff(pelanggan$customer_id, transaksi_raw$cust_id)## [1] "C020" "C021" "C022" "C023" "C024" "C025" "C026" "C027" "C028" "C029"
# Customer yang ada di transaksi tetapi tidak ada di pelanggan
setdiff(transaksi_raw$cust_id, pelanggan$customer_id)## [1] "C999"
merge(..., all.x = TRUE) digunakan untuk mempertahankan
seluruh pelanggan dan mengambil informasi transaksi yang cocok.
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 1 2.5e+05
## 12 C012 Lina 4 1.4e+06
## 13 C013 Made 6 2.2e+06
## 14 C014 Nita 9 4.5e+06
## 15 C015 Oki 3 1.0e+06
## 16 C016 Putri NA NA
## 17 C017 Qori 7 2.8e+06
## 18 C018 Rian 2 7.0e+05
## 19 C019 Sari 10 4.2e+06
## 20 C020 Tono NA NA
## 21 C021 Umi NA NA
## 22 C022 Vino NA NA
## 23 C023 Wati NA NA
## 24 C024 Xaverius NA NA
## 25 C025 Yani NA NA
## 26 C026 Zaki NA NA
## 27 C027 Ayu NA NA
## 28 C028 Bayu NA NA
## 29 C029 Cinta NA NA
# Apakah jumlah baris berubah secara tidak wajar?
c(sebelum = nrow(pelanggan), sesudah = nrow(data_terintegrasi))## sebelum sesudah
## 29 29
## [1] 0
# Missing value baru akibat tidak ditemukan pasangan transaksi
colSums(is.na(data_terintegrasi[, c("jumlah_transaksi", "total_purchase")]))## jumlah_transaksi total_purchase
## 11 11
Pelanggan yang tidak memiliki pasangan transaksi ditampilkan untuk diperiksa lebih lanjut.
## customer_id nama
## 16 C016 Putri
## 20 C020 Tono
## 21 C021 Umi
## 22 C022 Vino
## 23 C023 Wati
## 24 C024 Xaverius
## 25 C025 Yani
## 26 C026 Zaki
## 27 C027 Ayu
## 28 C028 Bayu
## 29 C029 Cinta
Jika setelah pemeriksaan diketahui bahwa NA memang
berarti pelanggan belum pernah bertransaksi, nilai dapat diubah menjadi
0.
data_terintegrasi$jumlah_transaksi_final <- data_terintegrasi$jumlah_transaksi
data_terintegrasi$total_purchase_final <- data_terintegrasi$total_purchase
data_terintegrasi$jumlah_transaksi_final[
is.na(data_terintegrasi$jumlah_transaksi_final)
] <- 0
data_terintegrasi$total_purchase_final[
is.na(data_terintegrasi$total_purchase_final)
] <- 0Hanya variabel yang diperlukan untuk dataset akhir dipertahankan.
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 Pekanbaru Aktif 4500000
## 2 C002 Budi 25 Pekanbaru Aktif 4800000
## 3 C003 Citra 23 Pekanbaru Aktif 5200000
## 4 C004 Dodi 50 Dumai Aktif 4800000
## 5 C005 Eka 27 Pekanbaru Tidak Aktif 4900000
## 6 C006 Fani 28 Dumai Tidak Aktif 5100000
## 7 C007 Gilang 31 Pekanbaru Aktif 500000000
## 8 C008 Hana 29 Siak Aktif 4700000
## 9 C009 Indra 22 Pekanbaru Aktif 4600000
## 10 C010 Joko 35 Dumai Tidak Aktif 5300000
## 11 C011 Kiki 28 Tidak diketahui Aktif 4800000
## 12 C012 Lina 24 Pekanbaru Aktif 4550000
## 13 C013 Made 26 Pekanbaru Aktif 4650000
## 14 C014 Nita 30 Dumai Tidak Aktif 4750000
## 15 C015 Oki 33 Pekanbaru Aktif 4800000
## 16 C016 Putri 19 Siak Aktif 4400000
## 17 C017 Qori 45 Dumai Aktif 5400000
## 18 C018 Rian 38 Pekanbaru Tidak Aktif 4950000
## 19 C019 Sari 27 Pekanbaru Aktif -500000
## 20 C020 Tono 29 Pekanbaru Tidak Aktif 4850000
## 21 C021 Umi 0 Dumai Aktif 4700000
## 22 C022 Vino 34 Siak Aktif 4900000
## 23 C023 Wati 26 Pekanbaru Aktif 4800000
## 24 C024 Xaverius 41 Dumai Tidak Aktif 5250000
## 25 C025 Yani 23 Pekanbaru Aktif 4650000
## 26 C026 Zaki 37 Pekanbaru Aktif 5050000
## 27 C027 Ayu 28 Dumai Tidak Aktif 4800000
## 28 C028 Bayu 29 Siak Aktif 4700000
## 29 C029 Cinta 25 Pekanbaru Tidak Aktif 5300000
## pendapatan_missing outlier_pendapatan usia_minmax pendapatan_minmax
## 1 0 FALSE 0.42 0.00999001
## 2 1 FALSE 0.50 0.01058941
## 3 0 FALSE 0.46 0.01138861
## 4 0 FALSE 1.00 0.01058941
## 5 0 FALSE 0.54 0.01078921
## 6 0 FALSE 0.56 0.01118881
## 7 0 TRUE 0.62 1.00000000
## 8 0 FALSE 0.58 0.01038961
## 9 0 FALSE 0.44 0.01018981
## 10 0 FALSE 0.70 0.01158841
## 11 1 FALSE 0.56 0.01058941
## 12 0 FALSE 0.48 0.01008991
## 13 0 FALSE 0.52 0.01028971
## 14 0 FALSE 0.60 0.01048951
## 15 1 FALSE 0.66 0.01058941
## 16 0 FALSE 0.38 0.00979021
## 17 0 FALSE 0.90 0.01178821
## 18 0 FALSE 0.76 0.01088911
## 19 0 TRUE 0.54 0.00000000
## 20 0 FALSE 0.58 0.01068931
## 21 0 FALSE 0.00 0.01038961
## 22 0 FALSE 0.68 0.01078921
## 23 1 FALSE 0.52 0.01058941
## 24 0 FALSE 0.82 0.01148851
## 25 0 FALSE 0.46 0.01028971
## 26 0 FALSE 0.74 0.01108891
## 27 0 FALSE 0.56 0.01058941
## 28 0 FALSE 0.58 0.01038961
## 29 0 FALSE 0.50 0.01158841
## 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 1 2.5e+05
## 12 4 1.4e+06
## 13 6 2.2e+06
## 14 9 4.5e+06
## 15 3 1.0e+06
## 16 0 0.0e+00
## 17 7 2.8e+06
## 18 2 7.0e+05
## 19 10 4.2e+06
## 20 0 0.0e+00
## 21 0 0.0e+00
## 22 0 0.0e+00
## 23 0 0.0e+00
## 24 0 0.0e+00
## 25 0 0.0e+00
## 26 0 0.0e+00
## 27 0 0.0e+00
## 28 0 0.0e+00
## 29 0 0.0e+00
## atribut tipe jumlah_missing persen_missing jumlah_unik
## 1 customer_id character 0 0 29
## 2 nama character 0 0 29
## 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 12
## 12 total_purchase_final numeric 0 0 19
## [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
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 30 29
## 2 Duplikasi customer_id 1 0
## 3 Total missing value 7 0
## 4 Kategori kota unik 11 4
## 5 Kategori status unik 9 2
Praktikum ini memperlihatkan alur praproses data yang sistematis: mengevaluasi struktur dasar data, merapikan variabel kategorik, mengeliminasi duplikasi, memperbaiki data anomali, menangani nilai kosong, serta memetakan outlier. Alur ini ditutup dengan normalisasi numerik, integrasi antar-dataset, dan pemeriksaan kualitas secara menyeluruh.
Hal terpenting yang dapat disimpulkan adalah data preprocessing menuntut alasan logis pada setiap perlakuan data. Menghapus outlier atau membuang missing value secara spontan bukanlah langkah yang bijak, sebagaimana proses integrasi data juga memerlukan konfirmasi ulang agar integritas data tetap terjaga tanpa menimbulkan anomali baru.
Data mentah
↓
Eksplorasi dan audit awal
↓
Standardisasi kategori
↓
Deduplikasi dan pemeriksaan domain
↓
Penanganan missing value
↓
Deteksi dan evaluasi outlier
↓
Transformasi data
↓
Integrasi data
↓
Audit akhir
↓
Data siap digunakan untuk analisis/data mining