Pendahuluan

Bayangkan sebuah perusahaan e-commerce yang ingin menganalisis pelanggannya. Data yang dipakai berasal dari dua sumber:

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

Data tersebut belum siap dianalisis karena masih memiliki beberapa masalah: nilai hilang, kategori yang tidak seragam, data duplikat, nilai ekstrem, dan nama identifier yang berbeda. Untuk mengatasi masalah tersebut, perlu dilakukan preprocessing data, seperti menangani data hilang, data duplikat, kategori yang tidak seragam, dan nilai ekstrem. Dengan demikian, data menjadi lebih rapi dan konsisten sehingga analisis yang dilakukan dapat menghasilkan kesimpulan yang lebih baik.

Memeriksa lingkungan kerja

getwd()
#> [1] "C:/Users/Dell/Downloads"
R.version.string
#> [1] "R version 4.4.2 (2024-10-31 ucrt)"

Dataset Pelanggan

berikut merupakan data dummy yang sengaja dibuat untuk memudahkan langkah-langkah dalam pre-processing.

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
)

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
)

pelanggan_raw
transaksi_raw

Bagian I — Konsep Data Preprocessing

Memahami struktur data

Sebelum melakukan langkah Preprocessing, sebaiknya kita harus mengenali struktur data yang ada Sebelum membersihkan apa pun, kita kenali dulu datanya: berapa baris dan kolomnya, apa nama atributnya, bagaimana tipenya, dan seperti apa isi beberapa baris awal.

Jumlah baris dan kolom

dim(pelanggan_raw)
#> [1] 12  6

Nama Atribut/Variabel

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

Tipe 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" ...

6 baris awal

head(pelanggan_raw)

Statistik Deskriptif

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

Mengukur kualitas awal

Kualitas data bisa dilihat dari beberapa sisi, yaitu accuracy, completeness, consistency, timeliness, believability, dan interpretability. Tidak semuanya bisa dinilai hanya dari tabel yang kita punya. Karena itu, di tahap awal kita fokus pada masalah yang memang bisa terdeteksi langsung dari dataset.

Beberapa pemeriksaan sederhana berikut membantu kita menemukan masalah yang terlihat sejak awal.

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

banyaknya data duplikat

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

Banyaknya customer_id yang berulang

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

Kategori untuk atribut kota

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

Kategori untuk atribut status

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

Rentang atribut usia

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

Rentang atribut Pendapatan

range(pelanggan_raw$pendapatan, na.rm = TRUE)
#> [1]   4500000 500000000

ringkasan kualitas 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

Interpretasi: berdasarkan tabel di atas, kita dapat mengetahui adanya missing value pada variabel usia, pendapatan dan kota yaitu sebanyak 4. Namun, kita harus melihat lebih detail untuk penanganannya.

Bagian II — Implementasi Data Cleaning

Membuat salinan kerja

salinan kerja dibuat agar kita tidak kehilangan data asli, sehingga mudah untuk menemukan perubahan ataupun perbedaan sebelum dan sesudah tahap preprocessing.

pelanggan <- pelanggan_raw

Membersihkan spasi dan kapitalisasi

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

Menyeragamkan kategori

Standardisasi dilakukan berdasarkan aturan domain yang masuk akal. Dalam kondisi nyata, aturan seperti ini sebaiknya dikonfirmasi melalui data dictionary atau pemilik data.

# 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

# Menampilkan seluruh baris dengan customer_id yang berulang
pelanggan[duplicated(pelanggan$customer_id) |
          duplicated(pelanggan$customer_id, fromLast = TRUE), ]
# Mempertahankan kemunculan pertama untuk customer_id yang identik
pelanggan <- pelanggan[!duplicated(pelanggan$customer_id), ]
rownames(pelanggan) <- NULL

dim(pelanggan)
#> [1] 11  6

Catatan: penghapusan duplikasi tidak boleh dilakukan otomatis. Langkah ini tepat hanya jika satu customer_id memang seharusnya mewakili satu pelanggan.

Memeriksa aturan domain

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

Nilai usia 150 tidak masuk akal. Setelah dicek kembali pada sumber data asli, ternyata nilai yang benar adalah 50. Oleh karena itu, nilai tersebut diperbaiki menjadi 50.

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

