• RSTUDIO

Menata Data, Membangun Kualitas

Dari data mentah menuju data siap analisis

Data yang baik tidak hanya membutuhkan jumlah yang cukup, tetapi juga kualitas yang baik. Melalui proses preprocessing, data diperiksa, dibersihkan, diolah, dan divalidasi agar siap digunakan untuk analisis.

01
MEMAHAMI DATA
02
MEMBERSIHKAN DATA
03
TRANSFORMASI
04
VALIDASI

Latar Belakang

Data merupakan salah satu dasar penting dalam proses pengambilan keputusan.Sebelum melakukan analisis lebih lanjut, diperlukan proses data preprocessing.Data preprocessing merupakan serangkaian proses untuk mempersiapkan data mentah agar menjadi lebih bersih, konsisten, dan siap digunakan dalam analisis. Proses ini tidak hanya bertujuan untuk menghapus data yang bermasalah, tetapi juga untuk memahami kondisi data, menentukan tindakan yang tepat, serta memastikan bahwa perubahan yang dilakukan tetap dapat dipertanggungjawabkan.

Pada praktik ini digunakan data pelanggan dan data transaksi sebagai contoh. Dataset sengaja dibuat mengandung beberapa permasalahan agar proses preprocessing dapat dipelajari secara langsung. Masalah tersebut meliputi missing value, duplikasi customer_id, variasi penulisan kota dan status, usia yang tidak masuk akal, serta pendapatan yang sangat ekstrem.

Proses preprocessing dilakukan secara bertahap mulai dari audit data, cleaning, penanganan missing value, evaluasi outlier, transformasi, integrasi data, hingga validasi akhir. Dengan pendekatan ini, perubahan terhadap data dapat dilakukan secara sistematis dan dapat ditelusuri.

Inti Latar Belakang

Data yang baik bukan hanya data yang banyak, tetapi data yang memiliki kualitas yang cukup baik untuk menjawab tujuan analisis.

Memeriksa Lingkungan Kerja

R.version.string
[1] "R version 4.5.1 (2025-06-13 ucrt)"

Data 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"
  ),

  stringsAsFactors = FALSE
)

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

Data Transaksi

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
)

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

Gambaran awal

Data pelanggan terdiri dari 12 baris dan 6 atribut. Data transaksi terdiri dari 11 baris.

Beberapa masalah memang sengaja dimasukkan ke dalam dataset, misalnya missing value, duplikasi C010, variasi penulisan kota dan status, usia 150 tahun, serta pendapatan 500 juta.

Bagian I — Konsep Data Preprocessing

Memahami Struktur Data

Sebelum melakukan perubahan, periksa struktur, dimensi, tipe atribut, dan beberapa observasi awal.

Dimensi Data

dim(pelanggan_raw)
[1] 12  6

Nama Atribut

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

Struktur Data

str(pelanggan_raw)
'data.frame':   12 obs. of  6 variables:
 $ customer_id: chr  "C001" "C002" "C003" "C004" ...
 $ nama       : chr  "Ani" "Budi" "Citra" "Dodi" ...
 $ usia       : num  21 25 23 150 27 NA 31 29 22 35 ...
 $ pendapatan : num  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" ...

Beberapa Observasi Awal

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

Ringkasan Data

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  
                                      
                                      
                                      
                                      

Apa yang perlu diperhatikan?

Dari pemeriksaan awal, terlihat bahwa dataset mempunyai:

  • missing value pada usia, pendapatan, dan kota;
  • customer_id yang berulang;
  • variasi penulisan kategori;
  • usia yang sangat tinggi;
  • serta pendapatan yang jauh lebih besar dibandingkan sebagian besar observasi lainnya.

Mengukur Kualitas Awal

Enam dimensi kualitas data yang dibahas Han, Kamber, dan Pei meliputi accuracy, completeness, consistency, timeliness, believability, dan interpretability.

Tidak semua dimensi dapat dihitung hanya dari tabel.

