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 menyelesaikan praktikum, mahasiswa mampu:

  1. menjelaskan kondisi data yang menyebabkan preprocessing diperlukan;
  2. mengidentifikasi masalah kualitas data menggunakan R;
  3. menerapkan data cleaning secara sistematis;
  4. memilih dan menerapkan strategi penanganan missing values;
  5. mendeteksi serta mengevaluasi outlier;
  6. melakukan transformasi atribut numerik; dan
  7. mengintegrasikan dua sumber data serta memvalidasi hasilnya.

Skenario Praktikum

Data berasal dari dua sumber:

  • data pelanggan, yang memuat identitas, usia, kota, pendapatan, dan status pelanggan;
  • data transaksi, yang memuat jumlah transaksi dan total pembelian.

Dataset awal sengaja mengandung missing values, kategori yang tidak konsisten, duplikasi, nilai ekstrem, dan perbedaan nama identifier. Tahapan preprocessing dilakukan secara sistematis sampai menghasilkan satu dataset analisis yang bersih.

Pada versi ini, proses preprocessing tetap ditampilkan agar hasil dapat direproduksi, tetapi seluruh analisis akhir menggunakan dataset yang sudah dipreprocessing.

Prinsip utama: jangan membersihkan data hanya karena sebuah nilai terlihat aneh. Setiap perubahan harus memiliki alasan, aturan, dan catatan.

Persiapan RStudio

Memeriksa lingkungan kerja

knitr::opts_chunk$set(
  echo = TRUE,
  warning = FALSE,
  message = FALSE
)

getwd()
## [1] "D:/Downloads"
R.version.string
## [1] "R version 4.5.1 (2025-06-13 ucrt)"

Modul menggunakan fungsi dasar R sehingga tidak memerlukan paket tambahan selain knitr, yang otomatis tersedia saat proses Knit.

Membangun Dataset Praktikum

Data pelanggan

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
##    customer_id   nama usia pendapatan      kota      status
## 1         C001    Ani   21    4.5e+06 Pekanbaru       Aktif
## 2         C002   Budi   25         NA       PKU       aktif
## 3         C003  Citra   23    5.2e+06 PEKANBARU      ACTIVE
## 4         C004   Dodi  150    4.8e+06     Dumai           A
## 5         C005    Eka   27    4.9e+06 pekanbaru Tidak Aktif
## 6         C006   Fani   NA    5.1e+06     DUMAI    nonaktif
## 7         C007 Gilang   31    5.0e+08 Pekanbaru       Aktif
## 8         C008   Hana   29    4.7e+06      Siak       AKTIF
## 9         C009  Indra   22    4.6e+06       PKU           A
## 10        C010   Joko   35    5.3e+06     Dumai Tidak aktif
## 11        C010   Joko   35    5.3e+06     Dumai Tidak aktif
## 12        C011   Kiki   28         NA      <NA>       Aktif

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
)

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    C012                1        2.5e+05

Bagian I — Konsep Data Preprocessing

Memahami 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  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" ...
str(transaksi_raw)
## 'data.frame':    11 obs. of  3 variables:
##  $ cust_id         : chr  "C001" "C002" "C003" "C004" ...
##  $ jumlah_transaksi: num  5 3 7 2 6 4 20 5 3 8 ...
##  $ total_purchase  : num  1.5e+06 9.0e+05 2.7e+06 6.0e+05 2.1e+06 1.3e+06 2.5e+07 1.7e+06 8.0e+05 3.2e+06 ...
dim(pelanggan_raw)
## [1] 12  6
dim(transaksi_raw)
## [1] 11  3

Mengukur kualitas awal

Enam dimensi kualitas data yang dibahas Han, Kamber, dan Pei meliputi accuracy, completeness, consistency, timeliness, believability, dan interpretability. Tidak semua dimensi dapat dihitung hanya dari isi tabel.

Pemeriksaan awal berikut berfokus pada masalah yang dapat dideteksi dari dataset.