Membuat log perubahan

log_perubahan <- data.frame(
  tahap = c("Standardisasi", "Standardisasi", "Deduplikasi", "Koreksi domain"),
  atribut = c("kota", "status", "customer_id", "usia"),
  tindakan = c(
    "PKU dan variasi kapital menjadi Pekanbaru",
    "ACTIVE/A menjadi Aktif; nonaktif menjadi Tidak Aktif",
    "Menghapus kemunculan kedua C010",
    "Usia C004 dikoreksi dari 150 menjadi 50 berdasarkan sumber asli"
  ),
  stringsAsFactors = FALSE
)

log_perubahan

Bagian III — Penanganan Missing Values

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

Kode berikut hanya digunakan sebagai contoh untuk melihat dampak metode listwise deletion. Data asli pada objek pelanggan tidak langsung diubah.

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

Pertanyaan: berapa persen data yang hilang jika seluruh baris tidak lengkap dihapus?

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

Strategi 2: imputasi mean dan median

Pendapatan memiliki satu nilai yang sangat besar. Jadi, sebelum memilih cara imputasi, bandingkan dulu mean dan median.

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

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

Karena nilai 500 juta sangat berbeda dengan data lainnya sehingga membuat distribusi pendapatan menjadi sangat menceng maka median lebih aman digunakan dari pada mean.

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

Strategi 3: imputasi nilai kategorik

Nilai kategorik yang hilang tidak selalu harus diisi dengan modus. Pada contoh ini, kita menggunakan kategori Tidak diketahui agar informasi bahwa data tersebut awalnya kosong tetap dapat terlihat.

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

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

Menambahkan indikator missing

Kita juga bisa membuat indikator missing untuk menandai bahwa suatu nilai sebelumnya kosong.

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

Imputasi dapat mengubah distribusi dan mengecilkan variasi. Karena itu, evaluasi tidak berhenti setelah semua NA hilang.

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

Mengevaluasi tindakan

Nilai 500 juta tidak boleh langsung dihapus. Ada 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 apabila pelanggan korporasi tercampur dengan pelanggan ritel.

Karena mempertimbangkan alasan di atas maka nilai 500 juta tetap dipertahankan. Kita juga membuat variabel versi winsorized hanya untuk melihat perbedaan hasil dari kedua metode.

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

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

Winsorizing hanya digunakan sebagai contoh tambahan. Metode ini sebaiknya diterapkan jika memang sesuai dengan tujuan analisis, dan perubahan yang dilakukan perlu dicatat agar jelas bahwa nilai ekstrem telah dibatasi.

Bagian V — Transformasi Data

Normalisasi min–maks

Rumus normalisasi min–maks untuk mengubah nilai ke rentang 0 sampai 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")]

Normalisasi z-score

Rumus z-score adalah:

\[ z = \frac{x-\bar{x}}{s} \]

Keterangan:

  • \(x\) = nilai pengamatan
  • \(\bar{x}\) = rata-rata
  • \(s\) = standar deviasi
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

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.

Bagian VI — Integrasi Data

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)
#> [1] "C011"
setdiff(transaksi_raw$cust_id, pelanggan$customer_id)
#> [1] "C012"

Hasil setdiff() membantu menemukan ID yang hanya muncul di salah satu sumber, sehingga kita tahu siapa yang belum punya pasangan saat data digabungkan.

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

Dengan all.x = TRUE, semua pelanggan tetap dipertahankan. Kalau seorang pelanggan tidak punya pasangan transaksi, nilai transaksi akan menjadi NA.

Memvalidasi hasil integrasi

# Apakah jumlah baris berubah secara tidak wajar?
c(sebelum = nrow(pelanggan), sesudah = nrow(data_terintegrasi))
#> sebelum sesudah 
#>      11      11
# 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
# Pelanggan tanpa pasangan transaksi
data_terintegrasi[is.na(data_terintegrasi$jumlah_transaksi),
                  c("customer_id", "nama")]

Mengisi nol atau mempertahankan NA?

