Eksplorasi & Preprocessing Data Pelanggan

Data Cleaning, Transformation & Integration

R Programming   •   Data Analysis   •   Data Preprocessing

R Data Cleaning Data Analysis Data Transformation Data Integration

Project ini merupakan proses pengolahan data pelanggan dari kondisi awal hingga menjadi dataset yang lebih rapi dan siap digunakan untuk analisis.

Data berasal dari dua sumber, yaitu data pelanggan dan data transaksi. Fokus utama project adalah memahami masalah pada data, menentukan aturan pembersihan, melakukan transformasi, kemudian menggabungkan kedua sumber data tersebut.

Latar Belakang

Sebuah perusahaan e-commerce ingin melakukan analisis terhadap pelanggan. Data yang tersedia berasal dari dua sumber berbeda.

Data pelanggan memuat identitas pelanggan, usia, kota, pendapatan, dan status pelanggan. Sementara itu, data transaksi berisi jumlah transaksi dan total pembelian.

Pada kondisi awal, data belum dapat langsung digunakan karena terdapat beberapa persoalan seperti nilai hilang, penulisan kategori yang tidak konsisten, duplikasi, nilai ekstrem, serta perbedaan nama identifier antar dataset.

Karena itu, sebelum masuk ke tahap analisis, data perlu diperiksa dan dipersiapkan terlebih dahulu.

Prinsip pengolahan data:

data tidak dibersihkan hanya karena sebuah nilai terlihat berbeda. Setiap perubahan harus memiliki alasan, aturan yang jelas, dan dapat dijelaskan kembali.

Data yang Digunakan

Terdapat dua dataset yang digunakan dalam project ini.

Data pertama berisi informasi dasar pelanggan, sedangkan data kedua berisi aktivitas transaksi pelanggan.

Data Pelanggan

Variabel Keterangan
customer_id ID pelanggan
nama Nama pelanggan
usia Usia pelanggan
pendapatan Pendapatan pelanggan
kota Kota tempat tinggal
status Status pelanggan

Data Transaksi

Variabel Keterangan
cust_id ID pelanggan
jumlah_transaksi Jumlah transaksi
total_purchase Total pembelian

Menyiapkan Dataset

Saya mulai dengan membuat dataset pelanggan yang akan digunakan dalam proses eksplorasi.

Data ini sengaja dibuat memiliki beberapa permasalahan sederhana agar proses preprocessing dapat dilakukan secara lengkap.

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

Kemudian saya membuat dataset transaksi.

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

Eksplorasi Data Awal

Sebelum melakukan perubahan pada data, saya melihat kondisi dataset terlebih dahulu.

Hal ini dilakukan agar perubahan yang dilakukan pada tahap preprocessing dapat dibandingkan dengan kondisi awal.

Melihat Data

head(pelanggan)

Dataset awal terdiri dari 12 observasi dan 6 variabel.

dim(pelanggan)
## [1] 12  6

Nama variabel kemudian diperiksa untuk memastikan struktur dataset.

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

Struktur Data

Saya melihat tipe data dari setiap variabel.

str(pelanggan)
## '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" ...

Variabel usia dan pendapatan merupakan variabel numerik. Sementara itu, customer_id, nama, kota, dan status merupakan variabel karakter.

Ringkasan Data

summary(pelanggan)
##  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  
##                                       
##                                       
##                                       
## 

Ringkasan ini memberikan gambaran awal mengenai distribusi variabel numerik serta kategori pada variabel lainnya.

Memeriksa Kualitas Data

Setelah melihat struktur data, saya memeriksa beberapa masalah yang dapat ditemukan dalam dataset.

Missing Value

Missing value diperiksa menggunakan is.na().

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

Persentase missing value juga dihitung agar proporsinya lebih mudah dibandingkan.

round(
  colMeans(is.na(pelanggan)) * 100,
  2
)
## customer_id        nama        usia  pendapatan        kota      status 
##        0.00        0.00        8.33       16.67        8.33        0.00

Saya kemudian membuat tabel ringkasan.

missing_data <- data.frame(

  Variabel = names(pelanggan),

  Jumlah_Missing = colSums(
    is.na(pelanggan)
  ),

  Persentase_Missing = round(
    colMeans(is.na(pelanggan)) * 100,
    2
  )

)

