Pendahuluan

Data pelanggan dan transaksi merupakan jenis data yang dapat digunakan untuk memahami karakteristik pelanggan serta aktivitas pembelian yang terjadi. Sebelum digunakan dalam proses analisis, data perlu diperiksa dan dipersiapkan terlebih dahulu karena data yang diperoleh belum tentu memiliki kualitas yang baik.

Pada pengolahan ini digunakan dua sumber data, yaitu data pelanggan dan data transaksi. Data pelanggan berisi informasi mengenai identitas pelanggan, usia, pendapatan, kota, dan status pelanggan. Sementara itu, data transaksi berisi jumlah transaksi dan total pembelian yang dilakukan oleh pelanggan.

Kedua dataset tersebut memiliki beberapa permasalahan yang perlu ditangani, seperti adanya nilai yang hilang, ketidakkonsistenan dalam penulisan kategori, data pelanggan yang tercatat lebih dari satu kali, nilai numerik yang tidak sesuai dengan aturan data, serta adanya nilai pendapatan yang sangat ekstrem. Selain itu, terdapat perbedaan nama identifier antara data pelanggan dan data transaksi.

Oleh karena itu, dilakukan serangkaian proses preprocessing untuk mempersiapkan data sebelum digunakan dalam analisis. Tahapan tersebut meliputi pemeriksaan kondisi awal data, data cleaning, penanganan missing value, deteksi outlier, transformasi data, integrasi dua sumber data, dan evaluasi dataset akhir.

Skenario Pengolahan Data

Pengolahan data dilakukan pada kasus perusahaan e-commerce yang ingin melakukan analisis terhadap pelanggan. Data yang tersedia berasal dari dua sumber yang berbeda, yaitu data pelanggan dan data transaksi.

Data pelanggan digunakan untuk menggambarkan karakteristik pelanggan, sedangkan data transaksi digunakan untuk menggambarkan aktivitas pembelian. Kedua sumber data tersebut kemudian dihubungkan menggunakan identifier pelanggan.

Tujuan utama preprocessing pada kasus ini bukan hanya menghilangkan data yang dianggap bermasalah, tetapi memastikan bahwa setiap perubahan yang dilakukan memiliki alasan yang jelas dan dapat dijelaskan. Dengan demikian, hasil preprocessing dapat digunakan sebagai dataset yang lebih konsisten untuk analisis berikutnya.

Persiapan RStudio

Memeriksa lingkungan kerja

getwd()
## [1] "C:/Users/Lenovo/Downloads"
R.version.string
## [1] "R version 4.5.1 (2025-06-13 ucrt)"

Membangun Dataset Praktikum

Data pelanggan dan transaksi yang digunakan dalam pengolahan ini dibuat dalam bentuk dataset yang memiliki beberapa kondisi yang perlu ditangani melalui preprocessing.

Data Pelanggan dan Transaksi

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

Data awal menunjukkan bahwa data pelanggan terdiri atas 12 baris dan 6 variabel. Pada data tersebut terdapat nilai kosong pada usia, pendapatan, dan kota. Selain itu, terdapat customer_id yang tercatat dua kali, yaitu C010. Data transaksi memiliki 11 baris dan menggunakan nama identifier cust_id.

Bagian I — Pemeriksaan Data Awal

Memahami struktur data

Pemeriksaan awal dilakukan untuk mengetahui ukuran dataset, nama variabel, tipe data, beberapa observasi pertama, dan ringkasan statistik. Tahap ini membantu mengetahui kondisi data sebelum dilakukan perubahan.

dim(pelanggan_raw)
## [1] 12  6
names(pelanggan_raw)
## [1] "customer_id" "nama"        "usia"        "pendapatan"  "kota"       
## [6] "status"
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" ...
head(pelanggan_raw)
##   customer_id  nama usia pendapatan      kota      status
## 1        C001   Ani   21    4500000 Pekanbaru       Aktif
## 2        C002  Budi   25         NA       PKU       aktif
## 3        C003 Citra   23    5200000 PEKANBARU      ACTIVE
## 4        C004  Dodi  150    4800000     Dumai           A
## 5        C005   Eka   27    4900000 pekanbaru Tidak Aktif
## 6        C006  Fani   NA    5100000     DUMAI    nonaktif
summary(pelanggan_raw)
##  customer_id            nama                usia          pendapatan       
##  Length: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  
##                                       
##                                       
##                                       
## 