# Banyak missing value per atribut
colSums(is.na(pelanggan_raw))
## customer_id        nama        usia  pendapatan        kota      status 
##           0           0           1           2           1           0
# Persentase missing value per atribut
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
# Banyak baris duplikat penuh
sum(duplicated(pelanggan_raw))
## [1] 1
# Banyak customer_id yang berulang
sum(duplicated(pelanggan_raw$customer_id))
## [1] 1
# Kategori yang tercatat
sort(unique(pelanggan_raw$kota))
## [1] "Dumai"     "DUMAI"     "pekanbaru" "Pekanbaru" "PEKANBARU" "PKU"      
## [7] "Siak"
sort(unique(pelanggan_raw$status))
## [1] "A"           "ACTIVE"      "aktif"       "Aktif"       "AKTIF"      
## [6] "nonaktif"    "Tidak aktif" "Tidak Aktif"
# Rentang atribut numerik
range(pelanggan_raw$usia, na.rm = TRUE)
## [1]  21 150
range(pelanggan_raw$pendapatan, na.rm = TRUE)
## [1] 4.5e+06 5.0e+08

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           8
## 6      status character              0           0.00           8

Interpretasi: Audit awal digunakan untuk mengidentifikasi bagian data yang membutuhkan pemeriksaan lebih lanjut. Audit bukan berarti setiap nilai yang berbeda harus dihapus atau diubah.

Bagian II — Implementasi Data Cleaning

Membuat salinan kerja

pelanggan <- pelanggan_raw

Membersihkan spasi dan kapitalisasi

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

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

sort(unique(pelanggan$kota))
## [1] "dumai"     "pekanbaru" "pku"       "siak"
sort(unique(pelanggan$status))
## [1] "a"           "active"      "aktif"       "nonaktif"    "tidak aktif"

Menyeragamkan kategori

# 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"
sort(unique(pelanggan$status))
## [1] "Aktif"       "Tidak Aktif"

Mendeteksi dan menghapus duplikasi

pelanggan[
  duplicated(pelanggan$customer_id) |
    duplicated(pelanggan$customer_id, fromLast = TRUE),
]
##    customer_id nama usia pendapatan  kota      status
## 10        C010 Joko   35    5300000 Dumai Tidak Aktif
## 11        C010 Joko   35    5300000 Dumai Tidak Aktif
pelanggan <- pelanggan[!duplicated(pelanggan$customer_id), ]
rownames(pelanggan) <- NULL

dim(pelanggan)
## [1] 11  6

Duplikasi dihapus karena pada skenario ini setiap customer_id mewakili satu pelanggan.

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>
# Nilai C004 dikoreksi berdasarkan sumber asli:
# 150 -> 50
pelanggan$usia[pelanggan$customer_id == "C004"] <- 50

pelanggan[pelanggan$customer_id == "C004", ]
##   customer_id nama usia pendapatan  kota status
## 4        C004 Dodi   50    4800000 Dumai  Aktif

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

colSums(is.na(pelanggan))
## customer_id        nama        usia  pendapatan        kota      status 
##           0           0           1           2           1           0
pelanggan[!complete.cases(pelanggan), ]
##    customer_id nama usia pendapatan      kota      status
## 2         C002 Budi   25         NA Pekanbaru       Aktif
## 6         C006 Fani   NA    5100000     Dumai Tidak Aktif
## 11        C011 Kiki   28         NA      <NA>       Aktif

Strategi 1: Menghapus baris

Strategi ini ditampilkan sebagai pembanding. Dataset utama tidak langsung menggunakan strategi ini karena dapat menghilangkan terlalu banyak observasi.

pelanggan_complete <- pelanggan[complete.cases(pelanggan), ]

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

Strategi 2: Imputasi mean dan median

Karena pendapatan memiliki nilai ekstrem, median dipilih sebagai nilai imputasi yang lebih stabil.

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

mean_pendapatan
## [1] 59900000
median_pendapatan
## [1] 4900000
pelanggan$pendapatan_imputasi <- pelanggan$pendapatan

pelanggan$pendapatan_imputasi[
  is.na(pelanggan$pendapatan_imputasi)
] <- median_pendapatan

median_usia <- median(pelanggan$usia, na.rm = TRUE)

pelanggan$usia_imputasi <- pelanggan$usia

pelanggan$usia_imputasi[
  is.na(pelanggan$usia_imputasi)
] <- median_usia

pelanggan[, c(
  "customer_id",
  "usia",
  "usia_imputasi",
  "pendapatan",
  "pendapatan_imputasi"
)]
##    customer_id usia usia_imputasi pendapatan pendapatan_imputasi
## 1         C001   21          21.0    4.5e+06             4.5e+06
## 2         C002   25          25.0         NA             4.9e+06
## 3         C003   23          23.0    5.2e+06             5.2e+06
## 4         C004   50          50.0    4.8e+06             4.8e+06
## 5         C005   27          27.0    4.9e+06             4.9e+06
## 6         C006   NA          27.5    5.1e+06             5.1e+06
## 7         C007   31          31.0    5.0e+08             5.0e+08
## 8         C008   29          29.0    4.7e+06             4.7e+06
## 9         C009   22          22.0    4.6e+06             4.6e+06
## 10        C010   35          35.0    5.3e+06             5.3e+06
## 11        C011   28          28.0         NA             4.9e+06