Pemeriksaan berikut berfokus pada masalah yang dapat dideteksi dari dataset.

Banyak Missing Value

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

Persentase 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 

Banyak Customer ID yang Berulang

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

Kategori Kota

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

Kategori Status

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

Rentang Usia

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

Rentang Pendapatan

range(
  pelanggan_raw$pendapatan,
  na.rm = TRUE
)
[1] 4.5e+06 5.0e+08

Initial Data Audit

Audit awal memberikan gambaran mengenai kualitas dataset sebelum dilakukan perubahan.

Tahap ini penting karena keputusan preprocessing sebaiknya didasarkan pada kondisi data yang benar-benar ditemukan.

Bagian II — Data Cleaning

Membuat Salinan Kerja

Data mentah dipertahankan sebagai referensi. Semua perubahan dilakukan pada objek pelanggan.

pelanggan <- pelanggan_raw

Membersihkan Spasi dan Kapitalisasi

Membersihkan Spasi

pelanggan$kota <- trimws(
  pelanggan$kota
)

pelanggan$status <- trimws(
  pelanggan$status
)

Menyeragamkan Kapitalisasi

pelanggan$kota <- tolower(
  pelanggan$kota
)

pelanggan$status <- tolower(
  pelanggan$status
)

Memeriksa Hasil

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

Menyeragamkan Kategori

Standardisasi Kota

pelanggan$kota[
  pelanggan$kota %in%
    c(
      "pku",
      "pekanbaru"
    )
] <- "Pekanbaru"

pelanggan$kota[
  pelanggan$kota == "dumai"
] <- "Dumai"

pelanggan$kota[
  pelanggan$kota == "siak"
] <- "Siak"

Standardisasi Status

pelanggan$status[
  pelanggan$status %in%
    c(
      "aktif",
      "active",
      "a"
    )
] <- "Aktif"

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

Memeriksa Hasil Standardisasi

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

Hasil Standardisasi

Kategori yang sebelumnya ditulis dengan berbagai bentuk telah diseragamkan menjadi kategori yang lebih konsisten.

Contohnya:

PKU, Pekanbaru, PEKANBARUPekanbaru

aktif, ACTIVE, AAktif

Mendeteksi dan Menghapus Duplikasi

Menampilkan Baris yang Berulang

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

Mempertahankan Kemunculan Pertama

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

rownames(
  pelanggan
) <- NULL

dim(
  pelanggan
)
[1] 11  6

Catatan

Menghapus duplikasi aman apabila setiap customer_id memang merepresentasikan satu pelanggan.

Jika satu pelanggan boleh memiliki banyak baris transaksi, duplikasi tidak boleh dihapus dengan cara ini.

Memeriksa Aturan Domain

Kandidat Usia Tidak Valid

Aturan bisnis yang digunakan adalah usia 15–100 tahun.

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

Kandidat Pendapatan Negatif

pelanggan[
  !is.na(pelanggan$pendapatan) &
  pelanggan$pendapatan < 0,
]
[1] customer_id nama        usia        pendapatan  kota        status     
<0 rows> (or 0-length row.names)

Mengoreksi Usia

Nilai usia 150 pada C004 merupakan nilai yang tidak masuk akal untuk aturan domain yang digunakan. Pada contoh ini, nilai dikoreksi menjadi 50 berdasarkan sumber asli.

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

Memastikan Koreksi

pelanggan[
  pelanggan$customer_id == "C004",
]
  customer_id nama usia pendapatan  kota status
4        C004 Dodi   50    4800000 Dumai  Aktif

Membuat Log Perubahan

log_perubahan <- data.frame(

  tahap = c(
    "Standardisasi",
    "Standardisasi",
    "Deduplikasi",
    "Koreksi domain"
  ),

  atribut = c(
    "kota",
    "status",
    "customer_id",
    "usia"
  ),

  tindakan = c(
    "PKU dan variasi kapital menjadi Pekanbaru",
    "ACTIVE/A menjadi Aktif; nonaktif menjadi Tidak Aktif",
    "Menghapus kemunculan kedua C010",
    "Usia C004 dikoreksi dari 150 menjadi 50 berdasarkan sumber asli"
  ),

  stringsAsFactors = FALSE
)

