Persiapan RStudio

Membuat proyek

  • Buka RStudio.
  • Pilih File → New Project → New Directory → New Project .
  • Beri nama proyek, misalnya praktikum_preprocessing .
  • Simpan file ini di dalam folder proyek.
  • Klik Knit untuk menghasilkan HTML.

Memeriksa lingkungan kerja

getwd()
## [1] "C:/Users/HP/Downloads"
R.version.string
## [1] "R version 4.5.0 (2025-04-11 ucrt)"

Modul ini menggunakan fungsi dasar R sehingga dapat dijalankan tanpa memasang paket tambahan.

Dataset Praktikum

Berikut merupakan dataset “kotor” yang akan digunakan untuk melakukan tahapan preprocessing.

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
)

Berdasarkan codingan di atas, didapatkanlah 2 data yaitu dataset pelanggan dan transaksi, berikut merupakan tabel datasetnya:

pelanggan_raw
##    customer_id   nama usia pendapatan       kota      status
## 1         C001    Ani   21    4.5e+06  Pekanbaru       Aktif
## 2         C002   Budi   25         NA        PKU       aktif
## 3         C003  Citra   23    5.2e+06  PEKANBARU      ACTIVE
## 4         C004   Dodi  150    4.8e+06      Dumai           A
## 5         C005    Eka   27    4.9e+06  pekanbaru Tidak Aktif
## 6         C006   Fani   NA    5.1e+06      DUMAI    nonaktif
## 7         C007 Gilang   31    5.0e+08 Pekanbaru        Aktif
## 8         C008   Hana   29    4.7e+06       Siak       AKTIF
## 9         C009  Indra   22    4.6e+06        PKU           A
## 10        C010   Joko   35    5.3e+06      Dumai Tidak aktif
## 11        C010   Joko   35    5.3e+06      Dumai Tidak aktif
## 12        C011   Kiki   28         NA       <NA>       Aktif
transaksi_raw
##    cust_id jumlah_transaksi total_purchase
## 1     C001                5        1.5e+06
## 2     C002                3        9.0e+05
## 3     C003                7        2.7e+06
## 4     C004                2        6.0e+05
## 5     C005                6        2.1e+06
## 6     C006                4        1.3e+06
## 7     C007               20        2.5e+07
## 8     C008                5        1.7e+06
## 9     C009                3        8.0e+05
## 10    C010                8        3.2e+06
## 11    C012                1        2.5e+05

Bagian I — Konsep Data Preprocessing

Memahami struktur data

Terlebih dahulu, kita harus mengetahui struktur dari dataset tersebut.

  1. dim digunakan untuk mengetahui berapa banyak kolom dan baris.
dim(pelanggan_raw)
## [1] 12  6
  1. names digunakan untuk mengetahui nama dari setiap kolomnya.
names(pelanggan_raw)
## [1] "customer_id" "nama"        "usia"        "pendapatan"  "kota"       
## [6] "status"
  1. str digunakan untuk mengetahui deskripsi secara umum dari keseluruhan dataset.
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  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" ...
  1. head akan menampilkan beberapa baris awal dari dataset tersebut.
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
  1. summary digunakan untuk mengetahui statistik deskriptif dari tiap tiap kolom.
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  
##                                       
##                                       
##                                       
## 

Mengetahui kualitas awal data

Pada pemeriksaan mengenai kualitas data, dicari tau terlebih dahulu missing value, data duplikat, kesamaan kategori serta rentang data tersebut.

  1. Banyaknya missing value.
colSums(is.na(pelanggan_raw))
## customer_id        nama        usia  pendapatan        kota      status 
##           0           0           1           2           1           0
  1. Presentase missing value.
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
  1. Banyaknya data duplikat pada dataset.
sum(duplicated(pelanggan_raw))
## [1] 1
  1. Banyaknya data duplikat pada dataset kolom id customer.
sum(duplicated(pelanggan_raw$customer_id))
## [1] 1
  1. Jenis jenis kategori yang terdapat pada kolom kota.