Hasil pemeriksaan menunjukkan bahwa data pelanggan memiliki 12 observasi dan 6 variabel. Variabel customer_id, nama, kota, dan status merupakan data karakter, sedangkan usia dan pendapatan merupakan variabel numerik.

Mengukur kualitas awal

Pemeriksaan kualitas dilakukan untuk mengetahui permasalahan yang dapat ditemukan secara langsung dari dataset. Fokus pemeriksaan meliputi missing value, duplikasi, konsistensi kategori, serta rentang variabel numerik.

colSums(is.na(pelanggan_raw))
## customer_id        nama        usia  pendapatan        kota      status 
##           0           0           1           2           1           0
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
sum(duplicated(pelanggan_raw))
## [1] 1
sum(duplicated(pelanggan_raw$customer_id))
## [1] 1
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"
range(pelanggan_raw$usia, na.rm = TRUE)
## [1]  21 150
range(pelanggan_raw$pendapatan, na.rm = TRUE)
## [1] 4.5e+06 5.0e+08

Berdasarkan pemeriksaan tersebut, terdapat satu missing value pada usia, dua missing value pada pendapatan, dan satu missing value pada kota. Selain itu, ditemukan satu customer_id yang berulang. Kategori kota dan status juga memiliki beberapa bentuk penulisan yang berbeda, meskipun beberapa di antaranya sebenarnya menunjukkan kategori yang sama.

Rentang usia menunjukkan adanya nilai 150 tahun, sedangkan pendapatan memiliki nilai maksimum sebesar 500 juta. Kedua kondisi tersebut perlu diperiksa lebih lanjut sebelum data digunakan.

Membuat fungsi ringkasan kualitas

Untuk mempermudah pemeriksaan, dibuat fungsi yang memberikan ringkasan tipe data, jumlah missing value, persentase missing value, dan jumlah kategori unik.

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

Audit awal digunakan sebagai gambaran kondisi data sebelum dilakukan perubahan. Hasil audit selanjutnya menjadi dasar untuk menentukan bagian data yang perlu diperbaiki.

Bagian II — Implementasi Data Cleaning

Membuat salinan kerja

Data mentah tetap dipertahankan agar perubahan yang dilakukan selama preprocessing dapat dilacak dan dibandingkan dengan kondisi awal.

pelanggan <- pelanggan_raw

Membersihkan spasi dan kapitalisasi

Pada data pelanggan terdapat spasi tambahan pada beberapa nilai kota serta perbedaan penggunaan huruf besar dan kecil. Kondisi tersebut dapat menyebabkan kategori yang sebenarnya sama terbaca sebagai kategori berbeda.

pelanggan$kota <- trimws(pelanggan$kota)
pelanggan$status <- trimws(pelanggan$status)

pelanggan$kota <- tolower(pelanggan$kota)
pelanggan$status <- tolower(pelanggan$status)

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

Setelah spasi dan kapitalisasi diperbaiki, kategori menjadi lebih mudah untuk diseragamkan karena bentuk penulisannya sudah berada pada format yang sama.

Menyeragamkan kategori

Kategori kota dan status kemudian disesuaikan berdasarkan aturan yang digunakan pada data.

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

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

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

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

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

Standardisasi menghasilkan tiga kategori kota, yaitu Pekanbaru, Dumai, dan Siak. Variabel status juga telah memiliki dua kategori, yaitu Aktif dan Tidak Aktif.

Mendeteksi dan menghapus duplikasi

Duplikasi customer_id perlu diperiksa karena dalam data pelanggan satu identifier seharusnya mewakili satu pelanggan.

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

Pada data ditemukan C010 yang tercatat sebanyak dua kali dengan informasi yang sama. Karena customer_id digunakan sebagai identitas unik pelanggan, salah satu kemunculan tersebut dihapus.

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

rownames(pelanggan) <- NULL

dim(pelanggan)
## [1] 11  6

Setelah proses deduplikasi, jumlah pelanggan menjadi 11.

Memeriksa aturan domain

Nilai numerik juga diperiksa berdasarkan aturan yang sesuai dengan karakteristik data. Untuk usia digunakan rentang 15 sampai 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>

