Applied Data Mining: Resilient Preprocessing, Robust Feature Scaling, and Data Fusion

Muhammad Ilham Rasyid

2026-08-19

Pendahuluan

Melanjutkan dari pembelajaran pada pertemuan ke-3. Praktikum data mining dengan skenario dari sebuah kasus. Dimana, sebuah perusahaan e-commerce akan melakukan analisis pelanggan. terdapat dua sumber data, yaitu:

  1. data pelanggan, yang memuat identitas, usia, kota, pendapatan, dan status 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"),
  stringAsFactors = FALSE
)

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

transaksi_raw
##    cust_id jumlah_transaksi total_purchase stringAsFactors
## 1     C001                5        1.5e+06           FALSE
## 2     C002                3        9.0e+05           FALSE
## 3     C003                7        2.7e+06           FALSE
## 4     C004                2        6.0e+05           FALSE
## 5     C005                6        2.1e+06           FALSE
## 6     C006                4        1.3e+06           FALSE
## 7     C007               20        2.5e+07           FALSE
## 8     C008                5        1.7e+06           FALSE
## 9     C009                3        8.0e+05           FALSE
## 10    C010                8        3.2e+06           FALSE
## 11    C012                1        2.5e+05           FALSE

Datanya belum di integrasikan dan beberapa mengandung nilai hilang, kategori yang kurang konsisten, duplikasi, outlier, hingga perbedaan nama identifier.

Tujuan dari praktikum ini ialah untuk melakukan penyiapan dan pembersihan data sehingga hasil data akhir yang telah di integrasikan akan menjadi data bersih yang siap di analisis sehingga dapat memberikan insight yang bermakna.

Isi

Pada tahapan ini kita akan mencoba melakukan persiapan, pembersihan, transformasi, dan integrasi dari data mentah yang kita punya. Untuk nantinya dapat menghasilkan data bersih yang siap untuk di analisis.

Persiapan

Kita lihat terlebih dahulu tentang keadaaan datanya.

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          stringAsFactors
##  Length:12          Length:12          Mode :logical  
##  Class :character   Class :character   FALSE:12       
##  Mode  :character   Mode  :character                  
##                                                       
##                                                       
##                                                       
## 

Pada tabel 1. pelanggan_raw, terdapat beberapa informasi spesifik seperti ukuran tabel 12x6, kemudian hanya kolom usia dan pendapatan yang numerik (sisanya character). Terlihat juga ada outlier pada kolom usia dan pendapatan. Dimana di kolom usia ada nilai 150 yang belum di pastikan apakah itu tahun atau bulan. dan di kolom pendapatan ada pendapatan yang sebesar 500 juta, yang bisa jadi kebetulan atau kesalahan input angka 0 yang berlebihan.

summary(transaksi_raw)
##    cust_id          jumlah_transaksi total_purchase     stringAsFactors
##  Length:11          Min.   : 1.000   Min.   :  250000   Mode :logical  
##  Class :character   1st Qu.: 3.000   1st Qu.:  850000   FALSE:11       
##  Mode  :character   Median : 5.000   Median : 1500000                  
##                     Mean   : 5.818   Mean   : 3640909                  
##                     3rd Qu.: 6.500   3rd Qu.: 2400000                  
##                     Max.   :20.000   Max.   :25000000

Pada tabel 2. Ukuran tabel nya ialah 11x3, dimana hal ini terdapat perbedaan ukuran terhadap tabel 1. yang bisa jadi terdapat duplikat di tabel 1 (data pelanggan). Kemudian ada juga total pembelian yang sampai 25 juta. Hal ini perlu di pastikan kembali apakah jumlah pembelian dengan harga nya itu sesuai.

Selanjutnya ialah kita akan mengecek missing value dan duplikat.

colSums(is.na(pelanggan_raw))
##     customer_id            nama            usia      pendapatan            kota 
##               0               0               1               2               1 
##          status stringAsFactors 
##               0               0

Pada tabel 1. terdapat nilai hilang pada kolom usia, pendapatan, dan kota.

colSums(is.na(transaksi_raw))
##          cust_id jumlah_transaksi   total_purchase  stringAsFactors 
##                0                0                0                0

Pada tabel 2. Tidak terdapat nilai hilang.

Untuk pengecekkan duplikat, perlu di pastikan kolom identifier nya. Dimana disini ada customer_id dan cust_id.

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

Terlihat ada 1 baris data duplikat pada tabel 1 (data pelanggan).

sum(duplicated(transaksi_raw$cust_id))
## [1] 0

Tidak terdapat baris data duplikat pada tabel 2 (data transaksi).

Tak lupa juga kita mengecek kategori pada kolom yang berisi character. Karena untuk keseragaman label nantinya.

