Data Preprocessing Menggunakan RStudio

Apa Itu Data Preprocessing?

Data preprocessing merupakan proses yang saya lakukan untuk menyiapkan data sebelum memasuki tahap analisis. Data yang diperoleh belum tentu dapat langsung digunakan karena masih mungkin terdapat nilai yang hilang, data yang tercatat berulang, penulisan kategori yang berbeda, atau nilai yang tidak sesuai.

Melalui proses ini, saya memeriksa kondisi data dan melakukan penyesuaian yang diperlukan agar data menjadi lebih terstruktur dan konsisten. Tahap ini membantu saya memahami kondisi dataset sebelum melanjutkan ke proses analisis.


Latar Belakang

Saya menggunakan proses data preprocessing sebagai tahap awal untuk menyiapkan data pelanggan sebelum digunakan dalam analisis. Dataset yang saya gunakan masih memiliki beberapa kondisi yang perlu diperhatikan, seperti nilai yang hilang, penulisan kategori yang belum konsisten, customer_id yang berulang, nilai usia yang tidak sesuai, serta nilai pendapatan yang jauh lebih besar dibandingkan data lainnya.

Saya tidak langsung melakukan perubahan terhadap data tersebut. Saya terlebih dahulu memeriksa setiap kondisi untuk mengetahui masalah yang terdapat dalam dataset dan menentukan cara penanganan yang sesuai.

Proses yang saya lakukan dimulai dengan memeriksa struktur data, melakukan audit, menyeragamkan kategori, menangani data duplikat, memperbaiki nilai yang tidak sesuai, menangani missing value, mengidentifikasi outlier, melakukan transformasi, dan mengintegrasikan data pelanggan dengan data transaksi.


Menyiapkan Lingkungan Kerja

Saya memulai pengolahan dengan memeriksa direktori kerja dan versi R yang digunakan.

getwd()
## [1] "D:/SEMESTER 5/Data Mining"
R.version.string
## [1] "R version 4.4.2 (2024-10-31 ucrt)"

Menyiapkan Data Awal

Saya menggunakan dua dataset, yaitu data pelanggan dan data transaksi. Kedua dataset ini digunakan untuk menunjukkan proses pemeriksaan, pembersihan, transformasi, hingga integrasi data.

pelanggan_raw <- data.frame(
  customer_id = c(
    "C001", "C002", "C003", "C004",
    "C005", "C006", "C007", "C008",
    "C009", "C010", "C010", "C011"
  ),
  nama = c(
    "Ani", "Budi", "Citra", "Dodi",
    "Eka", "Fani", "Gilang", "Hana",
    "Indra", "Joko", "Joko", "Kiki"
  ),
  usia = c(
    21, 25, 23, 150, 27, NA,
    31, 29, 22, 35, 35, 28
  ),
  pendapatan = c(
    4500000, NA, 5200000, 4800000,
    4900000, 5100000, 500000000,
    4700000, 4600000, 5300000,
    5300000, NA
  ),
  kota = c(
    "Pekanbaru", " PKU", "PEKANBARU",
    "Dumai", "pekanbaru", "DUMAI",
    "Pekanbaru ", "Siak", "PKU",
    "Dumai", "Dumai", NA
  ),
  status = c(
    "Aktif", "aktif", "ACTIVE", "A",
    "Tidak Aktif", "nonaktif",
    "Aktif", "AKTIF", "A",
    "Tidak aktif", "Tidak aktif", "Aktif"
  ),
  stringsAsFactors = FALSE
)

transaksi_raw <- data.frame(
  cust_id = c(
    "C001", "C002", "C003", "C004",
    "C005", "C006", "C007", "C008",
    "C009", "C010", "C012"
  ),
  jumlah_transaksi = c(
    5, 3, 7, 2, 6, 4, 20, 5, 3, 8, 1
  ),
  total_purchase = c(
    1500000, 900000, 2700000,
    600000, 2100000, 1300000,
    25000000, 1700000, 800000,
    3200000, 250000
  ),
  stringsAsFactors = FALSE
)

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

Output menampilkan dataset pelanggan awal yang terdiri atas 12 baris dan 6 variabel. Dari tampilan awal terlihat beberapa kondisi yang perlu diperiksa lebih lanjut, yaitu adanya NA, perbedaan penulisan kota dan status, customer_id yang berulang, usia 150 tahun, serta pendapatan sebesar 500.000.000 yang terlihat jauh lebih besar dibandingkan nilai lainnya.

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

Output menunjukkan data transaksi yang terdiri atas 11 pelanggan. Dataset transaksi menggunakan cust_id sebagai identifier pelanggan sehingga nantinya perlu diseragamkan menjadi customer_id sebelum proses integrasi.


Mengenali Kondisi Awal Data

Sebelum melakukan perubahan, saya memeriksa ukuran dataset, nama variabel, struktur data, beberapa observasi awal, dan ringkasan statistik.

dim(pelanggan_raw)
## [1] 12  6

