Persiapan RStudio

Membuat Proyek

  1. Buka RStudio.
  2. Pilih File → New Project → New Directory → New Project.
  3. Gunakan nama proyek, misalnya praktikum_preprocessing.
  4. Simpan file .Rmd ini di dalam folder proyek.
  5. Gunakan tombol Knit untuk menghasilkan laporan HTML.

Memeriksa Lingkungan Kerja

Penjelasan langkah: sebelum mulai mengolah data, kita perlu memastikan RStudio bekerja pada folder proyek yang benar. Langkah ini penting agar file hasil preprocessing, gambar, dan laporan tersimpan pada lokasi yang konsisten.

Kode getwd() digunakan untuk melihat lokasi folder kerja aktif.

getwd()
## [1] "C:/SEMESTER 5/Data Mining"

Selanjutnya, R.version.string digunakan untuk mengetahui versi R yang sedang digunakan. Informasi ini berguna ketika hasil praktikum perlu direproduksi pada komputer lain.

R.version.string
## [1] "R version 4.4.2 (2024-10-31 ucrt)"

Modul ini menggunakan fungsi dasar R sehingga tidak membutuhkan paket tambahan untuk menjalankan proses utama.

Membangun Dataset Praktikum

Dataset berikut sengaja dibuat tidak sempurna agar setiap tahap preprocessing dapat dipraktikkan secara langsung.

Data Pelanggan

Penjelasan langkah: data pelanggan menjadi sumber utama yang akan dibersihkan. Dataset dibuat secara sengaja memiliki beberapa masalah kualitas, sehingga kita dapat melihat bagaimana setiap teknik preprocessing bekerja.

Kode berikut membuat data pelanggan dalam bentuk data.frame.

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
)

pelanggan_raw

Data Transaksi

Penjelasan langkah: data transaksi merupakan sumber kedua. Data ini nantinya akan digabungkan dengan data pelanggan menggunakan customer_id. Nama kolom cust_id sengaja dibuat berbeda agar proses penyelarasan identifier juga dapat dipraktikkan.

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
)

transaksi_raw

Bagian I — Pemeriksaan Awal Data

Sebelum memperbaiki data, kondisi awal perlu dipahami terlebih dahulu. Pemeriksaan ini membantu menentukan masalah apa saja yang benar-benar terdapat pada dataset.

Memahami Struktur Data

Penjelasan langkah: pemeriksaan struktur dilakukan sebelum data diubah. Tujuannya adalah mengetahui ukuran dataset, nama atribut, tipe data, beberapa baris awal, serta ringkasan statistik. Dengan begitu, masalah dapat dikenali sebelum tindakan cleaning dilakukan.

Pertama, dim() digunakan untuk mengetahui jumlah baris dan kolom.

dim(pelanggan_raw)
## [1] 12  6

Berikutnya, names() menampilkan seluruh nama atribut yang tersedia.

names(pelanggan_raw)
## [1] "customer_id" "nama"        "usia"        "pendapatan"  "kota"       
## [6] "status"

Fungsi str() membantu melihat struktur dan tipe masing-masing variabel.

str(pelanggan_raw)
## 'data.frame':    12 obs. of  6 variables:
##  $ customer_id: chr  "C001" "C002" "C003" "C004" ...
##  $ nama       : chr  "Ani" "Budi" "Citra" "Dodi" ...
##  $ usia       : num  21 25 23 150 27 NA 31 29 22 35 ...
##  $ pendapatan : num  4.5e+06 NA 5.2e+06 4.8e+06 4.9e+06 5.1e+06 5.0e+08 4.7e+06 4.6e+06 5.3e+06 ...
##  $ kota       : chr  "Pekanbaru" " PKU" "PEKANBARU" "Dumai" ...
##  $ status     : chr  "Aktif" "aktif" "ACTIVE" "A" ...

Untuk melihat contoh isi data, digunakan head().

head(pelanggan_raw)

Terakhir, summary() memberikan ringkasan statistik untuk membantu menemukan nilai yang tidak wajar.