Pemeriksaan menunjukkan adanya usia 150 tahun pada C004. Nilai tersebut tidak sesuai dengan aturan usia yang digunakan. Berdasarkan informasi sumber asli pada skenario data, nilai tersebut dikoreksi menjadi 50.

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

Pendapatan juga diperiksa untuk memastikan tidak terdapat nilai 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>

Tidak ditemukan pendapatan bernilai negatif sehingga tidak diperlukan koreksi pada bagian tersebut.

Membuat log perubahan

Setiap perubahan yang dilakukan dicatat agar proses preprocessing dapat ditelusuri kembali.

log_perubahan <- data.frame(
  tahap = c(
    "Standardisasi",
    "Standardisasi",
    "Deduplikasi",
    "Koreksi domain"
  ),
  atribut = c(
    "kota",
    "status",
    "customer_id",
    "usia"
  ),
  tindakan = c(
    "PKU dan variasi kapital menjadi Pekanbaru",
    "ACTIVE/A menjadi Aktif; nonaktif menjadi Tidak Aktif",
    "Menghapus kemunculan kedua C010",
    "Usia C004 dikoreksi dari 150 menjadi 50 berdasarkan sumber asli"
  ),
  stringsAsFactors = FALSE
)

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

Bagian III — Penanganan Missing Values

Mengidentifikasi lokasi nilai hilang

Setelah cleaning, missing value diperiksa kembali untuk mengetahui bagian data yang masih belum lengkap.

colSums(is.na(pelanggan))
## customer_id        nama        usia  pendapatan        kota      status 
##           0           0           1           2           1           0
pelanggan[
  !complete.cases(pelanggan),
]
##    customer_id nama usia pendapatan      kota      status
## 2         C002 Budi   25         NA Pekanbaru       Aktif
## 6         C006 Fani   NA    5100000     Dumai Tidak Aktif
## 11        C011 Kiki   28         NA      <NA>       Aktif

Missing value masih terdapat pada usia, pendapatan, dan kota. Data yang memiliki missing value kemudian menjadi bahan pertimbangan dalam pemilihan metode penanganan.

Strategi 1: menghapus baris

Sebagai perbandingan, dilakukan simulasi apabila seluruh baris yang memiliki missing value dihapus.

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

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

Penghapusan seluruh baris yang tidak lengkap akan mengurangi jumlah data secara cukup besar. Oleh karena itu, pada kasus ini pendekatan tersebut tidak langsung digunakan sebagai hasil akhir.

Strategi 2: imputasi mean dan median

Pendapatan memiliki satu nilai yang sangat ekstrem, yaitu 500 juta. Karena nilai ekstrem dapat memengaruhi nilai rata-rata, mean dan median dibandingkan terlebih dahulu.

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

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

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

Perbedaan yang besar antara mean dan median menunjukkan bahwa distribusi pendapatan sangat dipengaruhi oleh nilai ekstrem. Oleh karena itu, median digunakan untuk mengisi missing value pendapatan.

pelanggan$pendapatan_imputasi <-
  pelanggan$pendapatan

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

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

pelanggan$usia_imputasi <-
  pelanggan$usia

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

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

Dengan cara tersebut, data asli tetap disimpan dan nilai hasil imputasi tersedia pada variabel baru.

Strategi 3: imputasi nilai kategorik

Missing value pada kota tidak langsung diganti menggunakan modus karena hal tersebut dapat membuat ketidakpastian data menjadi tidak terlihat. Sebagai gantinya digunakan kategori eksplisit Tidak diketahui.

pelanggan$kota_imputasi <-
  pelanggan$kota

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

table(
  pelanggan$kota_imputasi,
  useNA = "ifany"
)
## 
##           Dumai       Pekanbaru            Siak Tidak diketahui 
##               3               6               1               1

Menambahkan indikator missing

Untuk mempertahankan informasi bahwa suatu nilai pendapatan awalnya merupakan missing value, dibuat variabel indikator.

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

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

Membandingkan distribusi sebelum dan sesudah imputasi

Perbandingan visual dilakukan untuk melihat perubahan distribusi pendapatan akibat 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
)

par(mfrow = c(1, 1))