kable(
  missing_data,
  caption = "Missing Value pada Dataset Awal"
)
Missing Value pada Dataset Awal
Variabel Jumlah_Missing Persentase_Missing
customer_id customer_id 0 0.00
nama nama 0 0.00
usia usia 1 8.33
pendapatan pendapatan 2 16.67
kota kota 1 8.33
status status 0 0.00

Missing value ditemukan pada usia, pendapatan, dan kota.

Data Duplikat

Selanjutnya saya memeriksa apakah terdapat baris yang sama.

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

Untuk melihat baris yang terindikasi duplikat:

pelanggan[
  duplicated(pelanggan) |
  duplicated(pelanggan, fromLast = TRUE),
]

Saya juga memeriksa duplikasi pada ID pelanggan.

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

Terdapat satu ID yang tercatat lebih dari satu kali, yaitu C010.

Karena customer_id digunakan sebagai identitas pelanggan, kondisi ini perlu ditangani sebelum proses berikutnya.

Menyeragamkan Data Kategorik

Menyeragamkan Kota

Variabel kota memiliki beberapa bentuk penulisan yang berbeda.

Misalnya Pekanbaru, PEKANBARU, pekanbaru, dan PKU sebenarnya merujuk pada kota yang sama.

Saya menyeragamkan kategori tersebut agar dapat diperlakukan sebagai satu kategori.

pelanggan <- pelanggan %>%

  mutate(

    kota = trimws(kota),

    kota = case_when(

      tolower(kota) %in%
        c("pku", "pekanbaru") ~
        "Pekanbaru",

      tolower(kota) == "dumai" ~
        "Dumai",

      tolower(kota) == "siak" ~
        "Siak",

      TRUE ~ kota

    )

  )

Kategori kota setelah cleaning:

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

Menyeragamkan Status

Variabel status juga memiliki beberapa bentuk penulisan yang berbeda.

Kategori tersebut diseragamkan menjadi Aktif dan Tidak Aktif.

pelanggan <- pelanggan %>%

  mutate(

    status = trimws(status),

    status = case_when(

      tolower(status) %in%
        c("aktif", "active", "a") ~
        "Aktif",

      tolower(status) %in%
        c("tidak aktif", "nonaktif") ~
        "Tidak Aktif",

      TRUE ~ status

    )

  )

Distribusi status setelah cleaning:

table(
  pelanggan$status,
  useNA = "ifany"
)
## 
##       Aktif Tidak Aktif 
##           8           4

Gambaran Status Pelanggan

Untuk melihat hasil penyeragaman kategori secara visual, saya membuat diagram batang.

status_data <- pelanggan %>%
  count(status)

ggplot(
  status_data,
  aes(
    x = status,
    y = n
  )
) +

  geom_col(
    width = 0.2,
    fill = "#6B8EAD"
  ) +

  geom_text(
    aes(label = n),
    vjust = -0.4,
    fontface = "bold",
    size = 4
  ) +

  labs(
    title = "Distribusi Status Pelanggan",
    subtitle = "Jumlah pelanggan berdasarkan status",
    x = NULL,
    y = "Jumlah Pelanggan"
  ) +

  theme_minimal() +

  theme(
    plot.title = element_text(
      size = 15,
      face = "bold"
    ),

    plot.subtitle = element_text(
      color = "#607D8B"
    ),

    panel.grid.minor = element_blank()
  )

Setelah kategori diseragamkan, distribusi pelanggan dapat dilihat dengan lebih jelas.

Menangani Data Duplikat

Karena satu pelanggan seharusnya memiliki satu customer_id, saya mempertahankan satu observasi untuk setiap ID.

pelanggan <- pelanggan %>%

  distinct(
    customer_id,
    .keep_all = TRUE
  )

rownames(pelanggan) <- NULL

Pengecekan dilakukan kembali.

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

Setelah proses ini, setiap pelanggan hanya memiliki satu ID.

Memeriksa Nilai Tidak Wajar

Selain missing value dan duplikasi, saya memeriksa apakah terdapat nilai yang tidak sesuai dengan konteks data.