Strategi 3: Imputasi nilai kategorik

Nilai kota yang hilang diberi kategori eksplisit Tidak diketahui agar ketidakpastian 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

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
)

par(mfrow = c(1, 1))

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               5e+08

Mengevaluasi tindakan

Nilai Rp500.000.000 tidak otomatis dihapus. Nilai tersebut dipertahankan sebagai observasi valid yang ekstrem, kemudian dibuat versi winsorized untuk melihat dampak pembatasan nilai ekstrem.

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           5750000

Bagian V — Transformasi Data

Normalisasi min–maks

Rumus normalisasi min–maks pada rentang [0,1] adalah:

\[ 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             4.5e+06      0.0000000000
## 2         C002   25  0.13793103             4.9e+06      0.0008072654
## 3         C003   23  0.06896552             5.2e+06      0.0014127144
## 4         C004   50  1.00000000             4.8e+06      0.0006054490
## 5         C005   27  0.20689655             4.9e+06      0.0008072654
## 6         C006   NA  0.22413793             5.1e+06      0.0012108981
## 7         C007   31  0.34482759             5.0e+08      1.0000000000
## 8         C008   29  0.27586207             4.7e+06      0.0004036327
## 9         C009   22  0.03448276             4.6e+06      0.0002018163
## 10        C010   35  0.48275862             5.3e+06      0.0016145308
## 11        C011   28  0.24137931             4.9e+06      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             4.5e+06      0.0000000000   -0.3041235
## 2         C002             4.9e+06      0.0008072654   -0.3014440
## 3         C003             5.2e+06      0.0014127144   -0.2994343
## 4         C004             4.8e+06      0.0006054490   -0.3021138
## 5         C005             4.9e+06      0.0008072654   -0.3014440
## 6         C006             5.1e+06      0.0012108981   -0.3001042
## 7         C007             5.0e+08      1.0000000000    3.0151095
## 8         C008             4.7e+06      0.0004036327   -0.3027837
## 9         C009             4.6e+06      0.0002018163   -0.3034536
## 10        C010             5.3e+06      0.0016145308   -0.2987645
## 11        C011             4.9e+06      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
)

Bagian VI — Integrasi Data

Memeriksa kesesuaian identifier

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

Hasil menunjukkan bahwa C011 merupakan pelanggan tanpa transaksi pada sumber transaksi, sedangkan C012 merupakan transaksi yang tidak memiliki pasangan pada data pelanggan.

Menyelaraskan nama identifier

transaksi <- transaksi_raw

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

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

Menangani missing pada data transaksi

Karena C011 tidak mempunyai catatan transaksi, nilai transaksi yang hilang dianggap sebagai belum pernah bertransaksi dalam skenario ini. Oleh sebab itu, nilai NA 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)
] <- 0

Dataset Akhir dan Evaluasi

Memilih atribut akhir

Dataset akhir menggunakan atribut yang telah dibersihkan, diimputasi, ditransformasi, dan diintegrasikan.

data_final <- data_terintegrasi[, c(
  "customer_id",
  "nama",
  "usia_imputasi",
  "kota_imputasi",
  "status",
  "pendapatan_imputasi",
  "pendapatan_missing",
  "outlier_pendapatan",
  "usia_minmax",
  "pendapatan_minmax",
  "jumlah_transaksi_final",
  "total_purchase_final"
)]

names(data_final)[
  names(data_final) == "kota_imputasi"
] <- "kota"

names(data_final)[
  names(data_final) == "usia_imputasi"
] <- "usia"

