Pendahuluan

Latar Belakang

Data preprocessing menjadi salah satu tahapan yang penting dalam data mining karena data yang diperoleh pada awalnya belum tentu berada dalam kondisi yang siap dianalisis. Pada data mentah dapat ditemukan berbagai permasalahan, seperti missing value, data ganda, perbedaan penulisan kategori, nilai yang tidak sesuai dengan aturan, serta nilai yang terlalu jauh dari pengamatan lainnya (outlier).

Pada tugas ini, tahapan yang digunakan mengacu pada materi Data Preprocessing Menggunakan RStudio. Prosesnya dimulai dengan melihat kondisi awal data, dilanjutkan dengan data cleaning, penanganan missing values, pemeriksaan outlier, transformasi variabel numerik, penggabungan dua sumber data, serta pemeriksaan kembali data setelah preprocessing. Seluruh proses dikerjakan menggunakan fungsi dasar R sehingga tidak membutuhkan package tambahan.

Tujuan Praktikum

Setelah mengikuti praktikum ini, beberapa kemampuan yang diharapkan dapat diperoleh adalah:

  1. Mengenali berbagai permasalahan kualitas data dengan bantuan R.
  2. Menerapkan proses data cleaning secara terstruktur.
  3. Menentukan cara penanganan missing values yang sesuai.
  4. Mengidentifikasi serta menilai keberadaan outlier pada data.
  5. Mengubah skala atribut numerik menggunakan metode transformasi.
  6. Menggabungkan informasi yang berasal dari dua sumber data.
  7. Memeriksa kembali kualitas data setelah seluruh proses preprocessing dilakukan.

Persiapan RStudio

Memeriksa lingkungan kerja

Perintah berikut digunakan untuk melihat lokasi direktori kerja serta mengetahui versi R yang sedang digunakan.

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

Membangun Dataset Praktikum

Pada bagian ini digunakan 10 data pelanggan mentah yang sengaja dibuat mengandung beberapa permasalahan agar setiap tahap preprocessing dapat diterapkan. Permasalahan tersebut berupa satu data customer_id yang berulang, missing value, penulisan kategori yang belum seragam, nilai usia yang tidak sesuai, serta nilai pendapatan yang jauh lebih besar dibandingkan data lainnya.

pelanggan_raw <- data.frame(
  customer_id = c("C001", "C002", "C003", "C004", "C005",
                  "C006", "C007", "C008", "C009", "C010"),
  nama = c("Ani", "Budi", "Citra", "Dodi", "Eka",
           "Fani", "Gilang", "Hana", "Indra", "Joko"),
  usia = c(21, 25, 23, 150, 27, NA, 31, 29, 22, 35),
  pendapatan = c(4500000, NA, 5200000, 4800000, 4900000,
                 5100000, 500000000, 4700000, 4600000, NA),
  kota = c("Pekanbaru", " PKU", "PEKANBARU", "Dumai", "pekanbaru",
           "DUMAI", "Pekanbaru ", "Siak", "PKU", NA),
  status = c("Aktif", "aktif", "ACTIVE", "A", "Tidak Aktif",
             "nonaktif", "Aktif", "AKTIF", "A", "Tidak aktif"),
  stringsAsFactors = FALSE
)

# Tambahkan satu baris duplikat untuk latihan deduplikasi.
pelanggan_raw <- rbind(
  pelanggan_raw,
  pelanggan_raw[pelanggan_raw$customer_id == "C010", ]
)

transaksi_raw <- data.frame(
  cust_id = c("C001", "C002", "C003", "C004", "C005",
              "C006", "C007", "C008", "C009", "C999"),
  jumlah_transaksi = c(5, 3, 7, 2, 6, 4, 20, 5, 3, 5),
  total_purchase = c(1500000, 900000, 2700000, 600000, 2100000,
                     1300000, 25000000, 1700000, 800000, 1200000),
  stringsAsFactors = FALSE
)

pelanggan_raw
transaksi_raw

Eksplorasi Awal Data

Melihat struktur dataset

Langkah ini dilakukan untuk memperoleh gambaran awal mengenai ukuran data, nama variabel, tipe masing-masing variabel, beberapa observasi pertama, serta ringkasan statistiknya.

