1 Pendahuluan

1.1 Latar Belakang

Data preprocessing merupakan salah satu tahap penting dalam proses data mining karena data yang diperoleh dari suatu sumber umumnya belum berada dalam kondisi yang sepenuhnya siap untuk digunakan dalam analisis. Data mentah dapat mengandung berbagai permasalahan, seperti missing value, data duplikat, kategori yang tidak konsisten, kesalahan dalam batas atau aturan nilai (domain error), serta nilai yang terlalu ekstrem atau outlier. Apabila permasalahan tersebut tidak diperiksa dan ditangani dengan tepat, kualitas data dapat menurun dan pada akhirnya dapat memengaruhi hasil analisis maupun proses pemodelan yang dilakukan.

Oleh karena itu, diperlukan proses preprocessing untuk memahami kondisi data sejak awal, mengidentifikasi berbagai permasalahan yang terdapat di dalamnya, serta menentukan tindakan yang sesuai untuk memperbaiki atau mengelola permasalahan tersebut. Proses ini tidak hanya berfokus pada menghilangkan data yang dianggap bermasalah, tetapi juga mempertimbangkan apakah suatu nilai benar-benar merupakan kesalahan atau justru memiliki informasi yang penting. Dengan demikian, preprocessing merupakan bagian yang membutuhkan pemeriksaan dan pengambilan keputusan secara sistematis sebelum data digunakan pada tahap analisis selanjutnya.

Pada tugas ini, proses preprocessing diterapkan secara bertahap menggunakan RStudio dengan mengacu pada materi Data Preprocessing Menggunakan RStudio. Tahapan dimulai dengan memahami struktur dan kondisi awal dataset, kemudian dilanjutkan dengan proses data cleaning untuk memperbaiki ketidakkonsistenan, memeriksa duplikasi, serta mengevaluasi kesesuaian nilai terhadap aturan domain. Setelah itu, dilakukan identifikasi dan penanganan missing values, kemudian dilanjutkan dengan pendeteksian dan evaluasi outlier menggunakan pendekatan yang sesuai.

Setelah kualitas data diperbaiki, tahap berikutnya adalah melakukan transformasi atribut numerik, seperti normalisasi Min-Max, normalisasi Z-Score, dan decimal scaling. Transformasi ini dilakukan untuk melihat bagaimana perubahan skala data dapat memengaruhi karakteristik variabel. Selanjutnya, dua sumber data diintegrasikan dengan memperhatikan kesesuaian identifier atau kunci penghubung antar-data. Hasil integrasi tersebut kemudian diperiksa kembali untuk memastikan bahwa proses penggabungan tidak menimbulkan masalah baru.

Melalui rangkaian tahapan tersebut, tugas ini bertujuan untuk menunjukkan bagaimana data yang awalnya masih memiliki berbagai permasalahan dapat diproses secara sistematis hingga menghasilkan dataset yang lebih konsisten dan siap digunakan untuk tahap analisis berikutnya. Seluruh proses dilakukan menggunakan fungsi dasar R, sehingga implementasinya tidak memerlukan pemasangan paket tambahan. Alur ini sejalan dengan materi praktikum yang menekankan pemahaman kondisi awal data, data cleaning, penanganan missing values, evaluasi outlier, transformasi data, integrasi sumber data, serta validasi kondisi data setelah preprocessing.

1.2 Tujuan Praktikum

Setelah menyelesaikan praktikum, diharapkan dapat:

  1. Mengidentifikasi masalah kualitas data menggunakan R.
  2. Melakukan data cleaning secara sistematis.
  3. Menangani missing values.
  4. Mendeteksi dan mengevaluasi outlier.
  5. Melakukan transformasi atribut numerik.
  6. Mengintegrasikan dua sumber data.
  7. Memvalidasi kondisi data setelah preprocessing.

2 Persiapan RStudio

2.1 Memeriksa lingkungan kerja

Kode berikut digunakan untuk mengetahui direktori kerja dan versi R yang digunakan.

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

3 Membangun Dataset Praktikum

Dataset berikut sengaja dibuat memiliki beberapa masalah agar seluruh tahapan preprocessing dapat dipraktikkan. Masalah tersebut meliputi duplikasi customer_id, missing value, kategori tidak konsisten, kesalahan nilai usia, dan nilai pendapatan yang sangat besar.

3.1 Data Pelanggan

