Capaian Kasus

Setelah menyelesaikan praktikum, mahasiswa mampu:

  1. Menjelaskan kondisi data yang menyebabkan preprocessing diperlukan;
  2. Mengidentifikasi masalah kualitas data menggunakan R;
  3. Menerapkan data cleaning secara sistematis;
  4. Memilih dan menerapkan strategi penanganan missing values;
  5. Mendeteksi serta mengevaluasi outlier;
  6. Melakukan transformasi atribut numerik; dan
  7. Mengintegrasikan dua sumber data serta memvalidasi hasilnya.

Studi Kasus

Sebuah perusahaan e-commerce akan melakukan analisis pelanggan. Data berasal dari dua sumber:

  • data pelanggan, yang memuat identitas, usia, kota, pendapatan, dan status pelanggan;
  • data transaksi, yang memuat jumlah transaksi dan total pembelian.

Data belum dapat langsung digunakan karena mengandung nilai hilang, kategori tidak konsisten, duplikasi, nilai ekstrem, dan perbedaan nama identifier. Mahasiswa diminta menyiapkan data tersebut sampai menjadi satu dataset analisis yang bersih.

Prinsip utama: jangan membersihkan data hanya karena sebuah nilai terlihat aneh. Setiap perubahan harus memiliki alasan, aturan, dan catatan.

Persiapan RStudio

Memeriksa lingkungan kerja

getwd()
## [1] "C:/Users/Lenovo/Downloads"
R.version.string
## [1] "R version 4.4.1 (2024-06-14 ucrt)"

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

  1. Fungsi getwd() digunakan untuk mengetahui lokasi file yang dibaca atau disimpan selama analisis di R.
  2. Fungsi R.version.string digunakan untuk mengetahui versi R yang sedang digunakan.
  3. Pengecekan versi R membantu memastikan kode dapat dijalankan dengan baik dan hasil analisis lebih mudah direproduksi.

Membangun Dataset

Jalankan kode berikut. Dataset sengaja dibuat “berantakan/mengandung kesalahan” agar seluruh tahap preprocessing dapat dipraktikkan.

Dataset pelanggan

options(scipen = 999)
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    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
## 7         C007 Gilang   31  500000000 Pekanbaru        Aktif
## 8         C008   Hana   29    4700000       Siak       AKTIF
## 9         C009  Indra   22    4600000        PKU           A
## 10        C010   Joko   35    5300000      Dumai Tidak aktif
## 11        C010   Joko   35    5300000      Dumai Tidak aktif
## 12        C011   Kiki   28         NA       <NA>       Aktif

Interpretasi:

  1. Data pelanggan terdiri dari 12 baris dengan beberapa variabel, yaitu customer_id, nama, usia, pendapatan, kota, dan status. 2.Terdapat data duplikat, yaitu customer_id C010 muncul dua kali dengan informasi yang sama.
  2. Terdapat data yang hilang (NA) pada variabel pendapatan untuk Budi (C002) dan Kiki (C011).
  3. Variabel kota pada C011 juga memiliki nilai NA.
  4. Terdapat ketidakkonsistenan penulisan data, misalnya nama kota ditulis sebagai Pekanbaru, PEKANBARU, pekanbaru, PKU, dan Dumai.
  5. Variabel status juga memiliki penulisan yang tidak konsisten, seperti Aktif, AKTIF, A, aktif, dan Tidak aktif.
  6. Terdapat nilai usia 150 tahun pada Dodi (C004) yang perlu diperiksa karena tidak wajar dan kemungkinan merupakan kesalahan input.
  7. Pendapatan Gilang (C007) sebesar 500 juta juga terlihat jauh lebih tinggi dibandingkan sebagian besar pelanggan lainnya sehingga perlu diperiksa lebih lanjut.

Secara keseluruhan, data masih berupa data mentah dan perlu dilakukan pembersihan (data cleaning) sebelum digunakan untuk analisis lebih lanjut.

Berikut data transaksi setiap pelanggan

options(scipen = 999)
transaksi_raw
##    cust_id jumlah_transaksi total_purchase
## 1     C001                5        1500000
## 2     C002                3         900000
## 3     C003                7        2700000
## 4     C004                2         600000
## 5     C005                6        2100000
## 6     C006                4        1300000
## 7     C007               20       25000000
## 8     C008                5        1700000
## 9     C009                3         800000
## 10    C010                8        3200000
## 11    C012                1         250000

Interpretasi :

  1. Data terdiri dari 11 transaksi dengan tiga variabel, yaitu cust_id, jumlah_transaksi, dan total_purchase.
  2. cust_id menunjukkan identitas pelanggan yang melakukan transaksi.
  3. jumlah_transaksi menunjukkan banyaknya transaksi yang dilakukan oleh masing-masing pelanggan.
  4. total_purchase menunjukkan total nilai pembelian pelanggan.
  5. Pelanggan C007 memiliki jumlah transaksi terbanyak, yaitu 20 transaksi, dengan total pembelian Rp25.000.000.
  6. Nilai transaksi paling rendah terdapat pada C012, yaitu 1 transaksi dengan total pembelian Rp250.000.

Data transaksi ini dapat digunakan untuk melihat aktivitas dan nilai pembelian setiap pelanggan.Sebelum analisis lebih lanjut, data perlu diperiksa untuk memastikan tidak terdapat kesalahan atau ketidaksesuaian data.

Bagian I — Konsep Data Preprocessing

Memahami struktur data

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

Dimensi dataset pelanggan

dim(pelanggan_raw)
## [1] 12  6

Interpretasi:

  1. Fungsi dim(pelanggan_raw) menghasilkan 12 dan 6.
  2. Angka 12 menunjukkan bahwa dataset “pelanggan_raw” memiliki 12 baris, yaitu terdapat 12 data pelanggan.
  3. Angka 6 menunjukkan bahwa dataset memiliki 6 kolom atau variabel, yaitu customer_id, nama, usia, pendapatan, kota, dan status.

Dengan demikian, struktur awal data pelanggan_raw adalah 12 observasi dan 6 variabel. Informasi ini digunakan sebagai gambaran awal untuk mengetahui ukuran dataset sebelum dilakukan proses pembersihan dan analisis data.

Variabel dataset pelanggan

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

Interpretasi:

  1. Fungsi names(pelanggan_raw) digunakan untuk menampilkan nama-nama variabel atau kolom yang terdapat dalam dataset.
  2. Dataset pelanggan_raw memiliki 6 variabel, yaitu:
  1. customer_id → identitas pelanggan.
  2. nama → nama pelanggan.
  3. usia → usia pelanggan.
  4. pendapatan → pendapatan pelanggan.
  5. kota → kota tempat pelanggan.
  6. status → status pelanggan.

Hasil tersebut menunjukkan bahwa nama kolom sudah tersedia dan dapat digunakan sebagai acuan dalam proses pengolahan serta pembersihan data.

Struktur dataset pelanggan

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  4500000 NA 5200000 4800000 4900000 5100000 500000000 4700000 4600000 5300000 ...
##  $ kota       : chr  "Pekanbaru" " PKU" "PEKANBARU" "Dumai" ...
##  $ status     : chr  "Aktif" "aktif" "ACTIVE" "A" ...