summary(pelanggan_raw)
##  customer_id            nama                usia          pendapatan       
##  Length:12          Length:12          Min.   : 21.00   Min.   :  4500000  
##  Class :character   Class :character   1st Qu.: 24.00   1st Qu.:  4725000  
##  Mode  :character   Mode  :character   Median : 28.00   Median :  5000000  
##                                        Mean   : 38.73   Mean   : 54440000  
##                                        3rd Qu.: 33.00   3rd Qu.:  5275000  
##                                        Max.   :150.00   Max.   :500000000  
##                                        NA's   :1        NA's   :2          
##      kota              status         
##  Length:12          Length:12         
##  Class :character   Class :character  
##  Mode  :character   Mode  :character  
##                                       
##                                       
##                                       
## 

Mengukur Kualitas Awal

Missing Value

Penjelasan langkah: NA menunjukkan nilai yang tidak tersedia. Kita tidak cukup hanya mengetahui ada atau tidaknya NA; persentasenya juga perlu dihitung agar dapat menilai seberapa besar masalah tersebut pada setiap atribut.

colSums(is.na(...)) menghitung jumlah nilai hilang pada setiap kolom.

colSums(is.na(pelanggan_raw))
## customer_id        nama        usia  pendapatan        kota      status 
##           0           0           1           2           1           0

Kode berikut mengubah jumlah tersebut menjadi persentase sehingga lebih mudah dibandingkan antaratribut.

round(colMeans(is.na(pelanggan_raw)) * 100, 2)
## customer_id        nama        usia  pendapatan        kota      status 
##        0.00        0.00        8.33       16.67        8.33        0.00

Duplikasi

Penjelasan langkah: duplikasi dapat menyebabkan satu pelanggan dihitung lebih dari satu kali. Karena customer_id berfungsi sebagai identifier, pemeriksaan dilakukan pada seluruh baris sekaligus pada kolom identifier tersebut.

Kode pertama menghitung baris yang benar-benar duplikat.

sum(duplicated(pelanggan_raw))
## [1] 1

Kode kedua memeriksa apakah ada customer_id yang muncul lebih dari sekali.

sum(duplicated(pelanggan_raw$customer_id))
## [1] 1

Konsistensi Kategori

Penjelasan langkah: kategori yang sama dapat ditulis dengan variasi huruf besar-kecil, spasi, atau singkatan. Jika tidak diseragamkan, satu kategori dapat terbaca sebagai beberapa kategori berbeda saat analisis.

unique() digunakan untuk melihat seluruh kategori kota yang muncul.

sort(unique(pelanggan_raw$kota))
## [1] " PKU"       "Dumai"      "DUMAI"      "pekanbaru"  "Pekanbaru" 
## [6] "PEKANBARU"  "Pekanbaru " "PKU"        "Siak"

Hal yang sama dilakukan pada variabel status pelanggan.

sort(unique(pelanggan_raw$status))
## [1] "A"           "ACTIVE"      "aktif"       "Aktif"       "AKTIF"      
## [6] "nonaktif"    "Tidak aktif" "Tidak Aktif"

Rentang Nilai Numerik

Penjelasan langkah: rentang nilai membantu menemukan angka yang secara logika perlu diperiksa. Contohnya, usia yang terlalu tinggi untuk konteks pelanggan dapat menjadi indikasi kesalahan pencatatan atau outlier.

Rentang usia diperiksa terlebih dahulu.

range(pelanggan_raw$usia, na.rm = TRUE)
## [1]  21 150

Kemudian rentang pendapatan diperiksa untuk melihat apakah terdapat nilai yang jauh berbeda dari observasi lainnya.

range(pelanggan_raw$pendapatan, na.rm = TRUE)
## [1] 4.5e+06 5.0e+08

Membuat Fungsi Audit Data

Penjelasan langkah: daripada melakukan pemeriksaan satu per satu berulang kali, kita membuat fungsi audit_data(). Fungsi ini merangkum tipe data, jumlah NA, persentase NA, dan jumlah nilai unik untuk setiap atribut. Ringkasan ini akan berguna lagi pada tahap audit akhir.

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

Bagian II — Data Cleaning

Pada tahap ini dilakukan perbaikan terhadap kategori, duplikasi, dan nilai yang tidak sesuai dengan aturan domain. Dataset asli tetap disimpan agar perubahan dapat dilacak.

Membuat Salinan Kerja

pelanggan <- pelanggan_raw

Membersihkan Spasi dan Kapitalisasi

