Pendahuluan

Latar Belakang

Kualitas data menjadi fondasi utama dalam setiap proses analisis data. Data yang diperoleh dari berbagai sumber sering kali memiliki beragam masalah seperti ketidakkonsistenan format, nilai yang hilang, duplikasi, serta nilai ekstrem yang dapat mengganggu hasil akhir. Oleh karena itu, diperlukan tahapan preprocessing untuk membersihkan dan menyiapkan data sebelum dilakukan analisis atau pemodelan lebih lanjut.

Tujuan Praktikum

Praktikum ini disusun dengan tujuan agar mahasiswa mampu:

  1. Memahami konsep dasar data preprocessing;
  2. Mengidentifikasi berbagai permasalahan kualitas data;
  3. Menerapkan teknik pembersihan data secara terstruktur;
  4. Menangani nilai hilang (missing values) dengan strategi yang tepat;
  5. Mendeteksi dan mengevaluasi keberadaan outlier;
  6. Melakukan transformasi pada atribut numerik;
  7. Menggabungkan dua sumber data yang berbeda.

Skenario Kasus

Sebuah perusahaan e-commerce ingin melakukan analisis terhadap profil pelanggannya. Data tersedia dalam dua sumber terpisah:

  • Data pelanggan: berisi informasi identitas, usia, kota asal, pendapatan, dan status keaktifan pelanggan.
  • Data transaksi: berisi catatan jumlah transaksi dan total belanja masing-masing pelanggan.

Kedua dataset tersebut masih dalam kondisi “mentah” dan mengandung berbagai masalah sehingga perlu melalui serangkaian tahap pembersihan sebelum dapat digunakan untuk analisis.


Persiapan Lingkungan Kerja

Membuat Proyek RStudio

Langkah awal yang dilakukan adalah membuat proyek baru di RStudio agar pekerjaan lebih terstruktur dan terdokumentasi:

  1. Buka aplikasi RStudio.
  2. Pilih menu File → New Project → New Directory → New Project.
  3. Beri nama proyek, misalnya praktikum_preprocessing.
  4. Simpan proyek pada direktori yang diinginkan.

Memeriksa Lingkungan Kerja

# Cek direktori kerja aktif
getwd()
## [1] "D:/rsconnect/documents"
# Cek versi R yang digunakan
R.version.string
## [1] "R version 4.4.2 (2024-10-31 ucrt)"

Modul ini menggunakan fungsi-fungsi dasar R sehingga tidak memerlukan instalasi paket tambahan.


Membangun Dataset Praktikum

Dataset berikut sengaja dibuat dengan berbagai masalah kualitas data agar seluruh tahap preprocessing dapat dipraktikkan secara menyeluruh.

# Data pelanggan dengan berbagai masalah kualitas
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
)

# Data transaksi
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
)

# Tampilkan data pelanggan
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

Eksplorasi Awal Data

Memahami Struktur Data

Sebelum melakukan perubahan, penting untuk memahami struktur, dimensi, tipe data, dan beberapa observasi awal dari dataset.

# Dimensi data
dim(pelanggan_raw)
## [1] 12  6
# Nama-nama kolom
names(pelanggan_raw)
## [1] "customer_id" "nama"        "usia"        "pendapatan"  "kota"       
## [6] "status"
# Struktur data
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  4500000 NA 5200000 4800000 4900000 5100000 500000000 4700000 4600000 5300000 ...
##  $ kota       : chr  "Pekanbaru" " PKU" "PEKANBARU" "Dumai" ...
##  $ status     : chr  "Aktif" "aktif" "ACTIVE" "A" ...
# Lima baris pertama
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

Ringkasan Statistik

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  
##                                       
##                                       
##                                       
## 

Identifikasi Masalah Kualitas Data

Dalam literatur data mining, dikenal enam dimensi kualitas data: akurasi, kelengkapan, konsistensi, ketepatan waktu, kredibilitas, dan interpretabilitas. Tidak semua dimensi dapat dihitung hanya dari isi tabel.

Pemeriksaan Awal

# Jumlah nilai hilang per atribut
colSums(is.na(pelanggan_raw))
## customer_id        nama        usia  pendapatan        kota      status 
##           0           0           1           2           1           0
# Persentase nilai hilang
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 baris penuh
sum(duplicated(pelanggan_raw))
## [1] 1
# Duplikasi customer_id
sum(duplicated(pelanggan_raw$customer_id))
## [1] 1
# Kategori unik pada atribut kota dan status
sort(unique(pelanggan_raw$kota))
## [1] " PKU"       "Dumai"      "DUMAI"      "pekanbaru"  "Pekanbaru" 
## [6] "PEKANBARU"  "Pekanbaru " "PKU"        "Siak"
sort(unique(pelanggan_raw$status))
## [1] "A"           "ACTIVE"      "aktif"       "Aktif"       "AKTIF"      
## [6] "nonaktif"    "Tidak aktif" "Tidak Aktif"
# Rentang nilai numerik
range(pelanggan_raw$usia, na.rm = TRUE)
## [1]  21 150
range(pelanggan_raw$pendapatan, na.rm = TRUE)
## [1]   4500000 500000000