Interpretasi

  1. Fungsi str(pelanggan_raw) digunakan untuk melihat struktur dan tipe data dari setiap variabel dalam dataset.
  2. Dataset pelanggan_raw terdiri dari 12 observasi dan 6 variabel.
  3. Variabel customer_id, nama, kota, dan status bertipe character (chr), sehingga berisi data berupa teks.
  4. Variabelusia dan pendapatan bertipe numeric (num), sehingga dapat digunakan dalam perhitungan numerik. Terdapat nilai NA yang menunjukkan adanya data yang hilang.
  5. Variabel kota dan status masih memiliki penulisan yang tidak seragam, seperti “Pekanbaru, PKU, PEKANBARU”, serta “Aktif, aktif, dan AKTIF”.

Hasil ini menunjukkan bahwa struktur data sudah terbentuk dengan baik, tetapi masih diperlukan pengecekan nilai yang hilang dan konsistensi data sebelum dilakukan analisis lebih lanjut.

Ringkasan dataset pelanggan

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

Berdasarkan hasil summary(pelanggan_raw), dataset pelanggan terdiri dari 12 observasi dengan variabel numerik usia dan pendapatan, serta variabel karakter berupa customer_id, nama, kota, dan status. Variabel usia memiliki rata-rata 28,73 tahun, dengan usia termuda 21 tahun dan tertua 150 tahun. Nilai 150 tahun terlihat tidak wajar sehingga perlu diperiksa sebagai kemungkinan kesalahan input. Terdapat 1 data usia yang hilang (NA).

Pada variabel pendapatan, nilai minimum adalah Rp4.500.000, maksimum Rp500.000.000, dan rata-ratanya Rp54.440.000. Terdapat 2 data pendapatan yang hilang (NA). Perbedaan yang cukup besar antara nilai maksimum dan nilai lainnya menunjukkan adanya nilai pendapatan yang sangat tinggi, yaitu Rp500.000.000, sehingga perlu diperiksa lebih lanjut.

Sementara itu, variabel customer_id, nama, kota, dan status merupakan data bertipe karakter. Hasil ringkasan ini menunjukkan bahwa sebelum analisis dilakukan, data perlu melalui tahap pemeriksaan dan pembersihan, terutama pada data yang hilang, nilai usia yang tidak wajar, serta nilai pendapatan yang sangat tinggi

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 isi tabel. Misalnya, accuracy memerlukan pembanding terhadap kondisi sebenarnya dan timeliness membutuhkan informasi waktu pemutakhiran.

Pemeriksaan awal berikut berfokus pada masalah yang dapat dideteksi dari dataset.

Missing value beserta persentasenya

# Banyak missing value per atribut
colSums(is.na(pelanggan_raw))
## customer_id        nama        usia  pendapatan        kota      status 
##           0           0           1           2           1           0

Berdasarkan hasil colSums(is.na(pelanggan_raw)), dapat diketahui jumlah missing value (data yang hilang) pada setiap variabel dalam dataset pelanggan:

  1. Variabel customer_id, nama dan status memiliki 0 missing value, sehingga seluruh data pelanggan tersedia.
  2. Variabel usia dan kota memiliki 1 missing value, sehingga masing masing variabel terdapat satu pelanggan yang belum memiliki data usia dan data informasi kota.
  3. Variabel pendapatan memiliki 2 missing value, sehingga terdapat dua pelanggan yang belum memiliki data pendapatan.

Secara keseluruhan, missing value terdapat pada tiga variabel, yaitu usia, pendapatan, dan kota. Data tersebut perlu ditangani pada tahap pembersihan agar tidak memengaruhi hasil analisis selanjutnya.

# Persentase missing value per atribut
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

Berdasarkan hasil perhitungan, variabel usia dan kota memiliki missing value sebesar 8,33, sedangkan pendapatan memiliki missing value sebesar 16,67%. Sementara itu, customer_id, nama, dan status tidak memiliki missing value sehingga persentasenya 0.00. Dengan demikian, pendapatan memiliki persentase data hilang paling tinggi dan perlu diperhatikan dalam proses pembersihan data.

Data duplikat

# Banyak baris duplikat penuh
sum(duplicated(pelanggan_raw))
## [1] 1
# Banyak customer_id yang berulang
sum(duplicated(pelanggan_raw$customer_id))
## [1] 1

Berdasarkan hasil pemeriksaan, terdapat 1 baris data yang terduplikasi pada dataset pelanggan_raw. Pemeriksaan berdasarkan customer_id juga menunjukkan terdapat 1 data customer_id yang berulang, sehingga data-data duplikat tersebut perlu diperiksa dan ditangani agar tidak memengaruhi hasil analisis.

Penulisan data yang tidak konsisten

# Kategori yang tercatat pada atribut kota dan status
sort(unique(pelanggan_raw$kota))
## [1] " PKU"       "Dumai"      "DUMAI"      "pekanbaru"  "Pekanbaru" 
## [6] "PEKANBARU"  "Pekanbaru " "PKU"        "Siak"
sort(unique(pelanggan_raw$status))
## [1] "A"           "ACTIVE"      "aktif"       "Aktif"       "AKTIF"      
## [6] "nonaktif"    "Tidak aktif" "Tidak Aktif"

Berdasarkan hasil pemeriksaan, terdapat ketidakkonsistenan penulisan pada atribut kota dan status. Pada atribut kota, satu kota ditulis dalam beberapa bentuk, seperti “Pekanbaru”, “pekanbaru”, “PEKANBARU”, dan “PKU”, sedangkan pada atribut status terdapat variasi seperti “Aktif”, “aktif”, “AKTIF”, “ACTIVE”, dan “A”. Kondisi ini menunjukkan bahwa kategori belum seragam sehingga perlu dilakukan standardisasi penulisan agar setiap kategori memiliki format yang konsisten.

outlier

# Rentang atribut numerik
range(pelanggan_raw$usia, na.rm = TRUE)
## [1]  21 150
range(pelanggan_raw$pendapatan, na.rm = TRUE)
## [1]   4500000 500000000

Berdasarkan hasil pemeriksaan tersebut, variabel usia memiliki rentang 21 – 150 tahun, sedangkan pendapatan memiliki rentang Rp 4.500.000 – Rp 500.000.000. Nilai usia 150 tahun dan pendapatan Rp500.000.000 terlihat sangat ekstrem dibandingkan nilai lainnya sehingga terindikasi sebagai adanya data outlier dan perlu diperiksa lebih lanjut sebelum dilakukan analisis.

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

Berikut ini Hasil secara keseluruhan, pemeriksaan ini memberikan gambaran umum mengenai kondisi kualitas data sebelum dilakukan proses cleaning lebih lanjut. Hasil audit bukan keputusan cleaning. Audit hanya memberi sinyal atribut mana yang perlu diperiksa lebih lanjut.

Bagian II — Penerapan Data Cleaning

Membuat salinan kerja

Pertahankan data mentah agar setiap perubahan dapat dilacak.

pelanggan <- pelanggan_raw
pelanggan
##    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
## 7         C007 Gilang   31  500000000 Pekanbaru        Aktif
## 8         C008   Hana   29    4700000       Siak       AKTIF
## 9         C009  Indra   22    4600000        PKU           A
## 10        C010   Joko   35    5300000      Dumai Tidak aktif
## 11        C010   Joko   35    5300000      Dumai Tidak aktif
## 12        C011   Kiki   28         NA       <NA>       Aktif

Proses standarisasi

Membersihkan spasi dan kapitalisasi

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

# Menyeragamkan huruf menjadi kecil untuk proses pencocokan
pelanggan$kota <- tolower(pelanggan$kota)
pelanggan$status <- tolower(pelanggan$status)

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

