Identitas Mahasiswa

Nama: Tengku Alfaisal Hardian

NIM: 2403112815

Prodi: Statistika

Kelas: A

Tujuan Praktikum

Setelah menyelesaikan praktikum ini, mahasiswa diharapkan mampu:

  1. menjelaskan kondisi data yang membuat proses preprocessing diperlukan;
  2. mengenali masalah kualitas data menggunakan R;
  3. melakukan pembersihan data secara sistematis dan terdokumentasi;
  4. memilih serta menerapkan strategi penanganan nilai hilang (missing values);
  5. mendeteksi dan mengevaluasi nilai ekstrem (outlier);
  6. melakukan transformasi pada atribut numerik; dan
  7. menggabungkan dua sumber data yang berbeda serta memvalidasi hasil penggabungannya.

Skenario Praktikum

Sebuah toko daring sedang bersiap melakukan analisis pelanggan. Data yang tersedia berasal dari dua sumber yang terpisah, yaitu data pelanggan yang memuat identitas, usia, kota, pendapatan, dan status keanggotaan, serta data transaksi yang memuat jumlah transaksi dan total nilai pembelian setiap pelanggan.

Kedua sumber data ini belum dapat langsung dianalisis karena masih memuat berbagai masalah, seperti nilai yang hilang, penulisan kategori yang tidak seragam, baris yang tercatat berulang, nilai yang secara logika tidak masuk akal, nilai ekstrem yang mencolok, serta perbedaan nama kolom identifier antar tabel. Tugas mahasiswa adalah menyiapkan kedua data tersebut hingga menjadi satu dataset analisis yang bersih dan siap dipakai.

Prinsip yang perlu dipegang selama praktikum adalah jangan membersihkan data hanya karena sebuah nilai terlihat aneh. Setiap perubahan yang dilakukan harus memiliki alasan yang jelas, mengikuti aturan tertentu, dan dicatat supaya bisa ditelusuri kembali.

Persiapan RStudio

Membuat proyek baru

  1. Buka aplikasi RStudio.
  2. Pilih menu File, lalu New Project, lalu New Directory, lalu New Project.
  3. Beri nama proyek, misalnya praktikum_preprocessing.
  4. Simpan berkas latihan ini ke dalam folder proyek yang baru dibuat.
  5. Klik tombol Knit untuk menghasilkan tampilan HTML.

Memeriksa lingkungan kerja

getwd()
## [1] "D:/PERKULIAHAN/SEMESTER 5/Data Mining sem 5"
R.version.string
## [1] "R version 4.4.1 (2024-06-14 ucrt)"

Modul ini hanya memakai fungsi bawaan R sehingga dapat langsung dijalankan tanpa perlu memasang paket tambahan apa pun.

Membangun Dataset Praktikum

Dataset pada latihan ini sengaja dibuat kotor supaya seluruh tahap preprocessing dapat benar benar dipraktikkan. Data pelanggan berjumlah 32 baris dan data transaksi berjumlah 31 baris. Sebagian besar baris awal tetap dipertahankan sama persis seperti modul aslinya, kemudian ditambahkan baris baris baru yang juga memuat berbagai masalah data agar latihan menjadi lebih lengkap.

pelanggan_raw <- data.frame(
  customer_id = c("C001","C002","C003","C004","C005","C006","C007","C008",
                   "C009","C010","C010","C011","C013","C014","C015","C016",
                   "C017","C018","C019","C020","C021","C022","C023","C024",
                   "C025","C026","C027","C028","C029","C030","C031","C032"),
  nama = c("Ani","Budi","Citra","Dodi","Eka","Fani","Gilang","Hana","Indra",
           "Joko","Joko","Kiki","Lestari","Made","Nita","Oscar","Putri","Qori",
           "Rian","Sinta","Tono","Umi","Vino","Wulan","Xena","Yoga","Zaki",
           "Aditya","Bella","Cahyo","Dewi","Eko"),
  usia = c(21,25,23,150,27,NA,31,29,22,35,35,28,24,26,33,19,41,NA,45,30,5,27,
           38,29,34,24,31,26,NA,37,28,33),
  pendapatan = c(4500000,NA,5200000,4800000,4900000,5100000,500000000,4700000,
                 4600000,5300000,5300000,NA,4750000,NA,5000000,4650000,5400000,
                 4850000,5600000,200000000,4700000,4900000,NA,5100000,4800000,
                 4950000,5050000,4700000,4800000,5150000,4900000,5200000),
  kota = c("Pekanbaru"," PKU","PEKANBARU","Dumai","pekanbaru","DUMAI",
           "Pekanbaru ","Siak","PKU","Dumai","Dumai",NA,"Pekanbaru"," pekanbaru",
           "DUMAI","Siak","Pekanbaru ","dumai","PKU","Pekanbaru","Siak",
           "PEKANBARU","Dumai"," Siak","pku","Dumai","Pekanbaru","DUMAI","Siak",
           "pekanbaru",NA,"Dumai"),
  status = c("Aktif","aktif","ACTIVE","A","Tidak Aktif","nonaktif","Aktif",
             "AKTIF","A","Tidak aktif","Tidak aktif","Aktif","Aktif","aktif",
             "AKTIF","A","Tidak aktif","nonaktif","Aktif","Aktif","Tidak Aktif",
             "ACTIVE","A","aktif","Tidak aktif","Aktif","nonaktif","AKTIF","A",
             "Tidak Aktif","Aktif","aktif"),
  stringsAsFactors = FALSE
)