log_perubahan
           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

Mengapa log perubahan penting?

Log membuat proses preprocessing lebih transparan.

Dengan adanya log, kita dapat mengetahui:

apa yang diubah → atribut yang diubah → tindakan yang dilakukan.

Bagian III — Penanganan Missing Values

Mengidentifikasi Lokasi Nilai Hilang

Jumlah Missing Value

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

Baris dengan Missing Value

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

Kode berikut hanya mendemonstrasikan strategi. Objek pelanggan tidak langsung diganti.

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

nrow(
  pelanggan
)
[1] 11
nrow(
  pelanggan_complete
)
[1] 8

Persentase Data yang Hilang

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

Pertimbangan

Menghapus seluruh baris yang mempunyai missing value dapat mengurangi jumlah observasi secara cukup besar.

Karena itu, keputusan ini perlu mempertimbangkan jumlah data yang hilang dan pentingnya variabel tersebut.

Strategi 2: Imputasi Mean dan Median

Pendapatan memiliki nilai ekstrem sehingga mean dan median perlu dibandingkan terlebih dahulu.

Mean Pendapatan

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

mean_pendapatan
[1] 59900000

Median Pendapatan

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

median_pendapatan
[1] 4900000

Mean vs Median

Karena terdapat nilai pendapatan sebesar 500 juta, mean menjadi jauh lebih besar daripada median.

Median lebih stabil terhadap pengaruh nilai ekstrem sehingga digunakan sebagai contoh strategi imputasi pada data ini.

Imputasi Pendapatan

pelanggan$pendapatan_imputasi <-
  pelanggan$pendapatan

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

Imputasi Usia

median_usia <-
  median(
    pelanggan$usia,
    na.rm = TRUE
  )

pelanggan$usia_imputasi <-
  pelanggan$usia

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

Melihat Hasil Imputasi

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

Nilai kota yang hilang tidak langsung diisi dengan modus.

Pada contoh ini digunakan kategori eksplisit “Tidak diketahui” agar informasi ketidakpastian tetap terlihat.

pelanggan$kota_imputasi <-
  pelanggan$kota

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

table(
  pelanggan$kota_imputasi,
  useNA = "ifany"
)

          Dumai       Pekanbaru            Siak Tidak diketahui 
              3               6               1               1 

Menambahkan Indikator Missing

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

par(
  mfrow = c(1, 2)
)

hist(
  pelanggan$pendapatan,
  main = "Sebelum Imputasi",
  xlab = "Pendapatan",
  col = "#A7C4BC",
  breaks = 8
)

hist(
  pelanggan$pendapatan_imputasi,
  main = "Sesudah Imputasi Median",
  xlab = "Pendapatan",
  col = "#52796F",
  breaks = 8
)

par(
  mfrow = c(1, 1)
)

Interpretasi

Imputasi dapat mengubah distribusi data dan mengurangi variasi.

Karena itu, evaluasi tidak berhenti hanya karena seluruh NA sudah tertangani.

Bagian IV — Penanganan Outlier

Visualisasi dengan Boxplot

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

Apa yang terlihat?

Satu nilai pendapatan jauh lebih besar dibandingkan sebagian besar observasi lainnya.

Nilai tersebut menjadi kandidat outlier yang perlu dievaluasi.

Menghitung Batas IQR

q1 <-
  quantile(
    pelanggan$pendapatan_imputasi,
    0.25,
    na.rm = TRUE
  )

q3 <-
  quantile(
    pelanggan$pendapatan_imputasi,
    0.75,
    na.rm = TRUE
  )

iqr <-
  IQR(
    pelanggan$pendapatan_imputasi,
    na.rm = TRUE
  )

