STA1382 Penyiapan Data

Cahya Alkahfi | Rizki Ananda

Package

library(tidyverse) # ggplot and dplyr
## ── Attaching packages ─────────────────────────────────────── tidyverse 1.3.2 ──
## ✔ ggplot2 3.4.0      ✔ purrr   0.3.5 
## ✔ tibble  3.1.8      ✔ dplyr   1.0.10
## ✔ tidyr   1.2.1      ✔ stringr 1.4.1 
## ✔ readr   2.1.3      ✔ forcats 0.5.2 
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
library(mlbench) # data pima Indian Dataset
library(cowplot) # menampilkan plot dalam bentuk Grid
library(caret) # features scaling
## Loading required package: lattice
## 
## Attaching package: 'caret'
## 
## The following object is masked from 'package:purrr':
## 
##     lift

Pemeriksaan Data

data(PimaIndiansDiabetes)

diabetes <- PimaIndiansDiabetes

str(diabetes)
## 'data.frame':    768 obs. of  9 variables:
##  $ pregnant: num  6 1 8 1 0 5 3 10 2 8 ...
##  $ glucose : num  148 85 183 89 137 116 78 115 197 125 ...
##  $ pressure: num  72 66 64 66 40 74 50 0 70 96 ...
##  $ triceps : num  35 29 0 23 35 0 32 0 45 0 ...
##  $ insulin : num  0 0 0 94 168 0 88 0 543 0 ...
##  $ mass    : num  33.6 26.6 23.3 28.1 43.1 25.6 31 35.3 30.5 0 ...
##  $ pedigree: num  0.627 0.351 0.672 0.167 2.288 ...
##  $ age     : num  50 31 32 21 33 30 26 29 53 54 ...
##  $ diabetes: Factor w/ 2 levels "neg","pos": 2 1 2 1 2 1 2 1 2 2 ...
head(diabetes)

Ringkasan dan Sebaran Data

summary(diabetes)
##     pregnant         glucose         pressure         triceps     
##  Min.   : 0.000   Min.   :  0.0   Min.   :  0.00   Min.   : 0.00  
##  1st Qu.: 1.000   1st Qu.: 99.0   1st Qu.: 62.00   1st Qu.: 0.00  
##  Median : 3.000   Median :117.0   Median : 72.00   Median :23.00  
##  Mean   : 3.845   Mean   :120.9   Mean   : 69.11   Mean   :20.54  
##  3rd Qu.: 6.000   3rd Qu.:140.2   3rd Qu.: 80.00   3rd Qu.:32.00  
##  Max.   :17.000   Max.   :199.0   Max.   :122.00   Max.   :99.00  
##     insulin           mass          pedigree           age        diabetes 
##  Min.   :  0.0   Min.   : 0.00   Min.   :0.0780   Min.   :21.00   neg:500  
##  1st Qu.:  0.0   1st Qu.:27.30   1st Qu.:0.2437   1st Qu.:24.00   pos:268  
##  Median : 30.5   Median :32.00   Median :0.3725   Median :29.00            
##  Mean   : 79.8   Mean   :31.99   Mean   :0.4719   Mean   :33.24            
##  3rd Qu.:127.2   3rd Qu.:36.60   3rd Qu.:0.6262   3rd Qu.:41.00            
##  Max.   :846.0   Max.   :67.10   Max.   :2.4200   Max.   :81.00

Jumlah NA

colSums(is.na(diabetes))
## pregnant  glucose pressure  triceps  insulin     mass pedigree      age 
##        0        0        0        0        0        0        0        0 
## diabetes 
##        0

Jumlah Nilai 0

colSums(diabetes==0)
## pregnant  glucose pressure  triceps  insulin     mass pedigree      age 
##      111        5       35      227      374       11        0        0 
## diabetes 
##        0

Persentase Nilai 0

round(colSums(diabetes==0)/length(diabetes$diabetes), 4)*100
## pregnant  glucose pressure  triceps  insulin     mass pedigree      age 
##    14.45     0.65     4.56    29.56    48.70     1.43     0.00     0.00 
## diabetes 
##     0.00

