1 Pendahuluan

Data preprocessing merupakan tahap penting dalam data mining karena data mentah sering kali belum siap langsung digunakan untuk analisis. Data dapat mengandung missing value, duplikasi, kategori yang tidak konsisten, kesalahan domain, dan nilai ekstrem (outlier).

Praktikum ini mengikuti alur preprocessing mulai dari memahami kondisi awal data, melakukan data cleaning, menangani missing values, mengevaluasi outlier, melakukan transformasi atribut numerik, mengintegrasikan dua sumber data, dan melakukan validasi akhir.

1.1 Tujuan Praktikum

Setelah menyelesaikan praktikum, diharapkan dapat:

  1. Mengidentifikasi masalah kualitas data menggunakan R.
  2. Melakukan data cleaning secara sistematis.
  3. Menangani missing values.
  4. Mendeteksi dan mengevaluasi outlier.
  5. Melakukan transformasi atribut numerik.
  6. Mengintegrasikan dua sumber data.
  7. Memvalidasi kondisi data setelah preprocessing.

2 Persiapan RStudio

2.1 Memeriksa lingkungan kerja

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

3 Membangun Dataset Praktikum

Dataset berikut terdiri dari 20 data pelanggan. Data sengaja dibuat memiliki beberapa masalah agar seluruh tahapan preprocessing dapat dipraktikkan, seperti missing value, kategori tidak konsisten, kesalahan nilai usia, duplikasi customer_id, dan nilai pendapatan ekstrem.

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"),
  nama = c("Andi", "Bella", "Cahyo", "Dina", "Eko", "Fira",
           "Galih", "Hani", "Iqbal", "Jihan", "Jihan", "Kevin",
           "Lina", "Mira", "Nadia", "Omar", "Putri", "Raka",
           "Salsa", "Taufik"),
  usia = c(22, 26, 24, 145, 28, NA, 32, 30, 23, 36,
           36, 29, 25, 27, 31, 20, NA, 34, 28, 42),
  pendapatan = c(4200000, NA, 5500000, 4800000, 5100000, 5300000,
                 75000000, 4600000, 4400000, 5800000, 5800000, NA,
                 4300000, 5600000, 4900000, 4000000, 5200000, 6000000,
                 5100000, 6500000),
  kota = c("Pekanbaru", " PKU", "PEKANBARU", "Dumai", "pekanbaru",
           "DUMAI", "Pekanbaru ", "Siak", "PKU", "Dumai", "Dumai",
           NA, "Pekanbaru", "pekanbaru", "DUMAI", "Siak",
           "PEKANBARU", "Pekanbaru", "PKU", "Dumai"),
  status = c("Aktif", "aktif", "ACTIVE", "A", "Tidak Aktif", "nonaktif",
             "Aktif", "AKTIF", "A", "Tidak aktif", "Tidak aktif", "Aktif",
             "active", "A", "Aktif", "tidak aktif", "AKTIF", "aktif",
             "nonaktif", "Aktif"),
  stringsAsFactors = FALSE
)

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(4, 6, 8, 3, 5, 4, 18, 7, 2, 9,
                       2, 5, 7, 10, 4, NA, 6, 3, 11, 5),
  total_purchase = c(1200000, 1800000, 3000000, 900000, 2400000, 1500000,
                     18000000, 2100000, 700000, 3600000, 400000, 1600000,
                     2500000, 5200000, 1300000, NA, 2700000, 850000,
                     4800000, 1400000),
  stringsAsFactors = FALSE
)

pelanggan_raw
transaksi_raw

4 Bagian I — Eksplorasi Awal Data

4.1 Melihat struktur dataset