sort(unique(pelanggan_raw$kota))
## [1] " PKU"       "Dumai"      "DUMAI"      "pekanbaru"  "Pekanbaru" 
## [6] "PEKANBARU"  "Pekanbaru " "PKU"        "Siak"
  1. Jenis jenis kategori yang terdapat pada kolom status.
sort(unique(pelanggan_raw$status))
## [1] "A"           "ACTIVE"      "aktif"       "Aktif"       "AKTIF"      
## [6] "nonaktif"    "Tidak aktif" "Tidak Aktif"
  1. Rentang pada kolom usia.
range(pelanggan_raw$usia, na.rm = TRUE)
## [1]  21 150
  1. Rentang pada kolom pendapatan.
range(pelanggan_raw$pendapatan, na.rm = TRUE)
## [1] 4.5e+06 5.0e+08

Membuat fungsi ringkasan kualitas

Setelah mengetahui mengenai pemeriksaan awal data, dibuatlah fungsi untuk mengetahui kualitas data secara ringkas, berikut syntaxnya:

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          11
## 2        nama character              0           0.00          11
## 3        usia   numeric              1           8.33          11
## 4  pendapatan   numeric              2          16.67          10
## 5        kota character              1           8.33          10
## 6      status character              0           0.00           8

Berdasarkan fungsi ringkas kualitas data, dapat diketahui atribut mana yang memiliki kualitas yang kurang sehingga perlu dilakukan pemeriksaan lebih lanjut.

Bagian II — Implementasi Data Cleaning

Membuat salinan kerja

Terlebih dahulu kita melakukan duplikasi dataset agar dataset sebelum preprocessing masih tetap ada dan tahapan preprocessing dapat dilakukan pada data duplikat,

pelanggan <- pelanggan_raw

Membersihkan spasi dan kapitalisasi

Agar kategori dalam sebuah atribut memiliki kesamaan, maka dilakukanlah pembersihan spasi di awal dan akhir serta menyamakan huruf menjadi kecil.

  1. Menghapus spasi di awal dan akhir.
pelanggan$kota <- trimws(pelanggan$kota)
pelanggan$status <- trimws(pelanggan$status)
  1. Menyeragamkan huruf menjadi kecil untuk proses pencocokan.
pelanggan$kota <- tolower(pelanggan$kota)
pelanggan$status <- tolower(pelanggan$status)

Maka hasil dari kesamaan spasi dan kapitalisasi adalah sebagai berikut.

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

Menyeragamkan kategori

Setelah melakukan kesamaan spasi dan kapitalisasi, lakukan juga standarisasi agar setiap kategori yang memiliki kemiripan diseragamkan.

  1. Standarisasi pada atribut kota.
pelanggan$kota[pelanggan$kota %in% c("pku", "pekanbaru")] <- "Pekanbaru"
pelanggan$kota[pelanggan$kota == "dumai"] <- "Dumai"
pelanggan$kota[pelanggan$kota == "siak"] <- "Siak"
  1. Standarisasi pada atribut status.
pelanggan$status[pelanggan$status %in% c("aktif", "active", "a")] <- "Aktif"
pelanggan$status[pelanggan$status %in% c("tidak aktif", "nonaktif")] <- "Tidak Aktif"

Maka hasil dari standarisasi keseragaman tersebut sebagai berikut.

sort(unique(pelanggan$kota))
## [1] "Dumai"     "Pekanbaru" "Siak"
sort(unique(pelanggan$status))
## [1] "Aktif"       "Tidak Aktif"

Mendeteksi dan menghapus duplikasi

Pertama kita harus mengetahui baris berapa yang merupakan baris duplikat pada customer id.

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

Setelah mengetahui pada baris keberapa yang mengalami duplikat, maka kita dapat menghapus salah satu baris dan mempertahankan salah satunya.

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

dim(pelanggan)
## [1] 11  6