Dari Summary di atas, terlihat tidak terdapat data NA, namun terlihat banyak kolom memiliki nilai 0 yang seharusnya tidak mungkin 0. Seperti glucose yang menunjukkan kadar gula atau pressure yang menunjukkan tekanan darah, serta kolom lainnya.

Kondisi ini perlu ditangani karena merupakan informasi yang keliru.

plots <- lapply(names(diabetes), function(var_x){
  p <- 
    ggplot(diabetes) +
    aes_string(var_x)

  if(is.numeric(diabetes[[var_x]])) {
    p <- p + geom_density(lwd=1, color="darkgreen")

  } else {
    p <- p + geom_bar(fill="darkred")
  } 

})
## Warning: `aes_string()` was deprecated in ggplot2 3.0.0.
## ℹ Please use tidy evaluation ideoms with `aes()`
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
plot_grid(plotlist = plots)

Masing-masing kolom memiliki sebaran yang bervariasi ada yang relatif simetris ada pula yang menjulur. Secara umum dalam konteks prediksi hal ini tidak terlalu menjadi masalah. Pengecekan penting untuk penanganan data hilang atau outlier.

Kolom pregnant

# Histogram Data Kolom Pregnant
pl1 <- ggplot(diabetes, aes(x=pregnant))
pl1 <- pl1 + 
  geom_bar(fill="red", alpha = 0.6, aes(fill=..count..))

# Boxplot Kolom Pregnant
pl2 <- ggplot(diabetes, aes(y=pregnant))

pl2 <- pl2 + 
  geom_boxplot(fill="red", alpha=0.6) + coord_flip() + theme_classic()

pl3 <- ggplot(diabetes, aes(x=diabetes, y=pregnant))

# Boxplot Kolom Pregnant menurut kondisi diabetes
pl3 <- pl3 + 
  geom_boxplot(aes(fill=diabetes)) + coord_flip() + theme_classic()

plot_grid(plotlist = list(pl1, pl2, pl3))
## Warning: The dot-dot notation (`..count..`) was deprecated in ggplot2 3.4.0.
## ℹ Please use `after_stat(count)` instead.

summary(diabetes$pregnant)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   0.000   1.000   3.000   3.845   6.000  17.000

Sebaran nilai pregnant cenderung menjulur jauh ke kanan.

Menurut visualisasi boxplot, terdapat beberapa data outlier namun secara umum masih dalam batas yang wajar. Apalagi jika melihat boxplot antara pregnant dan diabetes sebaran data relatif cukup baik.

Namun jika ingin dilakukan penanganan maka terdapat beberapa alternatif:

  • Menghapus data Outlier
  • Mengganti data dengan mean/ median data yang tidak outlier
  • Flooring & Capping Mengganti data dengan nilai max teoritis (Misal Q1 - 1.5xIQR dan Q3 + 1.5xIQR)

Menghapus Data Outlier

Melihat dari boxplot tunggal, terdapat 3 titik data outlier, sedangkan jika melihat berdasarkan kelompok diabetes atau tidakmaka terdapat dua outlier pada kelompok tidak diabetes. Misalkan kita menggunakan indikator dari boxplot tunggal, maka untuk menghapus data tersebut:

# Cari batas nilai maksimum (Q3 + 1.5*IQR)

q1 <- quantile(diabetes$pregnant, probs=0.25, names=F)
q3 <- quantile(diabetes$pregnant, probs=0.75, names=F)
IQR <- q3 - q1
max <- q3 + 1.5*IQR

# Hapus data yang lebih besar dari max

# R base
# diabetes <- diabetes[diabetes$pregnant <= max] 

# dplyr
#diabetes <- diabetes %>% filter(pregnant <= max)

Mengganti dengan mean/median dari data yang tidak outlier

non.outlier <- diabetes[diabetes$pregnant <= max, "pregnant"]
mean.non <- mean(non.outlier)
med.non <- median(non.outlier)

# Update outlier data menggunakan mean atau median

# R base
diabetes$pregnant <- ifelse(diabetes$pregnant > max, med.non, diabetes$pregnant)

# dplyr
# diabetes <- diabetes %>% mutate(pregnant = ifelse(pregnant > max, med.non, pregnant))