pelanggan_raw <- data.frame(
  customer_id = c(
    "C001", "C002", "C003", "C004", "C005",
    "C006", "C007", "C008", "C009", "C010",
    "C010", "C011", "C012", "C013", "C014",
    "C015", "C016", "C017", "C018", "C019"
  ),
  nama = c(
    "Ani", "Budi", "Citra", "Dodi", "Eka",
    "Fani", "Gilang", "Hana", "Indra", "Joko",
    "Joko", "Kiki", "Lala", "Maya", "Nanda",
    "Oki", "Putri", "Rian", "Sari", "Tono"
  ),
  usia = c(
    21, 25, 23, 150, 27,
    NA, 31, 29, 22, 35,
    35, 28, 24, 26, 30,
    19, NA, 33, 27, 41
  ),
  pendapatan = c(
    4500000, NA, 5200000, 4800000, 4900000,
    5100000, 500000000, 4700000, 4600000, 5300000,
    5300000, NA, 4200000, 5500000, 4800000,
    3900000, 5100000, 5700000, 5000000, 6000000
  ),
  kota = c(
    "Pekanbaru", " PKU", "PEKANBARU", "Dumai", "pekanbaru",
    "DUMAI", "Pekanbaru ", "Siak", "PKU", "Dumai",
    "Dumai", NA, "Pekanbaru", "pekanbaru", "DUMAI",
    "Siak", "PEKANBARU", "Pekanbaru", "PKU", "Dumai"
  ),
  status = c(
    "Aktif", "aktif", "ACTIVE", "A", "Tidak Aktif",
    "nonaktif", "Aktif", "AKTIF", "A", "Tidak aktif",
    "Tidak aktif", "Aktif", "active", "A", "Aktif",
    "tidak aktif", "AKTIF", "aktif", "nonaktif", "Aktif"
  ),
  stringsAsFactors = FALSE
)

pelanggan_raw

3.2 Data Transaksi

transaksi_raw <- data.frame(
  cust_id = c(
    "C001", "C002", "C003", "C004", "C005",
    "C006", "C007", "C008", "C009", "C010",
    "C011", "C012", "C013", "C014", "C015",
    "C016", "C017", "C018", "C999", "C019"
  ),
  jumlah_transaksi = c(
    5, 3, 7, 2, 6,
    4, 25, 5, 3, 8,
    1, 4, 6, 9, 3,
    NA, 7, 2, 5, 10
  ),
  total_purchase = c(
    1500000, 900000, 2700000, 600000, 2100000,
    1300000, 35000000, 1700000, 800000, 3200000,
    250000, 1400000, 2200000, 4500000, 1000000,
    NA, 2800000, 700000, 1200000, 4200000
  ),
  stringsAsFactors = FALSE
)

transaksi_raw

4 Eksplorasi Awal Data

4.1 Melihat struktur dataset

Tahap ini bertujuan memahami ukuran, nama variabel, tipe data, beberapa baris awal, dan ringkasan statistik.

dim(pelanggan_raw)
## [1] 20  6
names(pelanggan_raw)
## [1] "customer_id" "nama"        "usia"        "pendapatan"  "kota"       
## [6] "status"
str(pelanggan_raw)
## 'data.frame':    20 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)
summary(pelanggan_raw)
##  customer_id            nama                usia          pendapatan       
##  Length:20          Length:20          Min.   : 19.00   Min.   :  3900000  
##  Class :character   Class :character   1st Qu.: 24.25   1st Qu.:  4725000  
##  Mode  :character   Mode  :character   Median : 27.50   Median :  5050000  
##                                        Mean   : 34.78   Mean   : 32477778  
##                                        3rd Qu.: 32.50   3rd Qu.:  5300000  
##                                        Max.   :150.00   Max.   :500000000  
##                                        NA's   :2        NA's   :2          
##      kota              status         
##  Length:20          Length:20         
##  Class :character   Class :character  
##  Mode  :character   Mode  :character  
##                                       
##                                       
##                                       
## 

4.2 Audit awal kualitas data

4.2.1 Missing value

colSums(is.na(pelanggan_raw))
## customer_id        nama        usia  pendapatan        kota      status 
##           0           0           2           2           1           0
round(colMeans(is.na(pelanggan_raw)) * 100, 2)
## customer_id        nama        usia  pendapatan        kota      status 
##           0           0          10          10           5           0

4.2.2 Duplikasi

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

4.2.3 Konsistensi kategori

sort(unique(pelanggan_raw$kota))
## [1] " PKU"       "Dumai"      "DUMAI"      "pekanbaru"  "Pekanbaru" 
## [6] "PEKANBARU"  "Pekanbaru " "PKU"        "Siak"
sort(unique(pelanggan_raw$status))
##  [1] "A"           "active"      "ACTIVE"      "aktif"       "Aktif"      
##  [6] "AKTIF"       "nonaktif"    "tidak aktif" "Tidak aktif" "Tidak Aktif"