Untuk usia, saya menggunakan rentang 15 sampai 100 tahun sebagai batas pemeriksaan.

pelanggan %>%

  filter(
    usia < 15 |
    usia > 100
  )

Ditemukan satu nilai usia sebesar 150 tahun.

Nilai tersebut tidak masuk akal untuk konteks data pelanggan sehingga perlu diperiksa.

Koreksi Usia

Pada project ini, nilai tersebut saya ubah menjadi 50 tahun sebagai contoh koreksi kesalahan input.

pelanggan <- pelanggan %>%

  mutate(

    usia = ifelse(
      usia == 150,
      50,
      usia
    )

  )

Pada data sebenarnya, koreksi seperti ini sebaiknya dilakukan berdasarkan informasi dari sumber data.

Menangani Missing Value

Setelah mengetahui jenis missing value yang ada, saya menggunakan pendekatan yang berbeda sesuai dengan jenis variabel.

Untuk variabel numerik digunakan median, sedangkan untuk variabel kategorik kota digunakan kategori "Tidak diketahui".

Menyimpan Data Sebelum Imputasi

Nilai pendapatan sebelum imputasi disimpan agar dapat dibandingkan setelah proses dilakukan.

pendapatan_sebelum <-
  pelanggan$pendapatan

usia_sebelum <-
  pelanggan$usia

Imputasi Usia

Median usia dihitung terlebih dahulu.

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

median_usia
## [1] 27.5

Kemudian missing value pada usia diganti menggunakan median tersebut.

pelanggan <- pelanggan %>%

  mutate(

    usia_imputasi = ifelse(
      is.na(usia),
      median_usia,
      usia
    )

  )

Imputasi Pendapatan

Median pendapatan dihitung dari nilai yang tersedia.

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

median_pendapatan
## [1] 4900000

Missing value kemudian diganti menggunakan median pendapatan.

pelanggan <- pelanggan %>%

  mutate(

    pendapatan_imputasi = ifelse(
      is.na(pendapatan),
      median_pendapatan,
      pendapatan
    )

  )

Imputasi Kota

Missing value pada kota diganti menggunakan kategori "Tidak diketahui".

pelanggan <- pelanggan %>%

  mutate(

    kota = ifelse(
      is.na(kota),
      "Tidak diketahui",
      kota
    )

  )

Kategori kota kemudian diperiksa kembali.

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

Menggunakan Hasil Imputasi

Setelah proses imputasi selesai, hasil imputasi digunakan sebagai nilai akhir.

pelanggan <- pelanggan %>%

  mutate(

    usia = usia_imputasi,

    pendapatan =
      pendapatan_imputasi

  )

Saya tetap menyimpan kolom usia_imputasi dan pendapatan_imputasi untuk melihat hasil proses preprocessing.

Pemeriksaan Setelah Cleaning

Setelah proses cleaning dan imputasi, saya memeriksa kembali missing value.

colSums(
  is.na(
    pelanggan %>%
      select(
        customer_id,
        nama,
        usia,
        pendapatan,
        kota,
        status
      )
  )
)
## customer_id        nama        usia  pendapatan        kota      status 
##           0           0           0           0           0           0

Pada variabel utama pelanggan, missing value sudah ditangani.

Perubahan Pendapatan Setelah Imputasi

Untuk melihat perbedaan sebelum dan sesudah imputasi, saya membuat histogram pendapatan.

hist(

  pendapatan_sebelum,

  main =
    "Pendapatan Sebelum Imputasi",

  xlab =
    "Pendapatan",

  ylab =
    "Frekuensi",

  col =
    "#E8F1F7",

  border =
    "#6B8EAD",

  breaks =
    10

)

Distribusi setelah imputasi:

hist(

  pelanggan$pendapatan,

  main =
    "Pendapatan Setelah Imputasi",

  xlab =
    "Pendapatan",

  ylab =
    "Frekuensi",

  col =
    "#EDF4F8",

  border =
    "#7C9AAF",

  breaks =
    10

)

Perubahan utama terjadi pada nilai yang sebelumnya kosong. Setelah imputasi, nilai tersebut diisi menggunakan median pendapatan.

Mendeteksi Outlier

Setelah data dibersihkan, saya memeriksa kemungkinan adanya outlier pada pendapatan.