dim(pelanggan_raw)
## [1] 20  6
names(pelanggan_raw)
## [1] "customer_id" "nama"        "usia"        "pendapatan"  "kota"       
## [6] "status"
str(pelanggan_raw)
## 'data.frame':    20 obs. of  6 variables:
##  $ customer_id: chr  "C001" "C002" "C003" "C004" ...
##  $ nama       : chr  "Andi" "Bella" "Cahyo" "Dina" ...
##  $ usia       : num  22 26 24 145 28 NA 32 30 23 36 ...
##  $ pendapatan : num  4.2e+06 NA 5.5e+06 4.8e+06 5.1e+06 5.3e+06 7.5e+07 4.6e+06 4.4e+06 5.8e+06 ...
##  $ kota       : chr  "Pekanbaru" " PKU" "PEKANBARU" "Dumai" ...
##  $ status     : chr  "Aktif" "aktif" "ACTIVE" "A" ...
head(pelanggan_raw)
summary(pelanggan_raw)
##  customer_id            nama                usia          pendapatan      
##  Length:20          Length:20          Min.   : 20.00   Min.   : 4000000  
##  Class :character   Class :character   1st Qu.: 25.25   1st Qu.: 4650000  
##  Mode  :character   Mode  :character   Median : 28.50   Median : 5150000  
##                                        Mean   : 35.44   Mean   : 9005556  
##                                        3rd Qu.: 33.50   3rd Qu.: 5750000  
##                                        Max.   :145.00   Max.   :75000000  
##                                        NA's   :2        NA's   :2         
##      kota              status         
##  Length:20          Length:20         
##  Class :character   Class :character  
##  Mode  :character   Mode  :character  
##                                       
##                                       
##                                       
## 

4.2 Audit awal kualitas data

4.2.1 Missing value

colSums(is.na(pelanggan_raw))
## customer_id        nama        usia  pendapatan        kota      status 
##           0           0           2           2           1           0
round(colMeans(is.na(pelanggan_raw)) * 100, 2)
## customer_id        nama        usia  pendapatan        kota      status 
##           0           0          10          10           5           0

4.2.2 Duplikasi

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

4.2.3 Konsistensi kategori

sort(unique(pelanggan_raw$kota))
## [1] " PKU"       "Dumai"      "DUMAI"      "pekanbaru"  "Pekanbaru" 
## [6] "PEKANBARU"  "Pekanbaru " "PKU"        "Siak"
sort(unique(pelanggan_raw$status))
##  [1] "A"           "active"      "ACTIVE"      "aktif"       "Aktif"      
##  [6] "AKTIF"       "nonaktif"    "tidak aktif" "Tidak aktif" "Tidak Aktif"

4.2.4 Rentang atribut numerik

range(pelanggan_raw$usia, na.rm = TRUE)
## [1]  20 145
range(pelanggan_raw$pendapatan, na.rm = TRUE)
## [1] 4.0e+06 7.5e+07

4.3 Fungsi audit data

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

5 Bagian II — Data Cleaning

5.1 Menyiapkan data untuk dibersihkan

pelanggan <- pelanggan_raw

5.2 Standardisasi kategori

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"

Selanjutnya kategori diseragamkan menjadi label yang 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"
sort(unique(pelanggan$status))
## [1] "Aktif"       "Tidak Aktif"

5.3 Deduplikasi

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

Kemunculan kedua untuk C010 dihapus.

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

dim(pelanggan)
## [1] 19  6

5.4 Pemeriksaan aturan domain

Usia yang dianggap masuk akal adalah 15–100 tahun.

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

Pendapatan negatif juga diperiksa.

pelanggan[pelanggan$pendapatan < 0, ]

Nilai usia C004 sebesar 145 diperbaiki menjadi 45 berdasarkan sumber asli.

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

5.5 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 145 menjadi 45"
  ),
  stringsAsFactors = FALSE
)

log_perubahan

6 Bagian III — Penanganan Missing Values

6.1 Mengidentifikasi lokasi nilai hilang

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

6.2 Strategi 1 — Menghapus baris lengkap

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

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

6.3 Strategi 2 — Imputasi median

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

mean_pendapatan
## [1] 9194118
median_pendapatan
## [1] 5100000
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")]

6.4 Imputasi kategori

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 
##               5              11               2               1

6.5 Menambahkan indikator missing

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

6.6 Membandingkan distribusi sebelum dan sesudah imputasi

par(mfrow = c(1, 2))

hist(pelanggan$pendapatan,
     main = "Sebelum Imputasi",
     xlab = "Pendapatan",
     breaks = 8)

hist(pelanggan$pendapatan_imputasi,
     main = "Sesudah Imputasi Median",
     xlab = "Pendapatan",
     breaks = 8)

