Mempelajari algoritma klasifikasi dari dasar, menyelidiki dasar matematika yang mendukung algoritma logistic regression dan algoritma nearest neighbors.
glmLogistic Regression adalah algoritma untuk kasus klasifikasi yang
disesuaikan dari kurva regresi, y=f(x), di mana y adalah
variabel kategorik. Tujuan dari logistik regression yaitu melakukan
prediksi probability dengan menggunakan model regresi linier (yang dapat
digunakan untuk klasifikasi).
y adalah binery lebih dari 2 kategoriOutput dari logistic regression berupa log of odds.
Pada dasarnya, ketika kita melakukan klasifikasi, kita akan menghitung peluang.
\[P(yes) = \frac{n(yes)}{n(yes) + n(no)}\]
Contoh:
Anda adalah student Algoritma yang akan mengerjakan kuis C1. Pada batch sebelumnya, ada 24 dari 30 student yang berhasil mengerjakan kuis. Apakah Anda akan lulus pada kuis C1 ini?
# Probabilitas:
24/30#> [1] 0.8
Odds merupakan bentuk lain dari probabilitas, yaitu perbandingan antara probabilitas kejadian terjadi/probabilitas kejadian tidak terjadi.
\[\frac{p}{(1-p)}\]
p = probabilitas suatu kejadian terjadi
❓Contoh 1:
Berapa odds dari Anda lulus mengerjakan kuis C1?
# odds
p_lulus = 0.8
p_lulus/(1-p_lulus)#> [1] 4
📈 Interpretasi:
❓Contoh 2:
Anda hendak berpergian menggunakan pesawat dari Soekarno Hatta
Airport. Bila diketahui dari 100 penerbangan di Soekarno Hatta, terdapat
25 pesawat Delay. Berapa odds pesawat Anda
On Time?
# probability
p = 75/100
# odds
p/(1-p)#> [1] 3
📈 Interpretasi:
⁉️ Knowledge Check:
Apabila nilai odds = 1 berarti probabilitasnya?
1:1 -> 50:50 (punya peluang yang sama besar)
Berapa range nilai dari odds?
# odds: p/1-p
# min
p <- 0
p/(1-p)#> [1] 0
# max
p <- 1
p/(1-p)#> [1] Inf
Log of odds adalah nilai odds yang dilogaritmikkan:
\[logit(p) = log(\frac{p}{1-p})\]
Berapakah log(odds) dari penerbangan tepat waktu?
p = 75/100
log(p/(1-p))#> [1] 1.098612
Berapa range nilai log of odds?
# min
p <- 0
log(p/(1-p))#> [1] -Inf
# max
p <- 1
log(p/(1-p))#> [1] Inf
📝 Notes:
Terdapat cara lain:
logit(): probability -> log of odds (jarang
dipakai)inv.logit(): log of odds -> probabilitylibrary(gtools)
# probability -> log of odds dengan `logit()`
p <- 0.75
f_lods <- logit(p)
# log of odds -> peluang dengan `inv.logit()`
inv.logit(f_lods)#> [1] 0.75
Fungsi inv.logit() ini juga biasa disebut sigmoid
function.
# sigmoid function
curve(inv.logit(x), from = -10, to=10,
xlab = "Log of Odds",
ylab = "Probabolity")📝 Kesimpulan:
inv.logit()glm() & InterpretasiAnda adalah seorang analis performa student di universitas. Anda ditugaskan untuk memprediksi status kelulusan siswa dengan honors (cumlaude).
# read & inspect data
honors <- read.csv("data_input/sample.csv") %>%
select(-femalexmath)
glimpse(honors)#> Rows: 200
#> Columns: 5
#> $ female <int> 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
#> $ read <int> 57, 68, 44, 63, 47, 44, 50, 34, 63, 57, 60, 57, 73, 54, 45, 42,…
#> $ write <int> 52, 59, 33, 44, 52, 52, 59, 46, 57, 55, 46, 65, 60, 63, 57, 49,…
#> $ math <int> 41, 53, 54, 47, 57, 51, 42, 45, 54, 52, 51, 51, 71, 57, 50, 43,…
#> $ hon <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, …
Deskripsi kolom:
female: gender of student (1 for female, 0 for
male)read: score in reading testwrite: score in writing testmath: score in math testhon: status of graduating in honors (1 for honors, 0
for not honors)# cek missing value
colSums(is.na(honors))#> female read write math hon
#> 0 0 0 0 0
tidak ada missing value
# Ubah tipe data
honors <- honors %>%
mutate(female = as.factor(female),
hon = as.factor(hon))
# cek data
str(honors)#> 'data.frame': 200 obs. of 5 variables:
#> $ female: Factor w/ 2 levels "0","1": 1 2 1 1 1 1 1 1 1 1 ...
#> $ read : int 57 68 44 63 47 44 50 34 63 57 ...
#> $ write : int 52 59 33 44 52 52 59 46 57 55 ...
#> $ math : int 41 53 54 47 57 51 42 45 54 52 ...
#> $ hon : Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 1 1 1 ...
# eksplorasi singkat
summary(honors)#> female read write math hon
#> 0: 91 Min. :28.00 Min. :31.00 Min. :33.00 0:151
#> 1:109 1st Qu.:44.00 1st Qu.:45.75 1st Qu.:45.00 1: 49
#> Median :50.00 Median :54.00 Median :52.00
#> Mean :52.23 Mean :52.77 Mean :52.65
#> 3rd Qu.:60.00 3rd Qu.:60.00 3rd Qu.:59.00
#> Max. :76.00 Max. :67.00 Max. :75.00
📌 Cara membuat model Logistic Regression
Gunakan fungsi
glm(), parameternya: formula : tempat mendefinisikan target dan predictor (y~x) data : data yang digunakan untuk membuat model family : gunakan “binomial” bila ingin menggunakan logistic regression pada pemodelan ini
model_null <- glm(formula = hon~1,
data = honors,
family = "binomial")
summary(model_null)#>
#> Call:
#> glm(formula = hon ~ 1, family = "binomial", data = honors)
#>
#> Deviance Residuals:
#> Min 1Q Median 3Q Max
#> -0.7497 -0.7497 -0.7497 -0.7497 1.6772
#>
#> Coefficients:
#> Estimate Std. Error z value Pr(>|z|)
#> (Intercept) -1.1255 0.1644 -6.845 0.00000000000762 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> (Dispersion parameter for binomial family taken to be 1)
#>
#> Null deviance: 222.71 on 199 degrees of freedom
#> Residual deviance: 222.71 on 199 degrees of freedom
#> AIC: 224.71
#>
#> Number of Fisher Scoring iterations: 4
📈 Interpretasi dari summary model
Intercept: -1.1255 -> log of odds dari target variable
Berikut pembuktiannya:
# probability
table(honors$hon)#>
#> 0 1
#> 151 49
# probability student honors
49/200#> [1] 0.245
# probability ke log of odds
logit(49/200)#> [1] -1.12546
📈 Interpretasi:
Log of odds tidak dapat diinterpretasikan. Untuk interpretasi, nilai log of odds kita ubah ke odds.
# log of odds -> odds
exp( -1.1255)#> [1] 0.3244902
# log of odds -> probability
inv.logit( -1.1255)#> [1] 0.2449925
Kejadian student lulus dengan honors adalah 0.32 KALI lebih mungkin dibandingkan lulus tanpa honors. Probabilitas student lulus dengan predikat honors adalah 0.245 Students lebih mungkin untuk lulus tanpa predikat honors
❓Buat model untuk memprediksi honors berdasarkan gender
female:
glm(y ~ x1 + x2 , data, family = "binomial")
model_f <- glm(formula = hon ~ female,
data = honors,
family = "binomial")
summary(model_f)#>
#> Call:
#> glm(formula = hon ~ female, family = "binomial", data = honors)
#>
#> Deviance Residuals:
#> Min 1Q Median 3Q Max
#> -0.8337 -0.8337 -0.6431 -0.6431 1.8317
#>
#> Coefficients:
#> Estimate Std. Error z value Pr(>|z|)
#> (Intercept) -1.4709 0.2690 -5.469 0.0000000453 ***
#> female1 0.5928 0.3414 1.736 0.0825 .
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> (Dispersion parameter for binomial family taken to be 1)
#>
#> Null deviance: 222.71 on 199 degrees of freedom
#> Residual deviance: 219.61 on 198 degrees of freedom
#> AIC: 223.61
#>
#> Number of Fisher Scoring iterations: 4
✏️ Informasi Coefficients:
📈 Interpretasi:
# odds female dapat honors
exp(0.5928)#> [1] 1.809047
Kejadian student female memperoleh honors adalah 1.8 KALI lebih mungkin dibandingkan student male mendapat honors
Memiliki gender female menaikan kemungkinan student mendaparkan honors
⚙️ Contoh Aplikasi
Misalkan student ke-1 adalah seorang wanita, berapa probability dia mendapat honors?
formula: hon = m*female + c
hon =
# log of odds
log_odds_f <- -1.4709 + 0.5928*1
# log of odds -> probability
inv.logit(log_odds_f)#> [1] 0.2935717
Misalkan student ke-2 adalah seorang laki-laki, berapa probability dia mendapat honors?
formula: hon = m*female + c
hon =
# log of odds
log_odds_m <- -1.4709 + 0.5928*0
# log of odds -> probability
inv.logit(log_odds_m)#> [1] 0.1868059
ifelse(c(log_odds_f, log_odds_m) > 0.5, "honors", "not honors")#> [1] "not honors" "not honors"
Threshold 0.5 -> student baik laki laki maupun perempuan tidak mendapat honors - jenis kelamin perempuan : 0.29 - jenis kelamin laki-laki : 0.18
⚙️ [Opsional] Perhitungan manual memperoleh nilai intercept dan slope
Female: log of odds ratio dari student female mendapatkan honors dibandingkan student male mendapatkan honors.
# proportion
table(female = honors$female, honors = honors$hon)#> honors
#> female 0 1
#> 0 74 17
#> 1 77 32
# peluang dapat honors
p_female <- 32/(32+77) # female dapat honors/total female
p_male <- 17/(17+74) # male dapat honors/total male
# odds
o_female <- p_female/(1-p_female)
o_male <- p_male/(1-p_male)
# log of odds
log(o_female/o_male)#> [1] 0.5927822
Intercept: log of odds dari student male yang mendapatkan honors (basis)
log(o_male)#> [1] -1.470852
Buat model untuk memprediksi honors berdasarkan nilai
math:
model_math <- glm(formula = hon~math,
data = honors,
family = "binomial")
summary(model_math)#>
#> Call:
#> glm(formula = hon ~ math, family = "binomial", data = honors)
#>
#> Deviance Residuals:
#> Min 1Q Median 3Q Max
#> -2.0332 -0.6785 -0.3506 -0.1565 2.6143
#>
#> Coefficients:
#> Estimate Std. Error z value Pr(>|z|)
#> (Intercept) -9.79394 1.48174 -6.610 0.0000000000385 ***
#> math 0.15634 0.02561 6.105 0.0000000010294 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> (Dispersion parameter for binomial family taken to be 1)
#>
#> Null deviance: 222.71 on 199 degrees of freedom
#> Residual deviance: 167.07 on 198 degrees of freedom
#> AIC: 171.07
#>
#> Number of Fisher Scoring iterations: 5
y = b0 + b1x1 y = -9.79394 + 0.15634math
✏️ Informasi Coefficients:
📈 Interpretasi:
# log of odds -> odds
exp(0.15634)#> [1] 1.169224
Setiap kenaikan 1 nilai pada math maka odds seseorang mendapatkan honor naik sebesar 1.17 KALI.
⚙️ [Opsional] Perhitungan manual memperoleh nilai slope
baseline nilai numerik:
median(honors$math)#> [1] 52
hon = -9.79394 + 0.15634 * math
Student ke-1 memiliki nilai math 52, student kedua 53. Hitung masing-masing log of oddsnya, berapa selisihnya? (tanpa memperdulikan gender)
# log of odds
hon52 <- -9.79394 + 0.15634 * 52
hon53 <- -9.79394 + 0.15634 * 53
hon53-hon52#> [1] 0.15634
selisih == slope
💡 Tips cara cepat interpretasi:
Divedeeper
Buatlah model yang dapat memprediksi seseorang akan lulus dengan predikat honors berdasarkan gender dan nilai math nya, kemudian jawablah pertanyaan berikut:
# buat model
model_fm <- glm(formula = hon~female+math,
data = honors,
family = "binomial")
summary(model_fm)#>
#> Call:
#> glm(formula = hon ~ female + math, family = "binomial", data = honors)
#>
#> Deviance Residuals:
#> Min 1Q Median 3Q Max
#> -1.8494 -0.6506 -0.3471 -0.1361 2.5105
#>
#> Coefficients:
#> Estimate Std. Error z value Pr(>|z|)
#> (Intercept) -10.80595 1.61654 -6.685 0.0000000000232 ***
#> female1 0.96531 0.41599 2.321 0.0203 *
#> math 0.16422 0.02665 6.161 0.0000000007206 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> (Dispersion parameter for binomial family taken to be 1)
#>
#> Null deviance: 222.71 on 199 degrees of freedom
#> Residual deviance: 161.35 on 197 degrees of freedom
#> AIC: 167.35
#>
#> Number of Fisher Scoring iterations: 5
Jawab: 1. Interpretasi coefficient
# pake fungsi coef
coef(model_fm)#> (Intercept) female1 math
#> -10.8059538 0.9653096 0.1642220
# akses nilainya secara langsung
model_fm$coefficients#> (Intercept) female1 math
#> -10.8059538 0.9653096 0.1642220
exp(model_fm$coefficients)#> (Intercept) female1 math
#> 0.00002027841 2.62560049264 1.17847586504
coefficient math: setiap kenaikan 1 nilai math meningkatkan kemungkinan seseorang mendapatkan honors sebesar 1.178 kali dengan catatan semua prediktor lain nilainya sama
coefficient female: kemungkinan female lulus honors 2.62 kali lebih mungkin dibandingkan male lulus honors dengan catatan semua prediktor lain nilainya sama
📝 Notes:
Jawab: 2 dan 3. Mencari nilai peluang Prediksi manual menggunakan model logistic regression untuk melakukan klasifikasi. Hint: Log of odds yang dihasilkan model logistic regression dapat diubah ke bentuk probability. Probability yang didapatkan dapat digunakan untuk menentukan kelas pada klasifikasi.
Final formula:
hon = (Intercept) + (slope_female1 * female) + (slope_math * math)
kevin_score <- -10.8059538 + 0.9653096*0 + 0.1642220*60
wulan_score <- -10.8059538 + 0.9653096*1 + 0.1642220*80
pred_kw <- inv.logit(c(kevin_score, wulan_score))Hasil prediksi (gunakan fungsi ifelse())
ifelse(test = pred_kw > 0.5,
yes = "honors",
no = "not honors")#> [1] "not honors" "honors"
Kesimpulan: - kevin diprediksi tidak mendapatkan honors - wulan diprediksi mendapatkan honors
Bonus! Apa yang harus dilakukan Kevin agar ia dapat lulus dengan predikat honors?
# nilai minimal agar log of odds kevin bernilai 0
10.8059538 / 0.1642220#> [1] 65.80089
minimal Kevin harus dapat nilai math sebesar 66
kevin_score_upd <- inv.logit(-10.8059538 + 0.9653096*0 + 0.1642220*66)
ifelse(kevin_score_upd>0.5, "honors", "not honors")#> [1] "honors"
glm(y ~ x, data, family = "binomial"). Nilai
coefficients merupakan…Probability
Odds (menggunakan
fungsi: exp())
Log of odds
exp()inv.logit()log()yang kemudian ditentukan kelasnya berdasarkan batas tertentu (misal = 0.5).
bentuk umum dari model logistic regression Y= B0 + B1x1 + B2x2+….. log of odds : nilai Y, B0, B1 odds : ketika ingin menginterpretasikan nilai koef (B0, B1, B2, ..), bisa menggunakan fungsi exp() probability : ketika ingin mengetahui label dari data yang diprediksi, biasanya yang diubah menjadi probability adalah nilai Y.
glm()—End of Day 1—
summary(model_fm)#>
#> Call:
#> glm(formula = hon ~ female + math, family = "binomial", data = honors)
#>
#> Deviance Residuals:
#> Min 1Q Median 3Q Max
#> -1.8494 -0.6506 -0.3471 -0.1361 2.5105
#>
#> Coefficients:
#> Estimate Std. Error z value Pr(>|z|)
#> (Intercept) -10.80595 1.61654 -6.685 0.0000000000232 ***
#> female1 0.96531 0.41599 2.321 0.0203 *
#> math 0.16422 0.02665 6.161 0.0000000007206 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> (Dispersion parameter for binomial family taken to be 1)
#>
#> Null deviance: 222.71 on 199 degrees of freedom
#> Residual deviance: 161.35 on 197 degrees of freedom
#> AIC: 167.35
#>
#> Number of Fisher Scoring iterations: 5
Deviance is defined as the difference of likelihoods between the fitted model and the perfect (saturated) model. Deviance digunakan sebagai indikator goodness of fit suatu model.
Ambilah model dengan nilai residual deviance paling rendah.
model_null$deviance # model tanpa prediktor#> [1] 222.71
model_f$deviance # model dengan female sebagai prediktor #> [1] 219.6062
model_math$deviance # model dengan math sebagai prediktor#> [1] 167.0732
model_fm$deviance # model dengan female + math sebagai prediktor#> [1] 161.3454
summary(model_null)#>
#> Call:
#> glm(formula = hon ~ 1, family = "binomial", data = honors)
#>
#> Deviance Residuals:
#> Min 1Q Median 3Q Max
#> -0.7497 -0.7497 -0.7497 -0.7497 1.6772
#>
#> Coefficients:
#> Estimate Std. Error z value Pr(>|z|)
#> (Intercept) -1.1255 0.1644 -6.845 0.00000000000762 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> (Dispersion parameter for binomial family taken to be 1)
#>
#> Null deviance: 222.71 on 199 degrees of freedom
#> Residual deviance: 222.71 on 199 degrees of freedom
#> AIC: 224.71
#>
#> Number of Fisher Scoring iterations: 4
Mari buat model model_all untuk memprediksi
honors berdasarkan semua prediktor yang ada:
model_all <- glm(formula = hon~.,
data = honors,
family = "binomial")#> Warning: glm.fit: algorithm did not converge
#> Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
summary(model_all)#>
#> Call:
#> glm(formula = hon ~ ., family = "binomial", data = honors)
#>
#> Deviance Residuals:
#> Min 1Q Median 3Q Max
#> -0.000245237 -0.000000021 -0.000000021 -0.000000021 0.000176979
#>
#> Coefficients:
#> Estimate Std. Error z value Pr(>|z|)
#> (Intercept) -2176.87191 282900.61006 -0.008 0.994
#> female1 -4.44248 18078.08748 0.000 1.000
#> read 0.07478 489.40463 0.000 1.000
#> write 36.30917 4837.98255 0.008 0.994
#> math -0.36898 1362.81267 0.000 1.000
#>
#> (Dispersion parameter for binomial family taken to be 1)
#>
#> Null deviance: 222.71004668627 on 199 degrees of freedom
#> Residual deviance: 0.00000013527 on 195 degrees of freedom
#> AIC: 10
#>
#> Number of Fisher Scoring iterations: 25
model_all di atas?# log of odds -> odds
exp(0.07478)#> [1] 1.077647
exp(36.30917)#> [1] 5873164613974824
setiap kenaikan 1 nilai write kemungkinan seseorang mendapatkan honors naik 5873164613974824 kali
table(honors$hon, honors$write)#>
#> 31 33 35 36 37 38 39 40 41 42 43 44 45 46 47 49 50 52 53 54 55 57 59 60 61
#> 0 4 4 2 2 3 1 5 3 10 2 1 12 1 9 2 11 2 15 1 17 3 12 25 4 0
#> 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4
#>
#> 62 63 65 67
#> 0 0 0 0 0
#> 1 18 4 16 7
📝 Notes:
Cek proporsi nilai write terhadap variable honors
table(honors$hon, honors$write)#>
#> 31 33 35 36 37 38 39 40 41 42 43 44 45 46 47 49 50 52 53 54 55 57 59 60 61
#> 0 4 4 2 2 3 1 5 3 10 2 1 12 1 9 2 11 2 15 1 17 3 12 25 4 0
#> 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4
#>
#> 62 63 65 67
#> 0 0 0 0 0
#> 1 18 4 16 7
Perfect Separation adalah sebuah kondisi dimana ada 1 variabel prediktor yang dapat memisahkan kelas target secara sempurna. Cara mendeteksi:
Pada kasus ini, nilai write dapat memisahkan kelas honor dengan sempurna
Tidak disarankan menggunakan model dengan perfect separation, karena model amat bias pada salah satu variabel dan tidak mempertimbangkan variabel lainnya. Hal ini dapat membuat model tidak akurat (buruk) dalam memprediksi data baru.
Apa yang kita lakukan bila bertemu kondisi perfect separation:
honors2 <- honors %>%
select(-write)
# revisi model
model_all_w <- glm(formula = hon~. -write,
data = honors,
family = "binomial")
model_all_w2 <- glm(formula = hon~.,
data = honors2,
family = "binomial")
summary(model_all_w)#>
#> Call:
#> glm(formula = hon ~ . - write, family = "binomial", data = honors)
#>
#> Deviance Residuals:
#> Min 1Q Median 3Q Max
#> -1.8305 -0.6327 -0.3300 -0.1258 2.3896
#>
#> Coefficients:
#> Estimate Std. Error z value Pr(>|z|)
#> (Intercept) -11.77025 1.71068 -6.880 0.00000000000597 ***
#> female1 0.97995 0.42163 2.324 0.0201 *
#> read 0.05906 0.02655 2.224 0.0261 *
#> math 0.12296 0.03128 3.931 0.00008442731719 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> (Dispersion parameter for binomial family taken to be 1)
#>
#> Null deviance: 222.71 on 199 degrees of freedom
#> Residual deviance: 156.17 on 196 degrees of freedom
#> AIC: 164.17
#>
#> Number of Fisher Scoring iterations: 5
summary(model_all_w2)#>
#> Call:
#> glm(formula = hon ~ ., family = "binomial", data = honors2)
#>
#> Deviance Residuals:
#> Min 1Q Median 3Q Max
#> -1.8305 -0.6327 -0.3300 -0.1258 2.3896
#>
#> Coefficients:
#> Estimate Std. Error z value Pr(>|z|)
#> (Intercept) -11.77025 1.71068 -6.880 0.00000000000597 ***
#> female1 0.97995 0.42163 2.324 0.0201 *
#> read 0.05906 0.02655 2.224 0.0261 *
#> math 0.12296 0.03128 3.931 0.00008442731719 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> (Dispersion parameter for binomial family taken to be 1)
#>
#> Null deviance: 222.71 on 199 degrees of freedom
#> Residual deviance: 156.17 on 196 degrees of freedom
#> AIC: 164.17
#>
#> Number of Fisher Scoring iterations: 5
model_fm$deviance#> [1] 161.3454
AIC = Jumlah informasi yang hilang. Diinginkan AIC yang semakin kecil.
model_null$aic #> [1] 224.71
model_f$aic#> [1] 223.6062
model_math$aic #> [1] 171.0732
model_fm$aic #> [1] 167.3454
model_all_w$aic#> [1] 164.1696
Nilai AIC tidak dimiliki oleh semua model -> biasanya untuk membandingkan antar model logistic regression. Kalau mau membandingkan model secara lebih baik (bisa untuk semua model) gunakan error.
Logistic Regression menganut 3 asumsi:
vif() dari library carAsumsi logistic regression menuntut kita untuk memahami data secara mendalam dan memastikan data sudah siap dipakai untuk membuat model.
Kita akan mencoba melakukan pembahasan tentang asumsi menggunakan
data penerbangan pesawat flights_edit.csv
flights <- read.csv("data_input/flights_edit.csv")
head(flights)flight.model untuk memprediksi
DepDel15 berdasarkan Month +
DayofWeek, kemudian tampilkan hasil summaryflight.model <- glm(formula = DepDel15~Month+DayofWeek,
data = flights,
family = "binomial")
summary(flight.model)#>
#> Call:
#> glm(formula = DepDel15 ~ Month + DayofWeek, family = "binomial",
#> data = flights)
#>
#> Deviance Residuals:
#> Min 1Q Median 3Q Max
#> -0.7312 -0.6902 -0.6492 -0.6135 1.8855
#>
#> Coefficients:
#> Estimate Std. Error z value Pr(>|z|)
#> (Intercept) -0.926861 0.032296 -28.699 <0.0000000000000002 ***
#> Month -0.062459 0.003999 -15.619 <0.0000000000000002 ***
#> DayofWeek -0.005851 0.003964 -1.476 0.14
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> (Dispersion parameter for binomial family taken to be 1)
#>
#> Null deviance: 100312 on 99998 degrees of freedom
#> Residual deviance: 100065 on 99996 degrees of freedom
#> AIC: 100071
#>
#> Number of Fisher Scoring iterations: 4
exp(flight.model$coefficients[2])#> Month
#> 0.939452
exp(flight.model$coefficients[3])#> DayofWeek
#> 0.9941662
Nilai odds kurang dari 1 artinya pada bulan berikutnya akan memperkecil kemungkinan untuk delay
Nilai odds kurang dari 1 artinya pada hari berikutnya akan memperkecil kemungkinan untuk delay
library(car)
vif(flight.model)#> Month DayofWeek
#> 1.000012 1.000012
flights <- flights %>%
mutate(Month = as.factor(Month),
DayofWeek = as.factor(DayofWeek))
flight.model <- glm(formula = DepDel15~Month+DayofWeek,
data = flights,
family = "binomial")
summary(flight.model)#>
#> Call:
#> glm(formula = DepDel15 ~ Month + DayofWeek, family = "binomial",
#> data = flights)
#>
#> Deviance Residuals:
#> Min 1Q Median 3Q Max
#> -0.8704 -0.7093 -0.6269 -0.5383 2.0686
#>
#> Coefficients:
#> Estimate Std. Error z value Pr(>|z|)
#> (Intercept) -1.34692 0.02771 -48.611 < 0.0000000000000002 ***
#> Month5 -0.09491 0.03001 -3.163 0.001564 **
#> Month6 0.36353 0.02838 12.809 < 0.0000000000000002 ***
#> Month7 0.25606 0.02848 8.990 < 0.0000000000000002 ***
#> Month8 -0.03714 0.02972 -1.250 0.211286
#> Month9 -0.37841 0.03231 -11.713 < 0.0000000000000002 ***
#> Month10 -0.31266 0.03143 -9.948 < 0.0000000000000002 ***
#> DayofWeek2 -0.19895 0.02959 -6.723 0.000000000017787 ***
#> DayofWeek3 -0.05338 0.02890 -1.847 0.064697 .
#> DayofWeek4 0.20806 0.02797 7.438 0.000000000000102 ***
#> DayofWeek5 0.13247 0.02828 4.684 0.000002809421366 ***
#> DayofWeek6 -0.28909 0.03194 -9.052 < 0.0000000000000002 ***
#> DayofWeek7 -0.11055 0.02966 -3.727 0.000194 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> (Dispersion parameter for binomial family taken to be 1)
#>
#> Null deviance: 100312 on 99998 degrees of freedom
#> Residual deviance: 98967 on 99986 degrees of freedom
#> AIC: 98993
#>
#> Number of Fisher Scoring iterations: 4
Buat model untuk memprediksi peluang customer akan gagal bayar pinjaman (loan default), untuk mengindikasikan apakah customer tersebut baik atau tidak untuk diberikan pinjaman.
loans <- read.csv("data_input/loan2017Q4.csv")#, stringsAsFactors = T)
glimpse(loans)#> Rows: 1,556
#> Columns: 16
#> $ initial_list_status <chr> "w", "f", "w", "w", "w", "w", "w", "w", "w", "w", …
#> $ purpose <chr> "debt_consolidation", "debt_consolidation", "debt_…
#> $ int_rate <dbl> 14.08, 9.44, 28.72, 13.59, 15.05, 10.91, 15.05, 10…
#> $ installment <dbl> 675.99, 480.08, 1010.30, 484.19, 476.33, 130.79, 3…
#> $ annual_inc <dbl> 156700, 50000, 25000, 175000, 109992, 49000, 65000…
#> $ dti <dbl> 19.11, 19.35, 65.58, 12.60, 10.00, 5.12, 22.38, 33…
#> $ verification_status <chr> "Source Verified", "Not Verified", "Verified", "No…
#> $ grade <chr> "C", "B", "F", "C", "C", "B", "C", "B", "D", "D", …
#> $ revol_bal <int> 21936, 5457, 23453, 31740, 2284, 2016, 14330, 2758…
#> $ inq_last_12m <int> 3, 1, 0, 0, 3, 5, 0, 1, 8, 1, 0, 12, 4, 8, 1, 3, 0…
#> $ delinq_2yrs <int> 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,…
#> $ home_ownership <chr> "MORTGAGE", "RENT", "OWN", "MORTGAGE", "MORTGAGE",…
#> $ not_paid <int> 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1,…
#> $ log_inc <dbl> 11.962088, 10.819778, 10.126631, 12.072541, 11.608…
#> $ verified <int> 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1,…
#> $ grdCtoA <int> 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0,…
initial_list_status: Either w (whole) or f
(fractional).purpose: one of credit_card, debt_consolidation,
home_improvement, major_purchase and small_businessint_rate: Interest rate in percentagesinstallment: Monthly payment owed by the borrowerannual_inc: Self-reported annual income provided by the
borrower / co-borrowers during applicationdti: A ratio of the borrower’s total monthly debt
payments on his/her total obligations to the self-reported monthly
incomeverification_status: is the reported income verified,
not verified, or if the income source was verifiedgrade: software-assigned loan graderevol_bal: total credit revolving balance (in the case
of credit card, it refers to the portion of credit card spending that
goes unpaid at the end of a billing cycle)inq_last_12m: number of credit inquiries in the last 12
monthsdelinq_2yrs: number of 30+ days past-due incidences of
delinquency in the borrower’s credit file for the past 2 yearshome_ownership: one of MORTGAGE, OWN and RENTnot_paid: 0 for fully-paid loans, 1 for charged-off,
past-due / grace period or defaultedlog_inc: log of annual_incverified: 0 for “Not verified” under
verification_status, 1 otherwisegrdCtoA: 1 for a grade of A or B, and 0 for otherwise
(note: mislabelled column)Target: not_paid (paid = 0, not_paid = 1)
Adakah variabel yang tipe datanya belum sesuai? initial_list_status
purpose
grade grdCtoA
home_ownership
# data wrangling
loans <- loans %>%
mutate_if(is.character, as.factor) %>%
mutate(not_paid = as.factor(not_paid),
verified = as.factor(verified),
grdCtoA = as.factor(grdCtoA))
# cek data kembali
glimpse(loans)#> Rows: 1,556
#> Columns: 16
#> $ initial_list_status <fct> w, f, w, w, w, w, w, w, w, w, w, w, w, f, w, w, w,…
#> $ purpose <fct> debt_consolidation, debt_consolidation, debt_conso…
#> $ int_rate <dbl> 14.08, 9.44, 28.72, 13.59, 15.05, 10.91, 15.05, 10…
#> $ installment <dbl> 675.99, 480.08, 1010.30, 484.19, 476.33, 130.79, 3…
#> $ annual_inc <dbl> 156700, 50000, 25000, 175000, 109992, 49000, 65000…
#> $ dti <dbl> 19.11, 19.35, 65.58, 12.60, 10.00, 5.12, 22.38, 33…
#> $ verification_status <fct> Source Verified, Not Verified, Verified, Not Verif…
#> $ grade <fct> C, B, F, C, C, B, C, B, D, D, F, C, C, E, B, C, C,…
#> $ revol_bal <int> 21936, 5457, 23453, 31740, 2284, 2016, 14330, 2758…
#> $ inq_last_12m <int> 3, 1, 0, 0, 3, 5, 0, 1, 8, 1, 0, 12, 4, 8, 1, 3, 0…
#> $ delinq_2yrs <int> 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,…
#> $ home_ownership <fct> MORTGAGE, RENT, OWN, MORTGAGE, MORTGAGE, MORTGAGE,…
#> $ not_paid <fct> 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1,…
#> $ log_inc <dbl> 11.962088, 10.819778, 10.126631, 12.072541, 11.608…
#> $ verified <fct> 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1,…
#> $ grdCtoA <fct> 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0,…
Berdasarkan deskripsi kolom, kita dapat melihat indikasi adanya multicolonierity antara beberapa prediktor (berdasarkan pemahaman data dan pengetahuan bisnis)
table(loans$grade, loans$grdCtoA)#>
#> 0 1
#> A 0 210
#> B 0 385
#> C 464 0
#> D 326 0
#> E 118 0
#> F 38 0
#> G 15 0
kolom dengan indikasi multicolonierity (informasinya redundant): * grade dan grdCtoA * verified dan verification_status * annual_inc dan log_inc
Buang kolom log_inc, verification_status, grade
loans_clean <- loans %>%
select(-c(log_inc, verification_status, grade))
names(loans)#> [1] "initial_list_status" "purpose" "int_rate"
#> [4] "installment" "annual_inc" "dti"
#> [7] "verification_status" "grade" "revol_bal"
#> [10] "inq_last_12m" "delinq_2yrs" "home_ownership"
#> [13] "not_paid" "log_inc" "verified"
#> [16] "grdCtoA"
names(loans_clean)#> [1] "initial_list_status" "purpose" "int_rate"
#> [4] "installment" "annual_inc" "dti"
#> [7] "revol_bal" "inq_last_12m" "delinq_2yrs"
#> [10] "home_ownership" "not_paid" "verified"
#> [13] "grdCtoA"
colSums(is.na(loans_clean))#> initial_list_status purpose int_rate installment
#> 0 0 0 0
#> annual_inc dti revol_bal inq_last_12m
#> 0 0 0 0
#> delinq_2yrs home_ownership not_paid verified
#> 0 0 0 0
#> grdCtoA
#> 0
# explore with summary
summary(loans_clean)#> initial_list_status purpose int_rate installment
#> f: 323 credit_card :321 Min. : 5.32 Min. : 31.04
#> w:1233 debt_consolidation:996 1st Qu.:10.42 1st Qu.: 247.26
#> home_improvement :161 Median :14.08 Median : 381.23
#> major_purchase : 51 Mean :14.71 Mean : 468.72
#> small_business : 27 3rd Qu.:18.06 3rd Qu.: 635.86
#> Max. :30.94 Max. :1503.89
#> annual_inc dti revol_bal inq_last_12m
#> Min. : 2500 Min. : 0.00 Min. : 0 Min. : 0.000
#> 1st Qu.: 48000 1st Qu.: 11.00 1st Qu.: 4974 1st Qu.: 1.000
#> Median : 68000 Median : 17.18 Median : 10163 Median : 2.000
#> Mean : 81630 Mean : 18.86 Mean : 15258 Mean : 2.359
#> 3rd Qu.: 95700 3rd Qu.: 24.53 3rd Qu.: 18094 3rd Qu.: 3.000
#> Max. :1200000 Max. :198.56 Max. :258897 Max. :18.000
#> delinq_2yrs home_ownership not_paid verified grdCtoA
#> Min. :0.0000 MORTGAGE:776 0:778 0:578 0:961
#> 1st Qu.:0.0000 OWN :201 1:778 1:978 1:595
#> Median :0.0000 RENT :579
#> Mean :0.3059
#> 3rd Qu.:0.0000
#> Max. :8.0000
Discussion: Adakah kira-kira variabel dengan nilai yang aneh? beberapa variabel numerik punya indikasi outliers, tetapi yang paling terlihat aneh adalah delinq_2yrs delinq_2yrs -> kayanya nilai 0 nya banyak banget
nrow(loans_clean)#> [1] 1556
table(loans_clean$delinq_2yrs)#>
#> 0 1 2 3 4 5 6 7 8
#> 1254 204 56 26 9 1 2 3 1
1254/1556#> [1] 0.8059126
loans_clean <- loans_clean %>%
select(-delinq_2yrs)prop.table(table(loans_clean$not_paid))#>
#> 0 1
#> 0.5 0.5
Proporsi yang seimbang penting agar model klasifikasi mempelajari karakteristik setiap kelas secara seimbang, tidak dominan hanya satu kelas saja. Hal ini mencegah model bias terhadap model dengan nilai yang lebih besar proporsinya sehingga hanya baik untuk memprediksi 1 kelas saja.
Jika kita ingin melakukan sebuah prediksi, maka kita tidak disarankan
melihat nilai error pada data yang digunakan untuk melatih model, karena
itu hanya menunjukkan bahwa model dapat memprediksi data lama tetapi
belum tentu dapat memprediksi data baru. Data yang digunakan untuk
melatih model kita sebut dengan data train, sedangkan data
yang digunakan untuk mengevaluasi model disebut dengan
data test.
Berikut beberapa kondisi yang dapat terjadi pada model:
Untuk mengevaluasi model dan melihat kemampuannya memprediksi data
baru, data kita bagi menjadi 2: data train dan data test. Proses ini
kita sebut dengan cross-validation.
Analogi:
tujuan dari cross validation adalah untuk mengetahui seberapa baik model untuk memprediksi unseen data.
RNGkind(sample.kind = "Rounding")
set.seed(417)
# index sampling
index <- sample(x = nrow(loans_clean), size = nrow(loans_clean)*0.8)
# splitting
loans_train <- loans_clean[index,] # mengambil 80% dari total data untuk digunakan sebagai data train
loans_test <- loans_clean[-index,] # 20% sisanya digunakan sebagai data test# intuisi set seed: mengunci random number kita
set.seed(123)
sample(x = c("Kevin", "Tomy", "Algoritma"), size = 2)#> [1] "Kevin" "Tomy"
nrow(loans_train)#> [1] 1244
nrow(loans_test)#> [1] 312
# re-check class imbalance
prop.table(table(loans_train$not_paid))#>
#> 0 1
#> 0.4959807 0.5040193
proporsi kelas yang balance penting untuk data train karena kita akan melatih model menggunakan data train.
Buatlah model logistic regression untuk memprediksi status loan (not_paid). Silahkan lakukan feature selection berdasarkan pertimbangan bisnis atau/dan statistik!
💡Hint:
glm()loans_trainnot_paid# model base
model_loans <- glm(formula = not_paid~.,
data = loans_train,
family = "binomial")
summary(model_loans)#>
#> Call:
#> glm(formula = not_paid ~ ., family = "binomial", data = loans_train)
#>
#> Deviance Residuals:
#> Min 1Q Median 3Q Max
#> -2.1245 -1.1256 0.6808 1.1290 1.6647
#>
#> Coefficients:
#> Estimate Std. Error z value Pr(>|z|)
#> (Intercept) -0.697331425 0.368017266 -1.895 0.0581 .
#> initial_list_statusw -0.045609904 0.145177593 -0.314 0.7534
#> purposedebt_consolidation 0.120764481 0.152229204 0.793 0.4276
#> purposehome_improvement 0.060314803 0.233414863 0.258 0.7961
#> purposemajor_purchase 0.306076249 0.359744375 0.851 0.3949
#> purposesmall_business 0.647207368 0.471415289 1.373 0.1698
#> int_rate 0.016699683 0.015819405 1.056 0.2911
#> installment 0.001011847 0.000225449 4.488 0.00000719 ***
#> annual_inc -0.000002996 0.000001259 -2.379 0.0173 *
#> dti 0.005471991 0.005597793 0.978 0.3283
#> revol_bal -0.000001561 0.000003526 -0.443 0.6579
#> inq_last_12m -0.011914309 0.023455542 -0.508 0.6115
#> home_ownershipOWN 0.378262234 0.188100773 2.011 0.0443 *
#> home_ownershipRENT 0.120666700 0.133563972 0.903 0.3663
#> verified1 0.236640524 0.125378898 1.887 0.0591 .
#> grdCtoA1 -0.329885796 0.179903046 -1.834 0.0667 .
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> (Dispersion parameter for binomial family taken to be 1)
#>
#> Null deviance: 1724.5 on 1243 degrees of freedom
#> Residual deviance: 1647.4 on 1228 degrees of freedom
#> AIC: 1679.4
#>
#> Number of Fisher Scoring iterations: 4
table(loans_clean$purpose)#>
#> credit_card debt_consolidation home_improvement major_purchase
#> 321 996 161 51
#> small_business
#> 27
Interpretasi:
Mengubah log of odds ke nilai odds
exp(model_loans$coefficient)#> (Intercept) initial_list_statusw purposedebt_consolidation
#> 0.4979122 0.9554146 1.1283591
#> purposehome_improvement purposemajor_purchase purposesmall_business
#> 1.0621709 1.3580859 1.9101989
#> int_rate installment annual_inc
#> 1.0168399 1.0010124 0.9999970
#> dti revol_bal inq_last_12m
#> 1.0054870 0.9999984 0.9881564
#> home_ownershipOWN home_ownershipRENT verified1
#> 1.4597457 1.1282488 1.2669856
#> grdCtoA1
#> 0.7190058
Interpretasi untuk parameter:
purpose small business: Kemungkinan nasabah dengan purpose small business 1.9 KALI lebih mungkin untuk not paid dibandingkan dengan purpose credit card, dengan catatan variabel lainnya memiliki nilai yang sama
int_rate: Setiap kenaikan int_rate sebesar 1 satuan maka kecenderungan nasabah untuk gagal bayar naik sebesar 1.0168399 kali dengan catatan variabel lainnya konstan
Lakukan feature selection menggunakan stepwise (menghilangkan predictor satu persatu sampai didapat nilai AIC paling rendah) * stepwise: sama persis seperti linear model
# stepwise
model_step <- step(object = model_loans,
direction = "backward",
trace = 0)
summary(model_step)#>
#> Call:
#> glm(formula = not_paid ~ installment + annual_inc + home_ownership +
#> verified + grdCtoA, family = "binomial", data = loans_train)
#>
#> Deviance Residuals:
#> Min 1Q Median 3Q Max
#> -1.8493 -1.1419 0.6999 1.1254 1.6383
#>
#> Coefficients:
#> Estimate Std. Error z value Pr(>|z|)
#> (Intercept) -0.286398493 0.168555220 -1.699 0.08929 .
#> installment 0.001101418 0.000217259 5.070 0.000000399 ***
#> annual_inc -0.000003569 0.000001138 -3.137 0.00171 **
#> home_ownershipOWN 0.376873393 0.186611468 2.020 0.04343 *
#> home_ownershipRENT 0.147923501 0.127716614 1.158 0.24678
#> verified1 0.248585159 0.124280033 2.000 0.04548 *
#> grdCtoA1 -0.482879099 0.123357611 -3.914 0.000090605 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> (Dispersion parameter for binomial family taken to be 1)
#>
#> Null deviance: 1724.5 on 1243 degrees of freedom
#> Residual deviance: 1652.5 on 1237 degrees of freedom
#> AIC: 1666.5
#>
#> Number of Fisher Scoring iterations: 4
📈 Interpretasi Model:
link untuk log of odds, response
untuk probability *8. Evaluasi (confusion matrix)—END OF DAY 2— ___
predict(object model, newdata, type)
pada parameter type terdapat pilihan nilai:
link: menghasilkan log of oddsresponse: menghasilkan probabilityContoh: prediksi not_paid untuk 6 data teratas,
# log of odds
lods <- predict(object = model_step,
newdata = head(loans_test),
type = "link")
lods#> 6 8 9 10 26 38
#> -0.8001278 0.0478935 0.2131328 0.2568868 0.6049750 0.4224801
Lakukan prediksi probability not_paid untuk data
loans_test dan disimpan pada kolom baru bernama
pred_Risk pada variable loans_test.
loans_test$pred_Risk <- predict(object = model_step,
newdata = loans_test,
type = "response")Klasifikasikan data loans_test berdasarkan pred_Risk dan
simpan pada kolom baru bernama pred_Label, gunakan
threshold 0.5.
# ifelse(kondisi, benar, salah)
loans_test$pred_Label <- ifelse(test = loans_test$pred_Risk > 0.5,
yes = 1,
no = 0)
loans_test$pred_Label <- as.factor(loans_test$pred_Label)
class(loans_test$pred_Label)#> [1] "factor"
📝 Notes:
Penentuan label yang menjadi angka 1 pada model logistic regression adalah berdasarkan levels.
# lihat hasil prediksi
loans_test %>%
select(pred_Risk,
pred_Label,
not_paid)Tiap model pasti punya error. Kita ingin mendapatkan model dengan error prediksi sekecil mungkin.
table(predict = loans_test$pred_Label,
actual = loans_test$not_paid)#> actual
#> predict 0 1
#> 0 94 54
#> 1 67 97
Setelah dilakukan prediksi menggunakan model, masih ada saja prediksi yang salah. Pada klasifikasi, kita mengevaluasi model berdasarkan confusion matrix:
Penentuan kelas:
Contoh kasus:
Isi dari Confusion Matrix:
# confusion matrix
library(caret)
confusionMatrix(data = loans_test$pred_Label,
reference = loans_test$not_paid,
positive = "1")#> Confusion Matrix and Statistics
#>
#> Reference
#> Prediction 0 1
#> 0 94 54
#> 1 67 97
#>
#> Accuracy : 0.6122
#> 95% CI : (0.5557, 0.6666)
#> No Information Rate : 0.516
#> P-Value [Acc > NIR] : 0.0003933
#>
#> Kappa : 0.2256
#>
#> Mcnemar's Test P-Value : 0.2753129
#>
#> Sensitivity : 0.6424
#> Specificity : 0.5839
#> Pos Pred Value : 0.5915
#> Neg Pred Value : 0.6351
#> Prevalence : 0.4840
#> Detection Rate : 0.3109
#> Detection Prevalence : 0.5256
#> Balanced Accuracy : 0.6131
#>
#> 'Positive' Class : 1
#>
4 metrics performa model: Accuracy, Sensitivity/Recall, Precision, Specificity
Seberapa banyak yang benar diprediksi dari keseluruhan data (positif maupun negatif).
TP+TN/TOTAL
# total data: nrow(loans.test)
(97+94)/nrow(loans_test)#> [1] 0.6121795
Note: tidak ada batasan mutlak berapa akurasi yang dianggap akurat (sudah baik):
Digunakan ketika:
Ada kondisi ketika accuracy bukanlah metrics terpenting. Umumnya ketika:
Saat kita mementingkan kelas tertentu, maka kita dapat memilih antara menggunakan metrics Recall / Precision:
Seberapa banyak yang benar diprediksi positif, dari yang reality-nya (aktualnya) positif.
TP/(TP+FN)
97/(97+54)#> [1] 0.6423841
Seberapa banyak yang benar diprediksi positif, dari yang diprediksi positif.
TP/(TP+FP)
97/(97+67)#> [1] 0.5914634
Tahapan pemilihan recall/precision pada evaluasi model: 1. Definisikan kelas positifnya 2. Pahami resiko dari kesalahan prediksi (lihat dari FN dan FP-nya) 3. Pilih resiko yang paling tidak dapat ditolerir (resiko yang mau diminimalisir)
paid (0) — not_paid (1) pos
FN -> perusahaan merugi (pakai recall) TP/TP+FN FP -> kehilangan client (pakai precision)
93/(93+58)#> [1] 0.615894
100/(100+56)#> [1] 0.6410256
f1-score
non covid — covid (pos) FN -> penyebaran covid meningkat -> berpengaruh dengan ekonomi —- iiii FP -> mengisolasi orang yang sehat -> buang2 waktu/duit + pengaruh ke sosial orang tersebut —- i
Untuk memahaminya mari berdiskusi:
ROLE PLAY:
Bos: pa reynaldi - recall, tidak mau ada yang diprediksi tidak beli ternyata beli pa jeremia - recall, yang penting barangnya ada yang beli pa adhi - recall, yang penting kejual pa ibrahim - recall, cari cuan
Telemarketer: bu sofi - recall, tidak mau ada yang diprediksi tidak beli ternyata beli pa ibrahim - recall, cari bonus
Seberapa banyak yang tepat diprediksi negatif, dari yang reality-nya negatif. Jarang dipakai karena kita tidak sering fokus pada kelas negatif.
TN/(TN+FP)
94/(94+67)#> [1] 0.5838509
…
Bila hasil evaluasi (nilai metrics) belum memuaskan, dapat dilakukan Model Tuning:
📝 Notes: 1. ganti prediktor 2. ubah data pre-processingnya: misal upsample/downsample, scaling dll. 3. ganti modelnya (pakai model yang lebih robust) 4. ganti treshold prediction (tidak terlalu dianjurkan, karena bisa memaksakan): * geser mendekati 0: meningkatkan recall * geser mendekati 1: meningkatkan precision
# ifelse(kondisi, benar, salah)
# treshold diganti jadi 0.4 (direndahkan agar untuk meningkatkan recall)
loans_test$pred_Label_new <- ifelse(test = loans_test$pred_Risk > 0.4,
yes = 1,
no = 0)
loans_test$pred_Label_new <- as.factor(loans_test$pred_Label_new)
# confusion matrix
confusionMatrix(data = loans_test$pred_Label_new,
reference = loans_test$not_paid,
positive = "1")#> Confusion Matrix and Statistics
#>
#> Reference
#> Prediction 0 1
#> 0 44 24
#> 1 117 127
#>
#> Accuracy : 0.5481
#> 95% CI : (0.491, 0.6042)
#> No Information Rate : 0.516
#> P-Value [Acc > NIR] : 0.1409
#>
#> Kappa : 0.1122
#>
#> Mcnemar's Test P-Value : 0.00000000000000935
#>
#> Sensitivity : 0.8411
#> Specificity : 0.2733
#> Pos Pred Value : 0.5205
#> Neg Pred Value : 0.6471
#> Prevalence : 0.4840
#> Detection Rate : 0.4071
#> Detection Prevalence : 0.7821
#> Balanced Accuracy : 0.5572
#>
#> 'Positive' Class : 1
#>
nilai recall naik dari 0.64 jadi 0.84 setelah threshold diganti dari 0.5 menjadi 0.4
k-NN adalah K-nearest neighboor. Metode ini akan mengkasifikasi data baru dengan membandingkan karakteristik data baru (data test) dengan data yang ada (data train). Kedekatan karakteristik tersebut diukur dengan Euclidean Distance hingga didapatkan jarak. Kemudian akan dipilih k tetangga terdekat dari data baru tersebut, kemudian ditentukan kelasnya menggunakan majority voting.
knitr::include_graphics("img/KNN.png")sqrt(nrow(data))Contoh kasus: jumlah data 400 (honors not-honors) -> kelas target = 2 k = sqrt(400) = 20 –> 21 / 19
jumlah data 900 (low medium high) -> kelas target = 3 k = 30 –> 32 / 28
—END OF DAY 3— ___
Kanker payudara adalah kanker yang paling umum menyerang wanita di dunia. Kanker payudara dapat berupa kanker jinak (benign) atau sudah ganas (malignant). Kanker ganas dapat menyebar ke organ-organ tubuh lainnya. Pada kasus ini kita ingin membuat model untuk memprediksi apakah kanker masih jinak (benign) atau sudah ganas (malignant).
wbcd <- read.csv("data_input/wisc_bc_data.csv")# inspect data
str(wbcd)#> 'data.frame': 569 obs. of 32 variables:
#> $ id : int 87139402 8910251 905520 868871 9012568 906539 925291 87880 862989 89827 ...
#> $ diagnosis : chr "B" "B" "B" "B" ...
#> $ radius_mean : num 12.3 10.6 11 11.3 15.2 ...
#> $ texture_mean : num 12.4 18.9 16.8 13.4 13.2 ...
#> $ perimeter_mean : num 78.8 69.3 70.9 73 97.7 ...
#> $ area_mean : num 464 346 373 385 712 ...
#> $ smoothness_mean : num 0.1028 0.0969 0.1077 0.1164 0.0796 ...
#> $ compactness_mean : num 0.0698 0.1147 0.078 0.1136 0.0693 ...
#> $ concavity_mean : num 0.0399 0.0639 0.0305 0.0464 0.0339 ...
#> $ points_mean : num 0.037 0.0264 0.0248 0.048 0.0266 ...
#> $ symmetry_mean : num 0.196 0.192 0.171 0.177 0.172 ...
#> $ dimension_mean : num 0.0595 0.0649 0.0634 0.0607 0.0554 ...
#> $ radius_se : num 0.236 0.451 0.197 0.338 0.178 ...
#> $ texture_se : num 0.666 1.197 1.387 1.343 0.412 ...
#> $ perimeter_se : num 1.67 3.43 1.34 1.85 1.34 ...
#> $ area_se : num 17.4 27.1 13.5 26.3 17.7 ...
#> $ smoothness_se : num 0.00805 0.00747 0.00516 0.01127 0.00501 ...
#> $ compactness_se : num 0.0118 0.03581 0.00936 0.03498 0.01485 ...
#> $ concavity_se : num 0.0168 0.0335 0.0106 0.0219 0.0155 ...
#> $ points_se : num 0.01241 0.01365 0.00748 0.01965 0.00915 ...
#> $ symmetry_se : num 0.0192 0.035 0.0172 0.0158 0.0165 ...
#> $ dimension_se : num 0.00225 0.00332 0.0022 0.00344 0.00177 ...
#> $ radius_worst : num 13.5 11.9 12.4 11.9 16.2 ...
#> $ texture_worst : num 15.6 22.9 26.4 15.8 15.7 ...
#> $ perimeter_worst : num 87 78.3 79.9 76.5 104.5 ...
#> $ area_worst : num 549 425 471 434 819 ...
#> $ smoothness_worst : num 0.139 0.121 0.137 0.137 0.113 ...
#> $ compactness_worst: num 0.127 0.252 0.148 0.182 0.174 ...
#> $ concavity_worst : num 0.1242 0.1916 0.1067 0.0867 0.1362 ...
#> $ points_worst : num 0.0939 0.0793 0.0743 0.0861 0.0818 ...
#> $ symmetry_worst : num 0.283 0.294 0.3 0.21 0.249 ...
#> $ dimension_worst : num 0.0677 0.0759 0.0788 0.0678 0.0677 ...
❓ Variabel target: diagnosis
B = Benign M = Malignant
# unique(wbcd$diagnosis)
wbcd_clean <- wbcd %>%
select(-id) %>%
mutate(diagnosis = ifelse(diagnosis == "B", "Benign", "Malignant"),
diagnosis = as.factor(diagnosis))
wbcd_cleanprop.table(table(wbcd_clean$diagnosis))#>
#> Benign Malignant
#> 0.6274165 0.3725835
Insight: datanya masih cukup balance
# cek range nilai tiap variable
summary(wbcd_clean)#> diagnosis radius_mean texture_mean perimeter_mean
#> Benign :357 Min. : 6.981 Min. : 9.71 Min. : 43.79
#> Malignant:212 1st Qu.:11.700 1st Qu.:16.17 1st Qu.: 75.17
#> Median :13.370 Median :18.84 Median : 86.24
#> Mean :14.127 Mean :19.29 Mean : 91.97
#> 3rd Qu.:15.780 3rd Qu.:21.80 3rd Qu.:104.10
#> Max. :28.110 Max. :39.28 Max. :188.50
#> area_mean smoothness_mean compactness_mean concavity_mean
#> Min. : 143.5 Min. :0.05263 Min. :0.01938 Min. :0.00000
#> 1st Qu.: 420.3 1st Qu.:0.08637 1st Qu.:0.06492 1st Qu.:0.02956
#> Median : 551.1 Median :0.09587 Median :0.09263 Median :0.06154
#> Mean : 654.9 Mean :0.09636 Mean :0.10434 Mean :0.08880
#> 3rd Qu.: 782.7 3rd Qu.:0.10530 3rd Qu.:0.13040 3rd Qu.:0.13070
#> Max. :2501.0 Max. :0.16340 Max. :0.34540 Max. :0.42680
#> points_mean symmetry_mean dimension_mean radius_se
#> Min. :0.00000 Min. :0.1060 Min. :0.04996 Min. :0.1115
#> 1st Qu.:0.02031 1st Qu.:0.1619 1st Qu.:0.05770 1st Qu.:0.2324
#> Median :0.03350 Median :0.1792 Median :0.06154 Median :0.3242
#> Mean :0.04892 Mean :0.1812 Mean :0.06280 Mean :0.4052
#> 3rd Qu.:0.07400 3rd Qu.:0.1957 3rd Qu.:0.06612 3rd Qu.:0.4789
#> Max. :0.20120 Max. :0.3040 Max. :0.09744 Max. :2.8730
#> texture_se perimeter_se area_se smoothness_se
#> Min. :0.3602 Min. : 0.757 Min. : 6.802 Min. :0.001713
#> 1st Qu.:0.8339 1st Qu.: 1.606 1st Qu.: 17.850 1st Qu.:0.005169
#> Median :1.1080 Median : 2.287 Median : 24.530 Median :0.006380
#> Mean :1.2169 Mean : 2.866 Mean : 40.337 Mean :0.007041
#> 3rd Qu.:1.4740 3rd Qu.: 3.357 3rd Qu.: 45.190 3rd Qu.:0.008146
#> Max. :4.8850 Max. :21.980 Max. :542.200 Max. :0.031130
#> compactness_se concavity_se points_se symmetry_se
#> Min. :0.002252 Min. :0.00000 Min. :0.000000 Min. :0.007882
#> 1st Qu.:0.013080 1st Qu.:0.01509 1st Qu.:0.007638 1st Qu.:0.015160
#> Median :0.020450 Median :0.02589 Median :0.010930 Median :0.018730
#> Mean :0.025478 Mean :0.03189 Mean :0.011796 Mean :0.020542
#> 3rd Qu.:0.032450 3rd Qu.:0.04205 3rd Qu.:0.014710 3rd Qu.:0.023480
#> Max. :0.135400 Max. :0.39600 Max. :0.052790 Max. :0.078950
#> dimension_se radius_worst texture_worst perimeter_worst
#> Min. :0.0008948 Min. : 7.93 Min. :12.02 Min. : 50.41
#> 1st Qu.:0.0022480 1st Qu.:13.01 1st Qu.:21.08 1st Qu.: 84.11
#> Median :0.0031870 Median :14.97 Median :25.41 Median : 97.66
#> Mean :0.0037949 Mean :16.27 Mean :25.68 Mean :107.26
#> 3rd Qu.:0.0045580 3rd Qu.:18.79 3rd Qu.:29.72 3rd Qu.:125.40
#> Max. :0.0298400 Max. :36.04 Max. :49.54 Max. :251.20
#> area_worst smoothness_worst compactness_worst concavity_worst
#> Min. : 185.2 Min. :0.07117 Min. :0.02729 Min. :0.0000
#> 1st Qu.: 515.3 1st Qu.:0.11660 1st Qu.:0.14720 1st Qu.:0.1145
#> Median : 686.5 Median :0.13130 Median :0.21190 Median :0.2267
#> Mean : 880.6 Mean :0.13237 Mean :0.25427 Mean :0.2722
#> 3rd Qu.:1084.0 3rd Qu.:0.14600 3rd Qu.:0.33910 3rd Qu.:0.3829
#> Max. :4254.0 Max. :0.22260 Max. :1.05800 Max. :1.2520
#> points_worst symmetry_worst dimension_worst
#> Min. :0.00000 Min. :0.1565 Min. :0.05504
#> 1st Qu.:0.06493 1st Qu.:0.2504 1st Qu.:0.07146
#> Median :0.09993 Median :0.2822 Median :0.08004
#> Mean :0.11461 Mean :0.2901 Mean :0.08395
#> 3rd Qu.:0.16140 3rd Qu.:0.3179 3rd Qu.:0.09208
#> Max. :0.29100 Max. :0.6638 Max. :0.20750
Range tiap variabel berbeda sehingga perlu dilakukan feature rescaling di tahap data pre-processing.
Scaling: menyamaratakan range variable prediktor
Scaling bisa menggunakan min-max normalization atau z-score standarization.
min dan
maxnormalize <- function(x){
return (
(x - min(x))/(max(x) - min(x))
)
}# contoh:
normalize(c(1,2,3,4,5)) # memampatkan range nilai menjadi 0-1#> [1] 0.00 0.25 0.50 0.75 1.00
mean (rata-rata) dan
sdscale()# contoh:
cth <- scale(c(1,2,3,4,5)) # data kita seberapa menyimpang (sd) dari pusatnya (mean)scale(7, center = 3, scale = 1.581139)#> [,1]
#> [1,] 2.529822
#> attr(,"scaled:center")
#> [1] 3
#> attr(,"scaled:scale")
#> [1] 1.581139
Pada workflow machine learning, di tahap apa scaling dilakukan?
ketika sudah dilakukan cross validation, yang di scaling pertama adalah
data training. setelah itu informasi hasil scaling data training
digunakan untuk melakukan scaling pada data testing. [ ] data yang sudah
diolah pada data wrangling (keseluruhan data secara langsung) [ ] data
training dan data testing menggunakan informasi masing masing
[X] data training, lalu informasi scaling di data training digunakan
untuk scaling data testing
Note: untuk 1 data harus menggunakan 1 tipe scaling yang sama
RNGkind(sample.kind = "Rounding")
set.seed(123)
# index sampling
index <- sample(x = nrow(wbcd_clean),
size = 0.8*nrow(wbcd_clean))
# splitting
wbcd_train <- wbcd_clean[index,]
wbcd_test <- wbcd_clean[-index,]# recheck class balance
prop.table(table(wbcd_train$diagnosis))#>
#> Benign Malignant
#> 0.6307692 0.3692308
Insight: proporsi data setelah di cross validation tidak berbeda jauh dengan proporsi data awal (masih cukup balance)
Untuk k-NN, dipisahkan antara prediktor dan label (target variabelnya).
library(dplyr)
# prediktor
wbcd_train_x <- select(wbcd_train, -diagnosis)
wbcd_test_x <- select(wbcd_test, -diagnosis)
# target (bentuknya harus vector)
wbcd_train_y <- wbcd_train[,"diagnosis"]
wbcd_test_y <- wbcd_test[,"diagnosis"]Data prediktor akan discaling menggunakan z-score standarization. Data test juga harus discaling menggunakan parameter dari data train (karena menganggap data test adalah unseen data).
scale terdiri dari beberapa parameter
# scaling data
wbcd_train_x_scale <- scale(x = wbcd_train_x)
wbcd_test_x_scale <- scale(x = wbcd_test_x,
center = attr(wbcd_train_x_scale,"scaled:center"),
scale = attr(wbcd_train_x_scale,"scaled:scale"))k-NN tidak membuat model sehingga langsung ke predict.
# find optimum k
sqrt(nrow(wbcd_train))#> [1] 21.33073
Target 2 kelas: B - Benign M - Malignant
Fungsi knn() tidak melakukan build model melainkan langsung memprediksi data train. Parameter pada fungsi knn(): - train : data train, prediktor, yang sudah discaling, tipe numerik - test : data test, prediktor, yang sudah discaling, tipe numerik - cl : data train, label (target) aktual (kategorikal) - k : jumlah k yang ditentukan dari nilai sqrt(nrow(data))
library(class) # package untuk fungsi `knn()`
wbcd_pred <- knn(train = wbcd_train_x_scale,
test = wbcd_test_x_scale,
cl = wbcd_train_y,
k = 21)
# cek hasil prediksi
head(wbcd_pred)#> [1] Benign Malignant Benign Malignant Benign Benign
#> Levels: Benign Malignant
# confusion matrix
library(caret)
confusionMatrix(data = wbcd_pred,
reference = wbcd_test_y,
positive = "Malignant")#> Confusion Matrix and Statistics
#>
#> Reference
#> Prediction Benign Malignant
#> Benign 70 2
#> Malignant 0 42
#>
#> Accuracy : 0.9825
#> 95% CI : (0.9381, 0.9979)
#> No Information Rate : 0.614
#> P-Value [Acc > NIR] : <0.0000000000000002
#>
#> Kappa : 0.9627
#>
#> Mcnemar's Test P-Value : 0.4795
#>
#> Sensitivity : 0.9545
#> Specificity : 1.0000
#> Pos Pred Value : 1.0000
#> Neg Pred Value : 0.9722
#> Prevalence : 0.3860
#> Detection Rate : 0.3684
#> Detection Prevalence : 0.3684
#> Balanced Accuracy : 0.9773
#>
#> 'Positive' Class : Malignant
#>
Resiko: FN - diprediksi tidak ganas ternyata ganas -> orang dengan kanker tidak terdeteksi (perkembangan kankernya lebih cepat) FP - diprediksi ganas ternyata tidak ganas -> treatment untuk kanker padahal tidak terlalu dibutuhkan
Kesimpulannya: gunakan recall sebagai metrics