Penjelasan langkah: sebelum kategori diseragamkan, spasi yang tidak diperlukan dihapus dengan trimws(). Setelah itu, seluruh teks diubah menjadi huruf kecil menggunakan tolower() agar variasi kapitalisasi tidak dianggap sebagai kategori yang berbeda.

pelanggan$kota <- trimws(pelanggan$kota)
pelanggan$status <- trimws(pelanggan$status)

pelanggan$kota <- tolower(pelanggan$kota)
pelanggan$status <- tolower(pelanggan$status)

sort(unique(pelanggan$kota))
## [1] "dumai"     "pekanbaru" "pku"       "siak"

Setelah standardisasi awal, kategori diperiksa kembali.

sort(unique(pelanggan$status))
## [1] "a"           "active"      "aktif"       "nonaktif"    "tidak aktif"

Menyeragamkan Kategori

Penjelasan langkah: setelah spasi dan kapitalisasi dibersihkan, beberapa nilai masih memiliki makna yang sama tetapi bentuk penulisannya berbeda. Nilai-nilai tersebut dipetakan ke label standar agar analisis kategori menjadi konsisten.

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"

Pemeriksaan ulang dilakukan untuk memastikan kategori kota sudah menjadi lebih konsisten.

sort(unique(pelanggan$status))
## [1] "Aktif"       "Tidak Aktif"

Mendeteksi dan Menghapus Duplikasi

Penjelasan langkah: sebelum menghapus duplikasi, baris yang memiliki customer_id berulang ditampilkan terlebih dahulu. Ini penting agar kita dapat mengetahui data mana yang akan terdampak.

pelanggan[
  duplicated(pelanggan$customer_id) |
  duplicated(pelanggan$customer_id, fromLast = TRUE),
]

Setelah duplikasi diperiksa, hanya satu baris untuk setiap customer_id yang dipertahankan.

pelanggan <- pelanggan[!duplicated(pelanggan$customer_id), ]
rownames(pelanggan) <- NULL

dim(pelanggan)
## [1] 11  6

Memeriksa Aturan Domain

Penjelasan langkah: pemeriksaan domain digunakan untuk melihat apakah nilai masih masuk akal menurut aturan konteks data. Untuk contoh ini, usia di luar 15–100 tahun dianggap sebagai kandidat masalah. Kandidat tersebut diperiksa terlebih dahulu, bukan langsung dihapus.

pelanggan[pelanggan$usia < 15 | pelanggan$usia > 100, ]

Pendapatan negatif juga diperiksa sebagai kandidat nilai yang tidak valid.

pelanggan[pelanggan$pendapatan < 0, ]

Pada dataset praktikum, usia C004 bernilai 150. Nilai tersebut dikoreksi menjadi 50 berdasarkan sumber asli yang menjadi acuan praktikum.

pelanggan$usia[pelanggan$customer_id == "C004"] <- 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

Bagian III — Penanganan Missing Values

Nilai hilang perlu diidentifikasi sebelum menentukan tindakan. Tidak semua missing value harus dihapus; pilihan metode bergantung pada jenis atribut dan tujuan analisis.

Mengidentifikasi Lokasi Missing Value

colSums(is.na(pelanggan))
## customer_id        nama        usia  pendapatan        kota      status 
##           0           0           1           2           1           0
pelanggan[!complete.cases(pelanggan), ]

Strategi 1 — Menghapus Baris

Penjelasan langkah: salah satu cara menangani NA adalah mempertahankan hanya baris yang lengkap. Di sini metode tersebut digunakan sebagai pembanding, bukan sebagai keputusan otomatis.

pelanggan_complete <- pelanggan[complete.cases(pelanggan), ]
nrow(pelanggan)
## [1] 11

Kode berikut menunjukkan jumlah baris yang benar-benar lengkap.

nrow(pelanggan_complete)
## [1] 8

Persentase baris yang hilang akibat penghapusan juga dihitung.

round((1 - nrow(pelanggan_complete) / nrow(pelanggan)) * 100, 2)
## [1] 27.27

Pendekatan ini sederhana, tetapi dapat mengurangi jumlah observasi. Karena itu, penghapusan baris sebaiknya tidak dilakukan otomatis.

Strategi 2 — Imputasi Mean dan Median