Tahapan standardisasi ini berfungsi untuk menunjukkan bahwa spasi yang tidak diperlukan dan perbedaan kapitalisasi telah dibersihkan, sehingga kategori pada variabel kota menjadi seragam dalam huruf kecil. Kategori yang tersisa adalah “dumai”, “pekanbaru”, “pku”, dan “siak”, yang selanjutnya dapat digunakan sebagai dasar untuk proses penyeragaman kategori sesuai aturan yang telah ditentukan.

Menyeragamkan kategori

Standardisasi dilakukan menggunakan aturan domain. Dalam praktik nyata, aturan perlu dikonfirmasi melalui kamus data atau pemilik data.

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

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

Tahap standardisasi ini menunjukkan bahwa seluruh kategori pada variabel kota dan status telah berhasil diseragamkan. Kategori kota kini terdiri dari Dumai, Pekanbaru, dan Siak, sedangkan kategori status menjadi Aktif dan Tidak Aktif. Dengan demikian, perbedaan penulisan seperti kapitalisasi, singkatan, dan variasi kategori telah diperbaiki sehingga data lebih konsisten dan siap digunakan untuk tahap preprocessing berikutnya.

Data duplikat

Mendeteksi duplikasi

# Menampilkan seluruh baris dengan customer_id 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

Menghapus duplikasi

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

dim(pelanggan)
## [1] 11  6

Hasil pemeriksaan menunjukkan bahwa terdapat satu customer_id yang terduplikasi, yaitu “C010”, yang muncul sebanyak dua kali dengan informasi yang sama. Setelah dilakukan penghapusan baris dengan mempertahankan kemunculan pertama, jumlah data berkurang dari 12 menjadi 11 baris, sementara jumlah variabel tetap 6 kolom. Dengan demikian, data duplikat berhasil dihapus tanpa mengubah struktur variabel.

Catatan: menghapus duplikasi hanya aman jika setiap customer_id memang harus mewakili satu pelanggan. Jika satu pelanggan boleh memiliki banyak baris, tindakan ini justru salah.

Memeriksa aturan domain

# Kandidat usia tidak valid berdasarkan aturan bisnis 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>
# Kandidat pendapatan tidak valid jika 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>

Nilai usia 150 melanggar aturan domain. Misalkan pemeriksaan terhadap formulir asli menunjukkan bahwa nilai yang benar adalah 50.

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

Pemeriksaan aturan domain dilakukan untuk memastikan bahwa nilai dalam dataset sesuai dengan batas atau aturan yang telah ditentukan. Pada tahap ini, usia diperiksa berdasarkan rentang 15–100 tahun dan pendapatan diperiksa agar tidak bernilai negatif. Hasil pemeriksaan menunjukkan bahwa usia 150 tahun pada C004 tidak memenuhi aturan domain, sehingga setelah diverifikasi sebagai kesalahan input, nilainya dikoreksi menjadi 50 tahun.

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

Pembuatan log_perubahan dilakukan untuk mendokumentasikan setiap perubahan yang diterapkan pada dataset selama proses data cleaning. Tabel ini mencatat tahap proses, atribut yang mengalami perubahan, serta tindakan yang dilakukan, sehingga perubahan data dapat ditelusuri dengan jelas. Pada hasil tersebut, proses yang dilakukan meliputi standardisasi pada atribut kota dan status, deduplikasi pada customer_id dengan menghapus kemunculan kedua C010, serta koreksi domain pada atribut usia, yaitu mengubah usia C004 dari 150 menjadi 50 berdasarkan sumber asli. Dengan adanya log_perubahan, proses pembersihan data menjadi lebih transparan, sistematis, terdokumentasi, dan dapat dipertanggungjawabkan, karena setiap perubahan memiliki alasan dan catatan yang jelas.

Bagian III — Penanganan Missing Values

Mengidentifikasi lokasi nilai hilang

colSums(is.na(pelanggan))
## customer_id        nama        usia  pendapatan        kota      status 
##           0           0           1           2           1           0
# Baris yang memiliki sedikitnya satu 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

Berdasarkan hasil identifikasi tersebut, terdapat missing value pada tiga variabel, yaitu usia sebanyak 1 data, pendapatan sebanyak 2 data, dan kota sebanyak 1 data. Variabel customer_id, nama, dan status tidak memiliki missing value. Dari pemeriksaan baris yang tidak lengkap, terdapat 3 pelanggan, yaitu C002 dengan data pendapatan yang hilang, C006 dengan data usia yang hilang, serta C011 dengan data pendapatan dan kota yang hilang. Hasil ini menjadi dasar untuk menentukan strategi penanganan missing value pada tahap selanjutnya.

Strategi 1: menghapus baris

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

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

Dari hasil analisis yang dilakukan, diketahui bahwa data awal pelanggan memiliki total 11 baris observasi. Kemudian, diterapkan strategi pertama yaitu penghapusan baris (listwise deletion) dengan menggunakan fungsi complete.cases(), yang berfungsi untuk memilih hanya baris-baris yang tidak memiliki satupun nilai kosong (missing value) di seluruh kolomnya. Setelah proses ini, diperoleh objek baru bernama pelanggan_complete yang hanya menyisakan 8 baris data lengkap. Artinya, terdapat 3 baris yang terpaksa harus dibuang karena mengandung setidaknya satu nilai yang hilang.

Pertanyaan: berapa persen data yang hilang jika seluruh baris tidak lengkap dihapus?

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

Ketika kita menghitung proporsi data yang hilang akibat penghapusan ini, rumus yang digunakan adalah membandingkan jumlah baris yang tersisa dengan jumlah baris awal, lalu dikonversi ke dalam persentase kerugian data. Perhitungan tersebut menghasilkan angka 27,27%. Ini berarti lebih dari seperempat dari total dataset asli (tepatnya 3 dari 11 baris) harus dikorbankan hanya karena setiap baris tersebut memiliki kekurangan pada satu atau lebih variabel. Meskipun metode ini sederhana dan mudah diterapkan, konsekuensinya sangat signifikan karena kita kehilangan porsi data yang tidak kecil.

Kerugian sebesar 27,27% bukanlah angka yang bisa diabaikan, terutama jika ukuran sampel awal memang sudah terbatas. Dengan membuang hampir sepertiga data, kita berisiko mengurangi kekuatan statistik (statistical power), meningkatkan bias jika data yang hilang tidak terjadi secara acak (misalnya jika baris yang hilang memiliki karakteristik tertentu), dan tentu saja menyusutkan informasi yang tersedia untuk analisis lebih lanjut. Dalam konteks ini, strategi penghapusan baris memang hanya ditunjukkan sebagai demonstrasi awal, bukan sebagai rekomendasi akhir.

Jika pola data yang kosong bersifat acak, imputasi bisa menjadi pilihan yang lebih bijak untuk mempertahankan ukuran sampel, sedangkan jika ternyata data hilang secara sistematis, penghapusan baris justru bisa memperparah kesalahan inferensi. Oleh karena itu, persentase 27,27% ini adalah peringatan awal bahwa data perlu ditangani lebih hati-hati, dan keputusan untuk menghapus baris sebaiknya tidak diambil tanpa eksplorasi lebih lanjut terhadap pola missing value yang ada.

Strategi 2: imputasi mean dan median

Pada bagian ini, kita mulai memasuki strategi imputasi, yaitu mengisi nilai kosong (missing values) dengan nilai pengganti tertentu, sebagai alternatif dari penghapusan baris yang telah dilakukan sebelumnya.Pendapatan memiliki nilai ekstrem. Bandingkan mean dan median sebelum menentukan imputasi.

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

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