batas_bawah <-
  q1 - 1.5 * iqr

batas_atas <-
  q3 + 1.5 * iqr

c(
  Q1 = q1,
  Q3 = q3,
  IQR = iqr,
  batas_bawah = batas_bawah,
  batas_atas = batas_atas
)
         Q1.25%          Q3.75%             IQR batas_bawah.25%  batas_atas.75% 
        4750000         5150000          400000         4150000         5750000 

Menandai Kandidat Outlier

pelanggan$outlier_pendapatan <-

  pelanggan$pendapatan_imputasi <
    batas_bawah |

  pelanggan$pendapatan_imputasi >
    batas_atas

pelanggan[
  pelanggan$outlier_pendapatan,
  c(
    "customer_id",
    "nama",
    "pendapatan_imputasi"
  )
]
  customer_id   nama pendapatan_imputasi
7        C007 Gilang               5e+08

Mengevaluasi Tindakan

Outlier tidak otomatis dihapus.

Pada contoh ini, nilai ekstrem akan ditangani menggunakan pendekatan winsorization agar pengaruh ekstrem dapat dikurangi tanpa menghilangkan observasi.

Menentukan Nilai Winsorized

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

Catatan

Winsorization mengubah nilai ekstrem menjadi batas yang telah ditentukan.

Metode ini tidak selalu menjadi pilihan terbaik. Dalam data nyata, keputusan harus mempertimbangkan konteks bisnis dan sumber data.

Bagian V — Transformasi Data

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
}

Menerapkan Min–Maks

pelanggan$usia_minmax <-
  minmax(
    pelanggan$usia_imputasi
  )

pelanggan$pendapatan_minmax <-
  minmax(
    pelanggan$pendapatan_imputasi
  )

pelanggan[
  ,
  c(
    "customer_id",
    "usia_imputasi",
    "usia_minmax",
    "pendapatan_imputasi",
    "pendapatan_minmax"
  )
]
   customer_id usia_imputasi usia_minmax pendapatan_imputasi pendapatan_minmax
1         C001          21.0  0.00000000             4.5e+06      0.0000000000
2         C002          25.0  0.13793103             4.9e+06      0.0008072654
3         C003          23.0  0.06896552             5.2e+06      0.0014127144
4         C004          50.0  1.00000000             4.8e+06      0.0006054490
5         C005          27.0  0.20689655             4.9e+06      0.0008072654
6         C006          27.5  0.22413793             5.1e+06      0.0012108981
7         C007          31.0  0.34482759             5.0e+08      1.0000000000
8         C008          29.0  0.27586207             4.7e+06      0.0004036327
9         C009          22.0  0.03448276             4.6e+06      0.0002018163
10        C010          35.0  0.48275862             5.3e+06      0.0016145308
11        C011          28.0  0.24137931             4.9e+06      0.0008072654

Normalisasi Z-Score

Rumus:

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

Interpretasi Z-Score

Z-score menunjukkan posisi suatu nilai terhadap rata-rata dalam satuan simpangan baku.

Nilai positif berarti berada di atas rata-rata, sedangkan nilai negatif berarti berada di bawah rata-rata.

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

Menerapkan Decimal Scaling

pelanggan$pendapatan_decimal <-

  decimal_scale(
    pelanggan$pendapatan_imputasi
  )

range(
  pelanggan$pendapatan_decimal,
  na.rm = TRUE
)
[1] 0.0045 0.5000

Membandingkan Metode Transformasi

transformasi <-
  pelanggan[
    ,
    c(
      "customer_id",
      "pendapatan_imputasi",
      "pendapatan_minmax",
      "pendapatan_z",
      "pendapatan_decimal"
    )
  ]

transformasi
   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

Tiga pendekatan, tujuan berbeda

Min–maks mengubah nilai ke rentang tertentu, umumnya [0,1].

Z-score menempatkan data berdasarkan jarak terhadap rata-rata.