Penjelasan langkah: jika penghapusan baris terlalu banyak mengurangi data, nilai hilang dapat diisi menggunakan nilai statistik tertentu. Mean dan median dibandingkan terlebih dahulu. Median dipilih pada contoh ini karena lebih tahan terhadap pengaruh nilai ekstrem.

mean_pendapatan <- mean(pelanggan$pendapatan, na.rm = TRUE)
median_pendapatan <- median(pelanggan$pendapatan, na.rm = TRUE)

mean_pendapatan
## [1] 59900000
median_pendapatan
## [1] 4900000

Pada data praktikum, median digunakan sebagai dasar imputasi.

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")]

Strategi 3 — Imputasi Nilai Kategorik

Penjelasan langkah: atribut kategorik tidak dapat diimputasi menggunakan mean atau median. Untuk kota yang tidak diketahui, digunakan label Tidak diketahui sehingga informasi mengenai ketidaktersediaan data tetap terlihat.

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

Penjelasan langkah: selain mengisi NA, kita dapat menyimpan indikator yang menunjukkan apakah sebuah nilai pada awalnya hilang. Variabel indikator ini berguna apabila kondisi missingness ingin tetap dipertimbangkan dalam analisis.

pelanggan$pendapatan_missing <- as.integer(is.na(pelanggan$pendapatan))
table(pelanggan$pendapatan_missing)
## 
## 0 1 
## 9 2

Membandingkan Distribusi Sebelum dan Sesudah Imputasi

Penjelasan langkah: imputasi dapat mengubah distribusi data. Karena itu, histogram sebelum dan sesudah imputasi dibandingkan untuk melihat perubahan pola secara visual.

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 = "navy",
  breaks = 8
)

par(mfrow = c(1, 1))

Bagian IV — Penanganan Outlier

Outlier adalah observasi yang berada jauh dari pola umum data. Nilai seperti ini tidak langsung dianggap salah karena bisa saja merupakan kejadian yang memang terjadi.

Visualisasi dengan Boxplot

Penjelasan langkah: boxplot digunakan untuk melihat persebaran data dan kandidat outlier. Titik yang berada jauh dari pola utama perlu diperiksa lebih lanjut, tetapi belum tentu merupakan kesalahan.

boxplot(
  pelanggan$pendapatan_imputasi,
  horizontal = TRUE,
  col = "lightblue4",
  main = "Boxplot Pendapatan",
  xlab = "Pendapatan"
)

Menghitung Batas IQR

Penjelasan langkah: metode IQR menggunakan kuartil pertama (Q1), kuartil ketiga (Q3), dan jarak antarkuartil (IQR). Batas kandidat outlier ditentukan dari Q1 − 1,5×IQR dan Q3 + 1,5×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

Penjelasan langkah: setelah batas IQR tersedia, setiap nilai pendapatan diberi penanda apakah berada di luar batas tersebut. Penanda ini membantu proses evaluasi tanpa langsung menghapus observasi.

pelanggan$outlier_pendapatan <-
  pelanggan$pendapatan_imputasi < batas_bawah |
  pelanggan$pendapatan_imputasi > batas_atas

pelanggan[
  pelanggan$outlier_pendapatan,
  c("customer_id", "nama", "pendapatan_imputasi")
]

Mengevaluasi Tindakan

Penjelasan langkah: outlier tidak langsung dihapus. Salah satu alternatif yang dapat diperagakan adalah winsorization, yaitu membatasi nilai ekstrem pada batas bawah dan batas atas yang telah dihitung.

pelanggan$pendapatan_winsor <- pmin(
  pmax(pelanggan$pendapatan_imputasi, batas_bawah),
  batas_atas
)

pelanggan[
  pelanggan$outlier_pendapatan,
  c("customer_id", "pendapatan_imputasi", "pendapatan_winsor")
]

Pada praktik nyata, keputusan untuk mempertahankan, mengoreksi, mentransformasi, atau menghapus outlier harus didasarkan pada konteks data. Nilai ekstrem tidak seharusnya dihapus hanya karena berbeda jauh dari observasi lain.

Bagian V — Transformasi Data

Transformasi digunakan untuk mengubah skala atribut numerik sehingga lebih sesuai untuk kebutuhan analisis atau algoritma tertentu.