pelanggan_raw
##    customer_id    nama usia pendapatan       kota      status
## 1         C001     Ani   21   4.50e+06  Pekanbaru       Aktif
## 2         C002    Budi   25         NA        PKU       aktif
## 3         C003   Citra   23   5.20e+06  PEKANBARU      ACTIVE
## 4         C004    Dodi  150   4.80e+06      Dumai           A
## 5         C005     Eka   27   4.90e+06  pekanbaru Tidak Aktif
## 6         C006    Fani   NA   5.10e+06      DUMAI    nonaktif
## 7         C007  Gilang   31   5.00e+08 Pekanbaru        Aktif
## 8         C008    Hana   29   4.70e+06       Siak       AKTIF
## 9         C009   Indra   22   4.60e+06        PKU           A
## 10        C010    Joko   35   5.30e+06      Dumai Tidak aktif
## 11        C010    Joko   35   5.30e+06      Dumai Tidak aktif
## 12        C011    Kiki   28         NA       <NA>       Aktif
## 13        C013 Lestari   24   4.75e+06  Pekanbaru       Aktif
## 14        C014    Made   26         NA  pekanbaru       aktif
## 15        C015    Nita   33   5.00e+06      DUMAI       AKTIF
## 16        C016   Oscar   19   4.65e+06       Siak           A
## 17        C017   Putri   41   5.40e+06 Pekanbaru  Tidak aktif
## 18        C018    Qori   NA   4.85e+06      dumai    nonaktif
## 19        C019    Rian   45   5.60e+06        PKU       Aktif
## 20        C020   Sinta   30   2.00e+08  Pekanbaru       Aktif
## 21        C021    Tono    5   4.70e+06       Siak Tidak Aktif
## 22        C022     Umi   27   4.90e+06  PEKANBARU      ACTIVE
## 23        C023    Vino   38         NA      Dumai           A
## 24        C024   Wulan   29   5.10e+06       Siak       aktif
## 25        C025    Xena   34   4.80e+06        pku Tidak aktif
## 26        C026    Yoga   24   4.95e+06      Dumai       Aktif
## 27        C027    Zaki   31   5.05e+06  Pekanbaru    nonaktif
## 28        C028  Aditya   26   4.70e+06      DUMAI       AKTIF
## 29        C029   Bella   NA   4.80e+06       Siak           A
## 30        C030   Cahyo   37   5.15e+06  pekanbaru Tidak Aktif
## 31        C031    Dewi   28   4.90e+06       <NA>       Aktif
## 32        C032     Eko   33   5.20e+06      Dumai       aktif
transaksi_raw <- data.frame(
  cust_id = c("C001","C002","C003","C004","C005","C006","C007","C008","C009",
              "C010","C012","C013","C014","C015","C016","C017","C018","C019",
              "C020","C021","C022","C023","C024","C025","C026","C027","C028",
              "C029","C030","C015","C034"),
  jumlah_transaksi = c(5,3,7,2,6,4,20,5,3,8,1,4,6,9,2,11,3,12,30,1,5,7,4,6,3,
                        8,5,2,9,9,4),
  total_purchase = c(1500000,900000,2700000,600000,2100000,1300000,25000000,
                      1700000,800000,3200000,250000,1350000,2400000,3600000,
                      650000,4600000,950000,5000000,60000000,300000,1650000,
                      2500000,1300000,2050000,900000,3100000,1600000,600000,
                      3400000,3600000,1250000),
  stringsAsFactors = FALSE
)

transaksi_raw
##    cust_id jumlah_transaksi total_purchase
## 1     C001                5        1500000
## 2     C002                3         900000
## 3     C003                7        2700000
## 4     C004                2         600000
## 5     C005                6        2100000
## 6     C006                4        1300000
## 7     C007               20       25000000
## 8     C008                5        1700000
## 9     C009                3         800000
## 10    C010                8        3200000
## 11    C012                1         250000
## 12    C013                4        1350000
## 13    C014                6        2400000
## 14    C015                9        3600000
## 15    C016                2         650000
## 16    C017               11        4600000
## 17    C018                3         950000
## 18    C019               12        5000000
## 19    C020               30       60000000
## 20    C021                1         300000
## 21    C022                5        1650000
## 22    C023                7        2500000
## 23    C024                4        1300000
## 24    C025                6        2050000
## 25    C026                3         900000
## 26    C027                8        3100000
## 27    C028                5        1600000
## 28    C029                2         600000
## 29    C030                9        3400000
## 30    C015                9        3600000
## 31    C034                4        1250000

Beberapa masalah yang sengaja disisipkan pada kedua tabel di atas antara lain nilai usia yang tidak masuk akal (150 tahun dan 5 tahun), nilai pendapatan yang jauh lebih besar dari pelanggan lain, penulisan nama kota dan status keanggotaan yang tidak konsisten, baris pelanggan yang tercatat dua kali, baris transaksi yang tercatat dua kali, pelanggan yang tidak memiliki catatan transaksi (C011, C031, dan C032), serta transaksi yang identifier pelanggannya tidak ditemukan pada tabel pelanggan (C012 dan C034).

Bagian Pertama: Memahami Konsep Data Preprocessing

Mengenali struktur data

Sebelum melakukan perubahan apa pun, periksa terlebih dahulu struktur, ukuran, tipe atribut, dan beberapa baris awal dari data.

dim(pelanggan_raw)
## [1] 32  6
names(pelanggan_raw)
## [1] "customer_id" "nama"        "usia"        "pendapatan"  "kota"       
## [6] "status"
str(pelanggan_raw)
## 'data.frame':    32 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" ...
head(pelanggan_raw)
##   customer_id  nama usia pendapatan      kota      status
## 1        C001   Ani   21    4500000 Pekanbaru       Aktif
## 2        C002  Budi   25         NA       PKU       aktif
## 3        C003 Citra   23    5200000 PEKANBARU      ACTIVE
## 4        C004  Dodi  150    4800000     Dumai           A
## 5        C005   Eka   27    4900000 pekanbaru Tidak Aktif
## 6        C006  Fani   NA    5100000     DUMAI    nonaktif
summary(pelanggan_raw)
##  customer_id            nama                usia          pendapatan       
##  Length:32          Length:32          Min.   :  5.00   Min.   :  4500000  
##  Class :character   Class :character   1st Qu.: 25.00   1st Qu.:  4787500  
##  Mode  :character   Mode  :character   Median : 29.00   Median :  4925000  
##                                        Mean   : 32.97   Mean   : 29603571  
##                                        3rd Qu.: 34.00   3rd Qu.:  5200000  
##                                        Max.   :150.00   Max.   :500000000  
##                                        NA's   :3        NA's   :4          
##      kota              status         
##  Length:32          Length:32         
##  Class :character   Class :character  
##  Mode  :character   Mode  :character  
##                                       
##                                       
##                                       
## 

