R Programming • Data Analysis • Data Preprocessing
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.
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.
Terdapat dua dataset yang digunakan dalam project ini.
Data pertama berisi informasi dasar pelanggan, sedangkan data kedua berisi aktivitas transaksi pelanggan.
| Variabel | Keterangan |
|---|---|
customer_id |
ID pelanggan |
nama |
Nama pelanggan |
usia |
Usia pelanggan |
pendapatan |
Pendapatan pelanggan |
kota |
Kota tempat tinggal |
status |
Status pelanggan |
| Variabel | Keterangan |
|---|---|
cust_id |
ID pelanggan |
jumlah_transaksi |
Jumlah transaksi |
total_purchase |
Total pembelian |
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
)
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.
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"
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.
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.
Setelah melihat struktur data, saya memeriksa beberapa masalah yang dapat ditemukan dalam dataset.
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"
)
| 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.
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.
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"
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
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.
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.
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.
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.
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".
Nilai pendapatan sebelum imputasi disimpan agar dapat dibandingkan setelah proses dilakukan.
pendapatan_sebelum <-
pelanggan$pendapatan
usia_sebelum <-
pelanggan$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
)
)
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
)
)
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
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.
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.
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.
Setelah data dibersihkan, saya memeriksa kemungkinan adanya outlier pada pendapatan.
Metode yang digunakan adalah Interquartile Range (IQR).
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.
Setelah proses cleaning, saya mencoba beberapa metode transformasi.
Metode yang digunakan:
Ketiga metode tersebut digunakan untuk melihat bagaimana satu variabel dapat direpresentasikan dalam skala yang berbeda.
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"
)
| 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 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"
)
| 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 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.
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"
)
| 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:
Pada data ini, nilai C007 tetap terlihat ekstrem setelah transformasi.
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 ...
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.
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 ...
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.
Setelah proses preprocessing selesai, saya mencoba melihat beberapa informasi sederhana dari dataset akhir.
mean(
data_final$pendapatan,
na.rm = TRUE
)
## [1] 49900000
sum(
data_final$jumlah_transaksi,
na.rm = TRUE
)
## [1] 63
sum(
data_final$total_purchase,
na.rm = TRUE
)
## [1] 39800000
Hasil tersebut memberikan gambaran awal mengenai karakteristik pelanggan dan aktivitas transaksi pada dataset.
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.
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"
)
| Indikator | Hasil |
|---|---|
| Jumlah observasi | 11 |
| Jumlah variabel | 15 |
| Total missing value | 2 |
| Duplicate customer ID | 0 |
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"
)
| 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 |
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.
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.
Dari project ini, saya mendapatkan beberapa pengalaman dalam menggunakan R untuk pengolahan data.
Beberapa hal yang saya pelajari antara lain:
dplyr untuk proses cleaning,left_join() untuk menggabungkan
dataset,Project ini juga membantu saya memahami bahwa kualitas data sangat berpengaruh terhadap proses analisis berikutnya.
Mengenali masalah pada data, memperbaikinya dengan tepat, dan memahami informasi yang dihasilkan.
Nia Andini
Statistika • Data Analysis • R
Programming