Normalisasi Min–Maks

Penjelasan langkah: normalisasi min–maks mengubah nilai numerik ke skala 0 sampai 1. Metode ini berguna ketika atribut memiliki rentang yang sangat berbeda dan analisis membutuhkan skala yang lebih seragam.

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"
)]

Normalisasi Z-Score

Penjelasan langkah: z-score mengubah nilai berdasarkan jaraknya dari rata-rata dalam satuan simpangan baku. Nilai positif menunjukkan posisi di atas rata-rata, sedangkan nilai negatif menunjukkan posisi di bawah rata-rata.

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)

Decimal Scaling

Penjelasan langkah: decimal scaling mengecilkan skala data dengan membaginya menggunakan pangkat 10 tertentu. Tujuannya adalah mengurangi besarnya angka tanpa mengubah urutan relatif observasi.

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

Penjelasan langkah: hasil dari beberapa metode transformasi ditampilkan berdampingan agar perbedaan skala dapat dibandingkan secara langsung.

transformasi <- pelanggan[, c(
  "customer_id",
  "pendapatan_imputasi",
  "pendapatan_minmax",
  "pendapatan_z",
  "pendapatan_decimal"
)]

transformasi

Dampak Outlier terhadap Normalisasi

Penjelasan langkah: pada tahap ini kita membandingkan normalisasi berdasarkan data sebelum dan sesudah winsorization. Tujuannya adalah melihat bagaimana nilai ekstrem memengaruhi skala sebagian besar observasi.

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)

Grafik membantu melihat bahwa keberadaan nilai ekstrem dapat membuat sebagian besar observasi terkonsentrasi pada rentang yang sangat sempit setelah normalisasi. Karena itu, pemeriksaan outlier sebaiknya dilakukan sebelum memilih metode transformasi.

Bagian VI — Integrasi Data

Setelah data pelanggan dibersihkan, data tersebut dapat digabungkan dengan data transaksi. Sebelum join, kecocokan identifier perlu diperiksa.

Memeriksa Kunci pada Kedua Sumber

Penjelasan langkah: sebelum dua dataset digabungkan, identifier harus diperiksa terlebih dahulu. Kita perlu memastikan customer_id tidak berulang dan mengetahui identifier yang hanya muncul pada salah satu sumber.

sum(duplicated(pelanggan$customer_id))
## [1] 0
sum(duplicated(transaksi_raw$cust_id))
## [1] 0
setdiff(pelanggan$customer_id, transaksi_raw$cust_id)
## [1] "C011"
setdiff(transaksi_raw$cust_id, pelanggan$customer_id)
## [1] "C012"

Hasil setdiff() menunjukkan identifier yang hanya muncul pada salah satu sumber data. Informasi ini penting untuk mengetahui apakah ada pelanggan tanpa transaksi atau transaksi tanpa data pelanggan.

Menyelaraskan Nama Identifier

Penjelasan langkah: kedua sumber harus memiliki nama kolom kunci yang sama agar proses penggabungan lebih mudah. cust_id pada data transaksi diselaraskan menjadi customer_id.

transaksi <- transaksi_raw

names(transaksi)[names(transaksi) == "cust_id"] <- "customer_id"

Melakukan Left Join dengan merge()

Penjelasan langkah: merge() digunakan untuk menggabungkan data pelanggan dengan data transaksi berdasarkan customer_id. Opsi all.x = TRUE memastikan seluruh pelanggan dari dataset utama tetap dipertahankan.

data_terintegrasi <- merge(
  pelanggan,
  transaksi,
  by = "customer_id",
  all.x = TRUE
)

data_terintegrasi[, c(
  "customer_id",
  "nama",
  "jumlah_transaksi",
  "total_purchase"
)]

Penggunaan all.x = TRUE membuat seluruh pelanggan dari tabel utama tetap dipertahankan meskipun tidak ditemukan pasangan transaksi.

Memvalidasi Hasil Integrasi

Penjelasan langkah: setelah penggabungan, hasil tidak boleh langsung dianggap benar. Jumlah baris, keunikan identifier, dan NA pada kolom transaksi perlu diperiksa kembali untuk memastikan integrasi tidak menghasilkan duplikasi atau kehilangan data yang tidak diinginkan.