Mengganti dengan nilai max

Sama seperti sebelumnya, cukup mengganti nilai outlier dengan nilai minimum/maksimum

# Update outlier data menggunakan nilai max

# R base
#diabetes$pregnant <- ifelse(diabetes$pregnant > max, max, diabetes$pregnant)

# dplyr
# diabetes <- diabetes %>% mutate(pregnant = ifelse(pregnant > max, max, pregnant))

Plot Setelah Modifikasi

pl2 <- ggplot(diabetes, aes(y=pregnant))
pl2 + 
  geom_boxplot(fill="red", alpha=0.6) + coord_flip() + theme_classic()

Kolom glucose

summary(diabetes$glucose)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##     0.0    99.0   117.0   120.9   140.2   199.0
# Histogram Data Kolom glucose
pl1 <- ggplot(diabetes, aes(x=glucose))
pl1 <- pl1 + 
  geom_histogram(fill="red", alpha = 0.6, aes(fill=..count..))

# Boxplot Kolom glucose
pl2 <- ggplot(diabetes, aes(y=glucose))

pl2 <- pl2 + 
  geom_boxplot(fill="red", alpha=0.6) + coord_flip() + theme_classic()

pl3 <- ggplot(diabetes, aes(x=diabetes, y=glucose))

# Boxplot Kolom glucose menurut kondisi diabetes
pl3 <- pl3 + 
  geom_boxplot(aes(fill=diabetes)) + coord_flip() + theme_classic()

plot_grid(plotlist = list(pl1, pl2, pl3))
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Pada kolom ini sebaran data relatif simetris, dan secara umum tidak terdapat data outlier kecuali pada data bernilai 0.

Nilai Glucose menunjukkan kadar gula dalam darah, sehingga tidak mungkin bernilai 0. Perlu dicek juga apakah terdapat nilai-nilai lainnya yang tidak masuk akal (misalkan menurut Dokter).Namun pada contoh ini kita hanya akan fokus pada nilai 0 saja, kita anggap nilai lainnya masih masuk dalam rentang nilai yang memungkinkan.

Cek jumlah data bernilai 0:

length(which(diabetes$glucose == 0))
## [1] 5

Terdapat 5 data yang memiliki nilai glucose = 0 yang perlu ditangani. Jumlah ini relatif sedikit jadi dapat saja kita hapus 5 baris ini atau alternatif lain kita imputasi misal dengan mean atau median.

Menghapus Data

# Hapus data dengan nilai glucose = 0

# R base
# diabetes <- diabetes[diabetes$glucose > 0] 

# dplyr
#diabetes <- diabetes %>% filter(glucose > 0)

Mengganti dengan Nilai Mean atau Median

Perlu diperhatikan, karena data bernilai 0 (bulan NA) maka perlu berhati-hati khususnya dalam menghitung mean.

Jika data bernilai NA, penghitungan mean dapat langsung dilakukan pada seluruh data, karena nilai NA akan diabaikan. Namun jika data bernilai 0 maka nilai tersebut akan dimasukkan dalam perhitungan rata-rata. Oleh karena itu perlu difilter terlebih dahulu data yang tidak bernilai 0.

Pemilihan mean biasanya untuk data yang memiliki sebaran relatif simetris, sementara untuk data dengan sebaran yang menjulur dapat menggunakan nilai median.

Melihat dari boxplot, tampak terdapat perbedaan sebaran nilai glucose antara yang terkena diabetes dan yang tidak. Penderita Diabetes cenderung memiliki nilai glucose yang lebih tinggi dibandingkan yang bukan penderita diabetes.

Opsi yang cocok adalah menggunakan mean atau median per kelompok. Data glucose 0 pada kelompok diabetes diganti dengan mean atau median pada kelompok tersebut begitu pula sebaliknya.

# Mengambil data glucose yang tidak bernilai 0
glucose.pos <- diabetes[(diabetes$glucose != 0) & (diabetes$diabetes == "pos"), "glucose"]
glucose.neg <- diabetes[(diabetes$glucose != 0) & (diabetes$diabetes == "neg"), "glucose"]