par(mfrow = c(1, 1))

7 Bagian IV — Penanganan Outlier

7.1 Visualisasi dengan boxplot

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

7.2 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% 
##         4700000         5550000          850000         3425000         6825000

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

7.4 Winsorization

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

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

8 Bagian V — Transformasi Data

Tiga metode transformasi yang digunakan adalah min–maks, z-score, dan decimal scaling.

8.1 Normalisasi min–maks

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

8.2 Normalisasi z-score

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)

8.3 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.04 0.75

8.4 Membandingkan metode transformasi

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

transformasi

8.5 Dampak outlier terhadap normalisasi

pelanggan$pendapatan_winsor_minmax <- minmax(pelanggan$pendapatan_winsor)

plot(pelanggan$pendapatan_minmax,
     pelanggan$pendapatan_winsor_minmax,
     pch = 19,
     xlab = "Min–maks Data Asli",
     ylab = "Min–maks Data Winsorized",
     main = "Dampak Penanganan Outlier terhadap Normalisasi")

abline(0, 1, lty = 2)

9 Bagian VI — Integrasi Data

9.1 Memeriksa kunci pada kedua sumber

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

9.2 Menyamakan nama kunci

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

9.3 Menggabungkan data

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

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

9.4 Validasi hasil integrasi

c(sebelum = nrow(pelanggan), sesudah = nrow(data_terintegrasi))
## sebelum sesudah 
##      19      19
sum(duplicated(data_terintegrasi$customer_id))
## [1] 0
colSums(is.na(data_terintegrasi[, c("jumlah_transaksi", "total_purchase")]))
## jumlah_transaksi   total_purchase 
##                1                1

Pelanggan yang tidak memiliki pasangan transaksi ditampilkan untuk diperiksa lebih lanjut.

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

9.5 Penanganan pelanggan tanpa 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

10 Bagian VII — Dataset Final dan Audit Akhir

10.1 Membentuk dataset final

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

10.2 Audit akhir

audit_akhir <- audit_data(data_final)
audit_akhir
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

10.3 Membandingkan kondisi sebelum dan sesudah

perbandingan <- data.frame(
  indikator = c(
    "Jumlah baris",
    "Duplikasi customer_id",
    "Total missing value",
    "Kategori kota unik",
    "Kategori status unik"
  ),
  sebelum = c(
    nrow(pelanggan_raw),
    sum(duplicated(pelanggan_raw$customer_id)),
    sum(is.na(pelanggan_raw)),
    length(unique(pelanggan_raw$kota)),
    length(unique(pelanggan_raw$status))
  ),
  sesudah = c(
    nrow(data_final),
    sum(duplicated(data_final$customer_id)),
    sum(is.na(data_final)),
    length(unique(data_final$kota)),
    length(unique(data_final$status))
  )
)

perbandingan

10.4 Menyimpan hasil

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

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

11 Kesimpulan

Berdasarkan seluruh tahapan praktikum, preprocessing dilakukan secara bertahap mulai dari memahami struktur dan kualitas awal data, membersihkan kategori, menghapus duplikasi, memperbaiki pelanggaran domain, menangani missing values, mendeteksi dan mengevaluasi outlier, melakukan transformasi numerik, mengintegrasikan data transaksi, hingga melakukan audit akhir.

Pada dataset 20 data pelanggan ini, proses preprocessing menunjukkan bahwa data mentah perlu diperiksa terlebih dahulu sebelum digunakan untuk analisis. Setiap perubahan terhadap data dilakukan berdasarkan alasan yang jelas, sehingga data akhir menjadi lebih konsisten dan siap digunakan untuk tahap analisis atau data mining.

12 Pertanyaan Refleksi

  1. Apakah dataset tanpa missing value selalu lebih berkualitas?
  2. Mengapa outlier tidak boleh otomatis dihapus?
  3. Bagaimana preprocessing dapat menimbulkan bias?
  4. Mengapa parameter imputasi dan transformasi seharusnya dihitung dari data pelatihan?
  5. Apa risiko integrasi data jika identifier tidak unik?

13 Ringkasan Alur

Data mentah 20 data
      ↓
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