Yang pertama ialah pada tabel 1 (data pelanggan).

sort(unique(pelanggan_raw$nama))
##  [1] "Ani"    "Budi"   "Citra"  "Dodi"   "Eka"    "Fani"   "Gilang" "Hana"  
##  [9] "Indra"  "Joko"   "Kiki"
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"

Dari hasil pengecekkan ini terlihat beberapa ketidaksamaan/ketidakseragaman pada baris data, sehingga hal ini akan menimbulkan masalah karena tidak terdefinisi dengan baik (case-sensitif label). Dan pada bagian nama, kita hanya mengecek penulisannya saja apakah nantinya hanya kata depannya saja yang besar atau lainnya.

Untuk di tabel 2 (data transaksi). Tidak ada kolom yang harus kita pastikan untuk keseragamannya, sehingga tahap pembersihan data dapat di laksanakan.

Pembersihan Data

Pada tahapan pembersihan data ini, kita akan melakukan pengerjaan dengan mulai dari menghapus duplikasi, missing value, menyeragamkan kategori/label, dan memeriksa atuan domain/logika di dalamnya.

sebelum di mulai pembersihan, akan lebih baik membuat salinan kerja atau salinan dataframe baru agar apabila terjadi kesalahan kita tidak kehilangan dataset aslinya.

pelanggan <- pelanggan_raw
transaksi <- transaksi_raw

Pada tahapan persiapan kita sudah mendapatkan indikasi duplikat yang ada pada tabel 1 (data pelanggan).

pelanggan[duplicated(pelanggan$customer_id) | duplicated(pelanggan$customer_id, fromLast = T), ]
##    customer_id nama usia pendapatan  kota      status stringAsFactors
## 10        C010 Joko   35    5300000 Dumai Tidak aktif           FALSE
## 11        C010 Joko   35    5300000 Dumai Tidak aktif           FALSE

Ternyata pada kustomer C010 terduplikasi sehingga hal ini bisa kita hapuskan salah satunya saja. Karena kolom customer_id merupakan identifier yang sifatnya itu unik (didalamnya tidak boleh sama). Sehingga logikanya satu transaksi memiliki 1 customer_id yang unik.

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

# Tampilkan data 
pelanggan$customer_id
##  [1] "C001" "C002" "C003" "C004" "C005" "C006" "C007" "C008" "C009" "C010"
## [11] "C011"

setelah data duplikat ditangani, langkah selanjutnya ialah menangani nilai hilang atau Na. Dimana kita akan menggunakan imputasi median untuk kolom usia dan pendapatan (karena terdapat outlier). kemudian mendifinisikan label “Tidak diketahui” untuk nilai hilang pada kolom character seperti kota.

# Imputasi median untuk usia dan pendapatan
median_pendapatan <- median(pelanggan$pendapatan, na.rm = T)
median_usia <- median(pelanggan$usia, na.rm = T) # Definisikan nilai urut 

pelanggan$pendapatan_imputasi <- pelanggan$pendapatan
pelanggan$usia_imputasi <- pelanggan$usia # Buat kolom baru di sebelahnya 

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

# Imputasi nilai kategorik dengan "Tidak diketahui"
pelanggan$kota_imputasi <- pelanggan$kota
pelanggan$kota_imputasi[is.na(pelanggan$kota_imputasi)] <- "Tidak diketahui"

# Tampilkan tabel 
pelanggan[, c("customer_id", "usia", "usia_imputasi", "pendapatan", "pendapatan_imputasi", "kota", "kota_imputasi")]
##    customer_id usia usia_imputasi pendapatan pendapatan_imputasi       kota
## 1         C001   21          21.0    4.5e+06             4.5e+06  Pekanbaru
## 2         C002   25          25.0         NA             4.9e+06        PKU
## 3         C003   23          23.0    5.2e+06             5.2e+06  PEKANBARU
## 4         C004  150         150.0    4.8e+06             4.8e+06      Dumai
## 5         C005   27          27.0    4.9e+06             4.9e+06  pekanbaru
## 6         C006   NA          27.5    5.1e+06             5.1e+06      DUMAI
## 7         C007   31          31.0    5.0e+08             5.0e+08 Pekanbaru 
## 8         C008   29          29.0    4.7e+06             4.7e+06       Siak
## 9         C009   22          22.0    4.6e+06             4.6e+06        PKU
## 10        C010   35          35.0    5.3e+06             5.3e+06      Dumai
## 11        C011   28          28.0         NA             4.9e+06       <NA>
##      kota_imputasi
## 1        Pekanbaru
## 2              PKU
## 3        PEKANBARU
## 4            Dumai
## 5        pekanbaru
## 6            DUMAI
## 7       Pekanbaru 
## 8             Siak
## 9              PKU
## 10           Dumai
## 11 Tidak diketahui