Catatan: Penanganan duplikasi dengan cara menghapus hanya bisa dilakukan jika dipastikan setiap customer id hanya memiliki satu baris saja, jika tidak maka penanganan ini akan salah.

Memeriksa aturan domain

Setelah itu kita memeriksa data anomali, dalam hal ini adalah usia yang tidak berada pada rentang aturan bisnis yaitu 15 - 100 tahun.

pelanggan[pelanggan$usia < 15 | pelanggan$usia > 100, ]
##    customer_id nama usia pendapatan  kota status
## 4         C004 Dodi  150    4800000 Dumai  Aktif
## NA        <NA> <NA>   NA         NA  <NA>   <NA>

Selain itu, juga kita harus memeriksa jika pendapatan bernilai negatif.

pelanggan[pelanggan$pendapatan < 0, ]
##      customer_id nama usia pendapatan kota status
## NA          <NA> <NA>   NA         NA <NA>   <NA>
## NA.1        <NA> <NA>   NA         NA <NA>   <NA>

Setelah diselidiki, usia pelanggan 150 yang sebenarnya adalah 50.

Oleh karena itu, dilakukanlah perubahan nilai usia pada pelanggan dengan usia 150.

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

Membuat log perubahan

Setelah melakukan Bagian II, kita membuat log agar mengetahui apa saja yang menjadi masalah dalam kualitas data serta penanganannya.

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
##            tahap     atribut
## 1  Standardisasi        kota
## 2  Standardisasi      status
## 3    Deduplikasi customer_id
## 4 Koreksi domain        usia
##                                                          tindakan
## 1                       PKU dan variasi kapital menjadi Pekanbaru
## 2            ACTIVE/A menjadi Aktif; nonaktif menjadi Tidak Aktif
## 3                                 Menghapus kemunculan kedua C010
## 4 Usia C004 dikoreksi dari 150 menjadi 50 berdasarkan sumber asli

Bagian III — Penanganan Missing Values

Mengidentifikasi lokasi nilai hilang

Pertama kali kita harus mencari tau terlebih dahulu pada kolom mana saja terdapat nilai yang hilang.

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

Setelah diketahui pada kolom mana saja yang terdapat nilai hilang, maka kita juga harus mencari tau pada baris mana yang memiliki nilai hilang tersebut.

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

Strategi 1: Menghapus baris

Salah satu penanganan dari nilai hilang adalah dengan cara menghapus keseluruhan baris.

Kode berikut hanya mendemonstrasikan strateginya. Kita tidak langsung mengganti objek pelanggan.

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

Berikut presentase jika keseluruhan baris yang memiliki nilai hilang tersebut dihapuskan.

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

Strategi 2: Imputasi mean dan median

Yang kedua adalah imputasi. Imputasi dapat menggunakan banyak hal, namun yang paling umum adalah nilai mean dan median.

Pada kasus ini, pendapatan memiliki nilai ekstrem sehingga lebih baik dibandingkan terlebih dahulu mean dan median sebelum menentukan imputasi mana yang akan digunakan.

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

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

Setelah mengetahui nilai mean dan median dari pendapatan, maka dapat diketahui imputasi mana yang lebih baik digunakan dalam kasus ini yaitu median.

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

Kita lakukan hal yang sama pada kolom usia. Dilakukan imputasi median dalam kolom usia.

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

Maka berikut adalah hasil dari imputasi mediannya.

pelanggan[, c("customer_id", "usia", "usia_imputasi",
              "pendapatan", "pendapatan_imputasi")]
##    customer_id usia usia_imputasi pendapatan pendapatan_imputasi
## 1         C001   21          21.0    4.5e+06             4.5e+06
## 2         C002   25          25.0         NA             4.9e+06
## 3         C003   23          23.0    5.2e+06             5.2e+06
## 4         C004   50          50.0    4.8e+06             4.8e+06
## 5         C005   27          27.0    4.9e+06             4.9e+06
## 6         C006   NA          27.5    5.1e+06             5.1e+06
## 7         C007   31          31.0    5.0e+08             5.0e+08
## 8         C008   29          29.0    4.7e+06             4.7e+06
## 9         C009   22          22.0    4.6e+06             4.6e+06
## 10        C010   35          35.0    5.3e+06             5.3e+06
## 11        C011   28          28.0         NA             4.9e+06