Decimal scaling mengecilkan skala berdasarkan pangkat 10.

Pemilihan metode bergantung pada karakteristik data dan kebutuhan analisis.

Dampak Outlier terhadap Normalisasi

pelanggan$pendapatan_winsor_minmax <-

  minmax(
    pelanggan$pendapatan_winsor
  )
plot(
  pelanggan$pendapatan_minmax,
  pelanggan$pendapatan_winsor_minmax,
  pch = 19,
  col = "#285943",
  xlab = "Min–maks Data Asli",
  ylab = "Min–maks Data Winsorized",
  main = "Dampak Penanganan Outlier terhadap Normalisasi"
)

abline(
  0,
  1,
  col = "#9B2C2C",
  lty = 2
)

Interpretasi

Keberadaan outlier dapat membuat sebagian besar nilai lain terkumpul pada rentang min–maks yang sangat sempit.

Hal ini menunjukkan bahwa deteksi outlier penting dilakukan sebelum memilih strategi transformasi.

Bagian VI — Integrasi Data

Memeriksa Kunci pada Kedua Sumber

Duplikasi pada Data Pelanggan

sum(
  duplicated(
    pelanggan$customer_id
  )
)
[1] 0

Duplikasi pada Data Transaksi

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

Mencari ID yang Tidak Memiliki Pasangan

Customer yang Tidak Memiliki Transaksi

setdiff(
  pelanggan$customer_id,
  transaksi_raw$cust_id
)
[1] "C011"

ID Transaksi yang Tidak Ada pada Data Pelanggan

setdiff(
  transaksi_raw$cust_id,
  pelanggan$customer_id
)
[1] "C012"

Hasil Pemeriksaan Identifier

Ditemukan perbedaan identifier:

  • C011 terdapat pada data pelanggan tetapi tidak terdapat pada data transaksi;
  • C012 terdapat pada data transaksi tetapi tidak terdapat pada data pelanggan.

Perbedaan ini penting diperhatikan sebelum melakukan merge.

Menyamakan Nama Identifier

transaksi <-
  transaksi_raw

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

Menggabungkan Data

all.x = TRUE digunakan agar seluruh pelanggan tetap dipertahankan meskipun tidak mempunyai pasangan transaksi.

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

Mengapa all.x = TRUE?

Karena fokus analisis adalah pelanggan.

Dengan all.x = TRUE, pelanggan yang belum memiliki transaksi tetap muncul pada dataset hasil integrasi.

Memvalidasi Hasil Integrasi

Membandingkan Jumlah Baris

c(
  sebelum =
    nrow(pelanggan),

  sesudah =
    nrow(data_terintegrasi)
)
sebelum sesudah 
     11      11 

Memeriksa Duplikasi Identifier

sum(
  duplicated(
    data_terintegrasi$customer_id
  )
)
[1] 0

Memeriksa Missing pada Data Transaksi

colSums(
  is.na(
    data_terintegrasi[
      ,
      c(
        "jumlah_transaksi",
        "total_purchase"
      )
    ]
  )
)
jumlah_transaksi   total_purchase 
               1                1 

Interpretasi Integrasi

Jumlah baris tetap sama dengan jumlah pelanggan karena proses penggabungan mempertahankan seluruh pelanggan.

Missing pada variabel transaksi menunjukkan bahwa pelanggan tersebut tidak memiliki pasangan transaksi pada sumber data transaksi.

Audit Akhir

Membuat Fungsi Audit

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 Dataset Sebelum Preprocessing

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

Audit Dataset Setelah Cleaning

audit_cleaning <-
  audit_data(
    pelanggan
  )

audit_cleaning
                    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           9.09          11