Karena distribusi pendapatan dalam data ini sangat tidak simetris atau miring (skewed) yang disebabkan oleh adanya satu nilai ekstrem sebesar 500 juta, maka nilai rata-rata (mean) menjadi tidak representatif karena sangat terpengaruh oleh pencilan tersebut. Hal ini terbukti dari output yang menunjukkan bahwa mean_pendapatan adalah 59.900.000, sedangkan median_pendapatan hanya 4.900.000. Perbedaan yang sangat jauh ini mengindikasikan bahwa mean terpengaruh oleh nilai 500 juta, sehingga tidak mencerminkan pusat data secara umum. Oleh karena itu, dalam contoh ini, median dipilih sebagai ukuran pemusatan yang lebih stabil dan robust terhadap pencilan.

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

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

pelanggan[, c("customer_id", "usia", "usia_imputasi",
              "pendapatan", "pendapatan_imputasi")]
##    customer_id usia usia_imputasi pendapatan pendapatan_imputasi
## 1         C001   21          21.0    4500000             4500000
## 2         C002   25          25.0         NA             4900000
## 3         C003   23          23.0    5200000             5200000
## 4         C004   50          50.0    4800000             4800000
## 5         C005   27          27.0    4900000             4900000
## 6         C006   NA          27.5    5100000             5100000
## 7         C007   31          31.0  500000000           500000000
## 8         C008   29          29.0    4700000             4700000
## 9         C009   22          22.0    4600000             4600000
## 10        C010   35          35.0    5300000             5300000
## 11        C011   28          28.0         NA             4900000

Proses imputasi dilakukan dengan mengganti nilai kosong dengan nilai median. Untuk variabel pendapatan, dibuat kolom baru bernama pendapatan_imputasi yang awalnya merupakan salinan dari kolom pendapatan asli. Kemudian, pada kolom baru tersebut, semua nilai NA (kosong) diganti dengan nilai median pendapatan yang sudah dihitung sebelumnya, yaitu 4.900.000. Proses yang sama juga diterapkan pada variabel usia, di mana dibuat kolom usia_imputasi yang merupakan salinan dari usia, lalu nilai NA pada kolom baru tersebut diisi dengan median usia (yang dihitung terlebih dahulu dari data yang tersedia). Dengan cara ini, tidak ada baris yang dihapus, melainkan setiap nilai kosong digantikan oleh nilai pengganti yang dianggap paling mewakili kecenderungan pusat data untuk masing-masing variabel.

Dengan demikian, strategi imputasi ini memungkinkan kita untuk mempertahankan seluruh 11 baris data tanpa kehilangan informasi, tidak seperti strategi penghapusan baris sebelumnya yang menghilangkan 27,27% data. Namun, perlu diingat bahwa imputasi dengan median ini juga memiliki kelemahan, yaitu dapat mengurangi variabilitas alami data dan berpotensi memperkenalkan bias jika proporsi data yang hilang cukup besar atau jika pola ketidakhadirannya tidak acak. Namun, untuk tahap eksplorasi pendekatan ini jauh lebih baik dibandingkan membuang baris secara langsung, terutama ketika ukuran sampel terbatas dan distribusi data tidak normal.

Strategi 3: imputasi nilai kategorik

Nilai kota yang hilang tidak selalu tepat diisi dengan modus. Untuk latihan ini kita gunakan kategori eksplisit Tidak diketahui agar ketidakpastian tidak disembunyikan.

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

Pada strategi ketiga ini, penanganan data hilang difokuskan pada variabel kategorik, yaitu kota. Berbeda dengan variabel numerik yang menggunakan median, untuk variabel kota yang dilakukan adalah tidak menggunakan modus (nilai paling sering muncul) karena akan memberi kesan seolah-olah kita mengetahui kota tersebut padahal sebenarnya hanya menebak. Sebagai gantinya, nilai NA pada kolom kota diisi dengan kategori eksplisit “Tidak diketahui” agar ketidakpastian tetap terjaga dan tidak disembunyikan. Dari hasil tabel, terlihat bahwa setelah imputasi terdapat 3 pelanggan di Dumai, 6 di Pekanbaru, 1 di Siak, dan 1 pelanggan dengan kategori “Tidak diketahui”, yang menunjukkan bahwa hanya ada satu baris dengan data kota yang hilang.

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

Selain itu, untuk variabel pendapatan juga ditambahkan indikator missing berupa kolom pendapatan_missing yang bernilai 1 jika pendapatan asli hilang dan 0 jika tersedia. Dari tabel terlihat ada 9 baris dengan data pendapatan lengkap dan 2 baris yang awalnya kosong. Indikator ini sangat berguna karena mempertahankan informasi bahwa suatu nilai pernah hilang, sehingga ketika kita sudah mengisi nilai tersebut dengan median, model atau analisis tetap bisa mengetahui pola ketidakhadiran data asli. Dengan menggabungkan imputasi nilai dan indikator missing, kita tidak hanya memperbaiki data tetapi juga menjaga transparansi dan kualitas analisis secara keseluruhan.

Membandingkan distribusi sebelum dan sesudah imputasi

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)

Dari kedua histogram yang ditampilkan, kita bisa membandingkan distribusi pendapatan sebelum dan sesudah imputasi dengan median. Pada histogram Sebelum Imputasi, terlihat bahwa data sangat miring ke kanan dengan satu nilai ekstrem di sekitar 500 juta, sementara sebagian besar data berada di kisaran rendah. Setelah imputasi median (Sesudah Imputasi Median), distribusi berubah karena dua nilai yang hilang kini diisi dengan median 4,9 juta. Akibatnya, frekuensi pada rentang nilai rendah (di sekitar median) meningkat, sedangkan bentuk distribusi secara keseluruhan tetap mempertahankan kemiringan yang sama karena nilai ekstrem 500 juta masih dipertahankan. Imputasi median berhasil mengisi kekosongan tanpa mengganggu struktur distribusi secara drastis, namun perlu diingat bahwa variabilitas data sedikit berkurang karena nilai imputasi tidak bervariasi seperti data asli. Perbandingan ini penting untuk memastikan bahwa proses imputasi tidak mengubah karakteristik utama data secara berarti.

par(mfrow = c(1, 1))

Setelah membandingkan histogram, kode menutup tata letak grafik dengan par(mfrow = c(1, 1)) untuk mengembalikan pengaturan plot ke mode standar. Imputasi dapat mengubah distribusi dan mengecilkan variasi. Oleh karena itu, evaluasi tidak berhenti setelah semua NA hilang. Proses imputasi, terutama dengan nilai tunggal seperti median, dapat mengubah distribusi data asli dan mengecilkan variasi alami karena nilai-nilai yang diimputasi cenderung berkerumun di sekitar pusat data. Akibatnya, ukuran penyebaran seperti standar deviasi bisa mengecil, dan hubungan antarvariabel bisa terganggu. Oleh karena itu, evaluasi harus dilanjutkan dengan memeriksa apakah perubahan yang terjadi masih dapat diterima untuk tujuan analisis, misalnya dengan membandingkan statistik deskriptif, visualisasi distribusi, atau bahkan menguji sensitivitas model terhadap metode imputasi yang berbeda. Intinya, mengisi nilai hilang hanyalah satu langkah, tetapi validasi dampaknya terhadap kualitas data merupakan bagian yang tidak kalah penting. Imputasi dapat mengubah distribusi dan mengecilkan variasi. Karena itu, evaluasi tidak berhenti setelah semua NA hilang.