# Menghitung mean glucose masing-masing kelompok
med.gluc.pos <- median(glucose.pos)
med.gluc.neg <- median(glucose.neg)

# Mengganti nilai 0 dengan mean

# R base
diabetes$glucose <- ifelse(diabetes$glucose == 0, 
                           ifelse(diabetes$diabetes == "pos", med.gluc.pos, med.gluc.neg), 
                           diabetes$glucose)

# dplyr
# diabetes <- diabetes %>% mutate(glucose = ifelse(glucose == 0, 
#                                                 ifelse(diabetes=="pos", med.gluc.pos, med.gluc.neg), 
#                                                 glucose))
Plot Setelah Imputasi
# Histogram Data Kolom glucose
pl1 <- ggplot(diabetes, aes(x=glucose))
pl1 <- pl1 + 
  geom_histogram(fill="red", alpha = 0.6, aes(fill=..count..))

# Boxplot Kolom glucose
pl2 <- ggplot(diabetes, aes(y=glucose))

pl2 <- pl2 + 
  geom_boxplot(fill="red", alpha=0.6) + coord_flip() + theme_classic()

pl3 <- ggplot(diabetes, aes(x=diabetes, y=glucose))

# Boxplot Kolom glucose menurut kondisi diabetes
pl3 <- pl3 + 
  geom_boxplot(aes(fill=diabetes)) + coord_flip() + theme_classic()

plot_grid(plotlist = list(pl1, pl2, pl3))
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

summary(diabetes$glucose)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   44.00   99.75  117.00  121.68  140.25  199.00

Kolom pressure

Kolom pressure berisi data tekanan darah juga terdapat nilai 0 yang seharusnya tidak mungkin.

Selain itu juga terdapat nilai-nilai outlier baik di kedua sisi.

summary(diabetes$pressure)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    0.00   62.00   72.00   69.11   80.00  122.00
# Histogram Data Kolom Pressure
pl1 <- ggplot(diabetes, aes(x=pressure))
pl1 <- pl1 + 
  geom_histogram(fill="red", alpha = 0.6, aes(fill=..count..))

# Boxplot Kolom Pressure
pl2 <- ggplot(diabetes, aes(y=pressure))

pl2 <- pl2 + 
  geom_boxplot(fill="red", alpha=0.6) + coord_flip() + theme_classic()

pl3 <- ggplot(diabetes, aes(x=diabetes, y=pressure))

# Boxplot Kolom Pressure menurut kondisi diabetes
pl3 <- pl3 + 
  geom_boxplot(aes(fill=diabetes)) + coord_flip() + theme_classic()

plot_grid(plotlist = list(pl1, pl2, pl3))
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Cek jumlah data bernilai 0:

length(which(diabetes$pressure == 0))
## [1] 35
zero.pressure <- diabetes %>% 
       filter(pressure == 0) %>%
       group_by(diabetes) %>% 
       summarise(count = length(pressure))
zero.pressure

Terdapat 35 data dengan nilai pressure = 0. Untuk kelompok Penderita diabetes terdapat 16 data bernilai 0 sedangkan untuk kelompok bukan penderita diabetes terdapat 19 data bernilai 0. Melihat dari boxplot kelompok, tampaknya tidak terdapat perbedaan yang berarti antara sebaran nilai pressure pada kelompok diabetes maupun tidak. Oleh karena itu kita mungkin cukup menggunakan mean data keseluruhan (yang tidak bernilai 0) untuk imputasi data 0.

Pada data pressure terindikasi terdapat beberapa amatan outlier, dapat pula dilakukan penanganan seperti pada bagian sebelumnya (pregnant). Namun di sini tidak akan kita lakukan penanganan. (Silahkan yang ingin mencoba)

Mengganti Nilai 0 dengan nilai Mean

# Mengambil data pressure yang tidak bernilai 0
pressure <- diabetes[diabetes$glucose != 0, "pressure"]

# Menghitung mean pressure
mean.pressure <- mean(pressure)

# Mengganti nilai 0 dengan mean

# R base
diabetes$pressure <- ifelse(diabetes$pressure == 0, mean.pressure, diabetes$pressure)