Metode yang digunakan adalah Interquartile Range (IQR).

Boxplot Pendapatan

boxplot(

  pelanggan$pendapatan,

  main =
    "Distribusi Pendapatan Pelanggan",

  ylab =
    "Pendapatan",

  col =
    "#DCEAF3",

  border =
    "#4F6D8A"

)

Boxplot menunjukkan adanya satu nilai yang jauh dari sebagian besar observasi.

Untuk melihatnya secara lebih terukur, saya menghitung Q1, Q3, dan IQR.

Q1 <- quantile(
  pelanggan$pendapatan,
  0.25,
  na.rm = TRUE
)

Q3 <- quantile(
  pelanggan$pendapatan,
  0.75,
  na.rm = TRUE
)

IQR_pendapatan <- IQR(
  pelanggan$pendapatan,
  na.rm = TRUE
)

Q1
##     25% 
## 4750000
Q3
##     75% 
## 5150000
IQR_pendapatan
## [1] 4e+05

Batas outlier dihitung menggunakan rumus:

Q1 - 1.5 × IQR

dan

Q3 + 1.5 × IQR

batas_bawah <-
  Q1 - 1.5 * IQR_pendapatan

batas_atas <-
  Q3 + 1.5 * IQR_pendapatan

batas_bawah
##     25% 
## 4150000
batas_atas
##     75% 
## 5750000

Observasi yang berada di luar batas tersebut:

outlier_pendapatan <- pelanggan %>%

  filter(

    pendapatan < batas_bawah |
    pendapatan > batas_atas

  ) %>%

  select(
    customer_id,
    nama,
    pendapatan
  )

outlier_pendapatan

C007 menjadi observasi yang paling menonjol karena memiliki pendapatan sebesar Rp500.000.000.

Nilai ekstrem tidak langsung dihapus. Dalam data nyata, nilai tersebut perlu diperiksa kembali untuk menentukan apakah merupakan kesalahan pencatatan atau memang menggambarkan kondisi sebenarnya.

Transformasi Data

Setelah proses cleaning, saya mencoba beberapa metode transformasi.

Metode yang digunakan:

  • Min-Max,
  • Z-score,
  • Decimal Scaling.

Ketiga metode tersebut digunakan untuk melihat bagaimana satu variabel dapat direpresentasikan dalam skala yang berbeda.

Min-Max Normalization

Min-Max mengubah nilai ke dalam rentang 0 sampai 1.

minmax <- function(x) {

  if (all(is.na(x))) {

    return(
      rep(NA_real_, length(x))
    )

  }

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

  if (rentang == 0) {

    return(
      rep(0, length(x))
    )

  }

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

}

Fungsi kemudian diterapkan pada usia dan pendapatan.

pelanggan$usia_minmax <-
  minmax(
    pelanggan$usia
  )

pelanggan$pendapatan_minmax <-
  minmax(
    pelanggan$pendapatan
  )

Hasil transformasi:

tabel_minmax <- pelanggan %>%

  select(
    customer_id,
    usia,
    usia_minmax,
    pendapatan,
    pendapatan_minmax
  )

kable(
  tabel_minmax,
  digits = 3,
  caption = "Hasil Min-Max Normalization"
)
Hasil Min-Max Normalization
customer_id usia usia_minmax pendapatan pendapatan_minmax
C001 21.0 0.000 4.5e+06 0.000
C002 25.0 0.138 4.9e+06 0.001
C003 23.0 0.069 5.2e+06 0.001
C004 50.0 1.000 4.8e+06 0.001
C005 27.0 0.207 4.9e+06 0.001
C006 27.5 0.224 5.1e+06 0.001
C007 31.0 0.345 5.0e+08 1.000
C008 29.0 0.276 4.7e+06 0.000
C009 22.0 0.034 4.6e+06 0.000
C010 35.0 0.483 5.3e+06 0.002
C011 28.0 0.241 4.9e+06 0.001

Pada pendapatan, C007 mendapatkan nilai 1 karena memiliki nilai maksimum.

Akibatnya, sebagian besar pendapatan pelanggan lainnya berada dekat dengan 0.

Z-Score

Z-score digunakan untuk melihat posisi suatu observasi terhadap rata-rata.

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

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

