Banyak metode statistika yang mensyaratkan bahwa peubah numerik yang digunakan harus mengikuti sebaran normal. Contohnya: uji-T pada Uji sigifikansi parameter model Regresi atau uji-F pada ANOVA
Asumsi bahwa data berdistribusi normal dengan ragam yang homogen harus selalu diperiksa, Jika asumsi terpenuhi maka kita dapat menganalisis data tsb dengan analisis statistika parametrik. Namun jika tidak, maka perlu dilakukan penanganan terhadap data tersebut.
Transformasi data dapat “memperbaiki” penyimpangan dari normalitas dan keragaman yang tidak homogen (heteroskedastisitas). Transformasi yang tepat memungkinkan kita menggunakan analisis statistika parametrik.
Simulasi Sebaran Simetris (Normal)
set.seed(123)
# Membangkitkan 1000 bilangan acak yang menyebar Normal dengan mean=10, sd = 1.5
data.norm <- rnorm(1000, mean=10, sd=1.5)
hist(data.norm, breaks=25)
# melihat 20 data pertama yang dibangkitkan
head(data.norm, 20)
## [1] 9.159287 9.654734 12.338062 10.105763 10.193932 12.572597 10.691374
## [8] 8.102408 8.969721 9.331507 11.836123 10.539721 10.601157 10.166024
## [15] 9.166238 12.680370 10.746776 7.050074 11.052034 9.290813
# summary statistik
# perhatikan pada data menyebar normal, nilai mean dan median cenderung mirip
summary(data.norm)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 5.785 9.058 10.014 10.024 10.997 14.862
Dapat dilihat bahwa sebaran data simulasi yang kita bangkitkan berdasarkan QQPlot sudah sesuai karena secara umum setiap amatan berada pada garis Normal
qqnorm(data.norm)
qqline(data.norm,col=2)
Uji Normalitas Nilai p-value > 0.05, Terima Ho (Data menyebar Normal)
shapiro.test(data.norm)
##
## Shapiro-Wilk normality test
##
## data: data.norm
## W = 0.99838, p-value = 0.4765
Simulasi Sebaran Menjulur (Contoh : Chi-Square)
set.seed(123)
# membangkitan 1000 data acak yang menyebar chi^2 dengan DF=5
data.chi<-rchisq(1000, df=5)
hist(data.chi, breaks=20)
# summary statistik
# note : pada data menjulur ke kanan, nilai mean cederung lebih besar dari median
summary(data.chi)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.1821 2.6257 4.3015 4.7939 6.3206 19.3367
Berdasarkan Normal QQ Plot, terlihat sebaran data secara umum tidak berada pada kisaran garis Normal. Begitu juga melalui uji Shapiro-Wilk, nilai p-value < 0.05 sehingga tolak Ho (Data tidak menyebar Normal)
qqnorm(data.chi)
qqline(data.chi, col="red")
shapiro.test(data.chi)
##
## Shapiro-Wilk normality test
##
## data: data.chi
## W = 0.92826, p-value < 2.2e-16
Pada data yang menjulur ke-kanan, kita dapat mencoba transformasi data menggunakan fungsi akar misalnya (X)^1/3 Proses ini mungkin memerlukan trial & error untuk mendapatkan hasil yang sesuai.
# transformasi x = x^1/2
# memberikan hasil yang juga mendekati sebaran Normal
chi.transs<-data.chi^(1/2)
hist(chi.transs, breaks=20)
qqnorm(chi.transs)
qqline(chi.transs, col="red")
shapiro.test(chi.transs)
##
## Shapiro-Wilk normality test
##
## data: chi.transs
## W = 0.99434, p-value = 0.0008126
Melalui transformasi (X)^1/3 , data yang sebelumnya sangat menjulur menjadi lebih simetris. Berdasarkan hasil Normal QQ Plot maupun uji Shapiro-Wick, data hasil transformasi mengikuti sebaran Normal
# transformasi x ï x1/3
chi.trans<-data.chi^(1/3)
hist(chi.trans, breaks=20)
qqnorm(chi.trans)
qqline(chi.trans, col="red")
shapiro.test(chi.trans)
##
## Shapiro-Wilk normality test
##
## data: chi.trans
## W = 0.99908, p-value = 0.9118
# transformasi x ï x1/4
# memberikan hasil yang juga mendekati sebaran Normal
chi.trans2<-data.chi^(1/4)
hist(chi.trans2, breaks=20)
qqnorm(chi.trans2)
qqline(chi.trans2, col="red")
shapiro.test(chi.trans2)
##
## Shapiro-Wilk normality test
##
## data: chi.trans2
## W = 0.9972, p-value = 0.07918
# Memuat pustaka yang diperlukan
library(ggplot2)
library(gridExtra)
# TRANSFORMASI LINEAR TIDAK AKAN MERUBAH BENTUK SEBARAN DATA
chi.trans3 <- (3/4*data.chi+100)
plot1 <- ggplot(data.frame(x=data.chi), aes(x = x)) +
geom_density(lwd=1, color="darkgreen")
plot2 <- ggplot(data.frame(x=chi.trans3), aes(x = x)) +
geom_density(lwd=1, color="darkred")
# Mengatur grid 2x1
grid.arrange(
plot1, plot2,
ncol = 1
)
shapiro.test(data.chi)
##
## Shapiro-Wilk normality test
##
## data: data.chi
## W = 0.92826, p-value < 2.2e-16
shapiro.test(chi.trans3)
##
## Shapiro-Wilk normality test
##
## data: chi.trans3
## W = 0.92826, p-value < 2.2e-16
Asumsi lainnya yang penting adalah Ragam Homogen. Kehomogenan ragam dapat kita lihat melalui plot residual. Jika ragam homogen, maka pada plot residual, sisaan akan bersifat acak dengan memusat di sekitar nilai 0 dengan simpangan baku tertentu dan tidak terdapat pola-pola hubungan tertentu (misal pola berbentuk corong). Jika ingin menggunakan ukuran tertentu, adpat pula dilakukan uji seperti Uji Breusch-Pagan (BP), white atau Uji Glejser.
Berikut Contoh Data dengan Ragam Yang Tidak Homogen
set.seed(123)
x<-rep(seq(1,5,),each=10)
e<-c()
for (i in 1:5) {
e0<-rnorm(10,0,0.5*i)
e<-c(e,e0) }
y<-15+(4*x)+e
mod<-lm(y~x)
res<-residuals(mod)
fits<-fitted(mod)
plot(x,y)
plot(fits,res)
# Transformasi x dengan 1/x dan y dengan y/x
xt<-1/x; yt<-y/x
mod.t<-lm(yt~xt)
res.t<-residuals(mod.t)
fitst<-fitted(mod.t)
plot(xt,yt)
plot(fitst,res.t)
library("rcompanion")
## Warning: package 'rcompanion' was built under R version 4.3.2
# Membuat fungsi transformasi Tukey's Ladder of Powers (Manual)
tfm.tukey <- function(X, lambda=0) {
if(lambda < 0) return(X^lambda)
else if (lambda == 0) return(log(X))
else return(X^lambda)
}
# Transformasi Tukey dengan lambda = 1/4
chi.tk.0.5 <- sapply(data.chi, tfm.tukey, lambda=1/4)
hist(chi.tk.0.5, breaks=20)
qqnorm(chi.tk.0.5)
qqline(chi.tk.0.5, col="red")
shapiro.test(chi.tk.0.5)
##
## Shapiro-Wilk normality test
##
## data: chi.tk.0.5
## W = 0.9972, p-value = 0.07918
Fungsi transformTukey dari package rcompanion dapat digunakan untuk melakukan transformasi tukey. pada package ini, nilai lambda ditentukan batas bawah dan atas. Fungsi akan melakukan proses pencarian secara iteratif untuk memperoleh nilai terbaik dalam proses transformasi.
# Membuat fungsi transformasi Box-Cox (Manual)
box.cox <- function(X, lambda=0) {
if(lambda==0) return(log(X)) # secara default fungsi log pada R menggunakan basis `e`
else {
return((X^lambda-1)/lambda)
}
}
# Transformasi Box-Cox dengan lambda = 0.5 # belum menyebar normal
chi.bc.0.5 <- sapply(data.chi, box.cox, lambda=0.5)
hist(chi.bc.0.5, breaks=20)
qqnorm(chi.bc.0.5)
qqline(chi.bc.0.5, col="red")
shapiro.test(chi.bc.0.5)
##
## Shapiro-Wilk normality test
##
## data: chi.bc.0.5
## W = 0.99434, p-value = 0.0008126
# Transformasi Box-Cox dengan lambda = 0.3 # sudah menyebar normal
chi.bc.0.3 <- sapply(data.chi, box.cox, lambda=0.3)
hist(chi.bc.0.3, breaks=20)
qqnorm(chi.bc.0.3)
qqline(chi.bc.0.3, col="red")
shapiro.test(chi.bc.0.3)
##
## Shapiro-Wilk normality test
##
## data: chi.bc.0.3
## W = 0.99869, p-value = 0.6839
library(caret)
## Loading required package: lattice
library(lattice)
df.chi = data.frame(bc=data.chi)
# Transformasi BoxCox menggunakan fungsi preProcess (package caret)
# lambda dicari secara iteratif sehingga menghasilkan nilai lambda yang paling sesuai
preprocessParams <- preProcess(df.chi, method=c("BoxCox"))
chi.bc.caret <- predict(preprocessParams, df.chi)
hist(chi.bc.caret$bc, breaks=20)
qqnorm(chi.bc.caret$bc)
qqline(chi.bc.caret$bc, col="red")
shapiro.test(chi.bc.caret$bc)
##
## Shapiro-Wilk normality test
##
## data: chi.bc.caret$bc
## W = 0.99869, p-value = 0.6839
# Dapatkan nilai lambda
preprocessParams
## Created from 1000 samples and 1 variables
##
## Pre-processing:
## - Box-Cox transformation (1)
## - ignored (0)
##
## Lambda estimates for Box-Cox transformation:
## 0.3
Alternatif lainnya yaitu menggunakan fungsi boxcox dari package MASS.
Feature scaling adalah salah satu langkah penting dalam pra-pemrosesan data khususnya dalam pembelajaran mesin. Features scaling merupakan teknik untuk mengubah rentang atau skala dari fitur-fitur (variabel-variabel) dalam dataset sehingga memiliki karakteristik yang serupa antar fitur. Hal ini penting karena banyak algoritma pembelajaran mesin yang sangat dipengaruhi oleh skala fitur, dan perbedaan skala pada fitur dapat memberikan hasil yang tidak diinginkan.
Terdapat dua metode umum yang digunakan dalam feature scaling:
Min-Max Scaling (Normalize Scaling): metode ini mengubah nilai-nilai fitur ke dalam rentang tertentu, biasanya antara (0 dan 1) atau (-1 dan 1).
Standardization (Z-score Scaling): metode ini mengubah nilai-nilai fitur sehingga memiliki rataan 0 dan simpangan baku 1.
Features scaling membantu algoritma pembelajaran mesin seperti regresi linier, K-Nearest Neighbors (KNN), dan Support Vector Machines (SVM) untuk beroperasi dengan lebih baik dan menghasilkan hasil yang lebih baik. Selain itu, ini juga dapat mempercepat konvergensi algoritma yang berbasis iterasi seperti Gradient Descent pada model Gradient Boosting atau Neural Network.
Dalam pemrosesan data, penting untuk memilih metode scaling yang sesuai dengan karakteristik data yand dimiliki. Pemilihan yang tepat akan membantu meningkatkan kualitas model dan analisis yang dilakukan.
Penting : Features scaling TIDAK MERUBAH sebaran data. Bentuk sebaran data asli dengan data yang sudah di scaling akan tetap sama persis
set.seed(111)
n = 1000
# Membangkitan 1000 data simulasi dengan 4 fitur (distribusi bervariasi)
X1 <- rnorm(n, mean=50, sd=6)
X2 <- rpois(n, lambda=7)
X3 <- rgamma(n,shape=5, scale=2000)
X4 <- rweibull(n, shape = 20)
Y <- sample(c("Sukses", "Gagal"), n, replace=T)
# Y berupa data kategorik : SUKSES atau GAGAL
# Menggabungkan vektor-vektor menjadi data frame
fake.data <- data.frame(
X1=round(X1, 2),
X2, # sudah diskrit, jadi dibiarkan apa adanya
X3=round(X3, 1),
X4=round(X4, 2),
Y=as.factor(Y))
head(fake.data, 10)
## X1 X2 X3 X4 Y
## 1 51.41 5 16381.2 1.03 Sukses
## 2 48.02 4 14043.3 1.05 Sukses
## 3 48.13 9 4572.4 0.98 Sukses
## 4 36.19 5 7031.2 0.94 Sukses
## 5 48.97 6 8682.9 1.04 Sukses
## 6 50.84 10 11677.3 0.98 Gagal
## 7 41.02 1 15417.9 0.97 Gagal
## 8 43.94 17 9079.8 0.98 Sukses
## 9 44.31 8 10782.9 0.88 Sukses
## 10 47.04 11 19129.3 0.91 Sukses
summary(fake.data)
## X1 X2 X3 X4 Y
## Min. :30.06 Min. : 1.000 Min. : 1230 Min. :0.7000 Gagal :506
## 1st Qu.:46.08 1st Qu.: 5.000 1st Qu.: 6686 1st Qu.:0.9300 Sukses:494
## Median :50.12 Median : 7.000 Median : 9549 Median :0.9800
## Mean :50.06 Mean : 6.884 Mean :10279 Mean :0.9703
## 3rd Qu.:54.05 3rd Qu.: 8.000 3rd Qu.:12908 3rd Qu.:1.0200
## Max. :67.56 Max. :20.000 Max. :36638 Max. :1.1100
plot1 <- ggplot(fake.data, aes(x = X1)) +
geom_density(lwd=1, color="darkgreen")
plot2 <- ggplot(fake.data, aes(x = X2)) +
geom_density(lwd=1, color="darkgreen")
plot3 <- ggplot(fake.data, aes(x = X3)) +
geom_density(lwd=1, color="darkgreen")
plot4 <- ggplot(fake.data, aes(x = X4)) +
geom_density(lwd=1, color="darkgreen")
# Mengatur grid 2x2
grid.arrange(
plot1, plot2, plot3, plot4,
ncol = 2
)
Contoh formula untuk menghitung Min-Max Scaling Manual (Min=0, Max=1):
# membuat fungsi Min-Max
# (dengan nilai default min=0 dan max=1, namun dapat berlaku umum untuk min-max berapapun)
minmax.scaling <- function(col, min=0, max=1) {
min.col <- min(col)
max.col <- max(col)
col.minmax <- (col - min.col)/(max.col-min.col) * (max - min) + min
col.minmax
}
# contoh Min-Max scaling (0 dan 1)
fake.data.mm1 = data.frame(sapply(fake.data[,1:4], minmax.scaling))
summary(fake.data.mm1)
## X1 X2 X3 X4
## Min. :0.0000 Min. :0.0000 Min. :0.0000 Min. :0.0000
## 1st Qu.:0.4271 1st Qu.:0.2105 1st Qu.:0.1541 1st Qu.:0.5610
## Median :0.5349 Median :0.3158 Median :0.2349 Median :0.6829
## Mean :0.5335 Mean :0.3097 Mean :0.2556 Mean :0.6593
## 3rd Qu.:0.6399 3rd Qu.:0.3684 3rd Qu.:0.3298 3rd Qu.:0.7805
## Max. :1.0000 Max. :1.0000 Max. :1.0000 Max. :1.0000
head(fake.data.mm1)
## X1 X2 X3 X4
## 1 0.5693333 0.2105263 0.42789401 0.8048780
## 2 0.4789333 0.1578947 0.36186581 0.8536585
## 3 0.4818667 0.4210526 0.09438369 0.6829268
## 4 0.1634667 0.2105263 0.16382641 0.5853659
## 5 0.5042667 0.2631579 0.21047459 0.8292683
## 6 0.5541333 0.4736842 0.29504400 0.6829268
# Contoh Min-Max scaling (-1 dan 1)
fake.data.mm2 = data.frame(sapply(fake.data[,1:4], minmax.scaling, min=-1, max=1))
summary(fake.data.mm2)
## X1 X2 X3 X4
## Min. :-1.00000 Min. :-1.0000 Min. :-1.0000 Min. :-1.0000
## 1st Qu.:-0.14573 1st Qu.:-0.5789 1st Qu.:-0.6918 1st Qu.: 0.1220
## Median : 0.06987 Median :-0.3684 Median :-0.5302 Median : 0.3659
## Mean : 0.06693 Mean :-0.3806 Mean :-0.4889 Mean : 0.3186
## 3rd Qu.: 0.27973 3rd Qu.:-0.2632 3rd Qu.:-0.3404 3rd Qu.: 0.5610
## Max. : 1.00000 Max. : 1.0000 Max. : 1.0000 Max. : 1.0000
head(fake.data.mm2)
## X1 X2 X3 X4
## 1 0.138666667 -0.57894737 -0.1442120 0.6097561
## 2 -0.042133333 -0.68421053 -0.2762684 0.7073171
## 3 -0.036266667 -0.15789474 -0.8112326 0.3658537
## 4 -0.673066667 -0.57894737 -0.6723472 0.1707317
## 5 0.008533333 -0.47368421 -0.5790508 0.6585366
## 6 0.108266667 -0.05263158 -0.4099120 0.3658537
0.3658537
## [1] 0.3658537
# Density Plot Data Asli
plot1 <- ggplot(fake.data, aes(x = X1)) +
geom_density(lwd=1, color="darkgreen")
plot2 <- ggplot(fake.data, aes(x = X2)) +
geom_density(lwd=1, color="darkgreen")
plot3 <- ggplot(fake.data, aes(x = X3)) +
geom_density(lwd=1, color="darkgreen")
plot4 <- ggplot(fake.data, aes(x = X4)) +
geom_density(lwd=1, color="darkgreen")
# Density Plot Scaled Data (Min: 0 Max: 1)
plot5 <- ggplot(fake.data.mm1, aes(x = X1)) +
geom_density(lwd=1, color="darkgreen")
plot6 <- ggplot(fake.data.mm1, aes(x = X2)) +
geom_density(lwd=1, color="darkgreen")
plot7 <- ggplot(fake.data.mm1, aes(x = X3)) +
geom_density(lwd=1, color="darkgreen")
plot8 <- ggplot(fake.data.mm1, aes(x = X4)) +
geom_density(lwd=1, color="darkgreen")
# Density Plot Scaled Data (Min: -1 Max: 1)
plot9 <- ggplot(fake.data.mm2, aes(x = X1)) +
geom_density(lwd=1, color="darkgreen")
plot10 <- ggplot(fake.data.mm2, aes(x = X2)) +
geom_density(lwd=1, color="darkgreen")
plot11 <- ggplot(fake.data.mm2, aes(x = X3)) +
geom_density(lwd=1, color="darkgreen")
plot12 <- ggplot(fake.data.mm2, aes(x = X4)) +
geom_density(lwd=1, color="darkgreen")
# Mengatur grid 4 * 3
grid.arrange(
plot1, plot2, plot3, plot4, # Plot sebaran data Asli
plot5, plot6, plot7, plot8, # Plot sebaran data (Min:0 Max:1)
plot9, plot10, plot11, plot12, # Plot sebaran data (Min:-1 Max:1)
ncol = 4
)
Formula untuk menghitung nilai standardized:
# membuat fungsi Standardized
std.scaling <- function(col) {
mean.col <- mean(col)
sd.col <- sd(col)
col.std <- (col - mean.col)/sd.col
col.std
}
# contoh
fake.data.std = data.frame(sapply(fake.data[,1:4], std.scaling))
summary(fake.data.std)
## X1 X2 X3 X4
## Min. :-3.374843 Min. :-2.29607 Min. :-1.9044 Min. :-4.0935
## 1st Qu.:-0.672678 1st Qu.:-0.73518 1st Qu.:-0.7562 1st Qu.:-0.6106
## Median : 0.009295 Median : 0.04527 Median :-0.1538 Median : 0.1466
## Mean : 0.000000 Mean : 0.00000 Mean : 0.0000 Mean : 0.0000
## 3rd Qu.: 0.673133 3rd Qu.: 0.43549 3rd Qu.: 0.5532 3rd Qu.: 0.7523
## Max. : 2.951438 Max. : 5.11816 Max. : 5.5473 Max. : 2.1152
cat("\n\nVarians :\n")
##
##
## Varians :
sapply(fake.data.std, var)
## X1 X2 X3 X4
## 1 1 1 1
head(fake.data.std)
## X1 X2 X3 X4
## 1 0.2269195 -0.7351801 1.2841528 0.9037386
## 2 -0.3449763 -1.1254030 0.7921276 1.2066000
## 3 -0.3264193 0.8257118 -1.2010806 0.1465849
## 4 -2.3407073 -0.7351801 -0.6836113 -0.4591380
## 5 -0.1847106 -0.3449571 -0.3360010 1.0551693
## 6 0.1307600 1.2159347 0.2941886 0.1465849
# Density Plot Data Asli
plot1 <- ggplot(fake.data, aes(x = X1)) +
geom_density(lwd=1, color="darkgreen")
plot2 <- ggplot(fake.data, aes(x = X2)) +
geom_density(lwd=1, color="darkgreen")
plot3 <- ggplot(fake.data, aes(x = X3)) +
geom_density(lwd=1, color="darkgreen")
plot4 <- ggplot(fake.data, aes(x = X4)) +
geom_density(lwd=1, color="darkgreen")
# Density Plot Scaled Data (Standardized)
plot5 <- ggplot(fake.data.std, aes(x = X1)) +
geom_density(lwd=1, color="darkgreen")
plot6 <- ggplot(fake.data.std, aes(x = X2)) +
geom_density(lwd=1, color="darkgreen")
plot7 <- ggplot(fake.data.std, aes(x = X3)) +
geom_density(lwd=1, color="darkgreen")
plot8 <- ggplot(fake.data.std, aes(x = X4)) +
geom_density(lwd=1, color="darkgreen")
# Mengatur grid 4 * 2
grid.arrange(
plot1, plot2, plot3, plot4, # Plot sebaran data (Asli)
plot5, plot6, plot7, plot8, # Plot sebaran (Standardized)
ncol = 4
)
Fungsi preProcess pada package caret dapat digunakan untuk melakukan scaling, baik untuk Min-Max scaling maupun standardization
library(caret)
# Min-Max (0 dan 1)
preprocessParams <- preProcess(fake.data[,1:4], method=c("range"))
normalized <- predict(preprocessParams, fake.data[,1:4])
cat("Min-Max Scaling (0-1):\n\n")
## Min-Max Scaling (0-1):
summary(normalized)
## X1 X2 X3 X4
## Min. :0.0000 Min. :0.0000 Min. :0.0000 Min. :0.0000
## 1st Qu.:0.4271 1st Qu.:0.2105 1st Qu.:0.1541 1st Qu.:0.5610
## Median :0.5349 Median :0.3158 Median :0.2349 Median :0.6829
## Mean :0.5335 Mean :0.3097 Mean :0.2556 Mean :0.6593
## 3rd Qu.:0.6399 3rd Qu.:0.3684 3rd Qu.:0.3298 3rd Qu.:0.7805
## Max. :1.0000 Max. :1.0000 Max. :1.0000 Max. :1.0000
# Standardized
preprocessParams <- preProcess(fake.data[,1:4], method=c("center", "scale"))
standardized <- predict(preprocessParams, fake.data[,1:4])
cat("\n\nStandardized:\n\n")
##
##
## Standardized:
summary(standardized)
## X1 X2 X3 X4
## Min. :-3.374843 Min. :-2.29607 Min. :-1.9044 Min. :-4.0935
## 1st Qu.:-0.672678 1st Qu.:-0.73518 1st Qu.:-0.7562 1st Qu.:-0.6106
## Median : 0.009295 Median : 0.04527 Median :-0.1538 Median : 0.1466
## Mean : 0.000000 Mean : 0.00000 Mean : 0.0000 Mean : 0.0000
## 3rd Qu.: 0.673133 3rd Qu.: 0.43549 3rd Qu.: 0.5532 3rd Qu.: 0.7523
## Max. : 2.951438 Max. : 5.11816 Max. : 5.5473 Max. : 2.1152
cat("\n\nVarians :\n")
##
##
## Varians :
sapply(standardized, var)
## X1 X2 X3 X4
## 1 1 1 1
head(normalized)
## X1 X2 X3 X4
## 1 0.5693333 0.2105263 0.42789401 0.8048780
## 2 0.4789333 0.1578947 0.36186581 0.8536585
## 3 0.4818667 0.4210526 0.09438369 0.6829268
## 4 0.1634667 0.2105263 0.16382641 0.5853659
## 5 0.5042667 0.2631579 0.21047459 0.8292683
## 6 0.5541333 0.4736842 0.29504400 0.6829268
head(standardized)
## X1 X2 X3 X4
## 1 0.2269195 -0.7351801 1.2841528 0.9037386
## 2 -0.3449763 -1.1254030 0.7921276 1.2066000
## 3 -0.3264193 0.8257118 -1.2010806 0.1465849
## 4 -2.3407073 -0.7351801 -0.6836113 -0.4591380
## 5 -0.1847106 -0.3449571 -0.3360010 1.0551693
## 6 0.1307600 1.2159347 0.2941886 0.1465849
Encoding adalah proses krusial yaitu mengubah data input, terutama data kategorikal atau teks, menjadi bentuk numerik agar dapat dimengerti dan diproses oleh algoritma atau model machine learning. Hal ini penting karena sebagian besar algoritma machine learning hanya dapat bekerja dengan data dalam bentuk numerik. Encoding memungkinkan model untuk memahami dan memanfaatkan informasi dalam data tersebut untuk tujuan seperti klasifikasi, prediksi, atau analisis data, membuatnya menjadi langkah awal yang kritis dalam pengembangan model machine learning yang efektif
Terdapat berbagai tipe encoding untuk transformasi data kategorik, beberapa diantaranya yaitu:
One-Hot Encoding
Binary Encoding
Label Encoding
set.seed(111)
n <- 1000
# Membangkitan 1000 data simulasi dengan 3 fitur Kategorik
Xa <- sample(c("Belum Menikah", "Menikah", "Cerai"), n, replace=T, prob=c(0.4, 0.5, 0.1))
Xb <- sample(c("Sumatera", "Jawa", "Kalimantan", "lainnya"), n, replace=T, prob=c(0.3, 0.5, 0.1, 0.1))
Xc <- sample(c("SD", "SMP", "SMA", "Sarjana"), n, replace=T, prob=c(0.05, 0.15, 0.4, 0.4))
Y <- sample(c("Sukses", "Gagal"), n, replace=T, prob=c(0.7, 0.3))
# Menggabungkan vektor-vektor menjadi data frame
fake.data.cat <- data.frame(
Xa=as.factor(Xa),
Xb=as.factor(Xb),
Xc=as.factor(Xc),
Y=as.factor(Y))
head(fake.data.cat, 10)
## Xa Xb Xc Y
## 1 Belum Menikah lainnya Sarjana Gagal
## 2 Belum Menikah Sumatera Sarjana Sukses
## 3 Menikah Jawa SMA Gagal
## 4 Belum Menikah Jawa Sarjana Sukses
## 5 Menikah Kalimantan SMA Sukses
## 6 Menikah Sumatera SMP Sukses
## 7 Menikah Sumatera Sarjana Sukses
## 8 Belum Menikah Jawa SD Sukses
## 9 Menikah Jawa SMA Gagal
## 10 Menikah lainnya SMP Sukses
Sebagai ilustrasi, misal kita akan melakukan encoding untuk fitur Xa menggunakan One-Hot Encoding, Xb menggunakan Binary Encoding dan Xc menggunakan Label Encoding
# Gunakan dummyVars() untuk one-hot encoding
one_hot_encoded <- dummyVars(~ Xa, data = fake.data.cat)
one_hot_Xa <- predict(one_hot_encoded, newdata = fake.data.cat)
# jika diperlukan dapat menghapus 1 kolom referensi : one_hot_Xa[,-1]
head(one_hot_Xa)
## Xa.Belum Menikah Xa.Cerai Xa.Menikah
## 1 1 0 0
## 2 1 0 0
## 3 0 0 1
## 4 1 0 0
## 5 0 0 1
## 6 0 0 1
one_hot_encoded <- dummyVars(~ Xb, data = fake.data.cat)
one_hot_Xb <- predict(one_hot_encoded, newdata = fake.data.cat)
# jika diperlukan dapat menghapus 1 kolom referensi : one_hot_Xb[,-1]
head(one_hot_Xb)
## Xb.Jawa Xb.Kalimantan Xb.lainnya Xb.Sumatera
## 1 0 0 1 0
## 2 0 0 0 1
## 3 1 0 0 0
## 4 1 0 0 0
## 5 0 1 0 0
## 6 0 0 0 1
# fungsi konversi teks angka binary menjadi int
bin.to.int <- function(bin_string) {
bin_len <- nchar(bin_string)
int_val <- 0
for (i in 1:bin_len) {
digit <- substr(bin_string, i, i)
if (digit == "1") {
int_val <- int_val + 2^(bin_len - i)
}
}
return(int_val)
}
# menggabung setiap kolom One-hot Encoding Xb
bin_xb <- do.call(paste, c(data.frame(one_hot_Xb), sep = ""))
head(bin_xb, 10)
## [1] "0010" "0001" "1000" "1000" "0100" "0001" "0001" "1000" "1000" "0010"
# konversi nilai binary menjadi integer
int_xb = sapply(bin_xb, bin.to.int)
head(int_xb, 10)
## 0010 0001 1000 1000 0100 0001 0001 1000 1000 0010
## 2 1 8 8 4 1 1 8 8 2
label_encoded_Xc <- as.integer(factor(fake.data.cat$Xc))
# Tampilkan hasil
head(label_encoded_Xc)
## [1] 1 1 3 1 3 4
# mengupdate dataframe menggunakan fitur hasil encoding
fake.data.cat <- cbind(one_hot_Xa, fake.data.cat[,-1])
fake.data.cat$Xb <- int_xb
fake.data.cat$Xc <- label_encoded_Xc
head(fake.data.cat)
## Xa.Belum Menikah Xa.Cerai Xa.Menikah Xb Xc Y
## 1 1 0 0 2 1 Gagal
## 2 1 0 0 1 1 Sukses
## 3 0 0 1 8 3 Gagal
## 4 1 0 0 8 1 Sukses
## 5 0 0 1 4 3 Sukses
## 6 0 0 1 1 4 Sukses
Missing value seringkali terdapat pada data yang akan dilakukan proses analisis. Adanya missing value pada data dapat menyebabkan ukuran sampel berkurang, sehingga analisis yang dilakukan dapat menghasilkan pendugaan yang berbias, sampai kesimpulan yang tidak valid
Diketahui data BostonHousing di R yang merupakan data Housing data untuk 506 census tracts di Boston pada 1970.
Akan dilakukan ilustrasi pemberian missing value pada data tersebut dan ilustrasi pengecekannya.
data("BostonHousing", package = 'mlbench')
original <- BostonHousing #backup original data
str(original)
## 'data.frame': 506 obs. of 14 variables:
## $ crim : num 0.00632 0.02731 0.02729 0.03237 0.06905 ...
## $ zn : num 18 0 0 0 0 0 12.5 12.5 12.5 12.5 ...
## $ indus : num 2.31 7.07 7.07 2.18 2.18 2.18 7.87 7.87 7.87 7.87 ...
## $ chas : Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 1 1 1 ...
## $ nox : num 0.538 0.469 0.469 0.458 0.458 0.458 0.524 0.524 0.524 0.524 ...
## $ rm : num 6.58 6.42 7.18 7 7.15 ...
## $ age : num 65.2 78.9 61.1 45.8 54.2 58.7 66.6 96.1 100 85.9 ...
## $ dis : num 4.09 4.97 4.97 6.06 6.06 ...
## $ rad : num 1 2 2 3 3 3 5 5 5 5 ...
## $ tax : num 296 242 242 222 222 222 311 311 311 311 ...
## $ ptratio: num 15.3 17.8 17.8 18.7 18.7 18.7 15.2 15.2 15.2 15.2 ...
## $ b : num 397 397 393 395 397 ...
## $ lstat : num 4.98 9.14 4.03 2.94 5.33 ...
## $ medv : num 24 21.6 34.7 33.4 36.2 28.7 22.9 27.1 16.5 18.9 ...
Variabel rad (index of accessibility to radial highways) dan pratio (pupil-teacher ratio by town) dibuat missing value masing-masing sebanyak 40 obs secara acak.
# Pengenalan Missing Values
set.seed(100)
BostonHousing[sample(1:nrow(BostonHousing), 40), "rad"] <- NA
BostonHousing[sample(1:nrow(BostonHousing), 40), "ptratio"] <- NA
head(BostonHousing,20)
## crim zn indus chas nox rm age dis rad tax ptratio b
## 1 0.00632 18.0 2.31 0 0.538 6.575 65.2 4.0900 1 296 15.3 396.90
## 2 0.02731 0.0 7.07 0 0.469 6.421 78.9 4.9671 2 242 17.8 396.90
## 3 0.02729 0.0 7.07 0 0.469 7.185 61.1 4.9671 2 242 17.8 392.83
## 4 0.03237 0.0 2.18 0 0.458 6.998 45.8 6.0622 NA 222 18.7 394.63
## 5 0.06905 0.0 2.18 0 0.458 7.147 54.2 6.0622 3 222 18.7 396.90
## 6 0.02985 0.0 2.18 0 0.458 6.430 58.7 6.0622 3 222 18.7 394.12
## 7 0.08829 12.5 7.87 0 0.524 6.012 66.6 5.5605 NA 311 NA 395.60
## 8 0.14455 12.5 7.87 0 0.524 6.172 96.1 5.9505 5 311 15.2 396.90
## 9 0.21124 12.5 7.87 0 0.524 5.631 100.0 6.0821 5 311 15.2 386.63
## 10 0.17004 12.5 7.87 0 0.524 6.004 85.9 6.5921 5 311 15.2 386.71
## 11 0.22489 12.5 7.87 0 0.524 6.377 94.3 6.3467 5 311 15.2 392.52
## 12 0.11747 12.5 7.87 0 0.524 6.009 82.9 6.2267 5 311 15.2 396.90
## 13 0.09378 12.5 7.87 0 0.524 5.889 39.0 5.4509 5 311 15.2 390.50
## 14 0.62976 0.0 8.14 0 0.538 5.949 61.8 4.7075 4 307 21.0 396.90
## 15 0.63796 0.0 8.14 0 0.538 6.096 84.5 4.4619 4 307 21.0 380.02
## 16 0.62739 0.0 8.14 0 0.538 5.834 56.5 4.4986 4 307 21.0 395.62
## 17 1.05393 0.0 8.14 0 0.538 5.935 29.3 4.4986 4 307 21.0 386.85
## 18 0.78420 0.0 8.14 0 0.538 5.990 81.7 4.2579 4 307 21.0 386.75
## 19 0.80271 0.0 8.14 0 0.538 5.456 36.6 3.7965 4 307 21.0 288.99
## 20 0.72580 0.0 8.14 0 0.538 5.727 69.5 3.7965 4 307 21.0 390.95
## lstat medv
## 1 4.98 24.0
## 2 9.14 21.6
## 3 4.03 34.7
## 4 2.94 33.4
## 5 5.33 36.2
## 6 5.21 28.7
## 7 12.43 22.9
## 8 19.15 27.1
## 9 29.93 16.5
## 10 17.10 18.9
## 11 20.45 15.0
## 12 13.27 18.9
## 13 15.71 21.7
## 14 8.26 20.4
## 15 10.26 18.2
## 16 8.47 19.9
## 17 6.58 23.1
## 18 14.67 17.5
## 19 11.69 20.2
## 20 11.28 18.2
# Pengecekan Pola Missing Value
library(mice)
## Warning: package 'mice' was built under R version 4.3.2
##
## Attaching package: 'mice'
## The following object is masked from 'package:stats':
##
## filter
## The following objects are masked from 'package:base':
##
## cbind, rbind
md.pattern(BostonHousing)
## crim zn indus chas nox rm age dis tax b lstat medv rad ptratio
## 430 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0
## 36 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1
## 36 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1
## 4 1 1 1 1 1 1 1 1 1 1 1 1 0 0 2
## 0 0 0 0 0 0 0 0 0 0 0 0 40 40 80
If you have large number of observations in your dataset, where all the classes to be predicted are sufficiently represented in the training data, then try deleting (or not to include missing values while model building, for example by setting na.action=na.omit) those observations (rows) that contain missing values. Make sure after deleting the observations, you have:
Have sufficent data points, so the model doesn’t lose power.
Not to introduce bias (meaning, disproportionate or non-representation of classes).
If a particular variable is having more missing values that rest of the variables in the dataset, and, if by removing that one variable you can save many observations.
Then, the suggest to remove that particular variable, unless it is a really important predictor that makes a lot of business sense would be essential.
It is a matter of deciding between the importance of the variable and losing out on a number of observations.
Replacing the missing values with the mean / median / mode is a crude way of treating missing values.
Depending on the context, like if the variation is low or if the variable has low leverage over the response, such a rough approximation is acceptable and could possibly give satisfactory results.
Prediction is most advanced method to impute your missing values nd includes different approaches such as:
kNN Imputation,
Rpart (Recursive Partitioning and Regression Trees),
Mice (Multivariate Imputation by Chained Equations).
Ilustrasi menyisihkan missing value untuk pemodelan regresi
lm(medv ~ ptratio + rad, data=BostonHousing, na.action=na.omit)
##
## Call:
## lm(formula = medv ~ ptratio + rad, data = BostonHousing, na.action = na.omit)
##
## Coefficients:
## (Intercept) ptratio rad
## 57.0465 -1.7691 -0.2032
library(Hmisc)
## Warning: package 'Hmisc' was built under R version 4.3.2
##
## Attaching package: 'Hmisc'
## The following objects are masked from 'package:base':
##
## format.pval, units
head(data.frame(impute(BostonHousing$ptratio, mean)),20) # replace with mean
## impute.BostonHousing.ptratio..mean.
## 1 15.30000
## 2 17.80000
## 3 17.80000
## 4 18.70000
## 5 18.70000
## 6 18.70000
## 7 18.43884
## 8 15.20000
## 9 15.20000
## 10 15.20000
## 11 15.20000
## 12 15.20000
## 13 15.20000
## 14 21.00000
## 15 21.00000
## 16 21.00000
## 17 21.00000
## 18 21.00000
## 19 21.00000
## 20 21.00000
head(data.frame(impute(BostonHousing$ptratio, median)),20) # median
## impute.BostonHousing.ptratio..median.
## 1 15.3
## 2 17.8
## 3 17.8
## 4 18.7
## 5 18.7
## 6 18.7
## 7 19.0
## 8 15.2
## 9 15.2
## 10 15.2
## 11 15.2
## 12 15.2
## 13 15.2
## 14 21.0
## 15 21.0
## 16 21.0
## 17 21.0
## 18 21.0
## 19 21.0
## 20 21.0
head(data.frame(impute(BostonHousing$ptratio, 20)),20) # replace specific numberK
## impute.BostonHousing.ptratio..20.
## 1 15.3
## 2 17.8
## 3 17.8
## 4 18.7
## 5 18.7
## 6 18.7
## 7 20.0
## 8 15.2
## 9 15.2
## 10 15.2
## 11 15.2
## 12 15.2
## 13 15.2
## 14 21.0
## 15 21.0
## 16 21.0
## 17 21.0
## 18 21.0
## 19 21.0
## 20 21.0
Keakuratan ketika diisi dengan nilai mean
# install package: DMwR dari github, karena tidak tersedia di R versi ini
#install.packages("devtools")
#devtools::install_github("cran/DMwR")
library(DMwR)
## Loading required package: grid
## Registered S3 method overwritten by 'quantmod':
## method from
## as.zoo.data.frame zoo
actuals <- original$ptratio[is.na(BostonHousing$ptratio)]
predicteds <- rep(mean(BostonHousing$ptratio, na.rm=T),
length(actuals))
regr.eval(actuals, predicteds)
## mae mse rmse mape
## 1.70028970 3.90808804 1.97688847 0.09577023
library(DMwR)
knnOutput <- knnImputation(BostonHousing[,
!names(BostonHousing) %in% "medv"]) # perform knn imputation.
anyNA(knnOutput)
## [1] FALSE
actuals <- original$ptratio[is.na(BostonHousing$ptratio)]
predicteds <- knnOutput[is.na(BostonHousing$ptratio), "ptratio"]
regr.eval(actuals, predicteds)
## mae mse rmse mape
## 0.55280823 1.07043179 1.03461673 0.03394082
library(rpart)
class_mod <- rpart(rad ~ . - medv,
data=BostonHousing[!is.na(BostonHousing$rad), ],
method="class", na.action=na.omit)
anova_mod <- rpart(ptratio ~ . - medv,
data=BostonHousing[!is.na(BostonHousing$ptratio), ],
method="anova", na.action=na.omit)
rad_pred <- predict(class_mod,
BostonHousing[is.na(BostonHousing$rad), ])
ptratio_pred <- predict(anova_mod,
BostonHousing[is.na(BostonHousing$ptratio), ])
actuals <- original$ptratio[is.na(BostonHousing$ptratio)]
predicteds <- ptratio_pred
regr.eval(actuals, predicteds)
## mae mse rmse mape
## 0.45583994 0.67213722 0.81983975 0.02592722
actuals <- original$rad[is.na(BostonHousing$rad)]
predicteds <- as.numeric(colnames(rad_pred)[apply(rad_pred, 1,
which.max)])
mean(actuals != predicteds) # compute misclass error.
## [1] 0.225
library(mice)
miceMod <- mice(BostonHousing[, !names(BostonHousing) %in%
"medv"], method="rf") # perform mice imputation, based on random forests.
##
## iter imp variable
## 1 1 rad ptratio
## 1 2 rad ptratio
## 1 3 rad ptratio
## 1 4 rad ptratio
## 1 5 rad ptratio
## 2 1 rad ptratio
## 2 2 rad ptratio
## 2 3 rad ptratio
## 2 4 rad ptratio
## 2 5 rad ptratio
## 3 1 rad ptratio
## 3 2 rad ptratio
## 3 3 rad ptratio
## 3 4 rad ptratio
## 3 5 rad ptratio
## 4 1 rad ptratio
## 4 2 rad ptratio
## 4 3 rad ptratio
## 4 4 rad ptratio
## 4 5 rad ptratio
## 5 1 rad ptratio
## 5 2 rad ptratio
## 5 3 rad ptratio
## 5 4 rad ptratio
## 5 5 rad ptratio
miceOutput <- complete(miceMod) # generate the completeddata.
anyNA(miceOutput)
## [1] FALSE
actuals <- original$ptratio[is.na(BostonHousing$ptratio)]
predicteds <- miceOutput[is.na(BostonHousing$ptratio), "ptratio"]
regr.eval(actuals, predicteds)
## mae mse rmse mape
## 0.25500000 0.59950000 0.77427385 0.01431601
actuals <- original$rad[is.na(BostonHousing$rad)]
predicteds <- miceOutput[is.na(BostonHousing$rad), "rad"]
mean(actuals != predicteds) # compute misclass error.
## [1] 0.3