Imputasi membuat data menjadi lengkap, tetapi proses tersebut juga dapat mengubah distribusi dan mengurangi variasi data. Oleh sebab itu, keputusan imputasi perlu tetap dicatat dan dipertimbangkan ketika melakukan analisis berikutnya.

Bagian IV — Penanganan Outlier

Visualisasi dengan boxplot

Outlier pada pendapatan diperiksa secara visual menggunakan boxplot.

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

Boxplot menunjukkan adanya satu nilai pendapatan yang jauh lebih tinggi dibandingkan observasi lainnya.

Menghitung batas IQR

Deteksi outlier dilakukan menggunakan metode Interquartile Range (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

Berdasarkan perhitungan IQR, batas bawah adalah 4.150.000 dan batas atas adalah 5.750.000. Nilai pendapatan yang berada di luar batas tersebut dianggap sebagai kandidat outlier.

Menandai kandidat outlier

pelanggan$outlier_pendapatan <-
  pelanggan$pendapatan_imputasi <
  batas_bawah |
  pelanggan$pendapatan_imputasi >
  batas_atas

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

Hasil pemeriksaan menunjukkan bahwa pendapatan C007 sebesar 500.000.000 merupakan kandidat outlier.

Mengevaluasi tindakan terhadap outlier

Nilai ekstrem tidak langsung dihapus karena nilai tersebut belum tentu merupakan kesalahan. Nilai 500 juta dapat merupakan data yang benar, misalnya pelanggan dengan karakteristik pendapatan yang berbeda. Karena tidak terdapat bukti bahwa nilai tersebut merupakan kesalahan input, nilai asli dipertahankan.

Sebagai pembanding, dibuat versi winsorized dengan membatasi nilai tersebut pada batas atas.

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

pelanggan[
  pelanggan$outlier_pendapatan,
  c(
    "customer_id",
    "pendapatan_imputasi",
    "pendapatan_winsor"
  )
]
##   customer_id pendapatan_imputasi pendapatan_winsor
## 7        C007               5e+08           5750000

Winsorizing pada tahap ini digunakan sebagai alternatif untuk melihat dampak penanganan outlier dan bukan sebagai pengganti otomatis terhadap nilai asli.

Bagian V — Transformasi Data

Transformasi digunakan untuk mengubah skala data numerik agar lebih sesuai dengan kebutuhan analisis.

Normalisasi min–maks

Normalisasi min–maks menggunakan rumus:

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

minmax <- function(x) {
  if (all(is.na(x))) {
    return(
      rep(
        NA_real_,
        length(x)
      )
    )
  }

  rentang <-
    max(x, na.rm = TRUE) -
    min(x, na.rm = TRUE)

  if (rentang == 0) {
    return(
      rep(
        0,
        length(x)
      )
    )
  }

  (
    x -
      min(
        x,
        na.rm = TRUE
      )
  ) / rentang
}

pelanggan$usia_minmax <-
  minmax(
    pelanggan$usia_imputasi
  )

pelanggan$pendapatan_minmax <-
  minmax(
    pelanggan$pendapatan_imputasi
  )

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

Hasil normalisasi berada pada rentang 0 sampai 1. Pada pendapatan, keberadaan nilai ekstrem menyebabkan sebagian besar nilai lainnya berada sangat dekat dengan nol.

Normalisasi z-score

Z-score digunakan untuk mengubah data berdasarkan rata-rata dan simpangan baku.

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

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

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

round(
  pelanggan[
    ,
    c(
      "usia_z",
      "pendapatan_z"
    )
  ],
  3
)
##    usia_z pendapatan_z
## 1  -0.984       -0.304
## 2  -0.489       -0.301
## 3  -0.737       -0.299
## 4   2.604       -0.302
## 5  -0.242       -0.301
## 6  -0.180       -0.300
## 7   0.253        3.015
## 8   0.006       -0.303
## 9  -0.860       -0.303
## 10  0.748       -0.299
## 11 -0.118       -0.301

Decimal scaling

Decimal scaling dilakukan dengan membagi nilai menggunakan pangkat sepuluh tertentu.

decimal_scale <- function(x) {
  maks <- max(
    abs(x),
    na.rm = TRUE
  )

  if (maks == 0) {
    return(x)
  }

  j <- ceiling(
    log10(
      maks + 1
    )
  )

  x / (10 ^ j)
}

pelanggan$pendapatan_decimal <-
  decimal_scale(
    pelanggan$pendapatan_imputasi
  )

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

Membandingkan metode transformasi

Ketiga hasil transformasi pendapatan dibandingkan untuk melihat perbedaan skala yang dihasilkan.

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

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

Perbedaan hasil transformasi menunjukkan bahwa setiap metode memiliki cara berbeda dalam mengubah skala data. Pemilihan metode transformasi selanjutnya perlu disesuaikan dengan tujuan analisis.

Dampak outlier terhadap normalisasi

Untuk melihat pengaruh outlier, dilakukan normalisasi terhadap data pendapatan yang telah di-winsorize.

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
)