Bagian IV — Penanganan Outlier

Visualisasi dengan boxplot

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

Pada bagian ini, dilakukan visualisasi untuk mendeteksi outlier (pencilan) pada variabel pendapatan_imputasi menggunakan boxplot horizontal. Dari boxplot yang dihasilkan, terlihat bahwa sebagian besar data pendapatan berada disekitar nilai rendah hingga sedang, yang ditunjukkan oleh kotak (box) yang sempit di sisi kiri sumbu. Namun, terdapat satu titik yang sangat jauh di sebelah kanan, yaitu pada nilai sekitar “500 juta”, yang muncul sebagai lingkaran terisolasi di luar garis “whisker” (batas atas). Titik ini adalah outlier yang jelas karena nilainya melampaui batas wajar dari sebaran data utama. Outlier ini sebenarnya adalah nilai ekstrem yang sudah teridentifikasi sebelumnya sebagai penyebab kemiringan distribusi. Dengan boxplot, kita bisa secara visual mengonfirmasi bahwa data pendapatan memiliki satu pencilan ekstrem yang perlu mendapat perhatian khusus, karena outlier seperti ini dapat mempengaruhi berbagai analisis statistik, seperti mean, regresi, atau pengujian hipotesis. Langkah selanjutnya biasanya adalah memutuskan apakah outlier ini perlu dihapus, ditransformasi, atau dipertahankan dengan berbagai pertimbangan. Boxplot ini menjadi visualisasi awal yang efektif untuk mengidentifikasi keberadaan dan tingkat keparahan outlier sebelum menentukan strategi penanganannya.

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

Setelah visualisasi boxplot dilakukan, langkah selanjutnya adalah menghitung secara numerik batas-batas untuk mendeteksi outlier menggunakan metode “IQR (Interquartile Range)”. Dari perhitungan, diperoleh kuartil pertama (Q1) sebesar 4.750.000” dan kuartil ketiga (Q3) sebesar 5.150.000, sehingga nilai IQR yang diperoleh adalah 400.000.

Dengan menggunakan aturan q1 - (1,5 × IQR) dan q3 + (1,5 × IQR) , batas bawah berada di 4.150.000 dan batas atas di 5.750.000. Ini berarti bahwa setiap nilai pendapatan di bawah 4.150.000 atau di atas 5.750.000 secara statistik dianggap sebagai outlier. Dari hasil ini, jelas terlihat bahwa nilai ekstrem 500 juta jauh melampaui batas atas 5,75 juta,

sehingga terkonfirmasi secara kuantitatif sebagai outlier yang sangat ekstrem. Perhitungan ini memberikan dasar objektif untuk memutuskan apakah outlier tersebut perlu ditangani, misalnya dengan transformasi data, winsorizing (mengganti nilai ekstrem dengan nilai batas), atau tetap dipertahankan. Metode IQR ini konsisten dengan visualisasi boxplot sebelumnya dan memberikan ukuran yang lebih presisi untuk mendukung pengambilan keputusan.

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           500000000

Setelah batas IQR ditetapkan, langkah berikutnya adalah menandai kandidat outlier secara otomatis dengan membuat kolom baru bernama outlier_pendapatan. Kolom ini berisi nilai logika TRUE jika nilai pendapatan imputasi berada di bawah batas bawah (4.150.000) atau di atas batas atas (5.750.000), dan FALSE jika sebaliknya. Kemudian, dilakukan filter untuk menampilkan hanya baris-baris yang teridentifikasi sebagai outlier, dengan memilih kolom customer_id, nama, dan pendapatan_imputasi.

Dari hasil output, terlihat bahwa hanya satu baris data yang memenuhi kriteria, yaitu pelanggan dengan ID “C007” bernama “Gilang”, yang memiliki nilai pendapatan imputasi sebesar 500 juta. Temuan ini mengonfirmasi bahwa dari seluruh data, hanya ada satu nilai ekstrem yang benar-benar menyimpang jauh dari sebaran utama. Dengan menandai outlier secara eksplisit, kita sekarang memiliki fleksibilitas untuk memutuskan apakah data ini akan dipertahankan, dihapus, atau ditransformasi dalam analisis selanjutnya.

Mengevaluasi tindakan

Nilai 500 juta tidak otomatis dihapus. Pertimbangkan tiga kemungkinan:

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

Pada modul ini, nilai dipertahankan dan dibuat variabel versi 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           500000000           5750000

Kode menggunakan dua fungsi utama untuk winsorizing (membatasi nilai ekstrem). pmax() berfungsi membandingkan setiap nilai pendapatan dengan batas bawah (4.150.000) dan mengambil yang lebih besar, sehingga nilai di bawah batas bawah dinaikkan ke batas tersebut. pmin() kemudian membandingkan hasilnya dengan batas atas (5.750.000) dan mengambil yang lebih kecil, sehingga nilai di atas batas atas diturunkan ke batas tersebut. Kombinasi keduanya menghasilkan nilai yang “diapit” di antara batas bawah dan atas. Pada output, outlier C007 dengan nilai 500.000.000 diubah menjadi 5.750.000 (batas atas), sementara data lain tetap.

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

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_imputasi", "usia_minmax",
              "pendapatan_imputasi", "pendapatan_minmax")]
##    customer_id usia_imputasi usia_minmax pendapatan_imputasi pendapatan_minmax
## 1         C001          21.0  0.00000000             4500000      0.0000000000
## 2         C002          25.0  0.13793103             4900000      0.0008072654
## 3         C003          23.0  0.06896552             5200000      0.0014127144
## 4         C004          50.0  1.00000000             4800000      0.0006054490
## 5         C005          27.0  0.20689655             4900000      0.0008072654
## 6         C006          27.5  0.22413793             5100000      0.0012108981
## 7         C007          31.0  0.34482759           500000000      1.0000000000
## 8         C008          29.0  0.27586207             4700000      0.0004036327
## 9         C009          22.0  0.03448276             4600000      0.0002018163
## 10        C010          35.0  0.48275862             5300000      0.0016145308
## 11        C011          28.0  0.24137931             4900000      0.0008072654