Hasil standardisasi:

tabel_zscore <- pelanggan %>%

  select(
    customer_id,
    usia_z,
    pendapatan_z
  ) %>%

  mutate(

    usia_z =
      round(usia_z, 3),

    pendapatan_z =
      round(pendapatan_z, 3)

  )

kable(
  tabel_zscore,
  caption = "Hasil Standardisasi Z-Score"
)
Hasil Standardisasi Z-Score
customer_id usia_z pendapatan_z
C001 -0.984 -0.304
C002 -0.489 -0.301
C003 -0.737 -0.299
C004 2.604 -0.302
C005 -0.242 -0.301
C006 -0.180 -0.300
C007 0.253 3.015
C008 0.006 -0.303
C009 -0.860 -0.303
C010 0.748 -0.299
C011 -0.118 -0.301

Nilai positif menunjukkan posisi di atas rata-rata, sedangkan nilai negatif menunjukkan posisi di bawah rata-rata.

Pendapatan C007 memiliki nilai Z-score yang jauh lebih tinggi dibandingkan pelanggan lainnya. Hal ini menunjukkan bahwa pendapatan tersebut berada jauh di atas rata-rata.

Decimal Scaling

Decimal scaling dilakukan dengan membagi nilai menggunakan pangkat 10 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)

}

Fungsi kemudian diterapkan pada pendapatan.

pelanggan$pendapatan_decimal <-
  decimal_scale(
    pelanggan$pendapatan
  )

Rentang hasil decimal scaling:

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

Hasil transformasi hanya mengubah skala angka. Pengaruh nilai ekstrem masih tetap terlihat.

Membandingkan Hasil Transformasi

Untuk melihat perbedaan ketiga metode, saya membuat satu tabel perbandingan.

transformasi <- pelanggan %>%

  select(

    customer_id,

    pendapatan,

    pendapatan_minmax,

    pendapatan_z,

    pendapatan_decimal

  )

kable(

  transformasi,

  digits = 3,

  caption =
    "Perbandingan Transformasi Pendapatan"

)
Perbandingan Transformasi Pendapatan
customer_id pendapatan pendapatan_minmax pendapatan_z pendapatan_decimal
C001 4.5e+06 0.000 -0.304 0.004
C002 4.9e+06 0.001 -0.301 0.005
C003 5.2e+06 0.001 -0.299 0.005
C004 4.8e+06 0.001 -0.302 0.005
C005 4.9e+06 0.001 -0.301 0.005
C006 5.1e+06 0.001 -0.300 0.005
C007 5.0e+08 1.000 3.015 0.500
C008 4.7e+06 0.000 -0.303 0.005
C009 4.6e+06 0.000 -0.303 0.005
C010 5.3e+06 0.002 -0.299 0.005
C011 4.9e+06 0.001 -0.301 0.005

Dari hasil tersebut:

  • Min-Max menghasilkan nilai pada rentang 0 sampai 1.
  • Z-score menunjukkan posisi observasi terhadap rata-rata.
  • Decimal Scaling mengubah skala angka tanpa mengubah pola dasarnya.

Pada data ini, nilai C007 tetap terlihat ekstrem setelah transformasi.

Menyiapkan Data Transaksi

Sebelum menggabungkan kedua dataset, saya memeriksa kembali data transaksi.

head(transaksi)

Pada data transaksi, ID pelanggan diberi nama cust_id.

Sementara pada data pelanggan, ID diberi nama customer_id.

Agar dapat digunakan sebagai kunci penggabungan, nama variabel tersebut disamakan.

transaksi <- transaksi %>%

  rename(
    customer_id = cust_id
  )

Struktur data transaksi kemudian diperiksa.

str(transaksi)
## 'data.frame':    11 obs. of  3 variables:
##  $ customer_id     : chr  "C001" "C002" "C003" "C004" ...
##  $ jumlah_transaksi: num  5 3 7 2 6 4 20 5 3 8 ...
##  $ total_purchase  : num  1.5e+06 9.0e+05 2.7e+06 6.0e+05 2.1e+06 1.3e+06 2.5e+07 1.7e+06 8.0e+05 3.2e+06 ...

Menggabungkan Dataset

