Nama : Christya Ruth Sellomitha Lubis
NIM : 2403113670
Kelas : Statistika B
Mata Kuliah : Data Mining
Tanggal : 25 August 2026
Topik Tugas : Data Preprocessing menggunakan RStudio

1 Pendahuluan

Tugas ini berisi tahapan data preprocessing yang saya kerjakan menggunakan RStudio, mulai dari eksplorasi data awal, audit kualitas data, pembersihan data (data cleaning), penanganan missing value, penanganan outlier, transformasi data, hingga integrasi dua sumber data yang berbeda. Data yang digunakan adalah data simulasi pelanggan dan data transaksi, yang sengaja dibuat mengandung berbagai masalah kualitas data (nilai hilang, duplikasi, inkonsistensi kategori, dan nilai tidak wajar) agar dapat dipraktikkan proses pembersihannya secara menyeluruh.

Seluruh proses dikerjakan menggunakan bahasa pemrograman R versi 4.4.1 pada RStudio.

2 Import dan Eksplorasi Data Awal

2.1 Membuat Data Pelanggan dan Data Transaksi

Berikut adalah data awal yang saya gunakan. Data pelanggan_raw berisi informasi identitas dan atribut demografis pelanggan, sedangkan transaksi_raw berisi ringkasan transaksi tiap pelanggan.

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
transaksi_raw

Dapat dilihat sekilas bahwa data pelanggan_raw mengandung beberapa masalah, misalnya customer_id “C010” tercatat dua kali, nilai usia sebesar 150 tahun yang tidak masuk akal, pendapatan sebesar Rp500.000.000 yang jauh di luar rentang wajar, serta penulisan kota dan status yang tidak konsisten (huruf besar/kecil, spasi berlebih, singkatan).

2.2 Struktur dan Ringkasan Data

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

Dari str() dan summary() terlihat bahwa kolom usia dan pendapatan bertipe numerik, sedangkan customer_id, nama, kota, dan status bertipe karakter. Nilai maksimum usia (150) dan pendapatan (500 juta) langsung terlihat tidak wajar dibandingkan nilai kuartil lainnya.

3 Audit Kualitas Data

Sebelum melakukan pembersihan, saya terlebih dahulu mengaudit kualitas data secara menyeluruh: jumlah dan persentase missing value, duplikasi data, kategori yang tidak konsisten, serta rentang nilai atribut numerik.

3.1 Missing Value

# Banyak missing value per atribut
colSums(is.na(pelanggan_raw))
## customer_id        nama        usia  pendapatan        kota      status 
##           0           0           1           2           1           0
# Persentase missing value per atribut
round(colMeans(is.na(pelanggan_raw)) * 100, 2)
## customer_id        nama        usia  pendapatan        kota      status 
##        0.00        0.00        8.33       16.67        8.33        0.00

3.2 Duplikasi

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

3.3 Kategori Tidak Konsisten

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"

Terlihat bahwa atribut kota memiliki variasi penulisan untuk kota yang sama (misalnya “PKU”, ” PKU”, “Pekanbaru”, “PEKANBARU”, “pekanbaru”, “Pekanbaru” yang seharusnya merujuk ke kota yang sama). Atribut status juga memiliki masalah serupa (misalnya “A”, “Aktif”, “aktif”, “AKTIF” yang seharusnya sama-sama berarti “Aktif”).

3.4 Rentang Nilai Atribut Numerik

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

Rentang usia 21–150 tahun dan rentang pendapatan Rp4.500.000–Rp500.000.000 mengindikasikan adanya nilai ekstrem yang perlu diperiksa lebih lanjut, apakah termasuk kesalahan input atau outlier yang sah.

3.5 Fungsi Audit Data

Agar audit dapat dilakukan berulang kali secara konsisten (misalnya untuk membandingkan kondisi data sebelum dan sesudah preprocessing), saya membuat fungsi audit_data().

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

audit_awal <- audit_data(pelanggan_raw)
audit_awal

Ringkasan temuan audit awal: data mengandung 1 baris duplikat penuh, 1 customer_id yang berulang, missing value pada atribut usia (8,33%), pendapatan (16,67%), dan kota (8,33%), serta kategori teks yang tidak konsisten pada kota (10 kategori unik, seharusnya 4) dan status (8 kategori unik, seharusnya 2).

4 Data Cleaning

4.1 Standardisasi Teks (Kota dan Status)

Langkah pertama adalah menyalin data mentah ke variabel kerja pelanggan, kemudian membersihkan spasi berlebih dan menyeragamkan huruf menjadi kecil agar proses pencocokan kategori lebih mudah.

pelanggan <- pelanggan_raw

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

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

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

Setelah spasi dan kapitalisasi diseragamkan, jumlah variasi penulisan sudah berkurang. Selanjutnya saya memetakan setiap variasi ke kategori baku yang sesuai.

# Standardisasi kota
pelanggan$kota[pelanggan$kota %in% c("pku", "pekanbaru")] <- "Pekanbaru"
pelanggan$kota[pelanggan$kota == "dumai"] <- "Dumai"
pelanggan$kota[pelanggan$kota == "siak"] <- "Siak"

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

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