Kode ini menerapkan normalisasi min-maks (min-max normalization) pada variabel usia_imputasi dan pendapatan_imputasi untuk menskalakan nilai-nilai ke rentang [0,1] menggunakan rumus \(x' = \frac{x - \min(x)}{\max(x) - \min(x)}\).

Hasilnya, pada kolom usia_minmax, usia terendah 21 tahun menjadi 0, usia tertinggi 50 tahun menjadi 1, dan nilai lainnya menyebar proporsional di antaranya. Sementara pada pendapatan_minmax, nilai terkecil 4.500.000 menjadi 0 dan nilai terbesar 500.000.000 (outlier C007) menjadi 1, sedangkan sebagian besar pendapatan lainnya hanya berada di kisaran 0 hingga 0,0016 karena rentang data sangat lebar akibat outlier. Normalisasi ini berguna untuk menyamakan skala antarvariabel yang berbeda satuan, misalnya sebelum menggunakan algoritma berbasis jarak seperti KNN atau SVM. Namun, perlu diperhatikan bahwa outlier ekstrem seperti 500 juta membuat sebagian besar nilai pendapatan terkonsentrasi di dekat 0, sehingga informasi variasi antar pelanggan ritel menjadi kurang terlihat.

Normalisasi z-score

Rumus 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

Metode ini menerapkan normalisasi z-score pada variabel usia_imputasi dan pendapatan_imputasi menggunakan fungsi bawaan scale(), yang secara otomatis menghitung z-score dengan rumus \(z = \frac{x - \bar{x}}{s}\). Fungsi as.numeric() digunakan untuk mengubah hasil menjadi vektor numerik biasa. Dari output yang dibulatkan hingga 3 desimal, terlihat bahwa nilai usia_z berkisar antara -0,984 hingga 2,604, yang berarti usia pelanggan C004 (50 tahun) berada 2,6 standar deviasi di atas rata-rata usia, sehingga tergolong cukup tinggi tetapi masih dalam batas wajar.

Sementara pada pendapatan_z, hampir semua pelanggan ritel memiliki nilai sekitar -0,3 (berarti sedikit di bawah rata-rata), sedangkan pelanggan C007 dengan pendapatan 500 juta memiliki z-score 3,015, yang berarti nilainya berada 3 standar deviasi di atas rata-rata dan secara statistik tergolong outlier ekstrem. Normalisasi z-score ini sangat berguna untuk menyetarakan skala antarvariabel yang berbeda satuan, serta memudahkan identifikasi outlier secara kuantitatif karena nilai z-score menunjukkan seberapa jauh suatu nilai menyimpang dari rata-rata dalam satuan standar deviasi.

Decimal scaling

Decimal scaling adalah teknik normalisasi yang membagi setiap nilai dengan pangkat 10 tertentu, sehingga semua nilai berada dalam rentang antara -1 dan 1 (atau 0 dan 1 jika semua data positif).

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

Cara kerja fungsi decimal_scale() adalah sebagai berikut:

  1. Mencari nilai absolut terbesar dari data (max(abs(x)));
  2. Menentukan pangkat 10 (j) dengan rumus ceiling(log10(maks + 1)), yaitu jumlah digit dari nilai terbesar;
  3. Membagi semua nilai dengan \(10^j\).

Dalam kasus ini, nilai maksimum pendapatan imputasi adalah 500.000.000, sehingga log10(500.000.001) ≈ 8,7, dibulatkan ke atas menjadi 9, lalu semua nilai dibagi dengan \(10^9\) atau 1.000.000.000. Hasilnya, rentang nilai menjadi antara 0,0045 (untuk pendapatan 4,5 juta) hingga 0,5000 (untuk 500 juta).

Kelebihan decimal scaling adalah mudah diimplementasikan dan tetap mempertahankan proporsi relatif antarnilai, tetapi kelemahannya adalah jika ada outlier ekstrem, sebagian besar data akan terkonsentrasi di dekat 0. Teknik ini cocok digunakan ketika data sudah dalam skala yang relatif seragam dan tidak terlalu dipengaruhi oleh outlier.

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             4500000      0.0000000000   -0.3041235
## 2         C002             4900000      0.0008072654   -0.3014440
## 3         C003             5200000      0.0014127144   -0.2994343
## 4         C004             4800000      0.0006054490   -0.3021138
## 5         C005             4900000      0.0008072654   -0.3014440
## 6         C006             5100000      0.0012108981   -0.3001042
## 7         C007           500000000      1.0000000000    3.0151095
## 8         C008             4700000      0.0004036327   -0.3027837
## 9         C009             4600000      0.0002018163   -0.3034536
## 10        C010             5300000      0.0016145308   -0.2987645
## 11        C011             4900000      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

Tabel ini membandingkan hasil tiga metode normalisasi pada variabel pendapatan: min-max, z-score, dan decimal scaling. Dari data terlihat bahwa metode min-max menghasilkan nilai antara 0 dan 1, di mana pendapatan terkecil (4,5 juta) menjadi 0 dan pendapatan terbesar (500 juta) menjadi 1, namun karena outlier 500 juta, hampir semua pendapatan ritel lainnya hanya berada di kisaran 0,0002 hingga 0,0016, sehingga perbedaan di antara mereka nyaris tidak terlihat.

Metode z-score menghasilkan nilai dengan rata-rata 0 dan standar deviasi 1, di mana hampir semua pelanggan ritel memiliki z-score sekitar -0,3 (berarti sedikit di bawah rata-rata), sementara pelanggan C007 dengan pendapatan 500 juta memiliki z-score yang sangat tinggi sekitar 3,015, sehingga outlier ini sangat menonjol secara statistik.

Metode decimal scaling membagi semua nilai dengan 1.000.000.000, menghasilkan rentang 0,0045 hingga 0,5000, di mana outlier 500 juta menjadi 0,5000 sementara pelanggan ritel lainnya hanya berkisar antara 0,0045 hingga 0,0053, lagi-lagi membuat perbedaan antar pelanggan ritel menjadi sulit dibedakan karena skala yang didominasi oleh outlier.

Dari ketiga metode, z-score adalah metode yang paling informatif karena secara eksplisit menunjukkan seberapa jauh setiap nilai menyimpang dari rata-rata dalam satuan standar deviasi, sehingga memudahkan identifikasi outlier dan perbandingan antar pelanggan secara relatif, sementara min-maks dan decimal scaling sangat terpengaruh oleh outlier ekstrem sehingga informasi variasi di antara sebagian besar data menjadi hilang.

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)

Grafik ini membandingkan hasil normalisasi min-maks antara data asli (sumbu X) dan data yang sudah diwinsorize (sumbu Y). Dari sebaran titik terlihat bahwa pada data asli, hampir semua nilai terkonsentrasi di dekat 0 (kiri bawah), karena adanya satu outlier ekstrem yang mendominasi rentang data. Setelah data di-winsorize (outlier dibatasi ke batas atas), nilai-nilai min-maks menjadi lebih tersebar dan membentuk pola yang lebih linier, terlihat dari titik-titik yang menyebar ke arah kanan atas. Garis putus-putus merah menunjukkan tren bahwa winsorizing membuat distribusi nilai normalisasi menjadi lebih merata.

Outlier ekstrem dapat menekan sebagian besar nilai min-maks ke rentang yang sangat sempit (mendekati 0), sehingga perbedaan antar pelanggan ritel menjadi tidak terlihat. Inilah sebabnya mengapa deteksi dan penanganan outlier sangat penting dilakukan sebelum memilih teknik transformasi atau normalisasi. Tanpa penanganan, informasi berharga dari sebagian besar data bisa hilang hanya karena satu nilai yang sangat menyimpang.

Bagian VI — Integrasi Data

Memeriksa kunci pada kedua sumber data

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

Pada bagian Integrasi Data ini, langkah pertama adalah memeriksa kunci pada kedua sumber data untuk memastikan tidak ada duplikasi dan mengetahui kesesuaian antar tabel. Dari hasil sum(duplicated()), terlihat bahwa tidak ada duplikasi ID pelanggan di kedua sumber, baik pada pelanggan$customer_id maupun transaksi_raw$cust_id, yang berarti masing-masing ID bersifat unik.

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

Namun, fungsi setdiff() mengungkapkan adanya ketidaksesuaian: ID “C011” ada di data pelanggan tetapi tidak ada di data transaksi, artinya pelanggan tersebut tidak memiliki riwayat transaksi apapun. Sebaliknya, ID “C012” ada di data transaksi tetapi tidak ada di data pelanggan, artinya ada transaksi yang tercatat tetapi tidak memiliki data profil pelanggan yang bersesuaian. Temuan ini penting karena menunjukkan bahwa kedua sumber data tidak sepenuhnya tumpang tindih, sehingga perlu diputuskan apakah akan menggabungkan secara inner join (hanya yang beririsan), left join (mempertahankan semua pelanggan), atau right join (mempertahankan semua transaksi), tergantung pada tujuan analisis. Pemeriksaan ini juga menjadi dasar untuk membersihkan atau melengkapi data sebelum integrasi dilakukan. Intinya, hasil setdiff() menunjukkan pelanggan yang tidak memiliki transaksi dan transaksi yang tidak memiliki pasangan data pelanggan.