# dplyr
# diabetes <- diabetes %>% mutate(pressure = ifelse(pressure == 0, mean.pressure, pressure))
Plot Setelah Imputasi
# Histogram Data Kolom Pressure
pl1 <- ggplot(diabetes, aes(x=pressure))
pl1 <- pl1 + 
  geom_histogram(fill="red", alpha = 0.6, aes(fill=..count..))

# Boxplot Kolom Pressure
pl2 <- ggplot(diabetes, aes(y=pressure))

pl2 <- pl2 + 
  geom_boxplot(fill="red", alpha=0.6) + coord_flip() + theme_classic()

pl3 <- ggplot(diabetes, aes(x=diabetes, y=pressure))

# Boxplot Kolom Pressure menurut kondisi diabetes
pl3 <- pl3 + 
  geom_boxplot(aes(fill=diabetes)) + coord_flip() + theme_classic()

plot_grid(plotlist = list(pl1, pl2, pl3))
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Kolom tricepts

Kolom tricepts menunjukkan ketebalan lipatan kulit tricepts (mm). Data ini juga seharusnya tidak dapat bernilai 0.

summary(diabetes$triceps)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    0.00    0.00   23.00   20.54   32.00   99.00
# Histogram Data Kolom Pressure
pl1 <- ggplot(diabetes, aes(x=triceps))
pl1 <- pl1 + 
  geom_histogram(fill="red", alpha = 0.6, aes(fill=..count..))

# Boxplot Kolom Pressure
pl2 <- ggplot(diabetes, aes(y=triceps))

pl2 <- pl2 + 
  geom_boxplot(fill="red", alpha=0.6) + coord_flip() + theme_classic()

pl3 <- ggplot(diabetes, aes(x=diabetes, y=triceps))

# Boxplot Kolom Pressure menurut kondisi diabetes
pl3 <- pl3 + 
  geom_boxplot(aes(fill=diabetes)) + coord_flip() + theme_classic()

plot_grid(plotlist = list(pl1, pl2, pl3))
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

length(which(diabetes$triceps == 0))
## [1] 227
length(which(diabetes$triceps == 0))/length(diabetes$triceps)
## [1] 0.2955729

Jumlah data bernilai 0 untuk kolom tricepts cukup besar yaitu Sekitar 30%. Kita dapat saja melakukan imputasi seperti kolom sebelumnya. Namun jika dirasa terlalu banyak data yang diimputasi mungkin sebaiknya kolom ini dapat dibuang dari model.

# Menghapus kolom triceps
diabetes$triceps <- NULL

Kolom insulin

Sama seperti kolom tricepts, kolom insulin juga tidak mungkin bernilai 0. Namun pada data terdapat nilai 0 yang sangat banyak bahkan hampir 50%. Oleh karena itu penanganan yang paling tepat adalah membuang kolom tersebut dari model.

length(which(diabetes$insulin == 0))
## [1] 374
length(which(diabetes$insulin == 0))/length(diabetes$insulin)
## [1] 0.4869792
# Menghapus kolom insulin
diabetes$insulin <- NULL

Kolom mass

Kolom mass menunjukkan nilai BMI

summary(diabetes$mass)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    0.00   27.30   32.00   31.99   36.60   67.10
# Histogram Data Kolom Pressure
pl1 <- ggplot(diabetes, aes(x=mass))
pl1 <- pl1 + 
  geom_histogram(fill="red", alpha = 0.6, aes(fill=..count..))

# Boxplot Kolom Pressure
pl2 <- ggplot(diabetes, aes(y=mass))

pl2 <- pl2 + 
  geom_boxplot(fill="red", alpha=0.6) + coord_flip() + theme_classic()

pl3 <- ggplot(diabetes, aes(x=diabetes, y=mass))

# Boxplot Kolom Pressure menurut kondisi diabetes
pl3 <- pl3 + 
  geom_boxplot(aes(fill=diabetes)) + coord_flip() + theme_classic()

plot_grid(plotlist = list(pl1, pl2, pl3))
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

length(which(diabetes$mass == 0))
## [1] 11
length(which(diabetes$mass == 0))/length(diabetes$mass)
## [1] 0.01432292