4.2.4 Rentang atribut numerik

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

4.3 Fungsi audit data

Fungsi berikut membuat ringkasan kualitas setiap atribut sehingga pemeriksaan dapat dilakukan secara lebih sistematis.

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

5 Data Cleaning

5.1 Menyiapkan data untuk dibersihkan

pelanggan <- pelanggan_raw

5.2 Standardisasi kategori

Pada data mentah terdapat perbedaan spasi dan penggunaan huruf besar-kecil. Tahap pertama adalah membersihkan spasi, kemudian menyeragamkan huruf menjadi kecil untuk memudahkan pencocokan kategori.

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"

Selanjutnya kategori diseragamkan menjadi label yang konsisten.

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"

5.3 Deduplikasi

customer_id digunakan sebagai identifier pelanggan. Baris dengan identifier yang berulang diperiksa terlebih dahulu.

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

Kemunculan kedua untuk C010 dihapus.

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

rownames(pelanggan) <- NULL

dim(pelanggan)
## [1] 19  6

5.4 Pemeriksaan aturan domain

Aturan domain digunakan untuk menemukan nilai yang tidak masuk akal berdasarkan konteks data. Pada contoh ini usia yang dianggap masuk akal adalah 15–100 tahun.

pelanggan[
  pelanggan$usia < 15 |
    pelanggan$usia > 100,
]

Pendapatan negatif juga diperiksa sebagai kandidat pelanggaran domain.

pelanggan[
  pelanggan$pendapatan < 0,
]

Nilai usia C004 sebesar 150 diperbaiki menjadi 50 berdasarkan sumber asli.

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

5.5 Log perubahan

Pencatatan perubahan penting agar proses preprocessing dapat ditelusuri.

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

log_perubahan

6 Penanganan Missing Values

6.1 Mengidentifikasi lokasi nilai hilang

colSums(is.na(pelanggan))
## customer_id        nama        usia  pendapatan        kota      status 
##           0           0           2           2           1           0
pelanggan[
  !complete.cases(pelanggan),
]

6.2 Strategi 1 — Menghapus baris lengkap

Sebagai ilustrasi, data tanpa missing value dapat diperoleh dengan complete.cases(). Jumlah baris sebelum dan sesudah dibandingkan.

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

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

6.3 Strategi 2 — Perbandingan Mean dan Median serta Imputasi Median

Strategi kedua dalam menangani missing value adalah melakukan imputasi menggunakan nilai median. Median dipilih karena lebih tahan terhadap pengaruh nilai ekstrem dibandingkan mean.

6.3.1 Perbandingan Mean dan Median

Sebelum melakukan imputasi, nilai mean dan median pada atribut numerik dibandingkan terlebih dahulu.

# Menghitung mean dan median usia
mean_usia <- mean(
  pelanggan$usia,
  na.rm = TRUE
)

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

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

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

# Menampilkan hasil perbandingan
data.frame(
  Atribut = c(
    "Usia",
    "Pendapatan"
  ),
  Mean = c(
    mean_usia,
    mean_pendapatan
  ),
  Median = c(
    median_usia,
    median_pendapatan
  )
)

Perbandingan mean dan median digunakan untuk melihat apakah terdapat perbedaan yang cukup besar antara keduanya. Perbedaan yang besar dapat menunjukkan adanya nilai ekstrem yang memengaruhi rata-rata.

6.3.2 Visualisasi Perbandingan Mean dan Median

Untuk memperjelas perbedaan antara mean dan median, digunakan visualisasi titik dengan nilai mean dan median ditampilkan secara langsung pada grafik. Visualisasi ini membantu pembaca melihat perbedaan kedua ukuran statistik tanpa terpengaruh oleh tinggi batang.

# Menyiapkan data
nilai <- c(
  mean_usia,
  median_usia,
  mean_pendapatan,
  median_pendapatan
)

nama <- c(
  "Mean Usia",
  "Median Usia",
  "Mean Pendapatan",
  "Median Pendapatan"
)

# Membuat grafik
plot(
  nilai,
  1:4,
  xlab = "Nilai",
  ylab = "",
  yaxt = "n",
  pch = 19,
  main = "Perbandingan Mean dan Median"
)

# Nama pada sumbu Y
axis(
  2,
  at = 1:4,
  labels = nama,
  las = 1
)