NA pada data transaksi belum tentu berarti nol. Bisa saja artinya:

  • pelanggan benar-benar belum pernah bertransaksi; atau
  • data transaksi pelanggan tidak tersedia atau gagal dipadankan.

Karena itu, ubah NA menjadi 0 hanya setelah definisinya sudah dipastikan.

# Contoh jika telah dikonfirmasi bahwa NA berarti belum pernah bertransaksi
data_terintegrasi$jumlah_transaksi_final <- data_terintegrasi$jumlah_transaksi
data_terintegrasi$total_purchase_final <- data_terintegrasi$total_purchase

data_terintegrasi$jumlah_transaksi_final[
  is.na(data_terintegrasi$jumlah_transaksi_final)
] <- 0

data_terintegrasi$total_purchase_final[
  is.na(data_terintegrasi$total_purchase_final)
] <- 0

Dataset Akhir dan Evaluasi

Memilih atribut akhir

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

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)

Pertanyaan Refleksi

1. Apakah dataset tanpa missing value selalu lebih berkualitas?

Jawaban: Tidak, Dataset tanpa missing value memang terlihat lebih lengkap, tetapi kualitas data tidak hanya dilihat dari ada atau tidaknya nilai kosong. Jika missing value diisi sembarangan, hasilnya justru bisa membuat data kurang sesuai dengan kondisi sebenarnya. Jadi, yang penting adalah menangani missing value dengan cara yang tepat tanpa menghilangkan informasi pentingsehingga tetap menggambarkan data sebenarnya.

2. Mengapa outlier tidak boleh otomatis dihapus?

Jawaban: Karena outlier belum tentu sebuah kesalahan tetapi bisa jadi merupakan kondisi nyata yang memang terjadi pada data. Sebelum menghapus outlier, kita perlu memeriksa apakah nilai tersebut merupakan kesalahan input, nilai yang memang valid tapi ekstrem, atau berasal dari kelompok/populasi yang berbeda.

3. Bagaimana preprocessing dapat menimbulkan bias?

Jawaban: Bias bisa terjadi jika cara kita membersihkan atau mengubah data membuat sebagian kelompok menjadi tidak terwakili dengan baik. Misalnya, terlalu banyak menghapus data yang memiliki missing value dapat menyebabkan jumlah data dari kelompok tertentu berkurang. Imputasi yang tidak tepat juga dapat mengubah pola atau distribusi data dari kondisi sebenarnya.

4. Mengapa parameter imputasi dan transformasi seharusnya dihitung dari data pelatihan?

Jawaban: Agar tidak terjadi data leakage, yaitu ketika informasi dari data pengujian ikut digunakan dalam proses pengolahan data. Misalnya, nilai mean atau median sebaiknya dihitung dari data pelatihan saja, kemudian digunakan untuk mengolah data pengujian. Dengan begitu, hasil evaluasi model menjadi lebih objektif.

5. Apa risiko integrasi data jika identifier tidak unik?

Jawaban: Identifier yang tidak unik dapat menyebabkan satu data dipasangkan dengan beberapa baris saat proses penggabungan. Akibatnya jumlah baris bisa bertambah secara tidak wajar, terjadi duplikasi informasi, dan hasil analisis dapat menjadi bias. Karena itu, keunikan identifier perlu diperiksa sebelum integrasi data.

Ringkasan

Secara garis besar, alur preprocessing yang telah di lakukan meliputi:

  1. memahami struktur dan kualitas awal data;
  2. membersihkan kategori, duplikasi, dan pelanggaran domain;
  3. menangani missing values tanpa menyembunyikan ketidakpastian;
  4. mendeteksi dan mengevaluasi outlier;
  5. mentransformasikan atribut numerik; dan
  6. mengintegrasikan data serta memvalidasi hasilnya.

Kesimpulan

Secara keseluruhan, preprocessing data bertujuan untuk membuat data menjadi lebih bersih, konsisten, dan siap digunakan untuk analisis. Proses ini mencakup penanganan berbagai masalah seperti data yang tidak sesuai, missing value, duplikasi, dan outlier. Setiap perubahan pada data perlu dilakukan dengan pertimbangan yang jelas agar informasi penting tetap terjaga dan hasil analisis dapat dipercaya.

Daftar Pustaka

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