Strategi 3: Imputasi nilai kategorik

Berikutnya adalah imputasi pada kolom yang bersifat kategorik. Pada saat ini dilakukan pada kolom kota. Nilai hilang pada kota tidak bisa langsung diisi dengan modus.

Oleh karena itu, dalam kasus ini bisa kita gunakan Tidakk diketahui agar tetap dapat diidentifikasi nilai hilangnya.

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

Indikator dapat mempertahankan informasi bahwa suatu nilai awalnya hilang.

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

Membandingkan distribusi sebelum dan sesudah imputasi

Setelah melakukan imputasi, kita dapat melihat dengan jelas perbedaan sebelum dan sesudah imputasi melalui perbandingan distribusi sebelum dan sesudah imputasi seperti berikut.

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

Setelah kita melakukan imputasi, tentunya akan berdampak pada hasil distribusi dan mengecilkan variasi. Karena itu, evaluasi tidak berhenti setelah semua NA hilang.

Bagian IV — Penanganan Outlier

Visualisasi dengan boxplot

Untuk mengetahui outlier dapat melalui visualisasi dengan boxplot.

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

Menghitung batas IQR

Untuk mendapatkan nilai pasti outliernya, bisa kita lakukan dengan cara mengetahui terlebih dahulu batas IQR pada kolom pendapatan.

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

Setelah mengetahui IQR nya, kita bisa mengidentifikasi nilai outlier jika berada di luar dari batas atas dan batas bawah. Maka didapatlah nilai dari outliernya.

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

Mengevaluasi tindakan

NIlai outlier pada pendapatan tidak secara otomatis akan kita hapus, namun kita harus pertimbangkan pada tiga kemungkinan:

  • Kesalahan input: koreksi jika nilai benar dapat diverifikasi.
  • Observasi valid tetapi ekstrem: pertahankan atau gunakan metode robust.
  • Populasi berbeda: pisahkan segmen apabila pelanggan korporasi tercampur dengan pelanggan ritel.

Pada kasus ini, nilai tersebut dipertahankan dan akan dilakukan penanganan dengan cara winsorized hanya untuk membandingkan dampak metode.

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           5750000

Winsorizing adalah demonstrasi tambahan. Terapkan hanya jika sesuai dengan tujuan analisis dan dokumentasikan bahwa nilai ekstrem telah dibatasi.

Bagian V — Transformasi Data

Normalisasi min–maks

Untuk transformasi data, terdapat banyak cara salah satunya adalah normalisasi min-maks.