Output menunjukkan jumlah baris dan kolom dataset. Dataset pelanggan awal memiliki 12 observasi dan 6 variabel. Informasi ini menjadi dasar untuk membandingkan ukuran data sebelum dan sesudah proses preprocessing.

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

Output menunjukkan nama variabel yang terdapat dalam dataset, yaitu customer_id, nama, usia, pendapatan, kota, dan status. Nama variabel tersebut digunakan sebagai acuan dalam proses pembersihan dan transformasi.

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

Output str() digunakan untuk melihat struktur dataset, tipe data setiap variabel, serta beberapa nilai awal. Pemeriksaan ini membantu memastikan bahwa variabel numerik seperti usia dan pendapatan tersimpan sebagai numerik, sedangkan variabel kategori tersimpan sebagai karakter.

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

Output menampilkan enam observasi pertama dari dataset. Pemeriksaan ini membantu saya melihat data secara langsung dan menemukan pola atau nilai yang perlu diperiksa lebih lanjut.

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

Output summary() memberikan ringkasan statistik untuk variabel numerik dan distribusi kategori untuk variabel karakter. Ringkasan ini membantu mengidentifikasi nilai minimum, maksimum, median, rata-rata, serta kemungkinan nilai yang tidak wajar.


Melakukan Audit Data

Saya memeriksa jumlah missing value, data duplikat, identitas pelanggan yang berulang, variasi kategori, dan rentang nilai numerik.

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

Output menunjukkan jumlah NA pada setiap variabel. Nilai NA menunjukkan bahwa terdapat informasi yang belum tersedia pada dataset. Informasi ini digunakan sebagai dasar untuk menentukan metode penanganan missing value.

Persentase missing value pada setiap variabel dihitung dengan rumus:

\[ \text{Persentase Missing} = \frac{\text{Jumlah Missing}} {\text{Jumlah Observasi}} \times 100\% \]

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

Output menunjukkan persentase nilai hilang pada setiap variabel. Variabel dengan persentase missing value lebih besar memerlukan perhatian lebih dalam menentukan metode penanganannya.

Saya memeriksa jumlah baris yang benar-benar sama.

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

Output menunjukkan jumlah baris yang sepenuhnya identik. Jika hasilnya nol, tidak terdapat baris yang seluruh nilainya sama. Kondisi tersebut belum menjamin bahwa identifier pelanggan tidak berulang sehingga pemeriksaan customer_id tetap diperlukan.

Saya juga memeriksa apakah terdapat customer_id yang berulang.

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

Output menunjukkan jumlah customer_id yang muncul kembali setelah kemunculan pertamanya. Pada data ini terdapat satu customer_id yang berulang, yaitu C010. Kondisi tersebut perlu diperiksa karena identifier pelanggan seharusnya unik.

Variasi kategori pada kota diperiksa menggunakan:

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

Output menunjukkan bahwa kota yang sama ditulis dengan beberapa bentuk, seperti Pekanbaru, PKU, PEKANBARU, pekanbaru, Pekanbaru , dan PKU. Perbedaan tersebut merupakan masalah konsistensi kategori sehingga perlu dilakukan standardisasi.

Variasi kategori pada status diperiksa menggunakan:

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

Output menunjukkan beberapa bentuk penulisan status yang memiliki makna sama, seperti Aktif, aktif, ACTIVE, dan A. Kondisi tersebut perlu diseragamkan agar setiap kategori memiliki satu bentuk penulisan yang konsisten.

Rentang usia diperiksa dengan:

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

Output menunjukkan rentang usia minimum hingga maksimum. Nilai maksimum sebesar 150 menunjukkan adanya nilai yang tidak wajar untuk usia pelanggan sehingga perlu diperiksa lebih lanjut.

Rentang pendapatan diperiksa dengan:

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

Output menunjukkan bahwa terdapat rentang pendapatan yang sangat lebar. Nilai 500.000.000 jauh lebih besar dibandingkan sebagian besar pendapatan lainnya sehingga perlu diperiksa sebagai kemungkinan outlier.

Agar hasil pemeriksaan lebih terstruktur, saya membuat fungsi audit_data.

audit_data <- function(data) {
  
  data.frame(
    atribut = names(data),
    tipe = sapply(
      data,
      function(x) class(x)[1]
    ),
    jumlah_missing = sapply(
      data,
      function(x) sum(is.na(x))
    ),
    persen_missing = round(
      sapply(
        data,
        function(x) mean(is.na(x)) * 100
      ),
      2
    ),
    jumlah_unik = sapply(
      data,
      function(x) length(unique(x))
    ),
    row.names = NULL
  )
}

audit_awal <- audit_data(pelanggan_raw)

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

Tabel audit memberikan gambaran kondisi setiap variabel secara lebih terstruktur, meliputi tipe data, jumlah missing value, persentase missing value, dan jumlah nilai unik. Tabel ini menjadi dasar untuk menentukan tindakan preprocessing yang diperlukan pada setiap atribut.


Membersihkan dan Menyeragamkan Data