Setelah kedua dataset memiliki nama ID yang sama, saya menggabungkannya menggunakan left_join().

data_final <- pelanggan %>%

  left_join(
    transaksi,
    by = "customer_id"
  )

Saya menggunakan left_join() karena ingin mempertahankan seluruh pelanggan yang terdapat pada dataset utama.

Jika pelanggan tidak memiliki transaksi, kolom transaksi akan berisi NA.

Melihat Hasil Integrasi

Beberapa baris pertama dataset hasil penggabungan:

head(data_final)

Struktur dataset akhir:

str(data_final)
## 'data.frame':    11 obs. of  15 variables:
##  $ customer_id        : chr  "C001" "C002" "C003" "C004" ...
##  $ nama               : chr  "Ani" "Budi" "Citra" "Dodi" ...
##  $ usia               : num  21 25 23 50 27 27.5 31 29 22 35 ...
##  $ pendapatan         : num  4.5e+06 4.9e+06 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" "Pekanbaru" "Pekanbaru" "Dumai" ...
##  $ status             : chr  "Aktif" "Aktif" "Aktif" "Aktif" ...
##  $ usia_imputasi      : num  21 25 23 50 27 27.5 31 29 22 35 ...
##  $ pendapatan_imputasi: num  4.5e+06 4.9e+06 5.2e+06 4.8e+06 4.9e+06 5.1e+06 5.0e+08 4.7e+06 4.6e+06 5.3e+06 ...
##  $ usia_minmax        : num  0 0.138 0.069 1 0.207 ...
##  $ pendapatan_minmax  : num  0 0.000807 0.001413 0.000605 0.000807 ...
##  $ usia_z             : num  -0.984 -0.489 -0.737 2.604 -0.242 ...
##  $ pendapatan_z       : num  -0.304 -0.301 -0.299 -0.302 -0.301 ...
##  $ pendapatan_decimal : num  0.0045 0.0049 0.0052 0.0048 0.0049 0.0051 0.5 0.0047 0.0046 0.0053 ...
##  $ jumlah_transaksi   : num  5 3 7 2 6 4 20 5 3 8 ...
##  $ total_purchase     : num  1.5e+06 9.0e+05 2.7e+06 6.0e+05 2.1e+06 1.3e+06 2.5e+07 1.7e+06 8.0e+05 3.2e+06 ...

Memeriksa Missing Value Setelah Join

Setelah penggabungan, saya kembali memeriksa missing value.

colSums(
  is.na(data_final)
)
##         customer_id                nama                usia          pendapatan 
##                   0                   0                   0                   0 
##                kota              status       usia_imputasi pendapatan_imputasi 
##                   0                   0                   0                   0 
##         usia_minmax   pendapatan_minmax              usia_z        pendapatan_z 
##                   0                   0                   0                   0 
##  pendapatan_decimal    jumlah_transaksi      total_purchase 
##                   0                   1                   1

Missing value pada jumlah_transaksi dan total_purchase tidak selalu menunjukkan masalah pada data pelanggan.

Contohnya, C011 terdapat pada data pelanggan tetapi tidak memiliki pasangan pada data transaksi.

Artinya, pelanggan tersebut belum memiliki data transaksi pada dataset yang digunakan.

Eksplorasi Setelah Preprocessing

Setelah proses preprocessing selesai, saya mencoba melihat beberapa informasi sederhana dari dataset akhir.

Rata-rata Pendapatan

mean(
  data_final$pendapatan,
  na.rm = TRUE
)
## [1] 49900000

Total Jumlah Transaksi

sum(
  data_final$jumlah_transaksi,
  na.rm = TRUE
)
## [1] 63

Total Pembelian

sum(
  data_final$total_purchase,
  na.rm = TRUE
)
## [1] 39800000

Hasil tersebut memberikan gambaran awal mengenai karakteristik pelanggan dan aktivitas transaksi pada dataset.

Pendapatan dan Total Pembelian

Saya kemudian melihat hubungan antara pendapatan pelanggan dan total pembelian.