Rumus untuk rentang \([0,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")]
##    customer_id usia usia_minmax pendapatan_imputasi pendapatan_minmax
## 1         C001   21  0.00000000             4.5e+06      0.0000000000
## 2         C002   25  0.13793103             4.9e+06      0.0008072654
## 3         C003   23  0.06896552             5.2e+06      0.0014127144
## 4         C004   50  1.00000000             4.8e+06      0.0006054490
## 5         C005   27  0.20689655             4.9e+06      0.0008072654
## 6         C006   NA  0.22413793             5.1e+06      0.0012108981
## 7         C007   31  0.34482759             5.0e+08      1.0000000000
## 8         C008   29  0.27586207             4.7e+06      0.0004036327
## 9         C009   22  0.03448276             4.6e+06      0.0002018163
## 10        C010   35  0.48275862             5.3e+06      0.0016145308
## 11        C011   28  0.24137931             4.9e+06      0.0008072654

Normalisasi z-score

Yang kedua ada normalisasi dengan z-score.

Rumus untuk normalisasi dengan z-score

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

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  -0.984       -0.304
## 2  -0.489       -0.301
## 3  -0.737       -0.299
## 4   2.604       -0.302
## 5  -0.242       -0.301
## 6  -0.180       -0.300
## 7   0.253        3.015
## 8   0.006       -0.303
## 9  -0.860       -0.303
## 10  0.748       -0.299
## 11 -0.118       -0.301

Decimal scaling

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

Setelah mengetahui tiga metode transformasi tersebut, kita dapat membandingkan ketiga metode tersebut agar mengetahui dampak dari transformasi tersebut terhadap data pendapatan.

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.5e+06      0.0000000000   -0.3041235
## 2         C002             4.9e+06      0.0008072654   -0.3014440
## 3         C003             5.2e+06      0.0014127144   -0.2994343
## 4         C004             4.8e+06      0.0006054490   -0.3021138
## 5         C005             4.9e+06      0.0008072654   -0.3014440
## 6         C006             5.1e+06      0.0012108981   -0.3001042
## 7         C007             5.0e+08      1.0000000000    3.0151095
## 8         C008             4.7e+06      0.0004036327   -0.3027837
## 9         C009             4.6e+06      0.0002018163   -0.3034536
## 10        C010             5.3e+06      0.0016145308   -0.2987645
## 11        C011             4.9e+06      0.0008072654   -0.3014440
##    pendapatan_decimal
## 1              0.0045
## 2              0.0049
## 3              0.0052
## 4              0.0048
## 5              0.0049
## 6              0.0051
## 7              0.5000
## 8              0.0047
## 9              0.0046
## 10             0.0053
## 11             0.0049

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() menunjukkan pelanggan yang tidak memiliki transaksi dan transaksi yang tidak memiliki pasangan data pelanggan.

Menyelaraskan nama identifier

Setelah itu, kita menyelaraskan kolom cust_id pada dataset transaksi dan customer_id pada dataset pelanggan

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

Melakukan left join dengan merge()

Setelah itu, kita dapat menggabungkan data dengan menggunakan megre()

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

Catatan: all.x = TRUE mempertahankan seluruh pelanggan meskipun tidak memiliki pasangan transaksi.

Memvalidasi hasil integrasi

  1. Melihat banyaknya jumlah baris, apakah terdapat perubahan
c(sebelum = nrow(pelanggan), sesudah = nrow(data_terintegrasi))
## sebelum sesudah 
##      11      11
  1. Melihat apakah kolom customer_id tetap unil
sum(duplicated(data_terintegrasi$customer_id))
## [1] 0
  1. Melihat apakah terdapat nilai hilang yang muncul setelah integrasi
colSums(is.na(data_terintegrasi[, c("jumlah_transaksi", "total_purchase")]))
## jumlah_transaksi   total_purchase 
##                1                1
  1. Mengidentifikasi pada baris mana pelanggan tanpa pasangan transaksi
data_terintegrasi[is.na(data_terintegrasi$jumlah_transaksi),
                  c("customer_id", "nama")]
##    customer_id nama
## 11        C011 Kiki

Mengisi nol atau mempertahankan NA?

NA pada transaksi dapat berarti dua hal berbeda:

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

Nilai hanya boleh diubah menjadi 0 jika definisinya sudah dikonfirmasi.

# 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
##    customer_id   nama usia            kota      status pendapatan_imputasi
## 1         C001    Ani 21.0       Pekanbaru       Aktif             4.5e+06
## 2         C002   Budi 25.0       Pekanbaru       Aktif             4.9e+06
## 3         C003  Citra 23.0       Pekanbaru       Aktif             5.2e+06
## 4         C004   Dodi 50.0           Dumai       Aktif             4.8e+06
## 5         C005    Eka 27.0       Pekanbaru Tidak Aktif             4.9e+06
## 6         C006   Fani 27.5           Dumai Tidak Aktif             5.1e+06
## 7         C007 Gilang 31.0       Pekanbaru       Aktif             5.0e+08
## 8         C008   Hana 29.0            Siak       Aktif             4.7e+06
## 9         C009  Indra 22.0       Pekanbaru       Aktif             4.6e+06
## 10        C010   Joko 35.0           Dumai Tidak Aktif             5.3e+06
## 11        C011   Kiki 28.0 Tidak diketahui       Aktif             4.9e+06
##    pendapatan_missing outlier_pendapatan usia_minmax pendapatan_minmax
## 1                   0              FALSE  0.00000000      0.0000000000
## 2                   1              FALSE  0.13793103      0.0008072654
## 3                   0              FALSE  0.06896552      0.0014127144
## 4                   0              FALSE  1.00000000      0.0006054490
## 5                   0              FALSE  0.20689655      0.0008072654
## 6                   0              FALSE  0.22413793      0.0012108981
## 7                   0               TRUE  0.34482759      1.0000000000
## 8                   0              FALSE  0.27586207      0.0004036327
## 9                   0              FALSE  0.03448276      0.0002018163
## 10                  0              FALSE  0.48275862      0.0016145308
## 11                  1              FALSE  0.24137931      0.0008072654
##    jumlah_transaksi_final total_purchase_final
## 1                       5              1.5e+06
## 2                       3              9.0e+05
## 3                       7              2.7e+06
## 4                       2              6.0e+05
## 5                       6              2.1e+06
## 6                       4              1.3e+06
## 7                      20              2.5e+07
## 8                       5              1.7e+06
## 9                       3              8.0e+05
## 10                      8              3.2e+06
## 11                      0              0.0e+00

Audit akhir

Setelah melakukan preprocessing, kita melakukan audit akhir atau identifikasi akhir apakah masih terdapat nilai hilang ataupun data duplikat.

  1. Missing value
audit_akhir <- audit_data(data_final)
audit_akhir
##                   atribut      tipe jumlah_missing persen_missing jumlah_unik
## 1             customer_id character              0              0          11
## 2                    nama character              0              0          11
## 3                    usia   numeric              0              0          11
## 4                    kota character              0              0           4
## 5                  status character              0              0           2
## 6     pendapatan_imputasi   numeric              0              0           9
## 7      pendapatan_missing   integer              0              0           2
## 8      outlier_pendapatan   logical              0              0           2
## 9             usia_minmax   numeric              0              0          11
## 10      pendapatan_minmax   numeric              0              0           9
## 11 jumlah_transaksi_final   numeric              0              0           9
## 12   total_purchase_final   numeric              0              0          11
  1. Data duplikat
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

Untuk melihat apakah terdapat perubahan sebelum dan sesudah preprocessing, kita dapat membandingkan dataset sebelum dan sesudah preprocessing.

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
##               indikator sebelum sesudah
## 1          Jumlah baris      12      11
## 2 Duplikasi customer_id       1       0
## 3   Total missing value       4       0
## 4    Kategori kota unik      10       4
## 5  Kategori status unik       8       2

Menyimpan hasil

Setelah melakukan preprocessing, maka kita dapat menyimpan data hasil preprocessing

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

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

Ringkasan

Alur yang telah dilakukan adalah:

  1. Mengetahui dan memahami struktur dan kualitas awal data
  2. Membuat salinan kerja, merapikan dengan memberikan spasi dan kapitalisasi, menyamakan kategori, mendeteksi dan menghapus duplikasi, serta memeriksa pelanggaran domain;
  3. Menangani missing values sesuai dengan strategi yang sesuai;
  4. Mendeteksi dan menanggani outlier;
  5. Melakukan tranfromasi data; dan
  6. Menggabungkan data serta memvalidasi hasilnya.

Bagusnya hasil dari preprocessing tergantung dari keputusan kita sesuai dengan pemahaman data, tujuan analisis. Sehingga R hanya cara kita mewujudkan atau menjalankan proses tersebut.

Daftar Pustaka

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