Sebelum melakukan perubahan, saya terlebih dahulu memeriksa kondisi setiap variabel. Saya tidak langsung menghapus atau mengubah suatu nilai hanya karena terlihat berbeda dari data lainnya.

Prinsip Utama

Jangan membersihkan data hanya karena sebuah nilai terlihat aneh. Setiap perubahan harus memiliki alasan, aturan, dan catatan.

Agar data awal tetap dapat dijadikan pembanding, saya membuat objek baru untuk proses pembersihan.

pelanggan <- pelanggan_raw

Saya menemukan perbedaan penulisan pada variabel kota dan status. Saya menghapus spasi yang tidak diperlukan dan menyeragamkan huruf terlebih dahulu.

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"

Output menunjukkan kategori yang telah dibersihkan dari spasi berlebih dan diubah menjadi huruf kecil. Langkah ini mempermudah proses pemetaan kategori yang memiliki arti sama.

sort(unique(pelanggan$status))
## [1] "a"           "active"      "aktif"       "nonaktif"    "tidak aktif"

Output menunjukkan bentuk kategori status setelah penyeragaman huruf. Beberapa kategori masih memiliki bentuk berbeda, tetapi selanjutnya akan digabungkan berdasarkan makna yang sama.

Setelah itu, saya menyeragamkan kategori yang memiliki makna yang sama.

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"

Kategori kota telah diseragamkan sehingga variasi penulisan Pekanbaru menjadi satu kategori, yaitu Pekanbaru. Dumai dan Siak juga menggunakan format penulisan yang konsisten.

sort(unique(pelanggan$status))
## [1] "Aktif"       "Tidak Aktif"

Kategori status telah disederhanakan menjadi dua kategori utama, yaitu Aktif dan Tidak Aktif. Penyeragaman ini membuat kategori lebih konsisten untuk analisis berikutnya.


Memeriksa Customer ID yang Berulang

Saya memeriksa seluruh baris yang memiliki customer_id 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

Output menampilkan seluruh baris yang memiliki customer_id berulang. Pada data ini, C010 muncul dua kali dengan informasi pelanggan yang sama. Karena satu pelanggan seharusnya hanya memiliki satu identitas, salah satu baris perlu dihilangkan.

Setelah diperiksa, saya mempertahankan kemunculan pertama dari setiap identitas pelanggan.

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

rownames(pelanggan) <- NULL

dim(pelanggan)
## [1] 11  6

Jumlah baris berkurang dari 12 menjadi 11 karena satu baris duplikat C010 telah dihapus. Setiap customer_id sekarang hanya muncul satu kali.


Memeriksa Nilai yang Tidak Sesuai

Saya memeriksa apakah terdapat nilai usia yang berada di luar rentang 15 hingga 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>

Output menunjukkan pelanggan C004 memiliki usia 150 tahun. Nilai tersebut tidak sesuai dengan rentang usia pelanggan yang digunakan sehingga perlu dilakukan pemeriksaan dan koreksi berdasarkan informasi yang tersedia.

Saya juga memeriksa kemungkinan adanya 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>

Jika output tidak menampilkan baris, berarti tidak terdapat pendapatan negatif. Dengan demikian, tidak ditemukan kesalahan domain berupa pendapatan yang bernilai kurang dari nol.

Setelah dilakukan pemeriksaan, nilai usia pelanggan C004 dikoreksi.

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

Nilai usia C004 diubah dari 150 menjadi 50 berdasarkan sumber informasi asli. Koreksi dilakukan karena nilai 150 dianggap sebagai kesalahan pencatatan, bukan kondisi nyata.


Mencatat Perubahan Data

Setiap perubahan yang saya lakukan dicatat agar proses preprocessing tetap dapat ditelusuri.

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

Tabel log_perubahan menunjukkan tindakan yang dilakukan selama preprocessing. Pencatatan ini membuat setiap perubahan dapat ditelusuri kembali sehingga proses pengolahan data menjadi lebih transparan dan dapat dipertanggungjawabkan.


Memeriksa Missing Value

Setelah pembersihan awal, saya kembali memeriksa jumlah nilai yang hilang.

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

Output menunjukkan jumlah NA yang masih terdapat pada setiap variabel setelah standardisasi dan deduplikasi. Nilai yang masih hilang akan ditangani pada tahap berikutnya menggunakan metode yang sesuai.

Saya juga menampilkan observasi yang memiliki setidaknya satu nilai yang hilang.

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

Output menunjukkan baris yang masih memiliki minimal satu nilai kosong. Pemeriksaan ini membantu mengetahui pelanggan mana yang terdampak missing value dan variabel apa yang perlu ditangani.

Saya membandingkan jumlah data sebelum dan sesudah penghapusan seluruh baris yang memiliki missing value.

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

nrow(pelanggan)
## [1] 11

Output menunjukkan jumlah baris dataset setelah deduplikasi tetapi sebelum penghapusan missing value. Jumlah tersebut digunakan sebagai pembanding untuk melihat dampak apabila seluruh baris dengan NA dihapus.

nrow(pelanggan_complete)
## [1] 8