Atribut kota sekarang hanya memiliki 4 kategori baku (Pekanbaru, Dumai, Siak, dan NA), sedangkan status hanya memiliki 2 kategori baku (Aktif dan Tidak Aktif).

4.2 Penanganan Duplikasi customer_id

# Menampilkan seluruh baris dengan customer_id yang berulang
pelanggan[duplicated(pelanggan$customer_id) |
              duplicated(pelanggan$customer_id, fromLast = TRUE), ]

Karena kedua baris dengan customer_id “C010” memiliki nilai yang identik pada seluruh atribut, saya mempertahankan kemunculan pertama saja dan menghapus baris duplikatnya.

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

dim(pelanggan)
## [1] 11  6

4.3 Koreksi Domain (Usia Tidak Valid)

Menggunakan aturan bisnis bahwa usia pelanggan yang wajar berada pada rentang 15–100 tahun, saya memeriksa apakah ada nilai usia di luar rentang tersebut.

# Kandidat usia tidak valid berdasarkan aturan bisnis 15–100 tahun
pelanggan[pelanggan$usia < 15 | pelanggan$usia > 100, ]
# Kandidat pendapatan tidak valid jika bernilai negatif
pelanggan[pelanggan$pendapatan < 0, ]

Pelanggan “C004” (Dodi) tercatat berusia 150 tahun. Setelah dikonfirmasi ke sumber data asli, nilai yang benar adalah 50 tahun (kemungkinan kesalahan input berupa angka tambahan di depan). Tidak ditemukan nilai pendapatan negatif.

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

4.4 Log Perubahan

Setiap perubahan yang dilakukan terhadap data saya catat dalam sebuah log agar proses preprocessing tetap dapat ditelusuri (traceable).

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

5 Penanganan Missing Value

5.1 Identifikasi Missing Value

colSums(is.na(pelanggan))
## customer_id        nama        usia  pendapatan        kota      status 
##           0           0           1           2           1           0
# Baris yang memiliki sedikitnya satu missing value
pelanggan[!complete.cases(pelanggan), ]
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

Jika baris yang mengandung missing value langsung dihapus (listwise deletion), sekitar 27,27% data akan hilang. Karena proporsi ini cukup besar untuk jumlah data yang sedikit, saya memilih untuk melakukan imputasi dibandingkan menghapus baris tersebut.

5.2 Imputasi Pendapatan

Karena atribut pendapatan memiliki nilai ekstrem (Rp500.000.000), saya menggunakan median sebagai nilai imputasi karena median lebih tahan (robust) terhadap outlier dibandingkan rata-rata.

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

mean_pendapatan
## [1] 59900000
median_pendapatan
## [1] 4900000
pelanggan$pendapatan_imputasi <- pelanggan$pendapatan
pelanggan$pendapatan_imputasi[is.na(pelanggan$pendapatan_imputasi)] <-
    median_pendapatan

Terlihat jelas bahwa rata-rata (Rp59.900.000) jauh lebih besar dibanding median (Rp4.900.000) akibat pengaruh nilai ekstrem milik “C007”, sehingga median menjadi pilihan imputasi yang lebih tepat.

5.3 Imputasi Usia

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

5.4 Imputasi Kota

Untuk atribut kategorik kota yang bersifat nominal, saya tidak menggunakan median/rata-rata melainkan kategori baru “Tidak diketahui” agar informasi bahwa data tersebut memang hilang tetap tersimpan.

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

5.5 Indikator Missing Value

Saya juga menambahkan kolom indikator biner untuk menandai baris mana saja yang sebelumnya memiliki nilai pendapatan yang hilang, agar informasi ini tidak sepenuhnya hilang setelah imputasi.

pelanggan$pendapatan_missing <- as.integer(is.na(pelanggan$pendapatan))
table(pelanggan$pendapatan_missing)
## 
## 0 1 
## 9 2

6 Deteksi dan Penanganan Outlier

6.1 Visualisasi Sebelum dan Sesudah Imputasi

par(mfrow = c(1, 2))
hist(pelanggan$pendapatan,
     main = "Sebelum Imputasi",
     xlab = "Pendapatan", col = "#F6D186", breaks = 8)
hist(pelanggan$pendapatan_imputasi,
     main = "Sesudah Imputasi Median",
     xlab = "Pendapatan", col = "#FCE8B8", breaks = 8)

par(mfrow = c(1, 1))

6.2 Boxplot dan Metode IQR

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

Untuk mendeteksi outlier secara objektif, saya menggunakan metode IQR (Interquartile Range) dengan batas 1,5 kali IQR di bawah Q1 dan di atas Q3.

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
pelanggan$outlier_pendapatan <-
    pelanggan$pendapatan_imputasi < batas_bawah |
    pelanggan$pendapatan_imputasi > batas_atas

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

Berdasarkan metode IQR, hanya pelanggan “C007” (Gilang) yang teridentifikasi sebagai outlier pada atribut pendapatan, dengan nilai Rp500.000.000 yang jauh melampaui batas atas Rp5.750.000.

6.3 Winsorizing