Mengukur kualitas data di awal

Han, Kamber, dan Pei menyebutkan enam dimensi kualitas data, yaitu akurasi, kelengkapan, konsistensi, ketepatan waktu, keterpercayaan, dan kemudahan diinterpretasikan. Tidak semua dimensi tersebut bisa dihitung langsung dari isi tabel. Akurasi misalnya memerlukan pembanding terhadap kondisi yang sebenarnya, sedangkan ketepatan waktu membutuhkan informasi kapan data terakhir diperbarui. Pemeriksaan berikut hanya berfokus pada masalah yang memang bisa dideteksi langsung dari dataset yang tersedia.

colSums(is.na(pelanggan_raw))
## customer_id        nama        usia  pendapatan        kota      status 
##           0           0           3           4           2           0
round(colMeans(is.na(pelanggan_raw)) * 100, 2)
## customer_id        nama        usia  pendapatan        kota      status 
##        0.00        0.00        9.38       12.50        6.25        0.00
sum(duplicated(pelanggan_raw))
## [1] 1
sum(duplicated(pelanggan_raw$customer_id))
## [1] 1
sort(unique(pelanggan_raw$kota))
##  [1] " pekanbaru" " PKU"       " Siak"      "dumai"      "Dumai"     
##  [6] "DUMAI"      "pekanbaru"  "Pekanbaru"  "PEKANBARU"  "Pekanbaru "
## [11] "pku"        "PKU"        "Siak"
sort(unique(pelanggan_raw$status))
## [1] "A"           "ACTIVE"      "aktif"       "Aktif"       "AKTIF"      
## [6] "nonaktif"    "Tidak aktif" "Tidak Aktif"
range(pelanggan_raw$usia, na.rm = TRUE)
## [1]   5 150
range(pelanggan_raw$pendapatan, na.rm = TRUE)
## [1] 4.5e+06 5.0e+08

Membuat fungsi ringkasan kualitas data

Supaya pemeriksaan kualitas data dapat dipakai berulang kali pada tahap tahap berikutnya, sebaiknya dibuat satu fungsi khusus yang merangkum kondisi setiap atribut.

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          31
## 2        nama character              0           0.00          31
## 3        usia   numeric              3           9.38          22
## 4  pendapatan   numeric              4          12.50          20
## 5        kota character              2           6.25          14
## 6      status character              0           0.00           8

Perlu diingat bahwa hasil audit ini bukan keputusan pembersihan data itu sendiri. Audit hanya memberi tanda atribut mana yang perlu diperiksa lebih lanjut, keputusan penanganannya tetap harus dipikirkan satu per satu.

Bagian Kedua: Melakukan Pembersihan Data

Membuat salinan kerja

Data mentah sebaiknya tetap dipertahankan apa adanya, sehingga setiap perubahan yang dilakukan pada salinannya masih bisa ditelusuri kembali ke sumber aslinya.

pelanggan <- pelanggan_raw

Membersihkan spasi dan perbedaan huruf besar kecil

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

Penyeragaman kategori sebaiknya mengikuti aturan yang sudah disepakati bersama pemilik data. Pada latihan ini aturan tersebut dianggap sudah dikonfirmasi sebelumnya.

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"

Menemukan dan menghapus baris yang terduplikasi

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] 31  6

Perlu dicatat bahwa menghapus baris duplikat semacam ini hanya tepat dilakukan apabila memang setiap customer_id seharusnya hanya mewakili satu pelanggan saja. Apabila satu pelanggan memang boleh memiliki banyak baris riwayat, maka tindakan penghapusan ini justru keliru.

Memeriksa aturan domain

Nilai yang hilang (NA) perlu dikecualikan terlebih dahulu pada saat pemeriksaan ini, sebab tanpa pengecualian tersebut baris dengan usia atau pendapatan yang kosong akan ikut tercetak sebagai baris kosong yang membingungkan, padahal bukan itu yang sedang diperiksa.

pelanggan[!is.na(pelanggan$usia) & (pelanggan$usia < 15 | pelanggan$usia > 100), ]
##    customer_id nama usia pendapatan  kota      status
## 4         C004 Dodi  150    4800000 Dumai       Aktif
## 20        C021 Tono    5    4700000  Siak Tidak Aktif
pelanggan[!is.na(pelanggan$pendapatan) & pelanggan$pendapatan < 0, ]
## [1] customer_id nama        usia        pendapatan  kota        status     
## <0 rows> (or 0-length row.names)

Pelanggan dengan kode C004 tercatat berusia 150 tahun, sedangkan pelanggan dengan kode C021 tercatat berusia 5 tahun. Kedua nilai ini melanggar aturan domain usia pelanggan yang wajar, misalnya rentang 15 sampai 100 tahun. Anggaplah setelah dicek ke formulir pendaftaran aslinya, usia C004 yang benar adalah 50 tahun, sedangkan usia C021 yang benar adalah 25 tahun.

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

Mencatat setiap perubahan yang dilakukan

Setiap tahap pembersihan sebaiknya dicatat, supaya siapa pun yang membaca ulang laporan ini dapat memahami alasan di balik setiap perubahan.

log_perubahan <- data.frame(
  tahap = c("Standardisasi","Standardisasi","Deduplikasi","Koreksi domain","Koreksi domain"),
  atribut = c("kota","status","customer_id","usia","usia"),
  tindakan = c(
    "Variasi penulisan PKU dan huruf besar kecil diseragamkan menjadi Pekanbaru",
    "Variasi ACTIVE, A, dan nonaktif diseragamkan menjadi Aktif atau Tidak Aktif",
    "Baris kedua dari customer_id yang sama dihapus",
    "Usia C004 dikoreksi dari 150 menjadi 50 berdasarkan formulir asli",
    "Usia C021 dikoreksi dari 5 menjadi 25 berdasarkan formulir asli"
  ),
  stringsAsFactors = FALSE
)