Output menunjukkan jumlah observasi yang lengkap tanpa NA. Perbedaan jumlah observasi dengan dataset sebelumnya menunjukkan berapa banyak baris yang akan hilang jika digunakan metode complete case deletion.

Persentase data yang hilang akibat penghapusan baris dihitung dengan rumus:

\[ \text{Persentase Data Terhapus} = \left( 1 - \frac{n_{\text{complete}}} {n_{\text{awal}}} \right) \times 100\% \]

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

Output menunjukkan persentase observasi yang akan hilang jika seluruh baris dengan missing value dihapus. Karena penghapusan baris dapat mengurangi jumlah informasi yang tersedia, saya memilih pendekatan imputasi untuk mempertahankan observasi yang masih dapat digunakan.


Mengisi Missing Value

Saya menghitung nilai mean dan median dari pendapatan sebagai dasar untuk menentukan metode imputasi.

Rumus mean adalah:

\[ \bar{x} = \frac{\sum_{i=1}^{n}x_i}{n} \]

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

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

mean_pendapatan
## [1] 59900000

Output menunjukkan rata-rata pendapatan setelah nilai NA diabaikan. Nilai ini digunakan sebagai dasar perbandingan dengan median. Keberadaan nilai pendapatan yang sangat besar dapat membuat rata-rata menjadi kurang representatif terhadap sebagian besar pelanggan.

median_pendapatan
## [1] 4900000

Output menunjukkan median pendapatan. Median lebih tahan terhadap nilai ekstrem dibandingkan rata-rata sehingga dipilih sebagai dasar imputasi pendapatan pada dataset ini.

Saya menggunakan median untuk mengisi nilai pendapatan yang hilang dan median usia untuk mengisi usia yang kosong.

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

Nilai pendapatan yang hilang telah digantikan menggunakan median pendapatan, sedangkan usia yang hilang digantikan menggunakan median usia. Pendekatan ini mempertahankan jumlah observasi tanpa menambahkan nilai ekstrem secara berlebihan.

Saya menggunakan kategori “Tidak diketahui” untuk nilai kota yang kosong.

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

Output menunjukkan jumlah pelanggan pada setiap kategori kota setelah nilai kosong diganti menjadi Tidak diketahui. Pendekatan ini digunakan karena tidak terdapat informasi yang cukup untuk menentukan kota sebenarnya dari pelanggan tersebut.

Saya juga membuat indikator untuk menunjukkan data pendapatan yang sebelumnya memiliki nilai hilang.

pelanggan$pendapatan_missing <-
  as.integer(
    is.na(pelanggan$pendapatan)
  )

table(pelanggan$pendapatan_missing)
## 
## 0 1 
## 9 2

Nilai 0 menunjukkan pendapatan awal tidak hilang, sedangkan nilai 1 menunjukkan pendapatan awal memiliki NA. Variabel indikator ini berguna untuk mempertahankan informasi mengenai observasi yang sebelumnya mengalami missing value.


Melihat Perubahan Setelah Imputasi

Saya membandingkan distribusi pendapatan sebelum dan sesudah imputasi menggunakan histogram.

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

Histogram sebelah kiri menunjukkan distribusi pendapatan sebelum imputasi, sedangkan histogram sebelah kanan menunjukkan distribusi setelah nilai hilang diganti dengan median. Imputasi menambah nilai pada posisi median sehingga jumlah observasi bertambah tanpa memasukkan nilai yang terlalu ekstrem.


Mengidentifikasi Outlier

Saya menggunakan boxplot untuk melihat kemungkinan adanya nilai pendapatan yang ekstrem.

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

Boxplot digunakan untuk melihat penyebaran pendapatan dan mendeteksi nilai yang berada jauh dari kelompok data utama. Nilai yang berada di luar batas whisker merupakan kandidat outlier dan perlu diperiksa lebih lanjut.

Selanjutnya, saya menggunakan metode IQR untuk menentukan batas bawah dan batas atas.

Rumus Interquartile Range adalah:

\[ IQR = Q_3 - Q_1 \]

\[ Batas\ Bawah = Q_1 - 1.5(IQR) \]

\[ Batas\ Atas = Q_3 + 1.5(IQR) \]

q1 <- quantile(
  pelanggan$pendapatan_imputasi,
  0.25
)

q3 <- quantile(
  pelanggan$pendapatan_imputasi,
  0.75
)

iqr <- IQR(
  pelanggan$pendapatan_imputasi
)

batas_bawah <- q1 - 1.5 * iqr

batas_atas <- q3 + 1.5 * iqr

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

Output menunjukkan nilai kuartil pertama, kuartil ketiga, IQR, batas bawah, dan batas atas. Nilai pendapatan yang berada di bawah batas bawah atau di atas batas atas dikategorikan sebagai kandidat outlier berdasarkan aturan IQR.

Data yang berada di luar batas tersebut kemudian saya tandai.

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

Output menampilkan pelanggan yang memiliki pendapatan di luar batas IQR. Nilai tersebut ditandai sebagai TRUE pada variabel outlier_pendapatan. Penandaan dilakukan terlebih dahulu sebelum menentukan tindakan terhadap nilai ekstrem.