# Menampilkan nilai pada grafik
text(
  nilai,
  1:4,
  labels = format(
    round(nilai),
    big.mark = ".",
    scientific = FALSE
  ),
  pos = 3
)
## Warning in prettyNum(.Internal(format(x, trim, digits, nsmall, width, 3L, :
## 'big.mark' and 'decimal.mark' are both '.', which could be confusing

Berdasarkan visualisasi tersebut, nilai mean dan median dapat dibandingkan secara langsung melalui angka yang ditampilkan pada grafik. Perbedaan antara mean dan median, khususnya pada pendapatan, menunjukkan bahwa terdapat nilai yang relatif ekstrem yang memengaruhi nilai mean. Oleh karena itu, median dipilih sebagai nilai imputasi karena lebih tahan terhadap pengaruh nilai ekstrem.

6.3.3 Melakukan Imputasi Median

Selanjutnya, nilai NA pada atribut usia dan pendapatan digantikan dengan nilai median masing-masing.

# Membuat salinan atribut usia
pelanggan$usia_imputasi <- pelanggan$usia

# Mengganti NA pada usia dengan median usia
pelanggan$usia_imputasi[
  is.na(pelanggan$usia_imputasi)
] <- median_usia

# Membuat salinan atribut pendapatan
pelanggan$pendapatan_imputasi <- pelanggan$pendapatan

# Mengganti NA pada pendapatan dengan median pendapatan
pelanggan$pendapatan_imputasi[
  is.na(pelanggan$pendapatan_imputasi)
] <- median_pendapatan

# Menampilkan hasil imputasi
pelanggan[
  ,
  c(
    "customer_id",
    "usia",
    "usia_imputasi",
    "pendapatan",
    "pendapatan_imputasi"
  )
]

6.3.4 Pemeriksaan Hasil Imputasi

Setelah proses imputasi dilakukan, jumlah missing value diperiksa kembali untuk memastikan bahwa nilai NA telah ditangani.

c(
  missing_usia = sum(
    is.na(pelanggan$usia_imputasi)
  ),
  missing_pendapatan = sum(
    is.na(pelanggan$pendapatan_imputasi)
  )
)
##       missing_usia missing_pendapatan 
##                  0                  0

Jika hasil pemeriksaan menunjukkan nilai 0 pada kedua atribut, maka seluruh missing value pada atribut usia dan pendapatan telah berhasil ditangani menggunakan metode imputasi median.

6.4 Imputasi kategori

Untuk kota yang tidak diketahui, digunakan label Tidak diketahui agar ketidakpastian tidak disamarkan sebagai kategori tertentu.

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 
##               5              11               2               1

6.5 Menambahkan indikator missing

Indikator biner dapat digunakan untuk mempertahankan informasi bahwa nilai pendapatan pada awalnya merupakan missing value.

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

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

6.6 Membandingkan distribusi sebelum dan sesudah imputasi

par(mfrow = c(1, 2))

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

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

par(mfrow = c(1, 1))

7 Penanganan Outlier

7.1 Visualisasi dengan boxplot

Boxplot digunakan untuk membantu melihat kandidat nilai ekstrem pada pendapatan.

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

7.2 Menghitung batas IQR

Metode IQR menggunakan Q1, Q3, dan IQR untuk menentukan batas bawah dan batas atas kandidat outlier.

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         5250000          500000         4000000         6000000

7.3 Menandai kandidat outlier

Nilai yang berada di luar batas IQR ditandai sebagai kandidat outlier. Penandaan ini bukan berarti nilai langsung dihapus.

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

pelanggan[
  pelanggan$outlier_pendapatan,
  c(
    "customer_id",
    "nama",
    "pendapatan_imputasi"
  )
]

7.4 Winsorization

Sebagai contoh penanganan, nilai yang berada di luar batas digeser ke batas bawah atau batas atas menggunakan winsorization.

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

pelanggan[
  pelanggan$outlier_pendapatan,
  c(
    "customer_id",
    "pendapatan_imputasi",
    "pendapatan_winsor"
  )
]

7.5 Visualisasi Setelah Penanganan Outlier

Setelah dilakukan winsorization, boxplot kembali digunakan untuk melihat perubahan distribusi pendapatan. Visualisasi ini digunakan untuk membandingkan kondisi data sebelum dan sesudah penanganan outlier.

par(mfrow = c(1, 2))

boxplot(
  pelanggan$pendapatan_imputasi,
  horizontal = TRUE,
  main = "Sebelum Winsorization",
  xlab = "Pendapatan"
)