ggplot(

  data_final,

  aes(
    x = pendapatan,
    y = total_purchase
  )

) +

  geom_point(
    size = 3,
    alpha = 0.75,
    color = "#6B8EAD"
  ) +

  scale_x_continuous(
    labels = comma
  ) +

  scale_y_continuous(
    labels = comma
  ) +

  labs(

    title =
      "Pendapatan dan Total Pembelian",

    subtitle =
      "Eksplorasi hubungan antarvariabel",

    x =
      "Pendapatan",

    y =
      "Total Pembelian"

  ) +

  theme_minimal() +

  theme(

    plot.title =
      element_text(
        face = "bold",
        size = 15
      ),

    plot.subtitle =
      element_text(
        color = "#607D8B"
      ),

    panel.grid.minor =
      element_blank()

  )

Grafik ini digunakan sebagai eksplorasi awal.

C007 memiliki pendapatan yang jauh lebih tinggi dibandingkan pelanggan lain sehingga posisinya perlu diperhatikan ketika membaca hubungan kedua variabel.

Pemeriksaan Dataset Akhir

Sebagai langkah terakhir, saya melakukan audit sederhana terhadap dataset hasil preprocessing.

cat(
  "Jumlah observasi :",
  nrow(data_final),
  "\n"
)
## Jumlah observasi : 11
cat(
  "Jumlah variabel  :",
  ncol(data_final),
  "\n"
)
## Jumlah variabel  : 15
cat(
  "Total missing    :",
  sum(is.na(data_final)),
  "\n"
)
## Total missing    : 2
cat(
  "Duplikat ID      :",
  sum(
    duplicated(
      data_final$customer_id
    )
  ),
  "\n"
)
## Duplikat ID      : 0

Untuk memudahkan pembacaan, hasil pemeriksaan dibuat menjadi tabel.

audit_akhir <- data.frame(

  Indikator = c(
    "Jumlah observasi",
    "Jumlah variabel",
    "Total missing value",
    "Duplicate customer ID"
  ),

  Hasil = c(

    nrow(data_final),

    ncol(data_final),

    sum(
      is.na(data_final)
    ),

    sum(
      duplicated(
        data_final$customer_id
      )
    )

  )

)

kable(

  audit_akhir,

  caption =
    "Pemeriksaan Dataset Akhir"

)
Pemeriksaan Dataset Akhir
Indikator Hasil
Jumlah observasi 11
Jumlah variabel 15
Total missing value 2
Duplicate customer ID 0

Dataset Akhir

Berikut beberapa variabel utama dari dataset setelah proses cleaning, transformasi, dan penggabungan.

data_tampil <- data_final %>%

  select(

    customer_id,
    nama,
    usia,
    pendapatan,
    kota,
    status,
    jumlah_transaksi,
    total_purchase,
    pendapatan_minmax,
    pendapatan_z,
    pendapatan_decimal

  )

kable(

  data_tampil,

  digits = 3,

  caption =
    "Dataset Akhir Setelah Preprocessing"

)
Dataset Akhir Setelah Preprocessing
customer_id nama usia pendapatan kota status jumlah_transaksi total_purchase pendapatan_minmax pendapatan_z pendapatan_decimal
C001 Ani 21.0 4.5e+06 Pekanbaru Aktif 5 1.5e+06 0.000 -0.304 0.004
C002 Budi 25.0 4.9e+06 Pekanbaru Aktif 3 9.0e+05 0.001 -0.301 0.005
C003 Citra 23.0 5.2e+06 Pekanbaru Aktif 7 2.7e+06 0.001 -0.299 0.005
C004 Dodi 50.0 4.8e+06 Dumai Aktif 2 6.0e+05 0.001 -0.302 0.005
C005 Eka 27.0 4.9e+06 Pekanbaru Tidak Aktif 6 2.1e+06 0.001 -0.301 0.005
C006 Fani 27.5 5.1e+06 Dumai Tidak Aktif 4 1.3e+06 0.001 -0.300 0.005
C007 Gilang 31.0 5.0e+08 Pekanbaru Aktif 20 2.5e+07 1.000 3.015 0.500
C008 Hana 29.0 4.7e+06 Siak Aktif 5 1.7e+06 0.000 -0.303 0.005
C009 Indra 22.0 4.6e+06 Pekanbaru Aktif 3 8.0e+05 0.000 -0.303 0.005
C010 Joko 35.0 5.3e+06 Dumai Tidak Aktif 8 3.2e+06 0.002 -0.299 0.005
C011 Kiki 28.0 4.9e+06 Tidak diketahui Aktif NA NA 0.001 -0.301 0.005