log_perubahan
##            tahap     atribut
## 1  Standardisasi        kota
## 2  Standardisasi      status
## 3    Deduplikasi customer_id
## 4 Koreksi domain        usia
## 5 Koreksi domain        usia
##                                                                      tindakan
## 1  Variasi penulisan PKU dan huruf besar kecil diseragamkan menjadi Pekanbaru
## 2 Variasi ACTIVE, A, dan nonaktif diseragamkan menjadi Aktif atau Tidak Aktif
## 3                              Baris kedua dari customer_id yang sama dihapus
## 4           Usia C004 dikoreksi dari 150 menjadi 50 berdasarkan formulir asli
## 5             Usia C021 dikoreksi dari 5 menjadi 25 berdasarkan formulir asli

Bagian Ketiga: Menangani Nilai yang Hilang

Menemukan lokasi nilai yang hilang

colSums(is.na(pelanggan))
## customer_id        nama        usia  pendapatan        kota      status 
##           0           0           3           4           2           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
## 13        C014  Made   26         NA Pekanbaru       Aktif
## 17        C018  Qori   NA    4850000     Dumai Tidak Aktif
## 22        C023  Vino   38         NA     Dumai       Aktif
## 28        C029 Bella   NA    4800000      Siak       Aktif
## 30        C031  Dewi   28    4900000      <NA>       Aktif

Cara pertama, menghapus baris yang tidak lengkap

Cara ini hanya diperagakan sebagai perbandingan, objek pelanggan yang sebenarnya belum diganti.

pelanggan_complete <- pelanggan[complete.cases(pelanggan), ]
nrow(pelanggan)
## [1] 31
nrow(pelanggan_complete)
## [1] 23
round((1 - nrow(pelanggan_complete) / nrow(pelanggan)) * 100, 2)
## [1] 25.81

Coba pikirkan sendiri, apakah proporsi data yang hilang tersebut cukup besar sehingga menghapus baris bukan pilihan yang bijak.

Cara kedua, mengisi dengan nilai rata rata atau nilai tengah

Pendapatan pada dataset ini memiliki nilai yang sangat ekstrem, sehingga sebaiknya nilai rata rata dan nilai tengah dibandingkan lebih dahulu sebelum menentukan cara pengisian yang dipakai.

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

mean_pendapatan
## [1] 30503704
median_pendapatan
## [1] 4900000

Karena sebaran pendapatan menjadi sangat miring akibat adanya nilai ratusan juta, nilai tengah atau median dipilih sebagai dasar pengisian karena lebih tahan terhadap pengaruh nilai ekstrem tersebut.

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.50e+06            4.50e+06
## 2         C002   25          25.0         NA            4.90e+06
## 3         C003   23          23.0   5.20e+06            5.20e+06
## 4         C004   50          50.0   4.80e+06            4.80e+06
## 5         C005   27          27.0   4.90e+06            4.90e+06
## 6         C006   NA          28.5   5.10e+06            5.10e+06
## 7         C007   31          31.0   5.00e+08            5.00e+08
## 8         C008   29          29.0   4.70e+06            4.70e+06
## 9         C009   22          22.0   4.60e+06            4.60e+06
## 10        C010   35          35.0   5.30e+06            5.30e+06
## 11        C011   28          28.0         NA            4.90e+06
## 12        C013   24          24.0   4.75e+06            4.75e+06
## 13        C014   26          26.0         NA            4.90e+06
## 14        C015   33          33.0   5.00e+06            5.00e+06
## 15        C016   19          19.0   4.65e+06            4.65e+06
## 16        C017   41          41.0   5.40e+06            5.40e+06
## 17        C018   NA          28.5   4.85e+06            4.85e+06
## 18        C019   45          45.0   5.60e+06            5.60e+06
## 19        C020   30          30.0   2.00e+08            2.00e+08
## 20        C021   25          25.0   4.70e+06            4.70e+06
## 21        C022   27          27.0   4.90e+06            4.90e+06
## 22        C023   38          38.0         NA            4.90e+06
## 23        C024   29          29.0   5.10e+06            5.10e+06
## 24        C025   34          34.0   4.80e+06            4.80e+06
## 25        C026   24          24.0   4.95e+06            4.95e+06
## 26        C027   31          31.0   5.05e+06            5.05e+06
## 27        C028   26          26.0   4.70e+06            4.70e+06
## 28        C029   NA          28.5   4.80e+06            4.80e+06
## 29        C030   37          37.0   5.15e+06            5.15e+06
## 30        C031   28          28.0   4.90e+06            4.90e+06
## 31        C032   33          33.0   5.20e+06            5.20e+06

Cara ketiga, mengisi kategori yang hilang dengan label eksplisit

Nilai kota yang hilang tidak selalu tepat diisi dengan kategori yang paling sering muncul. Pada latihan ini digunakan label “Tidak diketahui” supaya ketidakpastian data tetap terlihat, bukan malah disembunyikan seolah olah datanya lengkap.

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 
##               9              15               5               2

Menambahkan penanda bahwa suatu nilai pernah hilang

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

Membandingkan sebaran data sebelum dan sesudah pengisian

par(mfrow = c(1, 2))
hist(pelanggan$pendapatan, main = "Sebelum Pengisian", xlab = "Pendapatan", col = "skyblue", breaks = 8)
hist(pelanggan$pendapatan_imputasi, main = "Sesudah Pengisian dengan Median", xlab = "Pendapatan", col = "lightgreen", breaks = 8)

par(mfrow = c(1, 1))

Perlu diingat bahwa proses pengisian nilai yang hilang dapat mengubah bentuk sebaran data dan mengecilkan variasinya, sehingga evaluasi tidak boleh berhenti begitu saja setelah seluruh nilai kosong sudah terisi.

Bagian Keempat: Menangani Nilai Ekstrem (Outlier)

Melihat sebaran data dengan boxplot

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

Menghitung batas kewajaran dengan metode 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% 
##         4800000         5125000          325000         4312500         5612500

Menandai baris yang menjadi 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
## 19        C020  Sinta               2e+08