Missing value berhasil di tangani. alasan kenapa tidak menghapus baris saja itu karena kalo menghapus baris akan kehilangan lumayan banyak data. Dengan total observasi yang hanya 11 ini pastinya kehilangan 2 data saja akan mengurangi informasi yang akan di dapat nantinya.

langkah selanjutnya ialah menyeragamkan kategori/label pada kolom kota_imputasi dan status.

sort(unique(pelanggan$kota_imputasi))
##  [1] " PKU"            "Dumai"           "DUMAI"           "pekanbaru"      
##  [5] "Pekanbaru"       "PEKANBARU"       "Pekanbaru "      "PKU"            
##  [9] "Siak"            "Tidak diketahui"
sort(unique(pelanggan$status))
## [1] "A"           "ACTIVE"      "aktif"       "Aktif"       "AKTIF"      
## [6] "nonaktif"    "Tidak aktif" "Tidak Aktif"
# Dictionary/standarisasi untuk menyeragamkan kategori text
pelanggan$kota_imputasi[pelanggan$kota_imputasi %in% c("PKU", "pekanbaru", "PEKANBARU")] <- "Pekanbaru"
pelanggan$kota_imputasi[pelanggan$kota_imputasi == "DUMAI"] <- "Dumai"

pelanggan$status[pelanggan$status %in% c("aktif", "AKTIF", "A", "ACTIVE")] <- "Aktif"
pelanggan$status[pelanggan$status %in% c("Tidak aktif", "nonaktif")] <- "Tidak Aktif"

# Tampilkan 
pelanggan[, c("customer_id", "kota_imputasi", "status")]
##    customer_id   kota_imputasi      status
## 1         C001       Pekanbaru       Aktif
## 2         C002             PKU       Aktif
## 3         C003       Pekanbaru       Aktif
## 4         C004           Dumai       Aktif
## 5         C005       Pekanbaru Tidak Aktif
## 6         C006           Dumai Tidak Aktif
## 7         C007      Pekanbaru        Aktif
## 8         C008            Siak       Aktif
## 9         C009       Pekanbaru       Aktif
## 10        C010           Dumai Tidak Aktif
## 11        C011 Tidak diketahui       Aktif

Terdapat kejanggalan dimana kata PKU masih belum terseragamkan seperti yang lain. hal ini bisa jadi karena ada spasi tersembunyi.

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

# kategorisasi kembali 
pelanggan$kota_imputasi[pelanggan$kota_imputasi == "PKU"] <- "Pekanbaru"

pelanggan$kota_imputasi
##  [1] "Pekanbaru"       "Pekanbaru"       "Pekanbaru"       "Dumai"          
##  [5] "Pekanbaru"       "Dumai"           "Pekanbaru"       "Siak"           
##  [9] "Pekanbaru"       "Dumai"           "Tidak diketahui"

Sekarang data telah terkategorisasi dengan baik. langkah selanjutnya ialah kita akan mengecek aturan domain/logikanya sehingga apakah benar dengan adanya outlier di dalamnya itu menjadi masalah atau justru kebetulan.

Untuk usia yang 150 itu, misal saat dipastikan kembali ternyata usia sebenernya ialah 50 tahun maka kita perlu menggantinya menjadi angka sebenarnya.

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

Kemudian kita cek juga bagian pendapatan yang nilainya sungguh fantastis sebesar 500 juta. Hal ini sungguh wajar dan sifatnya kebetulan. Karena bisa jadi memang orang tersebut memiliki usaha yang banyak. Dan lagi umurnya 31 tahun. Untuk tabel 2 (transaksi). Nilai-nilainya sudah dipastikan tidak ada masalah seperti nilai tak wajar. Jadi kita bisa lanjutkan ke tahapan selanjutnya.

Transformasi Data

Dikarenakan ada baris data outlier dari pendapatan. Oleh karena itu, metode robust scaler akan lebih baik dibanding metode normalisasi yang biasanya.

robust_scaler <- function(x) {
  if (all(is.na(x))) return(rep(NA_real_, length(x)))
  iqr_val <- IQR(x, na.rm = TRUE)
  if (iqr_val == 0) return(rep(0, length(x)))
  (x - median(x, na.rm = TRUE)) / iqr_val
}
# Terapkan di tabel pelanggan
pelanggan$usia_robust <- robust_scaler(pelanggan$usia_imputasi)
pelanggan$pendapatan_robust <- robust_scaler(pelanggan$pendapatan_imputasi)

