Bu yazımızda, Kaggle.com içerisindeki mantar sınıflandırma “mushroom classification” problemi için lojistik regresyon ile analiz yapacağız. Mantarların yenilebilir mi yoksa ölümcül zehirli mi olduklarının modelini oluşturacağız.
Gerekli Kütüphaneler ve Tanımlamalar
Kullanacağımız tüm kütüphaneler ile ilgili detaylar şu şekildedir:
– dplyr, magrittr: Data manipülasyon
– caTools: Datayı traning/test setlerine ayırma
– rPSI: Population stability index (PSI)
– RWeka: Gain ratio
– caret: Confusion matrix
– pROC: ROC curve
– car: Variance inflation factor (vif)
Tüm yazılarımızda olduğu gibi öncelikle sisteminizde eksik olan kütüphanelerin kurulumunu yapalım.
install.packages(c("dplyr","magrittr","caTools",
"RWeka","caret","pROC","car"))
devtools::install_github("siyuany/rpsi")
Ana kütüphaneleri aktif hale getirelim.
library(dplyr)
library(magrittr)
Çalışacağımız datayı direkt olarak kaynağından alalım.
url <- "https://raw.githubusercontent.com/toygur/REgitimMerkezi/master/dataset/mushrooms.csv"
mushrooms <- readr::read_csv(url)
Mantarlar verisetini çalışma ortamımıza (environment) aldık. Amacımız, veriseti içerisindeki class değişkenini (p:poisonous, e:edible) yani mantarın zehirli mi yoksa yenilebilir mi olduğunun modelini oluşturmak. Data içerisinde kullanılan değişkenleri ve aldıkları değerleri (veri sözlüğü) yazının sonunda detaylı olarak sunulmuştur.
Datanın özelliklerini incelediğimizde str(mushrooms) tüm sütunların character tipinde olduğu görülmektedir. Analize başlamadan önce sütunları kategorik (factor) değişkene çevirmeliyiz. dplyr kütüphanesi fonksiyonları ile bunu kolaylıkla yapabiliriz. Daha önceki yazılarımızda %>% pipeline ifadesini sıklıkla kullanmıştık. Pipeline’ı ilk kez %<>% biçiminde kullandığımıza dikkat ediniz. Bu kullanım ile işlemin sonucu tekrar sol tarafta yer alan değişkene atanmaktadır.
mutate_all fonksiyonu ile as.factor fonksiyonunu tüm sütunlara uygulanmıştır. Öte yandan as.factor fonksiyonu içerisine yazdığımız . (nokta) sütunları temsil etmektedir.
Bir sonraki adımda sütun isimleri değiştirilecektir. Örnek olarak cap-shape biçiminde olan sütun başlığını cap_shape biçimine dönüştürmek için gsub fonksiyonu kullanılmıştır.
mushrooms %<>% mutate_all(funs(as.factor(.)))
colnames(mushrooms) %<>% gsub(pattern = "-",
replacement = "_",
x = .)
mushrooms
## # A tibble: 8,124 x 23
## class cap_~ cap_~ cap_~ brui~ odor gill~ gill~ gill~ gill~ stal~ stal~
## <fct> <fct> <fct> <fct> <fct> <fct> <fct> <fct> <fct> <fct> <fct> <fct>
## 1 p x s n t p f c n k e e
## 2 e x s y t a f c b k e c
## 3 e b s w t l f c b n e c
## 4 p x y w t p f c n n e e
## 5 e x s g f n f w b k t e
## 6 e x y y t a f c b n e c
## 7 e b s w t a f c b g e c
## 8 e b y w t l f c b n e c
## 9 p x y w t p f c n p e e
## 10 e b s y t a f c b g e c
## # ... with 8,114 more rows, and 11 more variables:
## # stalk_surface_above_ring <fctr>, stalk_surface_below_ring <fctr>,
## # stalk_color_above_ring <fctr>, stalk_color_below_ring <fctr>,
## # veil_type <fctr>, veil_color <fctr>, ring_number <fctr>, ring_type
## # <fctr>, spore_print_color <fctr>, population <fctr>, habitat <fctr>
Ön Hazırlık
Modelleme yapılırken verisetleri traning ve test olarak bağımlı değişkene göre belirli bir oranda ikiye ayrılmaktadır. Model, traning sete göre oluşturulur ve test sete göre sınanır. caTools kütüphanesi içerisindeki sample.split fonksiyonu ile kolaylıkla yapılabilmektedir. Bu örnek için verisetimizi %80 – %20 oranında ayırdık.
set.seed(123)
split <- caTools::sample.split(mushrooms$class, SplitRatio = 0.8)
trainingData <- mushrooms %>% filter(split)
testData <- mushrooms %>% filter(!split)
Population Stability Index (PSI)
Bağımlı değişkene göre iki parçaya ayırdığımız verisetinin aynı özellikleri taşıması gereklidir. Bunu sınamak için literatürde kullanılan yöntemlerden biri Population Stability Index olup rPSI kütüphanesi içerisindedir. Sonuçlar incelendiğinde traning ve test setleri başarılı şekilde ayrıştırıldığı görülmektedir.
PSI | Sonuç | Açıklama |
---|---|---|
PSI < 0.10 | Değiliklik yok | Ayırım başarılı |
0.10 < PSI < 0.25 | Küçük değişiklikler | Kontrol için Farklı metotlara başvur |
PSI > 0.25 | Büyük önemli değişiklik | Ayırım başarısız |
p <- rPSI::psi(original = trainingData$class,
current = testData$class)
summary(p)
## PSI: 0
##
## Levels OrgCnt CurCnt OrgPct CurPct Index
## 1 e 3366 842 0.5179 0.5182 0
## 2 p 3133 783 0.4821 0.4818 0
## 3 Total 6499 1625 1.0000 1.0000 0
Gain Ratio
Weka, Java ile yazılan, veri madenciliğinde (data mining) ön işleme (pre-processing), sınıflandırma (classification), regresyon, kümeleme (clustering), ilişkilendirme kuralları (association rules) ve görselleştirme (visualization) için araçlar içeren bir makine öğrenme algoritmaları (ML, machine learning) topluluğudur. Gain Ratio, Weka’nın attribute selection fonksiyonudur.
gainRatio <- RWeka::GainRatioAttributeEval(class ~ .,
data = trainingData)
broom::tidy(sort(gainRatio, decreasing = TRUE))
## # A tibble: 22 x 2
## names x
## <chr> <dbl>
## 1 odor 0.389
## 2 gill_size 0.255
## 3 stalk_surface_above_ring 0.232
## 4 spore_print_color 0.216
## 5 ring_type 0.206
## 6 bruises 0.197
## 7 stalk_surface_below_ring 0.191
## 8 gill_spacing 0.157
## 9 gill_color 0.138
## 10 stalk_color_above_ring 0.131
## # ... with 12 more rows
Confusion Matrisi
Confusion matris, modele göre tahmin edilen sonuçlar ile gerçek sonuçlar arasındaki ilişkilerin izlendiği matristir. caret kütüphanesi içerisindeki confusionMatrix fonksiyonu ile bulunmaktadır. Kullanımını kolaylaştırmak için kendi fonksiyonumuzu oluşturalım ve içerisinde hesaplamaları gerçekleştirelim.
Burada kütüphanenin fonksiyon içerisindeki kullanım biçimine değinmeliyiz. caret kütüphanesinin R dosyasının genelinde aktif hale getirilmediğini aksine fonksiyonun içerisinde sadece kullanıldığı satırda aktif hale getirildiğine dikkat ediniz.
confusionMatrix fonksiyonu, model sonuçları ile ilgili bazı rasyolar da hesaplamaktadır. Detaylara kütüphane aktif iken ?confusionMatrix kodunu çalıştırarak ulaşabilirsiniz. Bunlardan biri olan accuracy rate modelin başarı ölçütüdür ancak tek başına model performansı için yeterli değildir.
getConfusionMatrix <- function(prediction, threshold = 0.5) {
predictions <- ifelse(prediction > threshold, "p", "e")
res <- caret::confusionMatrix(data = testData$class,
reference = predictions)
return(res)
}
ROC Eğrisi (Curve)
ROC eğrisi, model sonuçlarının başarısını test etmektedir. Köşegen üzerindeki doğruya yaklaştıkça model sonuçlarının rassallaştığı söylenebilir. ROC curve üretmek için R’da bir çok kütüphane bulunmaktadır. Bu yazımızda pROC kütüphanesi içerisideki roc fonksiyonu kullanılacaktır. Bir önceki bölümde olduğu gibi kullanımı kolaylaştırmak için kendi fonksiyonumuzu oluşturalım.
plotROCCurve <- function(prediction, title, color = "red") {
plot(pROC::roc(testData$class, prediction, direction="<"),
print.auc = TRUE,
col = color,
lwd = 2,
main = title )
}
Lojistik (Logistic) Regresyon
Model 1: class ~ odor
GainRatio sonuçlarına göre ilk sırada çıkan oder değişkenini göz önüne alarak ilk modelimizi oluşturalım.
model1 <- glm(class ~ odor,
data = trainingData,
family = "binomial" )
predict1 <- predict(object = model1,
newdata = testData,
type = "response")
getConfusionMatrix(prediction = predict1)
## Confusion Matrix and Statistics
##
## Reference
## Prediction e p
## e 842 0
## p 17 766
##
## Accuracy : 0.9895
## 95% CI : (0.9833, 0.9939)
## No Information Rate : 0.5286
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.979
## Mcnemar's Test P-Value : 0.0001042
##
## Sensitivity : 0.9802
## Specificity : 1.0000
## Pos Pred Value : 1.0000
## Neg Pred Value : 0.9783
## Prevalence : 0.5286
## Detection Rate : 0.5182
## Detection Prevalence : 0.5182
## Balanced Accuracy : 0.9901
##
## 'Positive' Class : e
##
Confusion matris sonuçlarına göre, test veriseti içerisindeki yenebilen (edible, e) 842 mantarın tamamı doğru tahmin edilmiştir. Öte yandan test veriseti içerisindeki zehirli 783 mantarın 766 tanesi doğru tahmin edilmiştir, 17 tanesi ise zehirli olmasına rağmen yemelik olarak tahminlenmiştir. Model1’in model performansı 0.9895 olarak gözükmektedir.
plotROCCurve(prediction = predict1,
title = "Model 1: ROC Curve")
Model 2: class ~ odor + gill_size + stalk_surface_above_ring
Model1’de bulduğumuz sonuçları biraz daha iyileştirebilmek için bu sefer GainRatio sonuçlarındaki ilk üç sıradaki değişkeni seçelim ve modeli bu değişkenlere göre oluşturalım.
Elimizde birden fazla değişken olduğu için artık değişkenler arasında multicollinearity problemi olup olmadığını da incelememiz gereklidir. Bunun için car kütüphanesi içerisindeki vif (variance inflation factor) fonksiyonu kullanılacaktır. Literatürde farklı eşik değerleri olmasına rağmen genellikle VIF değerinin 10‘dan büyük olması, değişkenler arasında problem yaratabilecek multicollinearity olduğunun habercisidir.
model2 <- glm(class ~ odor + gill_size + stalk_surface_above_ring,
data = trainingData,
family = "binomial" )
car::vif(model2)
## GVIF Df GVIF^(1/(2*Df))
## odor 1.951775 8 1.042682
## gill_size 1.006947 1 1.003468
## stalk_surface_above_ring 1.965334 3 1.119196
predict2 <- predict(object = model2,
newdata = testData,
type = "response")
getConfusionMatrix(prediction = predict2)
## Confusion Matrix and Statistics
##
## Reference
## Prediction e p
## e 842 0
## p 10 773
##
## Accuracy : 0.9938
## 95% CI : (0.9887, 0.997)
## No Information Rate : 0.5243
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.9877
## Mcnemar's Test P-Value : 0.004427
##
## Sensitivity : 0.9883
## Specificity : 1.0000
## Pos Pred Value : 1.0000
## Neg Pred Value : 0.9872
## Prevalence : 0.5243
## Detection Rate : 0.5182
## Detection Prevalence : 0.5182
## Balanced Accuracy : 0.9941
##
## 'Positive' Class : e
##
Confusion matris sonuçlarına göre, test veriseti içerisindeki yenebilen (edible,e) 842 mantarın tamamı yine Model1’de olduğu gibi doğru tahmin edilmiştir. Öte yandan test veriseti içerisindeki zehirli 783 mantarın 773 tanesi doğru tahmin edilmiş, 10 tanesi ise zehirli olmasına rağmen yemelik olarak tahminlenmiştir. Model2’in model performansı 0.9938 olarak gözükmektedir. Model1’den Model2’ye iyileşme oldukça iyi gözükmektedir.
plotROCCurve(prediction = predict2,
title = "Model 2: ROC Curve")
Veri Sözlüğü
Değişken | Değerler |
---|---|
cap-shape | bell=b; conical=c; convex=x; flat=f; knobbed=k; sunken=s |
cap-surface | fibrous=f; grooves=g; scaly=y; smooth=s |
cap-color | brown=n; buff=b; cinnamon=c; gray=g; green=r; pink=p; purple=u; red=e; white=w; yellow=y |
bruises | bruises=t; no=f |
odor | almond=a; anise=l; creosote=c; fishy=y; foul=f; musty=m; none=n; pungent=p; spicy=s |
gill-attachment | attached=a; descending=d; free=f; notched=n |
gill-spacing | close=c; crowded=w; distant=d |
gill-size | broad=b; narrow=n |
gill-color | black=k; brown=n; buff=b; chocolate=h; gray=g; green=r; orange=o; pink=p; purple=u; red=e; white=w; yellow=y |
stalk-shape | enlarging=e; tapering=t |
stalk-root | bulbous=b; club=c; cup=u; equal=e; rhizomorphs=z; rooted=r; missing=? |
stalk-surface-above-ring | fibrous=f; scaly=y; silky=k; smooth=s |
stalk-surface-below-ring | fibrous=f; scaly=y; silky=k; smooth=s |
stalk-color-above-ring | brown=n; buff=b; cinnamon=c; gray=g; orange=o; pink=p; red=e; white=w; yellow=y |
stalk-color-below-ring | brown=n; buff=b; cinnamon=c; gray=g; orange=o; pink=p; red=e; white=w; yellow=y |
veil-type | partial=p; universal=u |
veil-color | brown=n; orange=o; white=w; yellow=y |
ring-number | none=n; one=o; two=t |
ring-type | cobwebby=c; evanescent=e; flaring=f; large=l; none=n; pendant=p; sheathing=s; zone=z |
spore-print-color | black=k; brown=n; buff=b; chocolate=h; green=r; orange=o; purple=u; white=w; yellow=y |
population | abundant=a; clustered=c; numerous=n; scattered=s; several=v; solitary=y |
habitat | grasses=g; leaves=l; meadows=m; paths=p; urban=u; waste=w; woods=d |