Menyelaraskan nama identifier

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

Pada Tahap ini, dilakukan penyelarasan nama identifier agar kedua sumber data memiliki nama kolom kunci yang sama. Data transaksi yang semula bernama transaksi_raw disalin ke objek baru bernama transaksi untuk menjaga agar data mentah tetap aman. Kemudian, fungsi names(transaksi)[names(transaksi) == "cust_id"] <- "customer_id" digunakan untuk mengganti nama kolom cust_id menjadi customer_id. Dengan demikian, kolom kunci pada tabel transaksi kini memiliki nama yang sama dengan tabel pelanggan (customer_id), sehingga memudahkan proses penggabungan (join) antar tabel. Penyelarasan nama ini merupakan langkah penting dalam integrasi data untuk menghindari kebingungan dan kesalahan saat melakukan operasi gabungan.

Melakukan left join dengan merge()

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

data_terintegrasi[, c(
  "customer_id", "nama", "jumlah_transaksi", "total_purchase"
)]
##    customer_id   nama jumlah_transaksi total_purchase
## 1         C001    Ani                5        1500000
## 2         C002   Budi                3         900000
## 3         C003  Citra                7        2700000
## 4         C004   Dodi                2         600000
## 5         C005    Eka                6        2100000
## 6         C006   Fani                4        1300000
## 7         C007 Gilang               20       25000000
## 8         C008   Hana                5        1700000
## 9         C009  Indra                3         800000
## 10        C010   Joko                8        3200000
## 11        C011   Kiki               NA             NA

Pada langkah ini, dilakukan penggabungan (join) antara tabel pelanggan dan transaksi menggunakan fungsi merge() dengan metode left join (all.x = TRUE). Parameter by = "customer_id" menunjukkan bahwa kedua tabel digabung berdasarkan kolom customer_id yang sudah diselaraskan namanya sebelumnya. Left join berarti semua baris dari tabel pelanggan akan dipertahankan, dan kolom-kolom dari tabel transaksi akan ditambahkan jika ada kecocokan ID. jika tidak ada transaksi untuk suatu pelanggan, maka kolom transaksi akan bernilai NA.Hasilnya disimpan dalam objek data_terintegrasi.

Dari output yang ditampilkan, terlihat bahwa ada 11 baris data pelanggan, dan hanya 10 baris yang memiliki data transaksi, karena pelanggan dengan ID C011 yang tidak memiliki data transaksi. Data terintegrasi ini sekarang memuat informasi pelanggan beserta jumlah transaksi dan total pembelian, sehingga siap untuk analisis lanjutan seperti segmentasi pelanggan berdasarkan perilaku belanja. Perintah all.x = TRUE mempertahankan seluruh pelanggan meskipun tidak memiliki pasangan transaksi.

Memvalidasi hasil integrasi

Setelah proses integrasi data selesai, langkah selanjutnya adalah melakukan validasi untuk memastikan bahwa penggabungan berjalan dengan benar dan tidak menimbulkan masalah. Validasi ini mencakup pemeriksaan jumlah baris, keunikan kunci, serta kemunculan nilai hilang baru akibat proses penggabungan.

# Apakah jumlah baris berubah secara tidak wajar?
c(sebelum = nrow(pelanggan), sesudah = nrow(data_terintegrasi))
## sebelum sesudah 
##      11      11

jumlah baris data diperiksa dengan membandingkan sebelum dan sesudah integrasi. Hasilnya menunjukkan bahwa jumlah baris tetap yaitu 11 baris, baik pada tabel pelanggan asli maupun pada data terintegrasi. Hal ini sesuai dengan yang diharapkan karena metode yang digunakan adalah left join, yang mempertahankan semua baris dari tabel pelanggan meskipun ada pelanggan yang tidak memiliki transaksi.

# Apakah customer_id tetap unik?
sum(duplicated(data_terintegrasi$customer_id))
## [1] 0

Kemudian, dilakukan pemeriksaan terhadap kemungkinan duplikasi pada kolom kunci customer_id. Hasilnya menunjukkan nilai 0, artinya tidak ada ID pelanggan yang terduplikasi dalam data terintegrasi. Dengan demikian, struktur kunci tetap unik dan tidak terjadi penggandaan data akibat proses penggabungan.

# Missing value baru akibat tidak ditemukan pasangan transaksi
colSums(is.na(data_terintegrasi[, c("jumlah_transaksi", "total_purchase")]))
## jumlah_transaksi   total_purchase 
##                1                1

Selanjutnya, dilakukan pengecekan terhadap nilai hilang (NA) yang baru muncul pada kolom hasil integrasi, yaitu jumlah_transaksi dan total_purchase. Dari hasil, terlihat bahwa masing-masing kolom memiliki 1 nilai NA. Nilai hilang ini muncul secara wajar karena pelanggan dengan ID C011 (bernama Kiki) tidak memiliki riwayat transaksi sama sekali, sehingga kolom transaksinya otomatis kosong ketika digabungkan.

# Pelanggan tanpa pasangan transaksi
data_terintegrasi[is.na(data_terintegrasi$jumlah_transaksi),
                  c("customer_id", "nama")]
##    customer_id nama
## 11        C011 Kiki

Untuk memastikan hasil tersebut, dilakukan penyaringan terhadap baris yang memiliki nilai kosong (NA) pada jumlah_transaksi. Hasilnya mengonfirmasi bahwa hanya pelanggan C011 bernama Kiki yang tidak memiliki pasangan data transaksi. Validasi ini sangat penting untuk memastikan bahwa proses integrasi tidak menimbulkan masalah seperti duplikasi, perubahan jumlah baris yang tidak wajar, atau nilai hilang yang tidak terduga, serta memberikan pemahaman yang jelas tentang karakteristik data hasil penggabungan.

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

Setelah validasi integrasi selesai, langkah berikutnya adalah memutuskan bagaimana menangani nilai NA yang muncul pada kolom transaksi, yaitu jumlah_transaksi dan total_purchase. Nilai NA ini memerlukan perhatian khusus karena dapat memiliki dua makna yang sangat berbeda, yaitu pelanggan tersebut belum pernah bertransaksi sama sekali atau data transaksinya tidak tersedia karena gagal dipindahkan atau direkam. Perbedaan makna ini sangat krusial karena akan mempengaruhi analisis selanjutnya, seperti perhitungan rata-rata pembelian atau segmentasi pelanggan.

Oleh karena itu, keputusan untuk mengubah NA menjadi 0 hanya boleh dilakukan jika kita sudah mengonfirmasi definisi dari nilai hilang tersebut. Dalam contoh ini, diasumsikan bahwa setelah dilakukan verifikasi, disimpulkan bahwa NA memang berarti pelanggan belum pernah bertransaksi. Berdasarkan asumsi ini, maka pengisian nol menjadi tindakan yang tepat.

Proses pengisian dilakukan dengan membuat kolom baru bernama jumlah_transaksi_final dan total_purchase_final sebagai salinan dari kolom asli. Kemudian, pada kolom baru tersebut, semua nilai NA diganti dengan 0 menggunakan indeks logika is.na(). Hasilnya, pelanggan C011 yang sebelumnya memiliki NA pada transaksi, kini memiliki nilai 0 pada kedua kolom transaksi, yang secara jelas mengindikasikan bahwa ia tidak memiliki riwayat pembelian.