Nilai ekstrem tidak langsung saya hapus. Saya menggunakan winsorization untuk membatasi nilai tersebut.

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

Winsorization membatasi nilai yang berada di luar batas IQR tanpa menghapus observasinya. Dengan demikian, jumlah pelanggan tetap dipertahankan tetapi pengaruh nilai ekstrem terhadap analisis dapat dikurangi.


Melakukan Transformasi Data

Saya melakukan normalisasi Min–Maks untuk menyesuaikan nilai ke dalam skala yang sama.

Rumus normalisasi Min–Maks adalah:

\[ x' = \frac{x-x_{\min}} {x_{\max}-x_{\min}} \]

Hasil transformasi berada pada rentang 0 sampai 1.

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)

Nilai usia_minmax dan pendapatan_minmax telah berada pada skala 0 sampai 1. Nilai yang mendekati 0 menunjukkan posisi relatif rendah terhadap nilai minimum, sedangkan nilai yang mendekati 1 menunjukkan posisi relatif tinggi terhadap nilai maksimum.

Saya juga melakukan standardisasi menggunakan Z-Score.

Rumus Z-Score adalah:

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

dengan x sebagai nilai pengamatan, x̄ sebagai rata-rata, dan s sebagai standar deviasi.

pelanggan$usia_z <-
  as.numeric(
    scale(pelanggan$usia_imputasi)
  )

pelanggan$pendapatan_z <-
  as.numeric(
    scale(pelanggan$pendapatan_imputasi)
  )

Z-Score menunjukkan posisi suatu pengamatan terhadap rata-rata dalam satuan standar deviasi. Nilai positif menunjukkan pengamatan berada di atas rata-rata, sedangkan nilai negatif menunjukkan pengamatan berada di bawah rata-rata.

Transformasi decimal scaling digunakan untuk memperkecil skala nilai berdasarkan pangkat sepuluh.

Rumus decimal scaling adalah:

\[ x' = \frac{x}{10^j} \]

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
  )

Transformasi decimal scaling menghasilkan nilai pendapatan dengan skala yang lebih kecil. Transformasi ini tidak mengubah urutan relatif antarobservasi, tetapi membuat angka lebih mudah digunakan pada beberapa metode analisis.


Menyiapkan Data untuk Integrasi

Setelah proses pembersihan, imputasi, dan transformasi selesai, saya menyiapkan dataset pelanggan dan transaksi untuk digabungkan. Sebelum integrasi dilakukan, saya memastikan bahwa identifier pada kedua dataset memiliki nama yang sama dan tidak terdapat duplikasi pada identifier pelanggan.

transaksi <- transaksi_raw

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

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

Output bernilai 0 menunjukkan bahwa setiap customer_id pada data pelanggan sudah unik. Kondisi ini penting karena satu pelanggan seharusnya hanya memiliki satu identitas pada dataset pelanggan.

sum(
  duplicated(
    transaksi$customer_id
  )
)
## [1] 0

Output bernilai 0 menunjukkan bahwa tidak terdapat customer_id yang berulang pada dataset transaksi. Identifier pada kedua dataset sudah memenuhi syarat untuk digunakan sebagai variabel penghubung.

Saya memeriksa identifier yang hanya terdapat pada salah satu dataset.

id_tanpa_transaksi <- setdiff(
  pelanggan$customer_id,
  transaksi$customer_id
)

id_tanpa_pelanggan <- setdiff(
  transaksi$customer_id,
  pelanggan$customer_id
)

id_tanpa_transaksi
## [1] "C011"

Output menunjukkan pelanggan yang terdapat pada dataset pelanggan tetapi tidak memiliki pasangan pada dataset transaksi. Kondisi tersebut tidak langsung dianggap sebagai kesalahan karena pelanggan dapat saja belum melakukan transaksi.

id_tanpa_pelanggan
## [1] "C012"

Output menunjukkan identifier transaksi yang tidak ditemukan pada dataset pelanggan. Jika terdapat nilai, data tersebut perlu diperiksa karena menunjukkan adanya transaksi yang belum memiliki informasi pelanggan.


Mengintegrasikan Data Pelanggan dan Transaksi

Saya menggabungkan kedua dataset menggunakan customer_id. Saya menggunakan merge() dengan all.x = TRUE agar seluruh pelanggan tetap dipertahankan, termasuk pelanggan yang belum memiliki transaksi.

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