Memutuskan tindakan terhadap outlier yang ditemukan

Nilai pendapatan yang jauh lebih tinggi dari pelanggan lain tidak boleh langsung dihapus begitu saja. Ada tiga kemungkinan yang perlu dipertimbangkan terlebih dahulu, yaitu kemungkinan kesalahan input yang bisa dikoreksi apabila nilai sebenarnya dapat diverifikasi, kemungkinan nilai tersebut memang benar terjadi walaupun ekstrem sehingga sebaiknya dipertahankan atau ditangani dengan metode yang lebih tahan terhadap nilai ekstrem, dan kemungkinan populasi yang berbeda misalnya pelanggan korporasi yang tercampur dengan pelanggan individu sehingga perlu dipisahkan segmennya.

Pada latihan ini nilai aslinya tetap dipertahankan, kemudian dibuat satu variabel tambahan hasil winsorizing hanya untuk membandingkan dampaknya terhadap analisis selanjutnya.

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           5612500
## 19        C020               2e+08           5612500

Winsorizing pada contoh ini hanya sebagai demonstrasi tambahan. Penerapannya di dunia nyata sebaiknya disesuaikan dengan tujuan analisis dan tetap didokumentasikan bahwa nilai ekstrem tersebut sudah dibatasi rentangnya.

Bagian Kelima: Mentransformasikan Data

Normalisasi rentang nol sampai satu (min maks)

Rumus untuk mengubah nilai ke rentang nol sampai satu adalah selisih antara nilai dengan nilai minimum, dibagi dengan selisih antara nilai maksimum dan nilai minimum.

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.06451613            4.50e+06      0.0000000000
## 2         C002   25  0.19354839            4.90e+06      0.0008072654
## 3         C003   23  0.12903226            5.20e+06      0.0014127144
## 4         C004   50  1.00000000            4.80e+06      0.0006054490
## 5         C005   27  0.25806452            4.90e+06      0.0008072654
## 6         C006   NA  0.30645161            5.10e+06      0.0012108981
## 7         C007   31  0.38709677            5.00e+08      1.0000000000
## 8         C008   29  0.32258065            4.70e+06      0.0004036327
## 9         C009   22  0.09677419            4.60e+06      0.0002018163
## 10        C010   35  0.51612903            5.30e+06      0.0016145308
## 11        C011   28  0.29032258            4.90e+06      0.0008072654
## 12        C013   24  0.16129032            4.75e+06      0.0005045409
## 13        C014   26  0.22580645            4.90e+06      0.0008072654
## 14        C015   33  0.45161290            5.00e+06      0.0010090817
## 15        C016   19  0.00000000            4.65e+06      0.0003027245
## 16        C017   41  0.70967742            5.40e+06      0.0018163471
## 17        C018   NA  0.30645161            4.85e+06      0.0007063572
## 18        C019   45  0.83870968            5.60e+06      0.0022199798
## 19        C020   30  0.35483871            2.00e+08      0.3945509586
## 20        C021   25  0.19354839            4.70e+06      0.0004036327
## 21        C022   27  0.25806452            4.90e+06      0.0008072654
## 22        C023   38  0.61290323            4.90e+06      0.0008072654
## 23        C024   29  0.32258065            5.10e+06      0.0012108981
## 24        C025   34  0.48387097            4.80e+06      0.0006054490
## 25        C026   24  0.16129032            4.95e+06      0.0009081736
## 26        C027   31  0.38709677            5.05e+06      0.0011099899
## 27        C028   26  0.22580645            4.70e+06      0.0004036327
## 28        C029   NA  0.30645161            4.80e+06      0.0006054490
## 29        C030   37  0.58064516            5.15e+06      0.0013118063
## 30        C031   28  0.29032258            4.90e+06      0.0008072654
## 31        C032   33  0.45161290            5.20e+06      0.0014127144

Standardisasi nilai z (z score)

Standardisasi ini mengubah nilai berdasarkan seberapa jauh sebuah data menyimpang dari rata rata, dinyatakan dalam satuan simpangan baku.

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  -1.290       -0.240
## 2  -0.710       -0.236
## 3  -1.000       -0.233
## 4   2.920       -0.237
## 5  -0.419       -0.236
## 6  -0.201       -0.234
## 7   0.162        5.004
## 8  -0.129       -0.238
## 9  -1.145       -0.239
## 10  0.742       -0.232
## 11 -0.274       -0.236
## 12 -0.855       -0.238
## 13 -0.564       -0.236
## 14  0.452       -0.235
## 15 -1.581       -0.239
## 16  1.613       -0.231
## 17 -0.201       -0.237
## 18  2.194       -0.229
## 19  0.016        1.829
## 20 -0.710       -0.238
## 21 -0.419       -0.236
## 22  1.178       -0.236
## 23 -0.129       -0.234
## 24  0.597       -0.237
## 25 -0.855       -0.236
## 26  0.162       -0.234
## 27 -0.564       -0.238
## 28 -0.201       -0.237
## 29  1.033       -0.233
## 30 -0.274       -0.236
## 31  0.452       -0.233