boxplot(
  pelanggan$pendapatan_winsor,
  horizontal = TRUE,
  main = "Sesudah Winsorization",
  xlab = "Pendapatan"
)

par(mfrow = c(1, 1))

Berdasarkan perbandingan boxplot, sebelum dilakukan winsorization terdapat nilai pendapatan yang sangat ekstrem dan berada jauh dari kelompok data utama. Setelah dilakukan winsorization, nilai ekstrem tersebut dibatasi berdasarkan batas yang diperoleh dari metode IQR. Dengan demikian, pengaruh nilai ekstrem terhadap distribusi pendapatan dapat dikurangi tanpa menghapus observasi dari dataset.

8 Transformasi Data

Transformasi digunakan untuk mengubah skala atribut numerik. Tiga metode yang digunakan adalah min–maks, z-score, dan decimal scaling.

8.1 Normalisasi min–maks

Rumus min–maks 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",
    "usia_minmax",
    "pendapatan_imputasi",
    "pendapatan_minmax"
  )
]

8.2 Normalisasi z-score

Z-score mengubah nilai menjadi ukuran relatif terhadap mean dan simpangan baku.

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
)

8.3 Decimal scaling

decimal_scale <- function(x) {

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

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

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

  x / (10 ^ j)
}

pelanggan$pendapatan_decimal <-
  decimal_scale(
    pelanggan$pendapatan_imputasi
  )

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

8.4 Membandingkan metode transformasi

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

transformasi

8.5 Dampak outlier terhadap normalisasi

Perbandingan berikut menunjukkan bagaimana penanganan outlier dapat memengaruhi hasil normalisasi min–maks.

pelanggan$pendapatan_winsor_minmax <-
  minmax(
    pelanggan$pendapatan_winsor
  )

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

abline(
  0,
  1,
  lty = 2
)

9 Integrasi Data

9.1 Memeriksa kunci pada kedua sumber

Sebelum melakukan integrasi, identifier harus diperiksa agar tidak menghasilkan penggandaan baris yang tidak diinginkan.

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

9.2 Menyamakan nama kunci

transaksi <- transaksi_raw

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

9.3 Menggabungkan data

merge(..., all.x = TRUE) digunakan untuk mempertahankan seluruh pelanggan dan mengambil informasi transaksi yang cocok.

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

data_terintegrasi[
  ,
  c(
    "customer_id",
    "nama",
    "jumlah_transaksi",
    "total_purchase"
  )
]

9.4 Validasi hasil integrasi

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

Pelanggan yang tidak memiliki pasangan transaksi ditampilkan untuk diperiksa lebih lanjut.

data_terintegrasi[
  is.na(
    data_terintegrasi$jumlah_transaksi
  ),
  c(
    "customer_id",
    "nama"
  )
]

9.5 Penanganan pelanggan tanpa transaksi

Jika setelah pemeriksaan diketahui bahwa NA memang berarti pelanggan belum pernah bertransaksi, nilai dapat diubah menjadi 0.

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

10 Dataset Final dan Audit Akhir

10.1 Membentuk dataset final

Hanya variabel yang diperlukan untuk dataset akhir dipertahankan.

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

10.2 Audit akhir

audit_akhir <- audit_data(
  data_final
)

audit_akhir
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

10.3 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

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

11 Kesimpulan

Berdasarkan seluruh tahapan praktikum, preprocessing dilakukan secara bertahap mulai dari memahami struktur dan kualitas awal data, membersihkan kategori, menghapus duplikasi, memperbaiki pelanggaran domain, menangani missing values, mendeteksi dan mengevaluasi outlier, melakukan transformasi numerik, mengintegrasikan data, hingga melakukan audit akhir.

Hal penting yang perlu diperhatikan adalah bahwa preprocessing bukan sekadar menjalankan kode. Setiap perubahan terhadap data merupakan keputusan yang perlu memiliki alasan. Outlier tidak otomatis dihapus, missing value tidak selalu harus dibuang, dan hasil integrasi harus divalidasi agar tidak menimbulkan duplikasi atau kesalahan baru.

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

13 Ringkasan Alur

Data mentah
    ↓
Eksplorasi dan audit awal
    ↓
Standardisasi kategori
    ↓
Deduplikasi dan pemeriksaan domain
    ↓
Penanganan missing value
    ↓
Deteksi dan evaluasi outlier
    ↓
Transformasi data
    ↓
Integrasi data
    ↓
Audit akhir
    ↓
Data siap digunakan untuk analisis/data mining