data_terintegrasi
##    customer_id   nama usia pendapatan      kota      status pendapatan_imputasi
## 1         C001    Ani   21    4.5e+06 Pekanbaru       Aktif             4.5e+06
## 2         C002   Budi   25         NA Pekanbaru       Aktif             4.9e+06
## 3         C003  Citra   23    5.2e+06 Pekanbaru       Aktif             5.2e+06
## 4         C004   Dodi   50    4.8e+06     Dumai       Aktif             4.8e+06
## 5         C005    Eka   27    4.9e+06 Pekanbaru Tidak Aktif             4.9e+06
## 6         C006   Fani   NA    5.1e+06     Dumai Tidak Aktif             5.1e+06
## 7         C007 Gilang   31    5.0e+08 Pekanbaru       Aktif             5.0e+08
## 8         C008   Hana   29    4.7e+06      Siak       Aktif             4.7e+06
## 9         C009  Indra   22    4.6e+06 Pekanbaru       Aktif             4.6e+06
## 10        C010   Joko   35    5.3e+06     Dumai Tidak Aktif             5.3e+06
## 11        C011   Kiki   28         NA      <NA>       Aktif             4.9e+06
##    usia_imputasi   kota_imputasi pendapatan_missing outlier_pendapatan
## 1           21.0       Pekanbaru                  0              FALSE
## 2           25.0       Pekanbaru                  1              FALSE
## 3           23.0       Pekanbaru                  0              FALSE
## 4           50.0           Dumai                  0              FALSE
## 5           27.0       Pekanbaru                  0              FALSE
## 6           27.5           Dumai                  0              FALSE
## 7           31.0       Pekanbaru                  0               TRUE
## 8           29.0            Siak                  0              FALSE
## 9           22.0       Pekanbaru                  0              FALSE
## 10          35.0           Dumai                  0              FALSE
## 11          28.0 Tidak diketahui                  1              FALSE
##    pendapatan_winsor usia_minmax pendapatan_minmax       usia_z pendapatan_z
## 1            4500000  0.00000000      0.0000000000 -0.984199667   -0.3041235
## 2            4900000  0.13793103      0.0008072654 -0.489287834   -0.3014440
## 3            5200000  0.06896552      0.0014127144 -0.736743750   -0.2994343
## 4            4800000  1.00000000      0.0006054490  2.603911118   -0.3021138
## 5            4900000  0.20689655      0.0008072654 -0.241831918   -0.3014440
## 6            5100000  0.22413793      0.0012108981 -0.179967939   -0.3001042
## 7            5750000  0.34482759      1.0000000000  0.253079914    3.0151095
## 8            4700000  0.27586207      0.0004036327  0.005623998   -0.3027837
## 9            4600000  0.03448276      0.0002018163 -0.860471709   -0.3034536
## 10           5300000  0.48275862      0.0016145308  0.747991747   -0.2987645
## 11           4900000  0.24137931      0.0008072654 -0.118103960   -0.3014440
##    pendapatan_decimal jumlah_transaksi total_purchase
## 1              0.0045                5        1.5e+06
## 2              0.0049                3        9.0e+05
## 3              0.0052                7        2.7e+06
## 4              0.0048                2        6.0e+05
## 5              0.0049                6        2.1e+06
## 6              0.0051                4        1.3e+06
## 7              0.5000               20        2.5e+07
## 8              0.0047                5        1.7e+06
## 9              0.0046                3        8.0e+05
## 10             0.0053                8        3.2e+06
## 11             0.0049               NA             NA

Output menunjukkan data pelanggan yang telah digabungkan dengan informasi transaksi berdasarkan customer_id. Seluruh pelanggan dari dataset pelanggan tetap dipertahankan. Pelanggan yang tidak memiliki pasangan transaksi akan memiliki nilai NA pada variabel transaksi.

Saya memeriksa nilai kosong yang muncul pada variabel transaksi setelah proses integrasi.

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

Nilai NA pada jumlah_transaksi dan total_purchase menunjukkan pelanggan yang tidak memiliki data transaksi. Berdasarkan konteks dataset, kondisi tersebut diinterpretasikan sebagai pelanggan yang belum melakukan transaksi sehingga nilai tersebut akan diganti menjadi 0.

Saya mengganti NA pada variabel transaksi menjadi 0.

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

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

Nilai NA pada variabel transaksi telah diganti menjadi 0. Dengan demikian, pelanggan yang tidak memiliki transaksi tetap dipertahankan dalam dataset dan dapat dibedakan dari pelanggan yang memiliki transaksi.


Menyusun Dataset Akhir

Setelah integrasi selesai, saya memilih variabel yang diperlukan untuk membentuk dataset akhir. Variabel hasil transformasi yang tidak digunakan dalam analisis akhir tidak dimasukkan kembali agar struktur data tetap sederhana.

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",
    "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
##    jumlah_transaksi total_purchase
## 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

Output menunjukkan dataset akhir yang telah melalui seluruh tahapan preprocessing dan integrasi. Dataset ini memuat identitas pelanggan, informasi demografi, pendapatan hasil imputasi, indikator missing value, indikator outlier, hasil normalisasi, serta informasi transaksi.


Validasi Dataset Akhir

Setelah dataset akhir terbentuk, saya melakukan satu tahap validasi untuk memastikan hasil preprocessing sudah sesuai. Pemeriksaan ini menjadi validasi akhir sehingga tidak perlu mengulang audit yang sama pada setiap tahap.