Mengganti Nilai 0 dengan nilai Mean

# Mengambil data mass yang tidak bernilai 0
mass <- diabetes[diabetes$mass != 0, "mass"]

# Menghitung mean mass
mean.mass <- mean(mass)

# Mengganti nilai 0 dengan mean

# R base
diabetes$mass <- ifelse(diabetes$mass == 0, mean.mass, diabetes$mass)

# dplyr
# diabetes <- diabetes %>% mutate(mass = ifelse(mass == 0, mean.mass, mass))

Mengganti Nilai Data Outlier

Dalam contoh ini, anggap saja nilai BMI di atas (Q3 + 1.5 IQR) merupakan nilai yang tidak masuk akal. Maka kita dapat menghapus amatan dengan mass > 50 atau mengganti dengan nilai (Q3 + 1.5 IQR)

q1 <- quantile(diabetes$mass, probs=0.25, names=F)
q3 <- quantile(diabetes$mass, probs=0.75, names=F)
IQR <- q3 - q1
max <- q3 + 1.5*IQR

# R base
diabetes$mass <- ifelse(diabetes$mass > max, max, diabetes$mass)

# dplyr
# diabetes <- diabetes %>% mutate(mass = ifelse(mass > max, max, mass))

Kolom pedigree

summary(diabetes$pedigree)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##  0.0780  0.2437  0.3725  0.4719  0.6262  2.4200
# Histogram Data Kolom Pressure
pl1 <- ggplot(diabetes, aes(x=pedigree))
pl1 <- pl1 + 
  geom_histogram(fill="red", alpha = 0.6, aes(fill=..count..))

# Boxplot Kolom Pressure
pl2 <- ggplot(diabetes, aes(y=pedigree))

pl2 <- pl2 + 
  geom_boxplot(fill="red", alpha=0.6) + coord_flip() + theme_classic()

pl3 <- ggplot(diabetes, aes(x=diabetes, y=pedigree))

# Boxplot Kolom Pressure menurut kondisi diabetes
pl3 <- pl3 + 
  geom_boxplot(aes(fill=diabetes)) + coord_flip() + theme_classic()

plot_grid(plotlist = list(pl1, pl2, pl3))
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

length(which(diabetes$pedigree == 0))
## [1] 0

Untuk melakukan penanganan outlier dapat menggunakan cara seperti sebelumnya. Namun pada contoh ini akan kita biarkan, karena nilai-nilai nya masih masuk dalam rentang nilai yang memungkinkan.

Kolom age

summary(diabetes$age)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   21.00   24.00   29.00   33.24   41.00   81.00
# Histogram Data Kolom Pressure
pl1 <- ggplot(diabetes, aes(x=age))
pl1 <- pl1 + 
  geom_histogram(fill="red", alpha = 0.6, aes(fill=..count..))

# Boxplot Kolom Pressure
pl2 <- ggplot(diabetes, aes(y=age))

pl2 <- pl2 + 
  geom_boxplot(fill="red", alpha=0.6) + coord_flip() + theme_classic()

pl3 <- ggplot(diabetes, aes(x=diabetes, y=age))

# Boxplot Kolom Pressure menurut kondisi diabetes
pl3 <- pl3 + 
  geom_boxplot(aes(fill=diabetes)) + coord_flip() + theme_classic()

plot_grid(plotlist = list(pl1, pl2, pl3))
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Tidak terdapat data yang aneh pada kolom age sehingga dapat kita biarkan apa adanya.

Features Scaling

Features Scaling adalah proses pengubahan skala pada peubah-peubah sehingga memiliki skala yang sama. Banyak algoritma machine learning, terutama yang berbasis jarak ataupun Neural Network akan bekerja lebih optimal jika skala setiap fitur sama.

Selain itu, dalam kasus model regresi, pada skala data yang sama, semakin besar nilai koefisiennya (mutlak) maka semakin besar pengaruhnya terhadap model.

feaures Scaling tidak merubah pola hubungan antara peubah tersebut dengan peubah respon.