Fungsi Audit Data

Untuk memudahkan pemeriksaan kualitas data, dibuat fungsi yang dapat digunakan berulang kali.

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 berfungsi sebagai sinyal awal, bukan keputusan final. Setiap indikasi masalah perlu diverifikasi lebih lanjut sebelum dilakukan tindakan.


Pembersihan Data (Data Cleaning)

Membuat Salinan Kerja

Data mentah dipertahankan agar setiap perubahan dapat dilacak dan dievaluasi.

pelanggan <- pelanggan_raw

Standardisasi Format

Menghapus Spasi dan Menyeragamkan Kapitalisasi

# Hapus spasi di awal dan akhir teks
pelanggan$kota <- trimws(pelanggan$kota)
pelanggan$status <- trimws(pelanggan$status)

# Ubah semua huruf menjadi kecil untuk memudahkan pencocokan
pelanggan$kota <- tolower(pelanggan$kota)
pelanggan$status <- tolower(pelanggan$status)

# Lihat kategori kota setelah pembersihan awal
sort(unique(pelanggan$kota))
## [1] "dumai"     "pekanbaru" "pku"       "siak"

Menyeragamkan Kategori

# Standardisasi nama 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
pelanggan$status[pelanggan$status %in% c("aktif", "active", "a")] <- "Aktif"
pelanggan$status[pelanggan$status %in% c("tidak aktif", "nonaktif")] <- "Tidak Aktif"

# Periksa hasil standardisasi
sort(unique(pelanggan$kota))
## [1] "Dumai"     "Pekanbaru" "Siak"
sort(unique(pelanggan$status))
## [1] "Aktif"       "Tidak Aktif"

Penanganan Duplikasi

# Tampilkan 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
# Hapus duplikat, pertahankan kemunculan pertama
pelanggan <- pelanggan[!duplicated(pelanggan$customer_id), ]
rownames(pelanggan) <- NULL

dim(pelanggan)
## [1] 11  6

Validasi Aturan Domain

# Cek usia yang tidak masuk akal (di luar rentang 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>
# Cek pendapatan negatif (tidak mungkin)
pelanggan[pelanggan$pendapatan < 0, ]
##      customer_id nama usia pendapatan kota status
## NA          <NA> <NA>   NA         NA <NA>   <NA>
## NA.1        <NA> <NA>   NA         NA <NA>   <NA>

Koreksi Nilai Domain

Nilai usia 150 jelas melanggar aturan domain. Berdasarkan verifikasi dari sumber data, nilai yang benar adalah 50.

pelanggan$usia[pelanggan$customer_id == "C004"] <- 50

Log Perubahan

Setiap tindakan yang dilakukan dicatat untuk keperluan audit dan reproduktifitas.

log_perubahan <- data.frame(
  tahap = c("Standardisasi", "Standardisasi", "Deduplikasi", "Koreksi domain"),
  atribut = c("kota", "status", "customer_id", "usia"),
  tindakan = c(
    "PKU dan variasi kapital distandardisasi menjadi Pekanbaru",
    "ACTIVE/A distandardisasi menjadi Aktif; nonaktif menjadi Tidak Aktif",
    "Menghapus kemunculan kedua customer_id C010",
    "Usia C004 dikoreksi dari 150 menjadi 50 berdasarkan verifikasi sumber"
  ),
  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 distandardisasi menjadi Pekanbaru
## 2  ACTIVE/A distandardisasi menjadi Aktif; nonaktif menjadi Tidak Aktif
## 3                           Menghapus kemunculan kedua customer_id C010
## 4 Usia C004 dikoreksi dari 150 menjadi 50 berdasarkan verifikasi sumber

Penanganan Nilai Hilang (Missing Values)

Identifikasi Lokasi Nilai Hilang

# Jumlah nilai hilang per atribut
colSums(is.na(pelanggan))
## customer_id        nama        usia  pendapatan        kota      status 
##           0           0           1           2           1           0
# Baris yang mengandung nilai hilang
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

Strategi 1: Menghapus Baris

Pendekatan ini hanya didemonstrasikan, tidak langsung diterapkan.

pelanggan_complete <- pelanggan[complete.cases(pelanggan), ]
nrow(pelanggan)
## [1] 11
nrow(pelanggan_complete)
## [1] 8
# Persentase data yang hilang jika baris tidak lengkap dihapus
round((1 - nrow(pelanggan_complete) / nrow(pelanggan)) * 100, 2)
## [1] 27.27

Strategi 2: Imputasi dengan Median

Pendapatan memiliki nilai ekstrem yang memengaruhi mean. Median lebih stabil sebagai ukuran pemusatan.

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

# Tampilkan perbandingan mean dan median
data.frame(
  ukuran = c("Mean", "Median"),
  nilai = c(mean_pendapatan, median_pendapatan)
)
##   ukuran    nilai
## 1   Mean 59900000
## 2 Median  4900000
# Imputasi pendapatan dengan median
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

# Bandingkan hasil imputasi
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 Kategorik

Untuk data kategorik, nilai hilang diisi dengan kategori eksplisit untuk menjaga transparansi.

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

Indikator Nilai Hilang

Menambahkan indikator untuk mempertahankan informasi bahwa suatu nilai awalnya hilang.

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

Evaluasi Dampak 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)