4                pendapatan   numeric              2          18.18          10
5                      kota character              1           9.09           4
6                    status character              0           0.00           2
7       pendapatan_imputasi   numeric              0           0.00           9
8             usia_imputasi   numeric              0           0.00          11
9             kota_imputasi character              0           0.00           4
10       pendapatan_missing   integer              0           0.00           2
11       outlier_pendapatan   logical              0           0.00           2
12        pendapatan_winsor   numeric              0           0.00           9
13              usia_minmax   numeric              0           0.00          11
14        pendapatan_minmax   numeric              0           0.00           9
15                   usia_z   numeric              0           0.00          11
16             pendapatan_z   numeric              0           0.00           9
17       pendapatan_decimal   numeric              0           0.00           9
18 pendapatan_winsor_minmax   numeric              0           0.00           9
min 
function (..., na.rm = FALSE)  .Primitive("min")

Audit Dataset Terintegrasi

audit_integrasi <-
  audit_data(
    data_terintegrasi
  )

audit_integrasi
                    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           9.09          11
4                pendapatan   numeric              2          18.18          10
5                      kota character              1           9.09           4
6                    status character              0           0.00           2
7       pendapatan_imputasi   numeric              0           0.00           9
8             usia_imputasi   numeric              0           0.00          11
9             kota_imputasi character              0           0.00           4
10       pendapatan_missing   integer              0           0.00           2
11       outlier_pendapatan   logical              0           0.00           2
12        pendapatan_winsor   numeric              0           0.00           9
13              usia_minmax   numeric              0           0.00          11
14        pendapatan_minmax   numeric              0           0.00           9
15                   usia_z   numeric              0           0.00          11
16             pendapatan_z   numeric              0           0.00           9
17       pendapatan_decimal   numeric              0           0.00           9
18 pendapatan_winsor_minmax   numeric              0           0.00           9
19         jumlah_transaksi   numeric              1           9.09           9
20           total_purchase   numeric              1           9.09          11

Membandingkan Kondisi Sebelum dan Sesudah

Missing Value

missing_comparison <-
  data.frame(

    kondisi = c(
      "Sebelum preprocessing",
      "Setelah cleaning"
    ),

    total_missing = c(

      sum(
        is.na(
          pelanggan_raw
        )
      ),

      sum(
        is.na(
          pelanggan
        )
      )
    )
  )

missing_comparison
                kondisi total_missing
1 Sebelum preprocessing             4
2      Setelah cleaning             4

Duplikasi Customer ID

duplicate_comparison <-
  data.frame(

    kondisi = c(
      "Sebelum preprocessing",
      "Setelah cleaning"
    ),

    duplicate_id = c(

      sum(
        duplicated(
          pelanggan_raw$customer_id
        )
      ),

      sum(
        duplicated(
          pelanggan$customer_id
        )
      )
    )
  )

duplicate_comparison
                kondisi duplicate_id
1 Sebelum preprocessing            1
2      Setelah cleaning            0

Visualisasi Perubahan

par(
  mfrow = c(1, 2)
)

barplot(
  c(
    sum(
      is.na(
        pelanggan_raw
      )
    ),
    sum(
      duplicated(
        pelanggan_raw$customer_id
      )
    )
  ),
  names.arg = c(
    "Missing",
    "Duplicate"
  ),
  main = "Sebelum Preprocessing",
  col = "#B8C4C2",
  ylab = "Jumlah"
)

barplot(
  c(
    sum(
      is.na(
        pelanggan
      )
    ),
    sum(
      duplicated(
        pelanggan$customer_id
      )
    )
  ),
  names.arg = c(
    "Missing",
    "Duplicate"
  ),
  main = "Sesudah Cleaning",
  col = "#52796F",
  ylab = "Jumlah"
)

par(
  mfrow = c(1, 1)
)

Data Quality Journey

Dataset telah melalui beberapa tahap:

Raw Data → Audit → Cleaning → Missing Value Treatment → Outlier Evaluation → Transformation → Integration → Validation

Tujuan akhirnya bukan hanya data yang lebih rapi, tetapi data yang lebih siap digunakan untuk analisis.

Membentuk Analysis-Ready Dataset