Karena nilai outlier tersebut kemungkinan bukan murni kesalahan input (bisa jadi pelanggan dengan pendapatan sangat tinggi), saya tidak menghapusnya, melainkan menggunakan teknik winsorizing, yaitu memotong nilai ekstrem agar tidak melebihi batas atas/bawah IQR.

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

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

7 Transformasi Data

7.1 Min-Max Normalization

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

7.2 Z-Score Standardization

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)

Pada hasil Z-score, pelanggan “C007” memiliki nilai Z pendapatan sebesar 3,015, jauh di atas pelanggan lain, sekali lagi menegaskan bahwa nilai tersebut adalah outlier.

7.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.0045 0.5000
transformasi <- pelanggan[, c(
    "customer_id", "pendapatan_imputasi", "pendapatan_minmax",
    "pendapatan_z", "pendapatan_decimal"
)]

transformasi

Ketiga teknik normalisasi menghasilkan skala yang berbeda-beda, namun ketiganya konsisten menunjukkan bahwa nilai pendapatan “C007” berada jauh di luar sebaran data pelanggan lainnya.

7.4 Dampak Winsorizing terhadap Normalisasi

pelanggan$pendapatan_winsor_minmax <- minmax(pelanggan$pendapatan_winsor)

plot(pelanggan$pendapatan_minmax,
     pelanggan$pendapatan_winsor_minmax,
     pch = 19, col = "#D9A441",
     xlab = "Min-maks Data Asli",
     ylab = "Min-maks Data Winsorized",
     main = "Dampak Penanganan Outlier terhadap Normalisasi")
abline(0, 1, col = "#4A4A4A", lty = 2)

Titik-titik yang menyimpang dari garis diagonal menunjukkan bagaimana proses winsorizing mengubah hasil normalisasi min-max, khususnya untuk data yang sebelumnya menjadi outlier.

8 Integrasi Data

8.1 Pemeriksaan Kunci Penghubung

Sebelum menggabungkan data pelanggan dan transaksi_raw, saya memastikan kedua tabel tidak memiliki duplikasi pada kolom kunci, serta memeriksa kecocokan customer_id antar tabel.

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"

Pelanggan “C011” tidak memiliki catatan transaksi, sedangkan “C012” pada data transaksi tidak memiliki data pelanggan yang sesuai (kemungkinan pelanggan baru yang belum tercatat di sistem pelanggan).

8.2 Merge Data Pelanggan dan Transaksi

Saya menggunakan left join (all.x = TRUE) agar seluruh data pelanggan tetap dipertahankan meskipun tidak memiliki pasangan transaksi.

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

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

data_terintegrasi[, c(
    "customer_id", "nama", "jumlah_transaksi", "total_purchase"
)]
# Apakah jumlah baris berubah secara tidak wajar?
c(sebelum = nrow(pelanggan), sesudah = nrow(data_terintegrasi))
## sebelum sesudah 
##      11      11
# Apakah customer_id tetap unik?
sum(duplicated(data_terintegrasi$customer_id))
## [1] 0

Jumlah baris tidak berubah (tetap 11) dan customer_id tetap unik, artinya proses merge tidak menghasilkan duplikasi data.

8.3 Penanganan Missing Value Hasil Integrasi

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

Karena telah dikonfirmasi bahwa nilai NA pada hasil integrasi berarti pelanggan tersebut belum pernah bertransaksi, saya mengisi nilai tersebut dengan 0, bukan dengan median atau rata-rata.

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

9 Finalisasi Dataset

9.1 Menyusun Data Final

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

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

Setelah seluruh tahapan preprocessing, dataset akhir sudah tidak lagi memiliki missing value, duplikasi customer_id, maupun inkonsistensi kategori.

9.3 Tabel Perbandingan 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

Ringkasan hasil akhir: jumlah baris berkurang dari 12 menjadi 11 (setelah menghapus 1 duplikat), duplikasi customer_id berkurang dari 1 menjadi 0, total missing value berkurang dari 4 menjadi 0, kategori kota yang semula 10 variasi menjadi 4 kategori baku, dan kategori status yang semula 8 variasi menjadi 2 kategori baku.

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

10 Kesimpulan

Melalui tugas ini, saya mempraktikkan tahapan lengkap data preprocessing menggunakan RStudio, mulai dari audit kualitas data, pembersihan teks dan duplikasi, koreksi nilai di luar domain, imputasi missing value dengan mempertimbangkan tipe data (numerik vs kategorik), deteksi dan penanganan outlier dengan metode IQR dan winsorizing, transformasi data dengan tiga teknik normalisasi (min-max, Z-score, dan decimal scaling), hingga integrasi dua sumber data yang berbeda menggunakan merge(). Setiap keputusan pembersihan data saya dasarkan pada karakteristik data itu sendiri (misalnya median dipilih untuk mengatasi skewness akibat outlier, sementara kategori “Tidak diketahui” dipilih untuk data kategorik yang hilang) dan seluruh perubahan dicatat dalam log agar proses tetap dapat ditelusuri.


Tugas Mata Kuliah Data Mining - Christya Ruth Sellomitha Lubis (2403113670) - Statistika B