dim(pelanggan_raw)
## [1] 11  6
names(pelanggan_raw)
## [1] "customer_id" "nama"        "usia"        "pendapatan"  "kota"       
## [6] "status"
str(pelanggan_raw)
## 'data.frame':    11 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 NA ...
##  $ kota       : chr  "Pekanbaru" " PKU" "PEKANBARU" "Dumai" ...
##  $ status     : chr  "Aktif" "aktif" "ACTIVE" "A" ...
head(pelanggan_raw)
summary(pelanggan_raw)
##  customer_id            nama                usia         pendapatan       
##  Length:11          Length:11          Min.   : 21.0   Min.   :  4500000  
##  Class :character   Class :character   1st Qu.: 23.5   1st Qu.:  4675000  
##  Mode  :character   Mode  :character   Median : 28.0   Median :  4850000  
##                                        Mean   : 39.8   Mean   : 66725000  
##                                        3rd Qu.: 34.0   3rd Qu.:  5125000  
##                                        Max.   :150.0   Max.   :500000000  
##                                        NA's   :1       NA's   :3          
##      kota              status         
##  Length:11          Length:11         
##  Class :character   Class :character  
##  Mode  :character   Mode  :character  
##                                       
##                                       
##                                       
## 

Audit awal kualitas data

Missing value

# Banyak missing value per atribut
colSums(is.na(pelanggan_raw))
## customer_id        nama        usia  pendapatan        kota      status 
##           0           0           1           3           2           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        9.09       27.27       18.18        0.00

Duplikasi

# Banyak baris duplikat penuh
sum(duplicated(pelanggan_raw))
## [1] 1
# Banyak customer_id yang berulang
sum(duplicated(pelanggan_raw$customer_id))
## [1] 1

Konsistensi kategori

# Kategori yang tercatat 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 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

Fungsi audit data

Fungsi berikut digunakan untuk merangkum kondisi setiap atribut sehingga proses pemeriksaan kualitas data dapat dilakukan dengan lebih teratur.

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

Data Cleaning

Menyiapkan data untuk dibersihkan

pelanggan <- pelanggan_raw

Standardisasi kategori

Data awal masih menunjukkan perbedaan dalam penggunaan spasi serta huruf kapital. Oleh karena itu, spasi dibersihkan terlebih dahulu, kemudian seluruh teks diubah menjadi huruf kecil agar proses penyamaan kategori lebih mudah dilakukan.

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

# Menyeragamkan huruf menjadi kecil untuk proses pencocokan
pelanggan$kota <- tolower(pelanggan$kota)
pelanggan$status <- tolower(pelanggan$status)

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

Setelah proses awal tersebut, setiap kategori kemudian disamakan ke dalam bentuk label yang seragam.

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

Deduplikasi

Variabel customer_id berperan sebagai identitas unik setiap pelanggan. Karena itu, data dengan customer_id yang muncul lebih dari sekali perlu diperiksa terlebih dahulu.

# Menampilkan seluruh baris dengan customer_id yang berulang
pelanggan[duplicated(pelanggan$customer_id) |
          duplicated(pelanggan$customer_id, fromLast = TRUE), ]

Salah satu baris C010 yang tercatat dua kali kemudian dihilangkan sehingga setiap pelanggan hanya memiliki satu identitas dan tersisa 9 pelanggan unik.

# Mempertahankan kemunculan pertama untuk customer_id yang identik
pelanggan <- pelanggan[!duplicated(pelanggan$customer_id), ]
rownames(pelanggan) <- NULL

dim(pelanggan)
## [1] 10  6

Pemeriksaan aturan domain

Pemeriksaan domain dilakukan untuk menemukan nilai yang tidak sesuai dengan batas yang telah ditetapkan berdasarkan konteks data. Dalam contoh ini, rentang usia yang dianggap wajar adalah 15–100 tahun.

# Kandidat usia tidak valid berdasarkan aturan bisnis 15–100 tahun
pelanggan[pelanggan$usia < 15 | pelanggan$usia > 100, ]

Nilai pendapatan yang bernilai negatif juga diperiksa karena dapat menunjukkan adanya ketidaksesuaian dengan aturan data.

# Kandidat pendapatan tidak valid jika bernilai negatif
pelanggan[pelanggan$pendapatan < 0, ]

Usia pelanggan C004 yang tercatat sebesar 150 kemudian dikoreksi menjadi 50 dengan mengacu pada sumber data aslinya.

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