Pendekatan ini mencerminkan langkah yang baik dalam pengolahan data, yaitu tidak mengubah nilai asli secara permanen (dengan membuat kolom baru) dan tidak mengubah nilai tanpa dasar yang jelas. Dengan demikian, jika di kemudian hari ternyata asumsi tersebut salah, kita masih memiliki data asli yang utuh untuk diperbaiki.

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             4500000
## 2         C002   Budi 25.0       Pekanbaru       Aktif             4900000
## 3         C003  Citra 23.0       Pekanbaru       Aktif             5200000
## 4         C004   Dodi 50.0           Dumai       Aktif             4800000
## 5         C005    Eka 27.0       Pekanbaru Tidak Aktif             4900000
## 6         C006   Fani 27.5           Dumai Tidak Aktif             5100000
## 7         C007 Gilang 31.0       Pekanbaru       Aktif           500000000
## 8         C008   Hana 29.0            Siak       Aktif             4700000
## 9         C009  Indra 22.0       Pekanbaru       Aktif             4600000
## 10        C010   Joko 35.0           Dumai Tidak Aktif             5300000
## 11        C011   Kiki 28.0 Tidak diketahui       Aktif             4900000
##    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              1500000
## 2                       3               900000
## 3                       7              2700000
## 4                       2               600000
## 5                       6              2100000
## 6                       4              1300000
## 7                      20             25000000
## 8                       5              1700000
## 9                       3               800000
## 10                      8              3200000
## 11                      0                    0

Pada tahap akhir, dilakukan pemilihan atribut yang akan digunakan dalam dataset final. Dari data terintegrasi, dipilih 12 kolom yang paling relevan, meliputi identitas pelanggan (customer_id, nama), hasil imputasi (usia_imputasi, kota_imputasi, pendapatan_imputasi), status pelanggan, indikator data hilang (pendapatan_missing), penanda outlier (outlier_pendapatan), hasil normalisasi (usia_minmax, pendapatan_minmax), serta kolom transaksi final (jumlah_transaksi_final, total_purchase_final).

Setelah itu, dilakukan penyederhanaan nama kolom agar lebih ringkas. kota_imputasi diubah menjadi kota, dan usia_imputasi menjadi usia. Perubahan ini dilakukan karena proses imputasi sudah selesai, sehingga tidak perlu lagi membedakan kolom asli dan hasil imputasi dalam dataset final.

Dataset final memiliki 11 baris dan 12 kolom, tanpa nilai hilang. Semua atribut sudah dalam format siap pakai, termasuk kolom hasil transformasi dan indikator yang memberikan fleksibilitas untuk analisis lanjutan. Dengan demikian, data_final siap digunakan untuk segmentasi pelanggan, pemodelan prediktif, atau laporan bisnis.

Audit akhir

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

Audit akhir dilakukan untuk memastikan kualitas dataset final. Hasilnya menunjukkan tidak ada nilai hilang pada seluruh atribut, yang berarti proses imputasi dan pengisian nol telah berhasil membersihkan semua NA. Selain itu, kolom jumlah_unik memberikan informasi tentang variasi nilai pada setiap atribut. Misalnya, customer_id dan nama memiliki 11 nilai unik (sesuai dengan jumlah pelanggan), kota memiliki 4 nilai unik (Dumai, Pekanbaru, Siak, Tidak Diketahui), dan status memiliki 2 nilai unik (Aktif/Tidak Aktif). Untuk kolom numerik, pendapatan_imputasi memiliki 9 nilai unik karena ada dua pelanggan dengan pendapatan yang sama setelah imputasi, sedangkan usia memiliki 11 nilai unik karena tidak ada duplikasi usia. Audit ini menegaskan bahwa data final sudah bersih, konsisten, dan siap digunakan untuk analisis lanjutan.

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

Pada tahap akhir verifikasi, dilakukan dua pemeriksaan kualitas utama. Pertama, pengecekan duplikasi pada customer_id menghasilkan nilai 0, yang berarti tidak ada ID pelanggan yang terduplikasi dan setiap baris mewakili pelanggan unik. Kedua, pengecekan nilai hilang menggunakan colSums(is.na()) menunjukkan bahwa semua kolom bernilai 0, artinya tidak ada satupun nilai kosong yang tersisa di seluruh atribut dataset final, termasuk kolom hasil imputasi, normalisasi, dan transaksi.

Dengan terpenuhinya kedua kriteria tersebut tanpa duplikasi dan tanpa nilai hilang, dataset final dinyatakan valid dan siap digunakan untuk analisis lanjutan tanpa perlu khawatir akan adanya masalah kualitas data yang mendasar.

Membandingkan kondisi sebelum dan sesudah

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

Tabel perbandingan ini menunjukkan perubahan kondisi data sebelum dan sesudah ditangani secara ringkas dan terukur. Sebelum diproses, data awal memiliki 12 baris, namun setelah dibersihkan dan diintegrasikan, jumlah baris menjadi 11 karena satu baris duplikat dihapus. Hal ini terbukti dari indikator duplikasi yang turun dari 1 menjadi 0. Total missing value juga berhasil dihilangkan seluruhnya, dari sebelumnya ada 4 nilai kosong menjadi 0 setelah imputasi dan pengisian nol.

Perubahan paling mencolok terlihat pada kategori unik kota dan status. Kota unik menyusut drastis dari 10 menjadi 4, karena data kota asli memiliki banyak variasi penulisan (misalnya “Pekanbaru”, “pekanbaru”, “PEKANBARU”) yang setelah dibersihkan distandarisasi menjadi hanya 4 kategori: Dumai, Pekanbaru, Siak, dan Tidak Diketahui. Demikian pula status unik berkurang dari 8 menjadi 2, setelah dilakukan standarisasi nilai status menjadi hanya “Aktif” dan “Tidak Aktif”.

Secara keseluruhan, perbandingan ini membuktikan bahwa proses pembersihan dan transformasi data berhasil meningkatkan kualitas data secara signifikan. proses ini membuat data dengan duplikasi dapat dihilangkan, missing value dapat ditangani, dan nilai-nilai kategorik distandarisasi sehingga dataset menjadi lebih bersih, konsisten, dan siap untuk analisis lanjutan.

Menyimpan hasil

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

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

Pertanyaan Refleksi

  1. Apakah dataset tanpa missing value selalu lebih berkualitas?
  2. Mengapa outlier tidak boleh otomatis dihapus?
  3. Bagaimana preprocessing dapat menimbulkan bias?
  4. Mengapa parameter imputasi dan transformasi seharusnya dihitung dari data pelatihan?
  5. Apa risiko integrasi data jika identifier tidak unik?

Ringkasan

Alur yang telah dilakukan adalah:

  1. memahami struktur dan kualitas awal data;
  2. membersihkan kategori, duplikasi, dan pelanggaran domain;
  3. menangani missing values tanpa menyembunyikan ketidakpastian;
  4. mendeteksi dan mengevaluasi outlier;
  5. mentransformasikan atribut numerik; dan
  6. mengintegrasikan data serta memvalidasi hasilnya.

Preprocessing merupakan proses pengambilan keputusan. Kode R hanya menjalankan keputusan tersebut; kualitas hasil tetap bergantung pada pemahaman data, tujuan analisis, dan dokumentasi perubahan.

Daftar Pustaka

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