Hasil perbandingan menunjukkan bahwa outlier dapat membuat sebagian besar observasi berada pada rentang min–maks yang sangat sempit. Kondisi tersebut menjadi salah satu alasan mengapa outlier perlu diperiksa sebelum memilih metode transformasi.

Bagian VI — Integrasi Data

Memeriksa kunci pada kedua sumber

Sebelum kedua dataset digabungkan, identifier diperiksa untuk memastikan tidak terdapat duplikasi dan untuk mengetahui perbedaan pelanggan antara kedua sumber data.

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

Hasil pemeriksaan menunjukkan bahwa C011 terdapat pada data pelanggan tetapi tidak memiliki pasangan pada data transaksi. Sebaliknya, C012 terdapat pada data transaksi tetapi tidak ditemukan pada data pelanggan.

Menyelaraskan nama identifier

Data transaksi menggunakan nama cust_id, sedangkan data pelanggan menggunakan customer_id. Nama identifier disesuaikan agar kedua data dapat digabungkan.

transaksi <- transaksi_raw

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

Melakukan left join dengan merge()

Penggabungan dilakukan dengan mempertahankan seluruh data pelanggan. Dengan demikian, pelanggan yang tidak memiliki transaksi tetap terdapat dalam dataset hasil integrasi.

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

data_terintegrasi[
  ,
  c(
    "customer_id",
    "nama",
    "jumlah_transaksi",
    "total_purchase"
  )
]
##    customer_id   nama jumlah_transaksi total_purchase
## 1         C001    Ani                5        1.5e+06
## 2         C002   Budi                3        9.0e+05
## 3         C003  Citra                7        2.7e+06
## 4         C004   Dodi                2        6.0e+05
## 5         C005    Eka                6        2.1e+06
## 6         C006   Fani                4        1.3e+06
## 7         C007 Gilang               20        2.5e+07
## 8         C008   Hana                5        1.7e+06
## 9         C009  Indra                3        8.0e+05
## 10        C010   Joko                8        3.2e+06
## 11        C011   Kiki               NA             NA

Memvalidasi hasil integrasi

Setelah penggabungan dilakukan, hasil integrasi diperiksa kembali.

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

Hasil integrasi mempertahankan 11 pelanggan. C011 memiliki nilai missing pada informasi transaksi karena tidak ditemukan pasangan transaksi.

Mengisi nol atau mempertahankan NA?

Nilai NA pada transaksi perlu dipahami terlebih dahulu. Nilai tersebut dapat berarti pelanggan memang belum pernah bertransaksi atau data transaksi pelanggan tidak tersedia. Oleh karena itu, perubahan NA menjadi nol hanya dilakukan apabila telah diketahui bahwa NA memang berarti tidak ada transaksi.

Pada pengolahan ini digunakan asumsi bahwa C011 belum pernah melakukan transaksi, sehingga nilai transaksi dapat direpresentasikan sebagai nol.

data_terintegrasi$jumlah_transaksi_final <-
  data_terintegrasi$jumlah_transaksi

data_terintegrasi$total_purchase_final <-
  data_terintegrasi$total_purchase

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

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

Dataset Akhir dan Evaluasi

Memilih atribut akhir

Setelah seluruh proses preprocessing selesai, dipilih atribut yang dibutuhkan untuk membentuk dataset akhir.