validasi_akhir <- data.frame(
  pemeriksaan = c(
    "Jumlah baris",
    "Jumlah kolom",
    "Duplikasi customer_id",
    "Total missing value",
    "Kategori kota",
    "Kategori status"
  ),
  
  hasil = c(
    nrow(data_final),
    ncol(data_final),
    sum(
      duplicated(
        data_final$customer_id
      )
    ),
    sum(
      is.na(data_final)
    ),
    length(
      unique(data_final$kota)
    ),
    length(
      unique(data_final$status)
    )
  )
)

validasi_akhir
##             pemeriksaan hasil
## 1          Jumlah baris    11
## 2          Jumlah kolom    12
## 3 Duplikasi customer_id     0
## 4   Total missing value     0
## 5         Kategori kota     4
## 6       Kategori status     2

Tabel validasi akhir memberikan gambaran kondisi dataset setelah seluruh proses selesai. Jumlah duplikasi customer_id digunakan untuk memastikan identifier tetap unik, sedangkan jumlah missing value digunakan untuk memastikan tidak terdapat nilai kosong pada dataset akhir. Jumlah kategori kota dan status digunakan untuk memastikan standardisasi kategori telah berhasil dilakukan.

Saya memastikan kembali bahwa seluruh customer_id pada dataset akhir bersifat unik.

all(
  !duplicated(
    data_final$customer_id
  )
)
## [1] TRUE

Output TRUE menunjukkan bahwa seluruh customer_id pada dataset akhir bersifat unik. Kondisi tersebut menunjukkan bahwa tidak terdapat duplikasi identifier setelah proses integrasi.

Saya juga memastikan bahwa dataset akhir tidak memiliki missing value.

all(
  !is.na(data_final)
)
## [1] TRUE

Output TRUE menunjukkan bahwa dataset akhir tidak memiliki nilai NA. Missing value yang sebelumnya terdapat pada data pelanggan telah ditangani melalui imputasi, sedangkan NA yang muncul akibat tidak adanya transaksi telah diubah menjadi 0 berdasarkan konteks data.


Membandingkan Kondisi Data Sebelum dan Sesudah Preprocessing

Untuk melihat perubahan yang terjadi secara keseluruhan, saya membuat tabel perbandingan antara data awal dan data akhir.