Log perubahan

Setiap perubahan dicatat agar seluruh proses preprocessing dapat diketahui dan ditelusuri kembali.

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

Penanganan Missing Values

Mengidentifikasi lokasi nilai hilang

colSums(is.na(pelanggan))
## customer_id        nama        usia  pendapatan        kota      status 
##           0           0           1           2           1           0
# Baris yang memiliki sedikitnya satu missing value
pelanggan[!complete.cases(pelanggan), ]

Strategi 1 — Menghapus baris lengkap

Sebagai salah satu contoh penanganan, baris yang tidak memiliki missing value dapat dipilih menggunakan complete.cases(). Selanjutnya jumlah data sebelum dan sesudah proses tersebut dibandingkan untuk melihat dampaknya.

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

nrow(pelanggan)
## [1] 10
nrow(pelanggan_complete)
## [1] 7
# Persentase baris yang terhapus
round((1 - nrow(pelanggan_complete) / nrow(pelanggan)) * 100, 2)
## [1] 30

Strategi 2 — Imputasi median

Pada variabel numerik, median digunakan sebagai salah satu alternatif untuk mengisi nilai yang hilang. Nilai mean turut dihitung sehingga keduanya dapat dibandingkan sebelum menentukan metode yang digunakan.

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

mean_pendapatan
## [1] 66725000
median_pendapatan
## [1] 4850000

Selanjutnya dibuat variabel hasil imputasi untuk pendapatan dan usia, sementara nilai pada kolom aslinya tetap dipertahankan agar kondisi awal data masih dapat diketahui.

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

# Imputasi usia dengan median
median_usia <- median(pelanggan$usia, na.rm = TRUE)
pelanggan$usia_imputasi <- pelanggan$usia
pelanggan$usia_imputasi[is.na(pelanggan$usia_imputasi)] <- median_usia

pelanggan[, c("customer_id", "usia", "usia_imputasi",
              "pendapatan", "pendapatan_imputasi")]

Imputasi kategori

Nilai kota yang tidak tersedia diberi label Tidak diketahui. Dengan cara ini, informasi bahwa nilai tersebut memang tidak diketahui tetap dipertahankan dan tidak dianggap sebagai bagian dari kategori tertentu.

pelanggan$kota_imputasi <- pelanggan$kota
pelanggan$kota_imputasi[is.na(pelanggan$kota_imputasi)] <- "Tidak diketahui"

table(pelanggan$kota_imputasi, useNA = "ifany")
## 
##           Dumai       Pekanbaru            Siak Tidak diketahui 
##               2               6               1               1

Menambahkan indikator missing

Variabel indikator biner ditambahkan untuk menunjukkan apakah suatu nilai pendapatan pada kondisi awal merupakan missing value.

pelanggan$pendapatan_missing <- as.integer(is.na(pelanggan$pendapatan))
table(pelanggan$pendapatan_missing)
## 
## 0 1 
## 8 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))

Penanganan Outlier

Visualisasi dengan boxplot

Boxplot digunakan sebagai visualisasi awal untuk mengetahui adanya nilai pendapatan yang berpotensi menjadi nilai ekstrem.

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

Menghitung batas IQR

Metode IQR menentukan kandidat outlier berdasarkan nilai Q1, Q3, dan IQR yang kemudian digunakan untuk memperoleh batas bawah serta batas atas.

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% 
##         4725000         5050000          325000         4237500         5537500

Menandai kandidat outlier

Pengamatan yang berada di luar batas IQR diberi tanda sebagai kandidat outlier. Namun, hasil penandaan tersebut tidak langsung dijadikan alasan untuk menghapus data.

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

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

Winsorization

Sebagai salah satu contoh perlakuan terhadap nilai ekstrem, nilai yang melewati batas dapat disesuaikan ke batas bawah atau batas atas melalui metode winsorization.

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

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

Transformasi Data

Transformasi dilakukan untuk menyesuaikan skala pada atribut numerik. Dalam praktikum ini digunakan tiga pendekatan, yaitu normalisasi min–maks, z-score, dan decimal scaling.

Normalisasi min–maks

Untuk mengubah nilai ke dalam rentang [0,1], normalisasi min–maks menggunakan rumus berikut:

\[x' = \frac{x - x_{min}}{x_{max} - x_{min}}\]

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

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

pelanggan[, c("customer_id", "usia", "usia_minmax",
              "pendapatan_imputasi", "pendapatan_minmax")]

Normalisasi z-score

Metode z-score menyatakan suatu nilai berdasarkan posisinya terhadap rata-rata dan simpangan baku data.

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

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

Dampak outlier terhadap normalisasi

Perbandingan berikut digunakan untuk melihat perubahan hasil normalisasi min–maks setelah dilakukan penanganan terhadap outlier.

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)

Integrasi Data

Memeriksa kunci pada kedua sumber

Sebelum kedua dataset digabungkan, identifier perlu diperiksa terlebih dahulu untuk memastikan proses integrasi tidak menyebabkan baris menjadi berlipat atau menghasilkan pasangan data yang keliru.

sum(duplicated(pelanggan$customer_id))
## [1] 0
sum(duplicated(transaksi_raw$cust_id))
## [1] 0
# Customer yang ada di pelanggan tetapi tidak ada di transaksi
setdiff(pelanggan$customer_id, transaksi_raw$cust_id)
## [1] "C010"
# Customer yang ada di transaksi tetapi tidak ada di pelanggan
setdiff(transaksi_raw$cust_id, pelanggan$customer_id)
## [1] "C999"

Menyamakan nama kunci

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

Menggabungkan data

Fungsi merge(..., all.x = TRUE) digunakan agar seluruh data pelanggan tetap dipertahankan, sedangkan informasi transaksi hanya ditambahkan ketika memiliki customer_id yang sesuai.

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

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

Validasi hasil integrasi

# Apakah jumlah baris berubah secara tidak wajar?
c(sebelum = nrow(pelanggan), sesudah = nrow(data_terintegrasi))
## sebelum sesudah 
##      10      10
# Apakah customer_id tetap unik?
sum(duplicated(data_terintegrasi$customer_id))
## [1] 0
# Missing value baru akibat tidak ditemukan pasangan transaksi
colSums(is.na(data_terintegrasi[, c("jumlah_transaksi", "total_purchase")]))
## jumlah_transaksi   total_purchase 
##                1                1

Data pelanggan yang tidak menemukan pasangan transaksi kemudian ditampilkan agar kondisi tersebut dapat diperiksa lebih lanjut.

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

Penanganan pelanggan tanpa transaksi

Apabila hasil pemeriksaan menunjukkan bahwa NA menandakan pelanggan memang belum melakukan transaksi, nilai tersebut dapat diperlakukan sebagai 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 Final dan Audit Akhir

Membentuk dataset final

Pada dataset akhir hanya variabel yang dianggap diperlukan untuk proses analisis selanjutnya yang dipertahankan.

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

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

data_final

Audit akhir

audit_akhir <- audit_data(data_final)
audit_akhir
# Memeriksa kembali duplikasi identifier
sum(duplicated(data_final$customer_id))
## [1] 0
# Memeriksa kembali 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

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

Menyimpan hasil

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

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

Kesimpulan

Dari seluruh proses yang telah dilakukan, dapat dilihat bahwa preprocessing berlangsung melalui beberapa tahap yang saling berkaitan. Proses dimulai dengan memeriksa struktur dan kualitas data, kemudian dilanjutkan dengan penyeragaman kategori, penanganan duplikasi dan kesalahan domain, pengelolaan missing values, pemeriksaan outlier, transformasi variabel numerik, integrasi data, serta pemeriksaan akhir terhadap hasil yang diperoleh.

Hal yang perlu diperhatikan dari proses ini adalah bahwa preprocessing tidak hanya berfokus pada penggunaan kode. Setiap perubahan pada data perlu didasarkan pada alasan yang jelas. Nilai outlier tidak selalu harus dihapus, missing value tidak selalu tepat jika langsung dibuang, dan hasil penggabungan data tetap perlu diperiksa agar tidak muncul duplikasi maupun permasalahan baru.

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?

Ringkasan Alur

Data mentah
    ↓
Eksplorasi dan audit awal
    ↓
Standardisasi kategori
    ↓
Deduplikasi dan pemeriksaan domain
    ↓
Penanganan missing value
    ↓
Deteksi dan evaluasi outlier
    ↓
Transformasi data
    ↓
Integrasi data
    ↓
Audit akhir
    ↓
Data siap digunakan untuk analisis/data mining