Contoh features scaling: 1. Standardize : mengubah setiap fitur menjadi distribusi dengan mean 0 dan varians 1 dengan formula \[x_{i \;scaled}=\frac{x_i-\bar{x}}{s}\]

  1. Normalize : mengubah setiap fitur sehinggga memiliki nilai pada rentang [0 - 1] dengan formula \[x_{i\;scaled}=\frac{x_i - min(x)}{max(x)-min(x)}\]

Contoh menggunakan library caret:

Standardize

summary(diabetes[,1:6])
##     pregnant         glucose          pressure           mass      
##  Min.   : 0.000   Min.   : 44.00   Min.   : 24.00   Min.   :18.20  
##  1st Qu.: 1.000   1st Qu.: 99.75   1st Qu.: 64.00   1st Qu.:27.50  
##  Median : 3.000   Median :117.00   Median : 72.00   Median :32.40  
##  Mean   : 3.783   Mean   :121.68   Mean   : 72.25   Mean   :32.40  
##  3rd Qu.: 6.000   3rd Qu.:140.25   3rd Qu.: 80.00   3rd Qu.:36.60  
##  Max.   :13.000   Max.   :199.00   Max.   :122.00   Max.   :50.25  
##     pedigree           age       
##  Min.   :0.0780   Min.   :21.00  
##  1st Qu.:0.2437   1st Qu.:24.00  
##  Median :0.3725   Median :29.00  
##  Mean   :0.4719   Mean   :33.24  
##  3rd Qu.:0.6262   3rd Qu.:41.00  
##  Max.   :2.4200   Max.   :81.00
sapply(diabetes[,1:6], sd)
##   pregnant    glucose   pressure       mass   pedigree        age 
##  3.2706442 30.4641606 12.1159316  6.6674658  0.3313286 11.7602315
# Standardisasi Fitur
preprocessParams <- preProcess(diabetes[,1:6], method=c("center", "scale"))
standardized <- predict(preprocessParams, diabetes[,1:6])

summary(standardized)
##     pregnant          glucose           pressure             mass           
##  Min.   :-1.1565   Min.   :-2.5498   Min.   :-3.98276   Min.   :-2.1290870  
##  1st Qu.:-0.8508   1st Qu.:-0.7198   1st Qu.:-0.68132   1st Qu.:-0.7342542  
##  Median :-0.2393   Median :-0.1535   Median :-0.02103   Median : 0.0006577  
##  Mean   : 0.0000   Mean   : 0.0000   Mean   : 0.00000   Mean   : 0.0000000  
##  3rd Qu.: 0.6780   3rd Qu.: 0.6097   3rd Qu.: 0.63926   3rd Qu.: 0.6305822  
##  Max.   : 2.8182   Max.   : 2.5382   Max.   : 4.10577   Max.   : 2.6778368  
##     pedigree            age         
##  Min.   :-1.1888   Min.   :-1.0409  
##  1st Qu.:-0.6885   1st Qu.:-0.7858  
##  Median :-0.2999   Median :-0.3606  
##  Mean   : 0.0000   Mean   : 0.0000  
##  3rd Qu.: 0.4659   3rd Qu.: 0.6598  
##  Max.   : 5.8797   Max.   : 4.0611
sapply(standardized, sd)
## pregnant  glucose pressure     mass pedigree      age 
##        1        1        1        1        1        1

Normalize

# Normalisasi Fitur
preprocessParams <- preProcess(diabetes[,1:6], method=c("range"))
normalized <- predict(preprocessParams, diabetes[,1:6])

summary(normalized)
##     pregnant          glucose          pressure           mass       
##  Min.   :0.00000   Min.   :0.0000   Min.   :0.0000   Min.   :0.0000  
##  1st Qu.:0.07692   1st Qu.:0.3597   1st Qu.:0.4082   1st Qu.:0.2902  
##  Median :0.23077   Median :0.4710   Median :0.4898   Median :0.4431  
##  Mean   :0.29097   Mean   :0.5011   Mean   :0.4924   Mean   :0.4429  
##  3rd Qu.:0.46154   3rd Qu.:0.6210   3rd Qu.:0.5714   3rd Qu.:0.5741  
##  Max.   :1.00000   Max.   :1.0000   Max.   :1.0000   Max.   :1.0000  
##     pedigree            age        
##  Min.   :0.00000   Min.   :0.0000  
##  1st Qu.:0.07077   1st Qu.:0.0500  
##  Median :0.12575   Median :0.1333  
##  Mean   :0.16818   Mean   :0.2040  
##  3rd Qu.:0.23409   3rd Qu.:0.3333  
##  Max.   :1.00000   Max.   :1.0000