Untuk kebutuhan analisis berikutnya, digunakan variabel hasil preprocessing.

data_final <-

  data_terintegrasi[
    ,
    c(
      "customer_id",
      "nama",
      "usia_imputasi",
      "kota_imputasi",
      "status",
      "pendapatan_imputasi",
      "pendapatan_missing",
      "outlier_pendapatan",
      "usia_minmax",
      "pendapatan_minmax",
      "usia_z",
      "pendapatan_z",
      "pendapatan_decimal",
      "jumlah_transaksi",
      "total_purchase"
    )
  ]

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

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

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
         usia_z pendapatan_z pendapatan_decimal jumlah_transaksi total_purchase
1  -0.984199667   -0.3041235             0.0045                5        1.5e+06
2  -0.489287834   -0.3014440             0.0049                3        9.0e+05
3  -0.736743750   -0.2994343             0.0052                7        2.7e+06
4   2.603911118   -0.3021138             0.0048                2        6.0e+05
5  -0.241831918   -0.3014440             0.0049                6        2.1e+06
6  -0.179967939   -0.3001042             0.0051                4        1.3e+06
7   0.253079914    3.0151095             0.5000               20        2.5e+07
8   0.005623998   -0.3027837             0.0047                5        1.7e+06
9  -0.860471709   -0.3034536             0.0046                3        8.0e+05
10  0.747991747   -0.2987645             0.0053                8        3.2e+06
11 -0.118103960   -0.3014440             0.0049               NA             NA

Pemeriksaan Akhir Dataset

c(
  jumlah_baris =
    nrow(data_final),

  jumlah_kolom =
    ncol(data_final),

  total_missing =
    sum(is.na(data_final))
)
 jumlah_baris  jumlah_kolom total_missing 
           11            15             2 

Pertanyaan Refleksi

1. Mengapa preprocessing diperlukan?

Preprocessing diperlukan karena data mentah dapat mengandung missing value, duplikasi, kategori yang tidak konsisten, nilai ekstrem, kesalahan domain, dan perbedaan struktur antar sumber.

2. Apakah missing value selalu harus dihapus?

Tidak.

Missing value dapat ditangani dengan beberapa strategi, seperti menghapus baris, imputasi, atau mempertahankan informasi missing melalui indikator.

3. Apakah outlier selalu harus dihapus?

Tidak.

Outlier perlu diperiksa terlebih dahulu untuk mengetahui apakah nilai tersebut merupakan kesalahan atau memang merupakan kondisi yang nyata.

4. Mengapa median digunakan untuk pendapatan?

Karena terdapat nilai pendapatan yang sangat ekstrem sehingga median lebih stabil dibandingkan mean pada contoh ini.

5. Mengapa all.x = TRUE digunakan?

Karena kita ingin mempertahankan seluruh pelanggan meskipun sebagian pelanggan tidak mempunyai transaksi yang cocok.

6. Mengapa validasi dilakukan setelah integrasi?

Karena proses penggabungan data dapat menghasilkan perubahan jumlah baris, duplikasi, atau missing value baru.


Ringkasan

Apa yang telah dilakukan?

01 — Memahami data

Struktur, dimensi, tipe atribut, dan kondisi awal diperiksa.

02 — Membersihkan data

Spasi, kapitalisasi, kategori, duplikasi, dan domain diperiksa.

03 — Menangani missing value

Beberapa strategi dibandingkan dan imputasi diterapkan.

04 — Mengevaluasi outlier

Boxplot dan metode IQR digunakan untuk mengidentifikasi kandidat outlier.

05 — Melakukan transformasi

Min–maks, Z-score, dan decimal scaling diterapkan.

06 — Mengintegrasikan data

Data pelanggan dan transaksi digabungkan menggunakan identifier.

07 — Melakukan validasi

Dataset diperiksa kembali setelah proses preprocessing.

Daftar Pustaka

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


DATA → INFORMATION → INSIGHT

Every reliable analysis begins with reliable data.