Penskalaan desimal (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 ketiga 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.50e+06      0.0000000000   -0.2402716
## 2         C002            4.90e+06      0.0008072654   -0.2360378
## 3         C003            5.20e+06      0.0014127144   -0.2328624
## 4         C004            4.80e+06      0.0006054490   -0.2370962
## 5         C005            4.90e+06      0.0008072654   -0.2360378
## 6         C006            5.10e+06      0.0012108981   -0.2339208
## 7         C007            5.00e+08      1.0000000000    5.0044237
## 8         C008            4.70e+06      0.0004036327   -0.2381547
## 9         C009            4.60e+06      0.0002018163   -0.2392131
## 10        C010            5.30e+06      0.0016145308   -0.2318039
## 11        C011            4.90e+06      0.0008072654   -0.2360378
## 12        C013            4.75e+06      0.0005045409   -0.2376254
## 13        C014            4.90e+06      0.0008072654   -0.2360378
## 14        C015            5.00e+06      0.0010090817   -0.2349793
## 15        C016            4.65e+06      0.0003027245   -0.2386839
## 16        C017            5.40e+06      0.0018163471   -0.2307454
## 17        C018            4.85e+06      0.0007063572   -0.2365670
## 18        C019            5.60e+06      0.0022199798   -0.2286285
## 19        C020            2.00e+08      0.3945509586    1.8290280
## 20        C021            4.70e+06      0.0004036327   -0.2381547
## 21        C022            4.90e+06      0.0008072654   -0.2360378
## 22        C023            4.90e+06      0.0008072654   -0.2360378
## 23        C024            5.10e+06      0.0012108981   -0.2339208
## 24        C025            4.80e+06      0.0006054490   -0.2370962
## 25        C026            4.95e+06      0.0009081736   -0.2355085
## 26        C027            5.05e+06      0.0011099899   -0.2344501
## 27        C028            4.70e+06      0.0004036327   -0.2381547
## 28        C029            4.80e+06      0.0006054490   -0.2370962
## 29        C030            5.15e+06      0.0013118063   -0.2333916
## 30        C031            4.90e+06      0.0008072654   -0.2360378
## 31        C032            5.20e+06      0.0014127144   -0.2328624
##    pendapatan_decimal
## 1             0.00450
## 2             0.00490
## 3             0.00520
## 4             0.00480
## 5             0.00490
## 6             0.00510
## 7             0.50000
## 8             0.00470
## 9             0.00460
## 10            0.00530
## 11            0.00490
## 12            0.00475
## 13            0.00490
## 14            0.00500
## 15            0.00465
## 16            0.00540
## 17            0.00485
## 18            0.00560
## 19            0.20000
## 20            0.00470
## 21            0.00490
## 22            0.00490
## 23            0.00510
## 24            0.00480
## 25            0.00495
## 26            0.00505
## 27            0.00470
## 28            0.00480
## 29            0.00515
## 30            0.00490
## 31            0.00520

Melihat dampak outlier terhadap hasil normalisasi

pelanggan$pendapatan_winsor_minmax <- minmax(pelanggan$pendapatan_winsor)

plot(pelanggan$pendapatan_minmax, pelanggan$pendapatan_winsor_minmax, pch = 19, col = "navy",
     xlab = "Hasil Minmaks dari Data Asli", ylab = "Hasil Minmaks dari Data Winsorized",
     main = "Perbandingan Dampak Penanganan Outlier terhadap Normalisasi")
abline(0, 1, col = "red", lty = 2)

Nilai ekstrem yang tidak ditangani dapat menekan hampir seluruh nilai hasil normalisasi ke rentang yang sangat sempit. Inilah sebabnya deteksi outlier perlu dilakukan lebih dahulu sebelum menentukan metode transformasi yang paling sesuai.

Bagian Keenam: Menggabungkan Dua Sumber Data

Memeriksa kunci penggabungan pada kedua tabel

sum(duplicated(pelanggan$customer_id))
## [1] 0
sum(duplicated(transaksi_raw$cust_id))
## [1] 1
setdiff(pelanggan$customer_id, transaksi_raw$cust_id)
## [1] "C011" "C031" "C032"
setdiff(transaksi_raw$cust_id, pelanggan$customer_id)
## [1] "C012" "C034"

Hasil dari fungsi setdiff di atas menunjukkan pelanggan yang tidak memiliki catatan transaksi sama sekali, dan sebaliknya, transaksi yang identifier pelanggannya tidak ditemukan pada tabel pelanggan. Selain itu, hasil sum(duplicated(transaksi_raw$cust_id)) menunjukkan bahwa ada satu cust_id pada data transaksi yang tercatat lebih dari sekali. Duplikasi ini perlu ditangani lebih dahulu, sebab apabila dibiarkan maka proses penggabungan akan menghasilkan baris ganda pada pelanggan yang bersangkutan.

Menghapus duplikasi pada data transaksi

transaksi_raw[duplicated(transaksi_raw$cust_id) | duplicated(transaksi_raw$cust_id, fromLast = TRUE), ]
##    cust_id jumlah_transaksi total_purchase
## 14    C015                9        3600000
## 30    C015                9        3600000
transaksi_raw <- transaksi_raw[!duplicated(transaksi_raw$cust_id), ]
rownames(transaksi_raw) <- NULL
dim(transaksi_raw)
## [1] 30  3
log_perubahan <- rbind(
  log_perubahan,
  data.frame(
    tahap = "Deduplikasi",
    atribut = "cust_id",
    tindakan = "Baris kedua dari cust_id yang sama pada data transaksi dihapus sebelum penggabungan",
    stringsAsFactors = FALSE
  )
)
log_perubahan
##            tahap     atribut
## 1  Standardisasi        kota
## 2  Standardisasi      status
## 3    Deduplikasi customer_id
## 4 Koreksi domain        usia
## 5 Koreksi domain        usia
## 6    Deduplikasi     cust_id
##                                                                              tindakan
## 1          Variasi penulisan PKU dan huruf besar kecil diseragamkan menjadi Pekanbaru
## 2         Variasi ACTIVE, A, dan nonaktif diseragamkan menjadi Aktif atau Tidak Aktif
## 3                                      Baris kedua dari customer_id yang sama dihapus
## 4                   Usia C004 dikoreksi dari 150 menjadi 50 berdasarkan formulir asli
## 5                     Usia C021 dikoreksi dari 5 menjadi 25 berdasarkan formulir asli
## 6 Baris kedua dari cust_id yang sama pada data transaksi dihapus sebelum penggabungan

Sama seperti penanganan duplikasi pada data pelanggan, penghapusan ini hanya tepat dilakukan apabila memang satu cust_id seharusnya hanya mewakili satu transaksi teragregasi. Perubahan ini juga ditambahkan ke dalam log_perubahan supaya seluruh riwayat pembersihan data tetap tercatat pada satu tempat yang sama.

Menyamakan nama kolom identifier

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

Menggabungkan data dengan left join

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        1500000
## 2         C002    Budi                3         900000
## 3         C003   Citra                7        2700000
## 4         C004    Dodi                2         600000
## 5         C005     Eka                6        2100000
## 6         C006    Fani                4        1300000
## 7         C007  Gilang               20       25000000
## 8         C008    Hana                5        1700000
## 9         C009   Indra                3         800000
## 10        C010    Joko                8        3200000
## 11        C011    Kiki               NA             NA
## 12        C013 Lestari                4        1350000
## 13        C014    Made                6        2400000
## 14        C015    Nita                9        3600000
## 15        C016   Oscar                2         650000
## 16        C017   Putri               11        4600000
## 17        C018    Qori                3         950000
## 18        C019    Rian               12        5000000
## 19        C020   Sinta               30       60000000
## 20        C021    Tono                1         300000
## 21        C022     Umi                5        1650000
## 22        C023    Vino                7        2500000
## 23        C024   Wulan                4        1300000
## 24        C025    Xena                6        2050000
## 25        C026    Yoga                3         900000
## 26        C027    Zaki                8        3100000
## 27        C028  Aditya                5        1600000
## 28        C029   Bella                2         600000
## 29        C030   Cahyo                9        3400000
## 30        C031    Dewi               NA             NA
## 31        C032     Eko               NA             NA

Penggunaan all.x sama dengan TRUE membuat seluruh pelanggan tetap dipertahankan pada hasil penggabungan, meskipun sebagian dari mereka tidak memiliki pasangan data transaksi.

Memvalidasi hasil penggabungan

c(sebelum = nrow(pelanggan), sesudah = nrow(data_terintegrasi))
## sebelum sesudah 
##      31      31
sum(duplicated(data_terintegrasi$customer_id))
## [1] 0
colSums(is.na(data_terintegrasi[, c("jumlah_transaksi","total_purchase")]))
## jumlah_transaksi   total_purchase 
##                3                3
data_terintegrasi[is.na(data_terintegrasi$jumlah_transaksi), c("customer_id","nama")]
##    customer_id nama
## 11        C011 Kiki
## 30        C031 Dewi
## 31        C032  Eko

Memutuskan antara mengisi nol atau tetap membiarkan kosong

Nilai kosong pada kolom transaksi setelah penggabungan bisa berarti dua hal yang berbeda, yaitu pelanggan tersebut memang belum pernah melakukan transaksi sama sekali, atau data transaksinya tersedia namun gagal dipadankan dengan tabel pelanggan. Nilai kosong tersebut hanya boleh diganti menjadi nol apabila makna sebenarnya sudah dipastikan lebih dahulu.

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

Menyusun Dataset Akhir dan Melakukan Evaluasi

Memilih atribut yang dipakai pada dataset 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
##    customer_id    nama usia            kota      status pendapatan_imputasi
## 1         C001     Ani 21.0       Pekanbaru       Aktif            4.50e+06
## 2         C002    Budi 25.0       Pekanbaru       Aktif            4.90e+06
## 3         C003   Citra 23.0       Pekanbaru       Aktif            5.20e+06
## 4         C004    Dodi 50.0           Dumai       Aktif            4.80e+06
## 5         C005     Eka 27.0       Pekanbaru Tidak Aktif            4.90e+06
## 6         C006    Fani 28.5           Dumai Tidak Aktif            5.10e+06
## 7         C007  Gilang 31.0       Pekanbaru       Aktif            5.00e+08
## 8         C008    Hana 29.0            Siak       Aktif            4.70e+06
## 9         C009   Indra 22.0       Pekanbaru       Aktif            4.60e+06
## 10        C010    Joko 35.0           Dumai Tidak Aktif            5.30e+06
## 11        C011    Kiki 28.0 Tidak diketahui       Aktif            4.90e+06
## 12        C013 Lestari 24.0       Pekanbaru       Aktif            4.75e+06
## 13        C014    Made 26.0       Pekanbaru       Aktif            4.90e+06
## 14        C015    Nita 33.0           Dumai       Aktif            5.00e+06
## 15        C016   Oscar 19.0            Siak       Aktif            4.65e+06
## 16        C017   Putri 41.0       Pekanbaru Tidak Aktif            5.40e+06
## 17        C018    Qori 28.5           Dumai Tidak Aktif            4.85e+06
## 18        C019    Rian 45.0       Pekanbaru       Aktif            5.60e+06
## 19        C020   Sinta 30.0       Pekanbaru       Aktif            2.00e+08
## 20        C021    Tono 25.0            Siak Tidak Aktif            4.70e+06
## 21        C022     Umi 27.0       Pekanbaru       Aktif            4.90e+06
## 22        C023    Vino 38.0           Dumai       Aktif            4.90e+06
## 23        C024   Wulan 29.0            Siak       Aktif            5.10e+06
## 24        C025    Xena 34.0       Pekanbaru Tidak Aktif            4.80e+06
## 25        C026    Yoga 24.0           Dumai       Aktif            4.95e+06
## 26        C027    Zaki 31.0       Pekanbaru Tidak Aktif            5.05e+06
## 27        C028  Aditya 26.0           Dumai       Aktif            4.70e+06
## 28        C029   Bella 28.5            Siak       Aktif            4.80e+06
## 29        C030   Cahyo 37.0       Pekanbaru Tidak Aktif            5.15e+06
## 30        C031    Dewi 28.0 Tidak diketahui       Aktif            4.90e+06
## 31        C032     Eko 33.0           Dumai       Aktif            5.20e+06
##    pendapatan_missing outlier_pendapatan usia_minmax pendapatan_minmax
## 1                   0              FALSE  0.06451613      0.0000000000
## 2                   1              FALSE  0.19354839      0.0008072654
## 3                   0              FALSE  0.12903226      0.0014127144
## 4                   0              FALSE  1.00000000      0.0006054490
## 5                   0              FALSE  0.25806452      0.0008072654
## 6                   0              FALSE  0.30645161      0.0012108981
## 7                   0               TRUE  0.38709677      1.0000000000
## 8                   0              FALSE  0.32258065      0.0004036327
## 9                   0              FALSE  0.09677419      0.0002018163
## 10                  0              FALSE  0.51612903      0.0016145308
## 11                  1              FALSE  0.29032258      0.0008072654
## 12                  0              FALSE  0.16129032      0.0005045409
## 13                  1              FALSE  0.22580645      0.0008072654
## 14                  0              FALSE  0.45161290      0.0010090817
## 15                  0              FALSE  0.00000000      0.0003027245
## 16                  0              FALSE  0.70967742      0.0018163471
## 17                  0              FALSE  0.30645161      0.0007063572
## 18                  0              FALSE  0.83870968      0.0022199798
## 19                  0               TRUE  0.35483871      0.3945509586
## 20                  0              FALSE  0.19354839      0.0004036327
## 21                  0              FALSE  0.25806452      0.0008072654
## 22                  1              FALSE  0.61290323      0.0008072654
## 23                  0              FALSE  0.32258065      0.0012108981
## 24                  0              FALSE  0.48387097      0.0006054490
## 25                  0              FALSE  0.16129032      0.0009081736
## 26                  0              FALSE  0.38709677      0.0011099899
## 27                  0              FALSE  0.22580645      0.0004036327
## 28                  0              FALSE  0.30645161      0.0006054490
## 29                  0              FALSE  0.58064516      0.0013118063
## 30                  0              FALSE  0.29032258      0.0008072654
## 31                  0              FALSE  0.45161290      0.0014127144
##    jumlah_transaksi_final total_purchase_final
## 1                       5              1500000
## 2                       3               900000
## 3                       7              2700000
## 4                       2               600000
## 5                       6              2100000
## 6                       4              1300000
## 7                      20             25000000
## 8                       5              1700000
## 9                       3               800000
## 10                      8              3200000
## 11                      0                    0
## 12                      4              1350000
## 13                      6              2400000
## 14                      9              3600000
## 15                      2               650000
## 16                     11              4600000
## 17                      3               950000
## 18                     12              5000000
## 19                     30             60000000
## 20                      1               300000
## 21                      5              1650000
## 22                      7              2500000
## 23                      4              1300000
## 24                      6              2050000
## 25                      3               900000
## 26                      8              3100000
## 27                      5              1600000
## 28                      2               600000
## 29                      9              3400000
## 30                      0                    0
## 31                      0                    0

Melakukan audit terakhir

audit_akhir <- audit_data(data_final)
audit_akhir
##                   atribut      tipe jumlah_missing persen_missing jumlah_unik
## 1             customer_id character              0              0          31
## 2                    nama character              0              0          31
## 3                    usia   numeric              0              0          21
## 4                    kota character              0              0           4
## 5                  status character              0              0           2
## 6     pendapatan_imputasi   numeric              0              0          19
## 7      pendapatan_missing   integer              0              0           2
## 8      outlier_pendapatan   logical              0              0           2
## 9             usia_minmax   numeric              0              0          21
## 10      pendapatan_minmax   numeric              0              0          19
## 11 jumlah_transaksi_final   numeric              0              0          14
## 12   total_purchase_final   numeric              0              0          26
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 data sebelum dan sesudah preprocessing

perbandingan <- data.frame(
  indikator = c("Jumlah baris","Duplikasi customer_id","Total nilai hilang",
                "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
##               indikator sebelum sesudah
## 1          Jumlah baris      32      31
## 2 Duplikasi customer_id       1       0
## 3    Total nilai hilang       9       0
## 4    Kategori kota unik      14       4
## 5  Kategori status unik       8       2

Menyimpan hasil akhir

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 yang sudah tidak memiliki nilai hilang sama sekali otomatis berarti dataset tersebut lebih berkualitas?
  2. Mengapa outlier tidak boleh dihapus begitu saja secara otomatis?
  3. Bagaimana proses preprocessing yang kurang hati hati justru bisa menimbulkan bias baru pada data?
  4. Mengapa parameter untuk imputasi maupun transformasi sebaiknya dihitung hanya dari data pelatihan, bukan dari seluruh data sekaligus?
  5. Apa risiko yang muncul saat menggabungkan dua sumber data apabila identifier yang dipakai ternyata tidak unik?

Ringkasan

Alur kerja yang telah dilalui pada praktikum ini meliputi memahami struktur serta kualitas awal data, membersihkan kategori yang tidak seragam, duplikasi, dan pelanggaran aturan domain, menangani nilai yang hilang tanpa menyembunyikan ketidakpastian yang ada, mendeteksi serta mengevaluasi outlier, mentransformasikan atribut numerik ke skala yang lebih sesuai, dan menggabungkan dua sumber data sekaligus memvalidasi hasil penggabungannya.

Data preprocessing pada dasarnya adalah proses pengambilan keputusan. Kode R yang dijalankan hanya menjalankan keputusan tersebut, sementara kualitas hasil akhirnya tetap bergantung pada pemahaman terhadap data itu sendiri, tujuan analisis yang ingin dicapai, serta kelengkapan dokumentasi dari setiap perubahan yang dilakukan.

Daftar Pustaka

Han, J., Kamber, M., dan Pei, J. (2012). Data Mining: Concepts and Techniques (edisi ketiga). Morgan Kaufmann. Bab 3, Data Preprocessing.

Panduan Tambahan Sebelum Publikasi ke RPubs

Sebelum dokumen ini dipublikasikan, sebaiknya setiap mahasiswa melakukan penyesuaian berikut agar hasilnya benar benar mencerminkan pemahaman masing masing:

  1. Tulis ulang kalimat penjelasan pada setiap bagian dengan gaya bahasa sendiri.
  2. Tambahkan interpretasi pribadi pada setiap hasil keluaran kode, jangan hanya menampilkan kodenya saja.
  3. Coba terapkan strategi penanganan data yang berbeda, misalnya mengisi nilai hilang dengan rata rata dibandingkan nilai tengah, lalu bandingkan hasil keduanya.
  4. Tambahkan visualisasi tambahan apabila dirasa dapat memperjelas penjelasan, misalnya grafik sebaran kategori.
  5. Sesuaikan urutan pembahasan apabila menurut pemahaman masing masing ada cara penyampaian yang lebih runtut.