Plot Sebaran Asal

plots <- lapply(names(diabetes[,1:6]), function(var_x){
  p <- 
    ggplot(diabetes) +
    aes_string(var_x)

  if(is.numeric(diabetes[[var_x]])) {
    p <- p + geom_density(lwd=1, color="darkgreen")

  } else {
    p <- p + geom_bar(fill="darkred")
  } 

})

plot_grid(plotlist = plots)

Plot Sebaran Hasil Standardisasi

plots <- lapply(names(standardized), function(var_x){
  p <- 
    ggplot(standardized) +
    aes_string(var_x) + 
    geom_density(lwd=1, color="darkgreen")
})

plot_grid(plotlist = plots)

Plot Sebaran Hasil Normalisasi

plots <- lapply(names(normalized), function(var_x){
  p <- 
    ggplot(normalized) +
    aes_string(var_x) + 
    geom_density(lwd=1, color="darkgreen")
})

plot_grid(plotlist = plots)

Transformation (Box-Cox)

Transformasi umumnya digunakan untuk merubah sebaran suatu feature ketika feature tersebut tidak normal sehingga menjadi mendekati sebaran normal.

Box-Cox Transformation biasanya dilakukan pada variabel dependen (Y) dalam analisis regresi atau analisis varian (ANOVA). Ini dilakukan untuk memastikan bahwa variabel dependen memiliki distribusi yang lebih dekat dengan normal sehingga asumsi distribusi normal dalam model regresi atau ANOVA terpenuhi.

Transformasi ini juga dapat digunakan pada variabel independen (X) jika diperlukan, tergantung pada distribusi data dan tujuan analisis. Namun, transformasi pada variabel independen lebih jarang dilakukan karena memiliki implikasi terhadap interpretasi model akhir.

preprocessParams <- preProcess(diabetes[,1:6], method=c("BoxCox"))
print(preprocessParams)
## Created from 768 samples and 5 variables
## 
## Pre-processing:
##   - Box-Cox transformation (5)
##   - ignored (0)
## 
## Lambda estimates for Box-Cox transformation:
## 0.1, 0.9, 0.3, -0.1, -1.1
transformed <- predict(preprocessParams, diabetes[,1:6])
summary(transformed)
##     pregnant         glucose         pressure           mass      
##  Min.   : 0.000   Min.   :3.784   Min.   : 24.00   Min.   :4.626  
##  1st Qu.: 1.000   1st Qu.:4.603   1st Qu.: 64.00   1st Qu.:5.676  
##  Median : 3.000   Median :4.762   Median : 72.00   Median :6.130  
##  Mean   : 3.783   Mean   :4.770   Mean   : 72.25   Mean   :6.087  
##  3rd Qu.: 6.000   3rd Qu.:4.943   3rd Qu.: 80.00   3rd Qu.:6.482  
##  Max.   :13.000   Max.   :5.293   Max.   :122.00   Max.   :7.462  
##     pedigree            age        
##  Min.   :-2.5510   Min.   :0.8772  
##  1st Qu.:-1.4116   1st Qu.:0.8815  
##  Median :-0.9875   Median :0.8867  
##  Mean   :-0.9599   Mean   :0.8874  
##  3rd Qu.:-0.4680   3rd Qu.:0.8938  
##  Max.   : 0.8838   Max.   :0.9019
plots <- lapply(names(transformed), function(var_x){
  p <- 
    ggplot(transformed) +
    aes_string(var_x)  + 
    geom_density(lwd=1, color="darkgreen")
})

plot_grid(plotlist = plots)

=====================================================================================================================

Referensi

referensi lebih lanjut : di sini

contoh serupa pada bahasa python : di sini