head(pelanggan[, c("customer_id", "usia_imputasi", "usia_robust", "pendapatan_imputasi", "pendapatan_robust")])
##   customer_id usia_imputasi usia_robust pendapatan_imputasi pendapatan_robust
## 1        C001          21.0 -1.08333333             4500000             -1.00
## 2        C002          25.0 -0.41666667             4900000              0.00
## 3        C003          23.0 -0.75000000             5200000              0.75
## 4        C004          50.0  3.75000000             4800000             -0.25
## 5        C005          27.0 -0.08333333             4900000              0.00
## 6        C006          27.5  0.00000000             5100000              0.50
# Terapkan di tabel transaksi 
transaksi$jumlah_transaksi_robust <- robust_scaler(transaksi$jumlah_transaksi)
transaksi$total_purchase_robust <- robust_scaler(transaksi$total_purchase)

head(transaksi[, c("cust_id", "jumlah_transaksi", "jumlah_transaksi_robust", "total_purchase", "total_purchase_robust")])
##   cust_id jumlah_transaksi jumlah_transaksi_robust total_purchase
## 1    C001                5               0.0000000        1500000
## 2    C002                3              -0.5714286         900000
## 3    C003                7               0.5714286        2700000
## 4    C004                2              -0.8571429         600000
## 5    C005                6               0.2857143        2100000
## 6    C006                4              -0.2857143        1300000
##   total_purchase_robust
## 1             0.0000000
## 2            -0.3870968
## 3             0.7741935
## 4            -0.5806452
## 5             0.3870968
## 6            -0.1290323

Nilai robust scaller yang 0 mengindikasikan bahwa nilai aslinya sama persis dengan nilai median nya. Sehingga hal itu menunjukkan nilai robust scaller berada tepat di titik tengah (median).

Untuk melihat bagaimana kinerja dari robust scaler ini kita akan melihat titik-titiknya.

plot(pelanggan$pendapatan_imputasi,
     pelanggan$pendapatan_robust,
     pch = 19,
     )
abline(0, 1, col = "red", lty = 2)

Interpretasinya, outliernya berhasil di tekan dengan nilai robust scaler dimana median dan iqr dari data berperan penting dalam menyeimbangkannya. Hal ini menjadi bukti bahwa teknik transformasi perlu memperhatikan outlier yang ada di datasetnya juga. Alasan mengapa tabel 2 (transaksi) juga diikutkan transformasi ialah karena nantinya kedua tabel ini akan digabungkan sehingga untuk tahap analisis lanjutan perlu menggunakan jarak data yang memiliki pola. Apabila nanti teknik transformasinya di bedakan, akibatnya model analisis kita akan berantakan.

Integrasi Data

Pada tahap ini kita akan mencoba menggabungkan tabel 1 (pelanggan) dan tabel 2 (transaksi) menjadi satu tabel utuh dengan kunci identifiernya. untuk itu kita perlu menyamakan kolom identifier nya dulu.

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

Selanjutnya ialah kita akan menggabungkannya merge().

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


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

Nah, ada hal menarik. Dimana customer C011 yaitu Kiki memiliki jumlah transaksi dan total purchase yang NA. Hal ini cukup wajar dimana bisa jadi si Kiki memang belum ada melakukan transaksi. Oleh karena itu, kita akan mengisinya dengan nilai 0 saja.

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

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

# Tampilkan
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                0        0.0e+00

Evaluasi

Pada tahap ini kita akan menyimpan dataset yang sudah bersih dan siap diolah. pada tahap transformasi sebelumnya, tabel 2 (transaksi) juga ikut di normalisasikan dengan robust scaler. Kita tetep menggunakan atribut yang di normalisasikan dengan baris C011 dihapuskan saja sehingga tersisa baris bersih dengan pelanggan dan transaksi.

data_terintegrasi <- data_terintegrasi[data_terintegrasi$customer_id != "C011", ]


# Simpan dalam file .csv
write.csv(data_terintegrasi,
          "data_pelanggan_dan_transaksi_setelah_preprocessing.csv",
          row.names = F)

Kesimpulan

Rangkaian proses preprocessing data ini berhasil membersihkan dan menyatukan data mentah pelanggan serta transaksi menjadi satu dataset yang terstandarisasi, utuh, dan bebas dari anomali. Melalui penanganan duplikasi baris, koreksi domain rule, imputasi nilai hilang secara kontekstual (median untuk numerik dan pelabelan untuk kategorik), serta penyeragaman tipografi teks, kualitas data dapat ditingkatkan tanpa kehilangan esensi informasi penting. Penerapan Robust Scaler secara konsisten pada kedua tabel terbukti efektif meredam dampak nilai ekstrem (outlier) pada atribut pendapatan dan transaksi, sehingga hasil akhir penggabungan data siap digunakan secara optimal dan andal untuk pemodelan data mining atau analisis analitik lanjutan.