perbandingan <- data.frame(
  indikator = c(
    "Jumlah baris",
    "Jumlah kolom",
    "Duplikasi customer_id",
    "Total missing value",
    "Kategori kota unik",
    "Kategori status unik"
  ),
  
  sebelum = c(
    nrow(pelanggan_raw),
    ncol(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),
    ncol(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          Jumlah kolom       6      12
## 3 Duplikasi customer_id       1       0
## 4   Total missing value       4       0
## 5    Kategori kota unik      10       4
## 6  Kategori status unik       8       2

Tabel perbandingan menunjukkan perubahan kondisi dataset dari sebelum hingga sesudah preprocessing. Jumlah baris dapat berubah karena adanya duplikasi yang dihapus, sedangkan jumlah missing value berkurang setelah dilakukan imputasi dan penanganan NA akibat integrasi. Jumlah kategori kota dan status juga menjadi lebih konsisten setelah dilakukan standardisasi.


Menyimpan Hasil Preprocessing

Setelah seluruh proses preprocessing dan validasi selesai, saya menyimpan dataset akhir dan catatan perubahan ke dalam file CSV.

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

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

Dataset akhir disimpan sebagai data_pelanggan_setelah_preprocessing.csv, sedangkan catatan perubahan disimpan sebagai log_perubahan_preprocessing.csv. Kedua file tersebut dapat digunakan kembali untuk analisis berikutnya dan dokumentasi proses preprocessing.

Saya memeriksa lokasi penyimpanan kedua file tersebut.

file.exists(
  "data_pelanggan_setelah_preprocessing.csv"
)
## [1] TRUE
file.exists(
  "log_perubahan_preprocessing.csv"
)
## [1] TRUE

Output TRUE menunjukkan bahwa file telah berhasil dibuat pada direktori kerja RStudio. Dataset akhir dan log perubahan dapat digunakan sebagai hasil akhir dari proses preprocessing.


Pertanyaan Refleksi

1. Apakah dataset tanpa missing value selalu lebih berkualitas?

Tidak. Dataset tanpa missing value belum tentu lebih berkualitas karena kualitas data tidak hanya ditentukan oleh ada atau tidaknya nilai yang hilang. Data juga perlu diperiksa dari aspek konsistensi, ketepatan, keunikan, validitas, dan kesesuaian dengan konteks analisis. Pengisian missing value dengan metode yang tidak tepat bahkan dapat menghasilkan data yang terlihat lengkap tetapi tidak menggambarkan kondisi sebenarnya.

Pada proses yang saya lakukan, nilai yang hilang tidak langsung dihapus. Saya terlebih dahulu memeriksa jumlah dan posisi missing value, kemudian menentukan metode penanganan yang sesuai. Pendekatan tersebut membantu menjaga informasi yang masih dapat digunakan dari dataset.


2. Mengapa outlier tidak boleh otomatis dihapus?

Outlier tidak boleh langsung dihapus karena nilai tersebut belum tentu merupakan kesalahan pencatatan. Nilai ekstrem dapat menunjukkan kondisi yang memang benar-benar terjadi pada objek pengamatan. Penghapusan tanpa pemeriksaan dapat menyebabkan hilangnya informasi penting dan mengubah karakteristik data.

Pada dataset yang saya gunakan, nilai pendapatan yang sangat besar ditandai sebagai outlier menggunakan metode IQR. Saya tidak langsung menghapus nilai tersebut, tetapi melakukan pemeriksaan dan menggunakan winsorization untuk membatasi pengaruh nilai ekstrem. Tindakan tersebut dilakukan agar informasi pengamatan tetap dipertahankan sekaligus mengurangi pengaruh ekstrem terhadap analisis.


3. Bagaimana preprocessing dapat menimbulkan bias?

Preprocessing dapat menimbulkan bias apabila metode yang digunakan untuk membersihkan atau mengubah data tidak sesuai dengan karakteristik data. Contohnya adalah mengisi seluruh nilai yang hilang dengan rata-rata, menghapus semua outlier, atau menyeragamkan kategori tanpa mempertimbangkan makna sebenarnya dari setiap kategori. Perlakuan tersebut dapat mengubah distribusi data dan menghasilkan kesimpulan yang berbeda dari kondisi sebenarnya.

Bias juga dapat muncul apabila keputusan preprocessing hanya didasarkan pada kemudahan pengolahan. Oleh karena itu, setiap perubahan data perlu memiliki alasan yang jelas dan dicatat. Pada proses yang saya lakukan, perubahan seperti standardisasi kategori, koreksi usia, imputasi, dan penanganan outlier dicatat dalam log_perubahan agar prosesnya dapat ditelusuri.


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

Parameter imputasi dan transformasi seharusnya dihitung dari data pelatihan agar informasi dari data pengujian tidak masuk ke dalam proses pembentukan model. Jika rata-rata, median, nilai minimum, nilai maksimum, atau standar deviasi dihitung menggunakan seluruh data sebelum pemisahan data pelatihan dan pengujian, informasi dari data pengujian dapat memengaruhi proses analisis.

Kondisi tersebut disebut data leakage dan dapat menyebabkan performa model terlihat lebih baik daripada kemampuan sebenarnya. Oleh karena itu, parameter seperti median untuk imputasi, nilai minimum dan maksimum untuk normalisasi, serta rata-rata dan standar deviasi untuk standardisasi sebaiknya dihitung hanya menggunakan data pelatihan. Parameter yang telah diperoleh kemudian digunakan untuk mentransformasi data pelatihan maupun data pengujian dengan aturan yang sama.


5. Apa risiko integrasi data jika identifier tidak unik?

Identifier yang tidak unik dapat menyebabkan kesalahan dalam proses integrasi karena satu objek dapat dicocokkan dengan lebih dari satu baris data. Kondisi tersebut dapat menghasilkan baris yang berlipat ganda, menyebabkan jumlah transaksi atau nilai lainnya terhitung lebih dari sekali, serta menghasilkan informasi yang tidak sesuai.

Pada proses yang saya lakukan, customer_id diperiksa terlebih dahulu sebelum data pelanggan dan transaksi digabungkan. Saya memastikan bahwa identifier pelanggan tidak memiliki duplikasi sehingga proses integrasi dapat dilakukan dengan lebih aman. Pemeriksaan seperti duplicated() dan setdiff() membantu saya mengetahui apakah terdapat identifier yang berulang atau tidak memiliki pasangan pada dataset lainnya.


Kesimpulan

Melalui proses data preprocessing ini, saya memulai pengolahan dengan memeriksa kondisi awal data pelanggan dan data transaksi. Pemeriksaan tersebut membantu saya menemukan beberapa permasalahan, seperti nilai yang hilang, kategori yang belum konsisten, identitas pelanggan yang berulang, nilai usia yang tidak sesuai, dan nilai pendapatan yang ekstrem.

Saya kemudian melakukan pembersihan dan standardisasi data, menangani missing value, mengidentifikasi outlier, melakukan transformasi, serta mengintegrasikan data pelanggan dengan data transaksi. Setiap perubahan dilakukan berdasarkan hasil pemeriksaan terhadap kondisi data dan dicatat dalam log_perubahan.

Tahap integrasi dilakukan setelah kedua dataset memiliki identifier yang konsisten dan tidak memiliki duplikasi. Seluruh pelanggan tetap dipertahankan melalui penggunaan all.x = TRUE, sedangkan nilai NA pada informasi transaksi diubah menjadi 0 karena menunjukkan tidak adanya transaksi.

Hasil akhirnya adalah data_final, yaitu dataset yang telah melalui proses pemeriksaan, pembersihan, imputasi, penanganan outlier, transformasi, dan integrasi. Dataset tersebut kemudian divalidasi untuk memastikan identifier unik dan tidak terdapat missing value sehingga dapat digunakan pada tahap analisis selanjutnya.


Daftar Pustaka

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