par(mfrow = c(1, 1))

Perhatian: Imputasi dapat mengubah distribusi dan mengecilkan variasi data. Evaluasi tidak berhenti setelah semua nilai hilang terisi.


Deteksi dan Penanganan Outlier

Visualisasi dengan Boxplot

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

Perhitungan 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

data.frame(
  keterangan = c("Q1", "Q3", "IQR", "Batas Bawah", "Batas Atas"),
  nilai = c(q1, q3, iqr, batas_bawah, batas_atas)
)
##    keterangan   nilai
## 1          Q1 4750000
## 2          Q3 5150000
## 3         IQR  400000
## 4 Batas Bawah 4150000
## 5  Batas Atas 5750000

Identifikasi 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

Evaluasi dan Tindakan Lanjut

Outlier tidak otomatis dihapus. Beberapa kemungkinan yang perlu dipertimbangkan:

  1. Kesalahan input → koreksi jika nilai benar dapat diverifikasi.
  2. Observasi valid tetapi ekstrem → pertahankan atau gunakan metode robust.
  3. Populasi berbeda → pisahkan segmen jika diperlukan.

Sebagai demonstrasi tambahan, dibuat versi winsorized untuk membandingkan dampak metode.

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

Transformasi Data

Normalisasi Min-Maks

Rumus: \(x' = \frac{x - \min(x)}{\max(x) - \min(x)}\)

minmax <- function(x) {
  if (all(is.na(x))) return(rep(NA_real_, length(x)))
  rentang <- max(x, na.rm = TRUE) - min(x, na.rm = TRUE)
  if (rentang == 0) return(rep(0, length(x)))
  (x - min(x, na.rm = TRUE)) / rentang
}

pelanggan$usia_minmax <- minmax(pelanggan$usia_imputasi)
pelanggan$pendapatan_minmax <- minmax(pelanggan$pendapatan_imputasi)

pelanggan[, c("customer_id", "usia", "usia_minmax",
              "pendapatan_imputasi", "pendapatan_minmax")]
##    customer_id usia usia_minmax pendapatan_imputasi pendapatan_minmax
## 1         C001   21  0.00000000             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

Rumus: \(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

Rumus: \(x' = \frac{x}{10^j}\) dengan \(j = \lceil \log_{10}(\max|x| + 1) \rceil\)

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

Perbandingan Metode Transformasi

# Format angka agar tidak pakai notasi ilmiah
transformasi <- pelanggan[, c(
  "customer_id", "pendapatan_imputasi", "pendapatan_minmax",
  "pendapatan_z", "pendapatan_decimal"
)]

transformasi$pendapatan_imputasi <- format(
  transformasi$pendapatan_imputasi, 
  big.mark = ".", 
  scientific = FALSE
)

transformasi
##    customer_id pendapatan_imputasi pendapatan_minmax pendapatan_z
## 1         C001           4.500.000      0.0000000000   -0.3041235
## 2         C002           4.900.000      0.0008072654   -0.3014440
## 3         C003           5.200.000      0.0014127144   -0.2994343
## 4         C004           4.800.000      0.0006054490   -0.3021138
## 5         C005           4.900.000      0.0008072654   -0.3014440
## 6         C006           5.100.000      0.0012108981   -0.3001042
## 7         C007         500.000.000      1.0000000000    3.0151095
## 8         C008           4.700.000      0.0004036327   -0.3027837
## 9         C009           4.600.000      0.0002018163   -0.3034536
## 10        C010           5.300.000      0.0016145308   -0.2987645
## 11        C011           4.900.000      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: Outlier dapat menekan sebagian besar nilai min-maks ke rentang yang sangat sempit. Inilah alasan deteksi outlier perlu dilakukan sebelum memilih transformasi.


Integrasi Data

Pemeriksaan Kunci pada Kedua Sumber

# Cek duplikasi pada masing-masing tabel
sum(duplicated(pelanggan$customer_id))
## [1] 0
sum(duplicated(transaksi_raw$cust_id))
## [1] 0
# Identifikasi ketidaksesuaian data
setdiff(pelanggan$customer_id, transaksi_raw$cust_id)
## [1] "C011"
setdiff(transaksi_raw$cust_id, pelanggan$customer_id)
## [1] "C012"

Penyelarasan Nama Kolom

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

Penggabungan Data

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

Validasi Hasil Integrasi

# Jumlah baris sebelum dan sesudah
c(sebelum = nrow(pelanggan), sesudah = nrow(data_terintegrasi))
## sebelum sesudah 
##      11      11
# Cek keunikan customer_id
sum(duplicated(data_terintegrasi$customer_id))
## [1] 0
# Missing value baru akibat tidak ada pasangan transaksi
colSums(is.na(data_terintegrasi[, c("jumlah_transaksi", "total_purchase")]))
## jumlah_transaksi   total_purchase 
##                1                1
# Pelanggan tanpa transaksi
data_terintegrasi[is.na(data_terintegrasi$jumlah_transaksi),
                  c("customer_id", "nama")]
##    customer_id nama
## 11        C011 Kiki

Mengisi Nilai NA pada Transaksi

# Hanya dilakukan jika telah dikonfirmasi bahwa NA berarti belum pernah transaksi
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

Pemilihan 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

audit_akhir <- audit_data(data_final)
audit_akhir
##                   atribut      tipe jumlah_missing persen_missing jumlah_unik
## 1             customer_id character              0              0          11
## 2                    nama character              0              0          11
## 3                    usia   numeric              0              0          11
## 4                    kota character              0              0           4
## 5                  status character              0              0           2
## 6     pendapatan_imputasi   numeric              0              0           9
## 7      pendapatan_missing   integer              0              0           2
## 8      outlier_pendapatan   logical              0              0           2
## 9             usia_minmax   numeric              0              0          11
## 10      pendapatan_minmax   numeric              0              0           9
## 11 jumlah_transaksi_final   numeric              0              0           9
## 12   total_purchase_final   numeric              0              0          11
# Cek duplikasi dan missing value
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 Kondisi Awal dan Akhir

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

Penyimpanan Hasil

# Simpan data akhir
write.csv(data_final,
          "data_pelanggan_setelah_preprocessing.csv",
          row.names = FALSE)

# Simpan log perubahan
write.csv(log_perubahan,
          "log_perubahan_preprocessing.csv",
          row.names = FALSE)

Refleksi

  1. Apakah dataset tanpa nilai hilang selalu lebih berkualitas?
    Tidak selalu. Menghapus atau mengisi nilai hilang tanpa pemahaman konteks dapat menghilangkan informasi penting atau menimbulkan bias.

  2. Mengapa outlier tidak boleh otomatis dihapus?
    Outlier bisa merupakan kesalahan input, tetapi juga bisa mencerminkan fenomena nyata yang penting untuk analisis.

  3. Bagaimana preprocessing dapat menimbulkan bias?
    Setiap keputusan dalam preprocessing (metode imputasi, transformasi, penghapusan data) dapat mempengaruhi hasil analisis akhir.

  4. Mengapa parameter imputasi dan transformasi seharusnya dihitung dari data pelatihan?
    Untuk menghindari kebocoran data (data leakage) yang dapat menyebabkan overestimasi kinerja model.

  5. Apa risiko integrasi data jika identifier tidak unik?
    Dapat menyebabkan penggandaan baris (cartesian product) dan hasil analisis yang tidak akurat.


Ringkasan

Alur preprocessing yang telah dilakukan mencakup:

  1. Pemahaman struktur dan kualitas awal data;
  2. Pembersihan kategori, duplikasi, dan pelanggaran domain;
  3. Penanganan nilai hilang tanpa menyembunyikan ketidakpastian;
  4. Deteksi dan evaluasi outlier;
  5. Transformasi atribut numerik;
  6. Integrasi data dan validasi hasil.

Preprocessing adalah proses pengambilan keputusan. Kode R hanya menjalankan keputusan tersebut; kualitas hasil tetap bergantung pada pemahaman data, tujuan analisis, dan dokumentasi perubahan.


Daftar Pustaka

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