Temuan Utama

Beberapa hal yang terlihat dari proses preprocessing ini adalah:

1. Data awal memiliki beberapa masalah kualitas

Terdapat missing value, duplikasi ID, kategori yang belum konsisten, dan satu nilai usia yang tidak sesuai dengan konteks data.

2. Kategori perlu diseragamkan

Variabel kota dan status memiliki beberapa bentuk penulisan yang berbeda meskipun merujuk pada kategori yang sama.

3. Nilai ekstrem perlu diperiksa

C007 memiliki pendapatan Rp500.000.000 dan terdeteksi sebagai outlier berdasarkan metode IQR.

4. Transformasi tidak menghilangkan nilai ekstrem

Min-Max, Z-score, dan decimal scaling hanya mengubah representasi atau skala data. Nilai ekstrem tetap memiliki pengaruh terhadap hasil.

5. Tidak semua pelanggan memiliki transaksi

Setelah left_join(), terdapat pelanggan yang tidak memiliki pasangan data transaksi. Kondisi tersebut menghasilkan NA pada variabel transaksi dan perlu dibedakan dari missing value pada data awal.

Kesimpulan

Project ini menunjukkan proses preprocessing data pelanggan menggunakan R mulai dari kondisi awal sampai menghasilkan dataset yang lebih terstruktur.

Pada tahap eksplorasi awal ditemukan beberapa masalah, yaitu missing value, kategori yang belum konsisten, duplikasi ID, dan nilai usia yang tidak wajar.

Data kemudian dibersihkan dengan menyeragamkan kategori kota dan status, menghapus duplikasi berdasarkan customer_id, serta melakukan koreksi pada nilai usia yang tidak sesuai.

Missing value pada variabel numerik ditangani menggunakan median, sedangkan missing value pada variabel kota diganti dengan kategori "Tidak diketahui".

Setelah itu dilakukan deteksi outlier menggunakan metode IQR. Hasilnya menunjukkan bahwa C007 memiliki pendapatan yang jauh lebih tinggi dibandingkan pelanggan lainnya. Nilai tersebut tidak langsung dihapus karena masih perlu dilihat berdasarkan konteks data.

Saya juga mencoba tiga metode transformasi, yaitu Min-Max, Z-score, dan decimal scaling. Hasilnya menunjukkan bahwa setiap metode memiliki karakteristik yang berbeda dan nilai ekstrem tetap dapat memengaruhi hasil transformasi.

Pada tahap integrasi, data pelanggan digabungkan dengan data transaksi menggunakan left_join() berdasarkan customer_id. Dari proses tersebut dapat diketahui bahwa tidak semua pelanggan memiliki data transaksi.

Secara keseluruhan, project ini menjadi latihan untuk memahami bahwa preprocessing bukan hanya tentang menghapus data yang bermasalah, tetapi juga tentang memahami kondisi data sebelum menentukan tindakan yang tepat.

Apa yang Saya Pelajari

Dari project ini, saya mendapatkan beberapa pengalaman dalam menggunakan R untuk pengolahan data.

Beberapa hal yang saya pelajari antara lain:

  • melakukan eksplorasi dataset menggunakan R,
  • menggunakan dplyr untuk proses cleaning,
  • menangani missing value,
  • memeriksa duplikasi,
  • menyeragamkan data kategorik,
  • mendeteksi nilai yang tidak wajar,
  • menggunakan IQR untuk mendeteksi outlier,
  • melakukan normalisasi dan standardisasi,
  • memahami perbedaan beberapa metode transformasi,
  • menggunakan left_join() untuk menggabungkan dataset,
  • serta melakukan pemeriksaan kembali setelah proses preprocessing.

Project ini juga membantu saya memahami bahwa kualitas data sangat berpengaruh terhadap proses analisis berikutnya.

From Data to Insight

Mengenali masalah pada data, memperbaikinya dengan tepat, dan memahami informasi yang dihasilkan.

Nia Andini
Statistika • Data Analysis • R Programming