data_final <- data_terintegrasi[
  ,
  c(
    "customer_id",
    "nama",
    "usia_imputasi",
    "kota_imputasi",
    "status",
    "pendapatan_imputasi",
    "pendapatan_missing",
    "outlier_pendapatan",
    "usia_minmax",
    "pendapatan_minmax",
    "jumlah_transaksi_final",
    "total_purchase_final"
  )
]

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

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

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

Dataset akhir berisi informasi pelanggan yang telah dibersihkan dan ditambah dengan informasi transaksi. Variabel hasil imputasi dan transformasi juga dipertahankan untuk kebutuhan analisis selanjutnya.

Audit akhir

Audit akhir dilakukan untuk memastikan tidak terdapat missing value yang tidak ditangani dan tidak terdapat duplikasi identifier.

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

Hasil audit menunjukkan bahwa dataset akhir tidak memiliki missing value dan tidak terdapat duplikasi customer_id. Dengan demikian, dataset telah berada pada kondisi yang lebih konsisten dibandingkan dataset awal.

Membandingkan kondisi sebelum dan sesudah

Perbandingan dilakukan untuk melihat perubahan kualitas data setelah preprocessing.

perbandingan <- data.frame(
  indikator = c(
    "Jumlah baris",
    "Duplikasi customer_id",
    "Total missing value",
    "Kategori kota unik",
    "Kategori status unik"
  ),
  sebelum = c(
    nrow(pelanggan_raw),
    sum(
      duplicated(
        pelanggan_raw$customer_id
      )
    ),
    sum(
      is.na(pelanggan_raw)
    ),
    length(
      unique(
        pelanggan_raw$kota
      )
    ),
    length(
      unique(
        pelanggan_raw$status
      )
    )
  ),
  sesudah = c(
    nrow(data_final),
    sum(
      duplicated(
        data_final$customer_id
      )
    ),
    sum(
      is.na(data_final)
    ),
    length(
      unique(
        data_final$kota
      )
    ),
    length(
      unique(
        data_final$status
      )
    )
  )
)

perbandingan
##               indikator sebelum sesudah
## 1          Jumlah baris      12      11
## 2 Duplikasi customer_id       1       0
## 3   Total missing value       4       0
## 4    Kategori kota unik      10       4
## 5  Kategori status unik       8       2

Perbandingan menunjukkan bahwa jumlah baris berkurang dari 12 menjadi 11 karena satu data duplikat dihapus. Duplikasi customer_id berkurang dari satu menjadi nol. Total missing value juga berhasil ditangani, sedangkan kategori kota dan status menjadi lebih sederhana dan konsisten.

Menyimpan hasil

Dataset akhir dan catatan perubahan disimpan agar dapat digunakan kembali pada tahap analisis selanjutnya.

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

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

Hasil dan Pembahasan

Berdasarkan seluruh tahapan preprocessing, kondisi awal data pelanggan dan transaksi menunjukkan beberapa permasalahan kualitas data. Pada data pelanggan terdapat missing value pada usia, pendapatan, dan kota. Selain itu, ditemukan satu customer_id yang tercatat dua kali serta beberapa variasi penulisan pada kategori kota dan status.

Tahap cleaning dilakukan dengan membersihkan spasi dan menyeragamkan kapitalisasi sebelum kategori distandardisasi. Variasi seperti PKU, PEKANBARU, dan pekanbaru disatukan menjadi kategori Pekanbaru. Begitu pula dengan variasi status seperti ACTIVE, A, dan aktif yang diseragamkan menjadi Aktif. Data C010 yang tercatat dua kali kemudian diperiksa dan salah satu kemunculannya dihapus.

Pemeriksaan aturan domain menemukan usia 150 tahun pada C004. Nilai tersebut tidak sesuai dengan rentang usia yang digunakan sehingga dikoreksi menjadi 50 berdasarkan informasi sumber asli. Pemeriksaan pendapatan tidak menemukan nilai negatif, tetapi ditemukan nilai pendapatan sebesar 500 juta yang sangat berbeda dari observasi lainnya.

Missing value pada pendapatan dan usia ditangani menggunakan median. Pemilihan median dilakukan karena nilai pendapatan ekstrem menyebabkan perbedaan yang cukup besar antara mean dan median. Untuk kota yang tidak diketahui digunakan kategori Tidak diketahui agar ketidakpastian data tidak disembunyikan.