Pertama, periksa apakah jumlah baris berubah secara tidak wajar.

c(
  sebelum = nrow(pelanggan),
  sesudah = nrow(data_terintegrasi)
)
## sebelum sesudah 
##      11      11

Selanjutnya, pastikan customer_id tetap unik.

sum(duplicated(data_terintegrasi$customer_id))
## [1] 0

Periksa pula missing value yang muncul pada atribut transaksi.

colSums(
  is.na(
    data_terintegrasi[, c("jumlah_transaksi", "total_purchase")]
  )
)
## jumlah_transaksi   total_purchase 
##                1                1

Cari pelanggan yang belum memiliki pasangan transaksi.

data_terintegrasi[
  is.na(data_terintegrasi$jumlah_transaksi),
  c("customer_id", "nama")
]

Mengisi Nol atau Mempertahankan NA?

Nilai NA pada data transaksi tidak otomatis berarti nol. Nilai tersebut dapat berarti pelanggan memang belum bertransaksi, atau data transaksi tidak tersedia. Oleh sebab itu, penggantian menjadi nol hanya dilakukan apabila definisinya sudah dikonfirmasi.

# Contoh: NA dianggap 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)
] <- 0

Dataset Akhir dan Evaluasi

Memilih Atribut Akhir

Penjelasan langkah: setelah seluruh proses selesai, hanya atribut yang dibutuhkan untuk dataset analisis akhir yang dipertahankan. Kolom hasil imputasi dan transformasi dipilih agar dataset akhir siap digunakan untuk tahap analisis berikutnya.

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

Audit Akhir

Penjelasan langkah: audit akhir dilakukan dengan prinsip yang sama seperti audit awal. Hasilnya dibandingkan untuk memastikan proses preprocessing benar-benar memperbaiki kondisi data sesuai tujuan.

audit_akhir <- audit_data(data_final)
audit_akhir

Pastikan tidak ada customer_id yang masih berulang.

sum(duplicated(data_final$customer_id))
## [1] 0

Periksa kembali seluruh missing value.

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

Membandingkan Kondisi Sebelum dan Sesudah

Penjelasan langkah: tabel perbandingan menjadi ringkasan perubahan kualitas data. Dengan membandingkan kondisi sebelum dan sesudah, kita dapat melihat apakah duplikasi dan missing value berkurang serta apakah kategori sudah lebih konsisten.

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

Tabel perbandingan digunakan sebagai pemeriksaan akhir untuk melihat perubahan kualitas data. Indikator seperti jumlah baris, duplikasi, missing value, serta jumlah kategori membantu memastikan bahwa proses preprocessing menghasilkan perubahan yang memang diharapkan.

Menyimpan Hasil

Penjelasan langkah: dataset akhir dan log perubahan disimpan ke file CSV. Menyimpan keduanya penting agar hasil preprocessing dapat digunakan kembali dan setiap perubahan tetap terdokumentasi.

write.csv(
  data_final,
  "data_pelanggan_setelah_preprocessing.csv",
  row.names = FALSE
)

write.csv(
  log_perubahan,
  "log_perubahan_preprocessing.csv",
  row.names = FALSE
)

Ringkasan

Alur preprocessing yang dilakukan dalam praktikum ini adalah:

  1. memahami struktur dan kualitas awal data;
  2. membersihkan kategori, duplikasi, dan pelanggaran aturan domain;
  3. menangani missing values dengan strategi yang dapat dijelaskan;
  4. mendeteksi dan mengevaluasi outlier;
  5. melakukan transformasi pada atribut numerik;
  6. mengintegrasikan data pelanggan dan transaksi; dan
  7. melakukan audit akhir serta menyimpan dataset hasil preprocessing.

Kesimpulan: preprocessing merupakan rangkaian keputusan untuk meningkatkan kesiapan data sebelum analisis. R menjalankan langkah yang telah ditentukan, tetapi kualitas hasil akhirnya tetap bergantung pada pemahaman terhadap konteks data, tujuan analisis, serta dokumentasi setiap perubahan.

Daftar Pustaka

Han, J., Kamber, M., & Pei, J. (2012). Data Mining: Concepts and Techniques (3rd ed.). Morgan Kaufmann. Chapter 3: Data Preprocessing.