data_final
##    customer_id   nama usia            kota      status pendapatan_imputasi
## 1         C001    Ani 21.0       Pekanbaru       Aktif             4.5e+06
## 2         C002   Budi 25.0       Pekanbaru       Aktif             4.9e+06
## 3         C003  Citra 23.0       Pekanbaru       Aktif             5.2e+06
## 4         C004   Dodi 50.0           Dumai       Aktif             4.8e+06
## 5         C005    Eka 27.0       Pekanbaru Tidak Aktif             4.9e+06
## 6         C006   Fani 27.5           Dumai Tidak Aktif             5.1e+06
## 7         C007 Gilang 31.0       Pekanbaru       Aktif             5.0e+08
## 8         C008   Hana 29.0            Siak       Aktif             4.7e+06
## 9         C009  Indra 22.0       Pekanbaru       Aktif             4.6e+06
## 10        C010   Joko 35.0           Dumai Tidak Aktif             5.3e+06
## 11        C011   Kiki 28.0 Tidak diketahui       Aktif             4.9e+06
##    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              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                      0              0.0e+00

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
# Jumlah customer_id yang masih duplikat
sum(duplicated(data_final$customer_id))
## [1] 0
# Jumlah missing value setiap atribut
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
# Jumlah baris dan kolom
dim(data_final)
## [1] 11 12

Data akhir diharapkan memiliki:

  • tidak ada customer_id yang duplikat;
  • tidak ada missing value pada atribut final;
  • kategori kota yang konsisten;
  • kategori status yang konsisten;
  • usia yang telah dikoreksi dan diimputasi;
  • pendapatan yang telah diimputasi;
  • indikator missing dan outlier yang terdokumentasi; dan
  • data transaksi yang telah terintegrasi.

Membandingkan kondisi sebelum dan sesudah

perbandingan <- data.frame(
  kondisi = c(
    "Sebelum preprocessing",
    "Sesudah preprocessing"
  ),
  jumlah_baris = c(
    nrow(pelanggan_raw),
    nrow(data_final)
  ),
  jumlah_kolom = c(
    ncol(pelanggan_raw),
    ncol(data_final)
  ),
  jumlah_missing_total = c(
    sum(is.na(pelanggan_raw)),
    sum(is.na(data_final))
  ),
  jumlah_customer_id_duplikat = c(
    sum(duplicated(pelanggan_raw$customer_id)),
    sum(duplicated(data_final$customer_id))
  )
)

perbandingan
##                 kondisi jumlah_baris jumlah_kolom jumlah_missing_total
## 1 Sebelum preprocessing           12            6                    4
## 2 Sesudah preprocessing           11           12                    0
##   jumlah_customer_id_duplikat
## 1                           1
## 2                           0

Menyimpan hasil

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

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

Pertanyaan Refleksi

1. Mengapa nilai ekstrem tidak selalu harus dihapus?

Nilai ekstrem dapat merupakan observasi yang benar-benar valid. Penghapusan tanpa verifikasi dapat menghilangkan informasi penting dan menghasilkan bias.

2. Mengapa median digunakan untuk imputasi pendapatan?

Karena pendapatan memiliki nilai ekstrem Rp500.000.000 sehingga mean menjadi sangat besar dan kurang representatif terhadap sebagian besar pelanggan. Median lebih tahan terhadap pengaruh nilai ekstrem.

3. Mengapa C012 tidak muncul pada dataset akhir?

C012 hanya terdapat pada data transaksi dan tidak memiliki pasangan pada data pelanggan. Karena integrasi menggunakan left join dari data pelanggan, C012 tidak masuk ke dataset akhir.

4. Mengapa C011 tetap dipertahankan?

C011 merupakan pelanggan yang valid, tetapi tidak mempunyai transaksi. Setelah dikonfirmasi bahwa NA berarti belum pernah bertransaksi, nilai jumlah transaksi dan total pembelian dapat direpresentasikan sebagai 0.

Ringkasan

Tahapan preprocessing yang dilakukan meliputi:

  1. audit kualitas data;
  2. pembersihan spasi dan kapitalisasi;
  3. standardisasi kategori;
  4. deduplikasi berdasarkan customer_id;
  5. pemeriksaan dan koreksi aturan domain;
  6. penanganan missing values dengan imputasi median dan kategori eksplisit;
  7. pembuatan indikator missing;
  8. deteksi outlier menggunakan metode IQR;
  9. pembuatan versi winsorized untuk evaluasi;
  10. transformasi min–maks, z-score, dan decimal scaling;
  11. penyelarasan identifier;
  12. integrasi data pelanggan dan transaksi;
  13. penanganan nilai transaksi yang tidak tersedia; dan
  14. audit serta validasi dataset akhir.

Hasil akhir berupa data_final merupakan dataset yang telah melalui preprocessing dan siap digunakan untuk tahap analisis atau data mining berikutnya.

Daftar Pustaka

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