Pendapatan kemudian diperiksa menggunakan metode IQR. Hasilnya menunjukkan bahwa pendapatan C007 sebesar 500 juta merupakan kandidat outlier. Nilai tersebut tidak langsung dihapus karena belum terdapat bukti bahwa nilai tersebut merupakan kesalahan. Sebagai alternatif, dilakukan winsorizing untuk melihat bagaimana pembatasan nilai ekstrem memengaruhi hasil transformasi.

Tahap transformasi dilakukan menggunakan Min-Max, Z-Score, dan Decimal Scaling. Hasil transformasi menunjukkan bahwa keberadaan outlier dapat mempengaruhi skala data, terutama pada normalisasi Min-Max. Oleh karena itu, pemeriksaan outlier menjadi bagian penting sebelum menentukan transformasi yang digunakan.

Pada tahap integrasi, data pelanggan dan transaksi digabungkan menggunakan customer_id. Hasil pemeriksaan menunjukkan bahwa C011 tidak memiliki pasangan transaksi, sedangkan C012 terdapat pada data transaksi tetapi tidak terdapat pada data pelanggan. Karena tujuan penggabungan adalah mempertahankan seluruh pelanggan, digunakan merge() dengan all.x = TRUE.

Setelah seluruh proses dilakukan, dataset akhir tidak lagi memiliki missing value dan duplikasi customer_id. Kategori kota dan status juga menjadi lebih konsisten. Hasil tersebut menunjukkan bahwa proses preprocessing berhasil menghasilkan dataset yang lebih terstruktur dan siap digunakan untuk tahap analisis berikutnya.

Kesimpulan

Data pelanggan dan transaksi yang digunakan dalam pengolahan ini memiliki beberapa permasalahan yang perlu diperbaiki sebelum dilakukan analisis. Permasalahan tersebut meliputi missing value, ketidakkonsistenan kategori, duplikasi identifier, nilai usia yang tidak sesuai, dan nilai pendapatan yang ekstrem.

Proses preprocessing dilakukan melalui beberapa tahap, yaitu pemeriksaan data awal, cleaning, penanganan missing value, deteksi outlier, transformasi, dan integrasi data. Missing value numerik ditangani menggunakan median, sedangkan missing value kategorik pada kota diberi kategori Tidak diketahui. Duplikasi C010 dihapus dan nilai usia C004 dikoreksi berdasarkan aturan domain.

Deteksi menggunakan metode IQR menunjukkan bahwa pendapatan C007 merupakan kandidat outlier. Nilai tersebut dipertahankan karena belum dapat dibuktikan sebagai kesalahan, sementara versi winsorized dibuat sebagai pembanding. Data numerik kemudian ditransformasikan menggunakan Min-Max, Z-Score, dan Decimal Scaling.

Pada tahap akhir, data pelanggan dan transaksi berhasil diintegrasikan menggunakan customer_id. Dataset hasil preprocessing memiliki struktur yang lebih konsisten, tidak memiliki missing value, dan tidak memiliki duplikasi customer_id. Dengan demikian, dataset tersebut dapat digunakan sebagai dasar untuk analisis pelanggan dan transaksi pada tahap selanjutnya.

Ringkasan

Alur preprocessing yang dilakukan dalam pengolahan data ini terdiri atas pemeriksaan kondisi awal, pembersihan data, penanganan missing value, pemeriksaan outlier, transformasi variabel numerik, integrasi data pelanggan dan transaksi, serta evaluasi dataset akhir.

Hasil preprocessing menunjukkan bahwa perbaikan kualitas data tidak hanya dilakukan dengan menghapus nilai yang dianggap bermasalah. Setiap perubahan perlu mempertimbangkan karakteristik data dan tujuan analisis. Oleh karena itu, proses cleaning, imputasi, deteksi outlier, dan integrasi dilakukan dengan mempertahankan informasi penting dari dataset awal.

Daftar Pustaka

Batini, C., & Scannapieco, M. (2016). Data and Information Quality: Dimensions, Principles and Techniques. Springer.

Kuhn, M., & Johnson, K. (2013). Applied Predictive Modeling. Springer.

Wickham, H., & Grolemund, G. (2017). R for Data Science: Import, Tidy, Transform, Visualize, and Model Data. O’Reilly Media.