Merhaba Dostlar,
Titanik veri seti ile detaylı analizimize başlıyoruz…
Bu bölümde, ham veriyi makine öğrenmesi modellerine uygun hale getirmeden önce veriyi tanıyacak, eksiklikleri giderecek ve gizli kalıpları görselleştireceğiz.
Öncelikle veri manipülasyonu, görselleştirme ve istatistiksel analiz için gerekli kütüphaneleri yüklüyoruz.
# Gerekli Kütüphaneler
library(tidyverse) # Veri manipülasyonu ve ggplot2 için
library(ggalluvial) # Sankey diyagramı için
library(corrplot) # Korelasyon matrisi için
library(naniar) # Eksik veri görselleştirmesi için
library(gridExtra) # Grafikleri yan yana çizmek için
library(knitr) # Tablo gösterimi için
# Veriyi Yükleme (Dosya yollarını kendi bilgisayarınıza göre güncelleyiniz)
# Genelde train ve test ayrı gelir ancak ön işleme tutarlılığı için bazen birleştirilir.
# Burada EDA odaklı olduğumuz için 'train' seti üzerinden gideceğiz.
df <- read.csv("titanic_train.csv", stringsAsFactors = FALSE)
# Veriye ilk bakış
glimpse(df)
## Rows: 891
## Columns: 12
## $ PassengerId <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,…
## $ Survived <int> 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1…
## $ Pclass <int> 3, 1, 3, 1, 3, 3, 1, 3, 3, 2, 3, 1, 3, 3, 3, 2, 3, 2, 3, 3…
## $ Name <chr> "Braund, Mr. Owen Harris", "Cumings, Mrs. John Bradley (Fl…
## $ Sex <chr> "male", "female", "female", "female", "male", "male", "mal…
## $ Age <dbl> 22, 38, 26, 35, 35, NA, 54, 2, 27, 14, 4, 58, 20, 39, 14, …
## $ SibSp <int> 1, 1, 0, 1, 0, 0, 0, 3, 0, 1, 1, 0, 0, 1, 0, 0, 4, 0, 1, 0…
## $ Parch <int> 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 0, 0, 5, 0, 0, 1, 0, 0, 0…
## $ Ticket <chr> "A/5 21171", "PC 17599", "STON/O2. 3101282", "113803", "37…
## $ Fare <dbl> 7.2500, 71.2833, 7.9250, 53.1000, 8.0500, 8.4583, 51.8625,…
## $ Cabin <chr> "", "C85", "", "C123", "", "", "E46", "", "", "", "G6", "C…
## $ Embarked <chr> "S", "C", "S", "S", "S", "Q", "S", "S", "S", "C", "S", "S"…
Veri biliminde en kritik kararlardan biri eksik veriye nasıl yaklaşılacağıdır. Önce eksik verinin haritasını çıkaralım.
# DİKKAT: R, string ifadelerdeki boşlukları NA olarak görmeyebilir.
# Önce boş metinleri ("") gerçek NA değerine dönüştürelim:
df[df == ""] <- NA
# Şimdi eksik verileri tekrar görselleştirelim
gg_miss_var(df, show_pct = TRUE) +
labs(title = "Değişkenlerdeki Eksik Veri Yüzdeleri (Düzeltilmiş)",
y = "Eksik Veri Yüzdesi (%)",
x = "Değişkenler") +
theme_minimal()
🧐 Yorum ve Karar Mekanizması Kod düzeltmesi yapıldıktan sonra ortaya çıkan gerçek tablo şöyledir:
Cabin (%77 Eksik): Verinin neredeyse 4’te 3’ü yok.
Yanlış Yöntem: Bu eksikleri doldurmaya çalışmak hayal ürünü veriler yaratır.
Doğru Karar: Bu sütunu silmek yerine, “Kabin Bilgisi Var mı?” (0/1) şeklinde yeni bir değişkene dönüştüreceğiz. Çünkü kabin numarasının bilinmesi, belki de yolcunun biletinin özel bir sınıf olduğunu gösteriyordur.
Age (~%20 Eksik):
Embarked (Çok Az Eksik):
Grafikte %1’in çok altında görünüyor (Sadece 2 kişi).
Karar: Mod (En çok tekrar eden liman) ile dolduracağız.
Makine öğrenmesi modelleri için “Ham Veri” yetersizdir. “İşlenmiş Veri” gereklidir. Önce Title (Ünvan) sütununu oluşturup sonra yaşları buna göre dolduralım.
# 1. Title (Ünvan) Çıkarımı
# İsimden nokta ile biten kelimeyi çekiyoruz (Mr. Mrs. vb)
df$Title <- str_extract(df$Name, " ([A-Za-z]+)\\.")
df$Title <- gsub("\\.", "", df$Title) # Noktayı temizle
df$Title <- str_trim(df$Title) # Boşlukları temizle
# Nadir ünvanları (Dr, Rev, Major vb.) daha genel gruplara toplayalım
rare_titles <- c('Lady', 'Countess','Capt', 'Col', 'Don', 'Dr', 'Major', 'Rev', 'Sir', 'Jonkheer', 'Dona')
df$Title[df$Title %in% rare_titles] <- 'Rare'
df$Title[df$Title == 'Mlle'] <- 'Miss'
df$Title[df$Title == 'Ms'] <- 'Miss'
df$Title[df$Title == 'Mme'] <- 'Mrs'
# 2. Yaş (Age) Doldurma - Ünvan Grubuna Göre Medyan
# Her ünvan grubu için medyan yaşı hesapla
age_by_title <- df %>%
group_by(Title) %>%
summarise(MedianAge = median(Age, na.rm = TRUE))
# Eksik yaşları bu tabloya göre doldur
for(t in age_by_title$Title){
df$Age[is.na(df$Age) & df$Title == t] <- age_by_title$MedianAge[age_by_title$Title == t]
}
# 3. Cabin Doldurma (Var/Yok Dönüşümü)
df$HasCabin <- ifelse(is.na(df$Cabin) | df$Cabin == "", 0, 1)
# 4. Embarked Doldurma (Mod ile)
embark_mode <- names(sort(table(df$Embarked), decreasing = TRUE))[1]
df$Embarked[df$Embarked == ""] <- embark_mode
df$Embarked[is.na(df$Embarked)] <- embark_mode
# Kontrol
sum(is.na(df$Age)) # Sonuç 0 olmalı
## [1] 0
⚠️ Akademik Not: Veri Sızıntısı ve Eksik Veri Doldurma
Bu çalışmada, kodun okunabilirliğini ve takibini kolaylaştırmak adına eksik verilerin doldurulması (Imputation) işlemi, veri seti eğitim ve test olarak ayrılmadan önce tüm veri üzerinde gerçekleştirilmiştir.
Akademik açıdan en hassas yaklaşım; eksik verileri dolduracak parametrelerin (örneğin “Mr.” ünvanı için medyan yaşın) sadece Eğitim Seti (Train Set) üzerinden hesaplanması ve bu hesaplanan değerlerin Test setindeki eksiklere uygulanmasıdır. Tüm veriden hesaplama yapmak, test setindeki yaş dağılımı bilgisinin çok küçük bir miktar da olsa eğitim sürecine sızmasına (Data Leakage) neden olabilir.
Ancak bu proje kapsamında; bu sızıntının model başarısına etkisi ihmal edilebilir düzeyde olduğundan ve caret paketinin standart akışını karmaşıklaştırmamak adına (“Recipes” katmanı eklememek için) bu yöntem tercih edilmiştir.
Verimiz temizlendi. Şimdi grafiklerle verinin hikayesini anlatalım.
⚠️ Önemli Metodolojik Not: Veri Kapsamı ve Mürettebat (Crew) Gerçeği
Bu bölümde, projenin genelinde kullanılan train.csv (Yolcu Listesi) veri setinden farklı olarak, R’ın dahili kütüphanesindeki Titanic veri seti kullanılmıştır. Bu bilinçli tercih, analiz kapsamına Mürettebatı (Crew) dahil edebilmek için yapılmıştır.
Neden Böyle Bir Ayrım Yaptık?
-Büyük Resmi Görmek (Domain Knowledge): Makine öğrenmesi projelerinde genellikle sadece “Yolcular” modellenir. Ancak Titanik faciasında gemideki ~2200 kişinin yaklaşık 900’ü gemi çalışanıydı. Sankey diyagramında göreceğiniz üzere, en büyük can kaybı bu grupta yaşanmıştır. Facianın boyutunu sosyolojik olarak tam kavrayabilmek için mürettebatı denkleme katmak bir zorunluluktur.
-Modelleme Kısıtı: İlerleyen bölümlerdeki Makine Öğrenmesi modellerimiz (Bölüm 5 ve sonrası), Age, Fare, Cabin, Title gibi detaylı özniteliklere ihtiyaç duymaktadır. Elimizdeki eğitim veri setinde (train.csv) mürettebat için bu detaylar bulunmamaktadır.
-Sonuç: Bu görselleştirme, çalışmanın “Keşifçi Analiz” kısmına derinlik katmak ve tarihsel gerçeğe sadık kalmak amacıyla eklenmiştir. Tahmin Modellerimiz (Prediction Models) ise veri tutarlılığını sağlamak adına sadece detaylı verisine sahip olduğumuz “Yolcular” üzerinden eğitilecektir.
# R'ın dahili "Titanic" veri setini yükleyip dataframe'e çevirelim
data("Titanic")
df_full <- as.data.frame(Titanic)
# Veri setini Türkçeleştirelim ve Düzenleyelim
df_full <- df_full %>%
mutate(
Class = case_when(
Class == "1st" ~ "1. Sınıf",
Class == "2nd" ~ "2. Sınıf",
Class == "3rd" ~ "3. Sınıf",
Class == "Crew" ~ "Mürettebat" # Eklenen Kısım
),
Sex = case_when(
Sex == "Male" ~ "Erkek",
Sex == "Female" ~ "Kadın"
),
Survived = case_when(
Survived == "No" ~ "Öldü",
Survived == "Yes" ~ "Kurtuldu"
)
)
# Sankey Çizimi (Freq ağırlığını kullanarak)
ggplot(df_full,
aes(y = Freq, axis1 = Class, axis2 = Sex, axis3 = Survived)) +
geom_alluvium(aes(fill = Survived), width = 1/12, alpha = 0.8) +
geom_stratum(width = 1/12, fill = "grey90", color = "black") +
geom_label(stat = "stratum", aes(label = after_stat(stratum)), size = 3) +
scale_fill_manual(values = c("Öldü" = "#E74C3C", "Kurtuldu" = "#2ECC71")) +
labs(title = "Titanik Tam Resim: Mürettebat Dahil Akış Diyagramı",
subtitle = "Mürettebatın (Crew) kaderi ve cinsiyet dağılımı",
y = "Kişi Sayısı",
fill = "Sonuç") +
theme_minimal() +
theme(legend.position = "bottom",
axis.text.y = element_blank(),
axis.ticks = element_blank(),
panel.grid = element_blank())
🧐 Grafik Analizi: “Görünmeyen” Kahramanlar ve İstatistiksel Gerçekler
Bu Sankey diyagramı, standart veri setlerinde genellikle göz ardı edilen Mürettebatı (Crew) analize dahil ettiğimizde, facianın boyutunun nasıl değiştiğini kanıtlamaktadır. Akışları soldan sağa (Sınıf -> Cinsiyet -> Sonuç) takip ettiğimizde şu kritik çıkarımları yapıyoruz:
Grafiğin en sol alt köşesindeki “Mürettebat” bloğuna dikkat ediniz.
-Hacim: Mürettebat, neredeyse 3. Sınıf yolcular kadar büyük bir kitleyi oluşturmaktadır.
-Cinsiyet Dağılımı: Mürettebat bloğundan çıkan akışın (gri şerit) neredeyse tamamı “Erkek” sütununa gitmektedir. Gemide çalışan kadın sayısı (hostesler, temizlikçiler) istatistiksel olarak ihmal edilebilir düzeydedir.
-Sonuç: “Erkek” sütunundan çıkan en kalın kırmızı (Öldü) akışın kaynağı büyük oranda mürettebattır. Gemi batarken makine dairesinde elektriği açık tutmaya çalışanlar, kazan dairesindekiler ve yolcuları tahliye eden güverte görevlileri, gemiyi en son terk edenler (veya terk edemeyenler) olmuştur.
Grafiğin tam ortasındaki sütun, hayatta kalma denklemindeki en büyük belirleyicidir.
-Kadın Bloğu (Güvenli Liman): Buraya giren akışların (1., 2., 3. Sınıf ve az sayıda Mürettebat) çok büyük bir kısmı sağ tarafa Yeşil (Kurtuldu) olarak geçmektedir.
-Erkek Bloğu (Ölüm Hunisi): Burası adeta bir darboğazdır. Buraya giren akışın (özellikle Mürettebat ve 3. Sınıftan gelenler) çok büyük bir kısmı Kırmızı (Öldü) olarak sonuçlanmaktadır.
Sol sütundaki blokların “Yeşil” (Kurtulma) oranlarına baktığımızda net bir sosyo-ekonomik sıralama görüyoruz:
-1. Sınıf: Grafiğin en üstünde. Yeşil akışın oransal olarak en yoğun olduğu grup.
-2. Sınıf: Geçiş formu, ancak 1. sınıfa kıyasla kırmızı akış artmaya başlıyor.
-3. Sınıf: Hem kadınlarda hem erkeklerde ölüm oranı (kırmızı akış) belirgin şekilde artıyor.
-Mürettebat: En alt tabaka. Oransal olarak en yüksek ölüm riskine sahip grup.
Grafiğe uzaktan baktığınızda, sağ alt köşedeki devasa kırmızı alan göze çarpar. Bu alan; Mürettebat ve 3. Sınıf Erkeklerinin toplamıdır. Bu grafik bize şunu anlatır: Titanik’te hayatta kalmak büyük oranda “Kadın olmak” ve “Üst sınıfa mensup olmak” ile ilişkiliyken; ölmek ise “Erkek olmak” ve “Gemi çalışanı/Alt sınıf olmak” ile ilişkilidir.
Bu bölümde, ana veri setimize (df) dönerek yolcuların kesin yaş dağılımlarını inceliyoruz. Yaşın hayatta kalma üzerindeki etkisi sınıflara göre değişiyor mu?
# Görselleştirme için faktör dönüşümleri
df$Survived_Cat <- factor(df$Survived, labels = c("Öldü", "Kurtuldu"))
df$Pclass_Cat <- factor(df$Pclass, labels = c("1. Sınıf", "2. Sınıf", "3. Sınıf"))
# Density Plot (Yoğunluk Grafiği)
ggplot(df, aes(x = Age, fill = Survived_Cat)) +
geom_density(alpha = 0.5) +
facet_wrap(~Pclass_Cat) +
labs(title = "Sınıflara Göre Yaş Dağılımı ve Hayatta Kalma İlişkisi",
subtitle = "Sınıflar arası yaş profili ve kaderin değişimi",
x = "Yaş",
y = "Yoğunluk (Density)",
fill = "Sonuç") +
scale_fill_manual(values = c("Öldü" = "#E74C3C", "Kurtuldu" = "#2ECC71")) +
theme_bw() +
theme(legend.position = "top")
🧐 Grafik Analizi: Yaş ve Sınıfın Ölümcül Dansı
Bu “Density Plot” (Yoğunluk Grafiği), sadece yaşın değil, yaşın sınıf (sosyo-ekonomik statü) ile girdiği etkileşimin hayatta kalma üzerindeki belirleyiciliğini gösterir. Panelleri soldan sağa (1. Sınıftan 3. Sınıfa) incelediğimizde dramatik bir değişim görüyoruz:
-Yaşlı Nüfusun Güvenliği: 1. Sınıf grafiği (en sol), diğerlerine göre daha sağa yatıktır, yani yolcu profili daha yaşlıdır.
-Yeşil Alanın Hakimiyeti: 30-50 yaş aralığında yeşil alanın (Kurtuldu), kırmızı alanın (Öldü) üzerinde olduğu veya başa baş gittiği görülmektedir.
-Kritik Çıkarım: 1. Sınıftaysanız, yaşınız ilerlemiş olsa bile (50-60 yaş bandına dikkat edin) kurtulma şansınız yüksektir. Burada para ve statü, biyolojik dezavantajları (yaşlılık) tolere etmiştir.
-0-10 Yaş Arasındaki Yeşil Zirve: Grafiğin en solundaki küçük tepeciğe dikkat edin. Yeşil alan tavan yaparken, altında kırmızı alan neredeyse yoktur. Bu, istatistiksel olarak “2. Sınıftaki bir çocuğun hayatta kalması neredeyse garantidir” demektir.
-Yetişkinlerin Kaderi: 20 yaşından sonra grafik aniden kırmızıya döner. Kırmızı tepe (Ölüm), yeşili yutar. Bu durum, 2. Sınıf erkeklerinin çocukları ve eşleri için kendilerini feda ettiklerini veya filikalara alınmadıklarını gösterir.
-Kritik Çıkarım: “Kadınlar ve Çocuklar Önce” kuralının en disiplinli uygulandığı sınıf burasıdır.
-Sarp ve Kırmızı Bir Zirve: En sağdaki grafikte 20-25 yaş aralığında göğe yükselen devasa bir kırmızı tepe görüyoruz. Kim Bu İnsanlar?: Bunlar, Amerika’ya yeni bir hayat kurmaya giden genç göçmenlerdir. Fiziksel olarak en güçlü ve dayanıklı yaş grubunda (20’li yaşlar) olmalarına rağmen, geminin en alt katlarında kapana kısılmışlardır.
-Çocukların Durumu: 2. Sınıfın aksine, burada 0-10 yaş aralığında kırmızı alan (Ölüm) da belirgindir. Yani 3. Sınıftaysanız, çocuk olmanız bile kurtulmanız için yeterli olmamıştır.
-Kritik Çıkarım: Veri setindeki en büyük can kaybı, 3. Sınıf genç yetişkin grubundadır. Modelimiz bu yaş grubunu ve sınıfı gördüğünde “Ölüm” tahminine ağırlık verecektir.
💡 Makine Öğrenmesi İçin İpuçları (Model Hazırlığı)
Bu grafik bize Feature Engineering (Öznitelik Mühendisliği) aşaması için 2 kritik tüyo veriyor:
Etkileşim Terimi (Interaction Term): Yaş değişkeni tek başına lineer değildir. Age tek başına kullanıldığında yanıltıcı olabilir; ancak Age * Pclass etkileşimi modelin başarısını artıracaktır.
Binning (Gruplama) Stratejisi: Yaşı sürekli değişken olarak bırakmak yerine, grafikteki kırılımlara göre gruplamak mantıklı olabilir:
-0-10: Çocuk (Hayatta kalma yüksek)
-20-35: Genç Yetişkin (3. Sınıfta risk çok yüksek)
-60+: Yaşlı (Riskli)
Bu tür bir AgeGroup değişkeni, özellikle Karar Ağaçları ve Random Forest için işi kolaylaştıracaktır.
Aile büyüklüğü ve Yalnız seyahat etme durumu. Yeni bir değişken türetelim: FamilySize.
# Aile Büyüklüğü: Kardeş/Eş + Ebeveyn/Çocuk + Kendisi
df$FamilySize <- df$SibSp + df$Parch + 1
# Kategorik gruplama
df$FamilyGroup <- cut(df$FamilySize,
breaks = c(0, 1, 4, 20),
labels = c("Yalnız", "Küçük Aile", "Büyük Aile"))
# Bar Plot
ggplot(df, aes(x = FamilyGroup, fill = Survived_Cat)) +
geom_bar(position = "fill") +
labs(title = "Aile Büyüklüğünün Hayatta Kalma Oranına Etkisi",
y = "Oran", x = "Aile Tipi") +
scale_y_continuous(labels = scales::percent) +
theme_minimal()
🧐 Grafik Analizi: Aile Büyüklüğü - “Yalnızlık Riskli, Kalabalık Ölümcül”
Bu grafik, yolcunun gemide yalnız mı yoksa ailesiyle mi olduğu sorusunun, yaşam ile ölüm arasındaki ince çizgiyi nasıl belirlediğini göstermektedir. İlişki, ters “U” şeklindedir:
-Durum: Grafiğin sol sütununda, yalnız seyahat edenlerin (SibSp + Parch = 0) hayatta kalma oranının %30 civarında (Turkuaz alan) kaldığını görüyoruz.
Neden Düşük?
-Demografik Faktör: Yalnız seyahat edenlerin çok büyük bir kısmı, iş bulmak için Amerika’ya giden 3. Sınıf genç erkeklerdi. “Önce Kadınlar ve Çocuklar” kuralı gereği filikalara en son onlar alındı.
-Psikolojik/Lojistik Faktör: Kriz anında onları uyandıracak, uyaracak veya filikaya binmeleri için ısrar edecek bir yakınları yoktu. Kalabalıkta tek başına hareket etmek zorunda kaldılar.
-Durum: Grafiğin ortasında, 2-4 kişilik ailelerin (Küçük Aile) hayatta kalma oranının %50’nin üzerine çıktığını görüyoruz. Bu, veri setindeki en güvenli gruptur.
Neden Yüksek?
-Koordinasyon: 2-3 kişilik bir grubu (örneğin Anne-Baba-Çocuk veya iki kardeş) kalabalıkta bir arada tutmak ve filikaya yönlendirmek yönetilebilir bir durumdur.
-Yardımlaşma: Birbirlerini uyandırdılar, can yeleklerini takmalarına yardım ettiler ve duygusal destek sağladılar.
-Ayrılmama İsteği: Görevliler genellikle küçük grupları (özellikle anne ve çocuğu) ayırmadan filikaya bindirmeye daha meyilliydi.
Durum: En sağdaki sütun, projenin en hüzünlü istatistiklerinden biridir. 5 ve üzeri kişiden oluşan geniş ailelerde hayatta kalma oranı %20’nin altına düşmektedir. Neredeyse tamamı ölmüştür.
Neden Çok Düşük?
-Lojistik İmkansızlık: Kaosun, çığlıkların ve karanlığın ortasında 7-8 çocuğu olan bir aileyi (Örn: Goodwin ailesi veya Sage ailesi) düşünün. Çocuklardan biri kaybolduğunda, anne-baba onu aramak için geride kalır. Aile bütünlüğünü korumaya çalışmak, tüm ailenin gemide kalmasına ve ölmesine neden olmuştur.
-Sınıf Etkisi: Dönemin sosyolojisi gereği, bu kadar kalabalık aileler genellikle 3. Sınıf yolculardı ve geminin en alt katlarındaydılar. Yukarı çıkmaları fiziksel olarak da çok zordu.
💡 Makine Öğrenmesi İçin Kritik Çıkarım (Feature Engineering)
Bu grafik bize modelleme aşaması için hayati bir ipucu veriyor:
Ham Veri vs. İşlenmiş Veri: Eğer modele sadece SibSp (Kardeş/Eş Sayısı) ve Parch (Ebeveyn/Çocuk Sayısı) sayısal değerlerini verirsek, model bu “Ters U” ilişkisini yakalamakta zorlanabilir (Özellikle Lojistik Regresyon gibi doğrusal modeller).
Doğru Hamle: Grafikteki gibi FamilySize değişkenini kategorik hale getirmek (Yalnız, Küçük, Büyük olarak 3 gruba ayırmak), modelin performansını ciddi şekilde artıracaktır. Çünkü “Büyük Aile” olmak ile “Yalnız” olmak farklı dinamiklerdir, ancak ikisi de risklidir. Modelin bu ayrımı “Gruplama” (Binning) sayesinde daha net görmesini sağlarız.
Hangi değişkenler birbirine benziyor? Sayısal dönüşüm yaparak inceleyelim.
ÖNEMLİ NOT: ⚠️ İstatistiksel Not: Değişken Türleri ve Korelasyon Yorumu
Bu analizde, değişkenler arasındaki ilişkilerin genel yönünü ve şiddetini hızlıca taramak amacıyla standart Pearson Korelasyon Katsayısı kullanılmıştır.
Analiz edilirken dikkat edilmelidir ki; Pclass (1, 2, 3) gibi değişkenler matematiksel olarak sürekli (continuous) değil, sıralı (ordinal) kategorik değişkenlerdir. Bu nedenle matriste görülen katsayılar (Örn: Pclass ile Fare arasındaki -0.55), kesin bir matematiksel doğrusallıktan ziyade, ilişkinin yönünü ve genel eğilimini (Sınıf numarası sayısal olarak arttıkça/kalite düştükçe, bilet fiyatının düşmesi eğilimi) göstermektedir. Kesin istatistiksel çıkarımlar için bu matris, önceki bölümlerdeki görselleştirmelerimizle (Density Plot, Box Plot) birlikte değerlendirilmelidir.
# Sadece sayısal ve dönüştürülebilir değişkenleri seçelim
cor_data <- df %>%
select(Survived, Pclass, Age, SibSp, Parch, Fare, FamilySize)
# Korelasyon matrisi
M <- cor(cor_data, use = "complete.obs")
# Çizim
corrplot(M, method = "color", type = "upper",
addCoef.col = "black", tl.col = "black",
title = "Değişkenler Arası Korelasyon", mar=c(0,0,1,0))
🧐 Grafik Analizi: İlişkiler Ağı ve “Multicollinearity” Riski
Bu matriste değerler -1 (Mükemmel Negatif İlişki) ile +1 (Mükemmel Pozitif İlişki) arasında değişir. 0 ise ilişki olmadığını gösterir. Analizi üç ana başlıkta toplayabiliriz:
Modelin tahmin etmeye çalışacağı Survived satırına (en üst satır) odaklanalım:
-Survived vs Pclass (-0.34): Matristeki hedef değişkenle en güçlü negatif ilişki budur. Sınıf numarası arttıkça (1 -> 2 -> 3), hayatta kalma ihtimali düşmektedir. Bu, önceki grafiklerimizi (Sankey ve Bar Plot) sayısal olarak doğrular.
-Survived vs Fare (+0.26): Bilet ücreti arttıkça hayatta kalma ihtimali artmaktadır. Bu da Pclass ile doğrudan bağlantılıdır.
-Survived vs Age (-0.08): Burası çok önemli bir tuzaktır! İlişki neredeyse 0 (yok) gibi görünüyor.
-Soru: Yaşın hayatta kalmaya etkisi yok mu?
-Cevap: Kesinlikle var! (Önceki grafiklerde çocukların kurtulduğunu, yaşlıların öldüğünü gördük). Ancak korelasyon katsayısı sadece doğrusal (düz çizgi) ilişkileri ölçer. Yaş ile hayatta kalma arasındaki ilişki doğrusal değildir (Çocuklar kurtulur, gençler ölür, yaşlılar kurtulur gibi dalgalı bir yapı). Bu yüzden korelasyon düşük çıkar. Bu durum, modele Age değişkenini ham haliyle vermektense, gruplayarak vermemiz gerektiğini bir kez daha hatırlatır.
Değişkenlerin kendi aralarındaki ilişki çok yüksekse, bu durum bazı modellerde (özellikle Lojistik Regresyon) kararsızlığa yol açar.
-Fare vs Pclass (-0.55): Güçlü bir negatif ilişki. Mantıken sınıf düştükçe (sayısal olarak 1’den 3’e çıktıkça), bilet fiyatı düşer. Bu iki değişken aslında birbirinin aynadaki yansıması gibidir (Proxy variables).
-Strateji: Ağaç tabanlı modeller (Random Forest, XGBoost) bu durumu dert etmez. Ancak Lojistik Regresyon kullanırken bu iki değişkenden birini çıkarmak modelin varyansını düşürebilir.
-Age vs Pclass (-0.35): Negatif ilişki. Sınıf numarası arttıkça (3. Sınıfa gittikçe) yaş ortalaması düşüyor. 1. Sınıf yolcuları daha yaşlı ve zengin, 3. Sınıf yolcuları daha genç ve göçmendir.
Grafiğin sağ alt köşesindeki koyu mavi kümeye dikkat edin.
-FamilySize vs SibSp (0.89) ve Parch (0.78): Neredeyse mükemmel bir korelasyon var.
-Neden? Çünkü FamilySize zaten SibSp ve Parch toplanarak oluşturuldu.
-Kritik Karar: Model eğitimine bu üç değişkeni aynı anda sokmak gereksiz bilgi tekrarıdır (Redundancy). Bu durum modelin kafasını karıştırabilir ve işlem yükünü artırır.
-Çözüm: Modeli kurarken SibSp ve Parch sütunlarını çıkarıp, sadece türettiğimiz FamilySize (veya FamilyGroup) değişkenini kullanmak en temiz yöntemdir.
📝 Özet ve Aksiyon Planı
Bu grafik sonucunda modelleme aşaması için şu kararları alıyoruz:
-Elemek: SibSp ve Parch yüksek korelasyon nedeniyle modelden çıkarılacak, yerlerine FamilySize kullanılacak. Dönüştürmek: Age değişkeni düşük lineer korelasyona sahip olduğu için, doğrusal olmayan etkisini yakalamak adına kategorik hale (Child, Adult vb.) getirilecek.
-Farkında Olmak: Fare ve Pclass birbirine çok benziyor. Bazı modellerde Fare değişkenini logaritmik dönüşüme sokarak (LogFare) bu ilişkiyi yumuşatabiliriz.
Analizlerimiz sonucunda veri setini makine öğrenmesi modelleri için en uygun hale getirecek son işlemleri yapıyoruz.
Yapılacak İşlemler:
- Gereksiz Sütunların Temizliği: PassengerId, Name, Ticket modelde kullanılmayacak.
-Multicollinearity Önlemi: SibSp ve Parch değişkenleri FamilySize içinde temsil edildiği için çıkarılacak.
-Yeni Değişkenler: EDA sırasında karar verdiğimiz HasCabin (Kabin Var mı?) ve IsAlone (Yalnız mı?) değişkenleri eklenecek.
-Encoding (Kodlama): Kategorik değişkenler (Sex, Embarked, Title, Pclass) sayısal matrislere (One-Hot Encoding) çevrilecek.
-⚠️ Önemli Not (Scaling Stratejisi): Age ve Fare gibi sayısal değişkenlerin ölçeklendirilmesi (Standardization) işlemi, Veri Sızıntısını (Data Leakage) önlemek amacıyla bu aşamada yapılmayacaktır. Bu işlem, eğitim ve test verisi ayrıldıktan sonra, model eğitimi sırasında (cross-validation döngüsü içinde) gerçekleştirilecektir.
library(caret)
# 1. Veri Kopyalama ve Temel İşlemler
df_model <- df
df_model$HasCabin <- ifelse(is.na(df_model$Cabin) | df_model$Cabin == "", 0, 1)
df_model$Title <- as.factor(df_model$Title)
df_model$FamilySize <- df_model$SibSp + df_model$Parch + 1
df_model$IsAlone <- ifelse(df_model$FamilySize == 1, 1, 0)
df_model$Pclass <- as.factor(df_model$Pclass)
df_model$Sex <- as.factor(df_model$Sex)
df_model$Embarked <- as.factor(df_model$Embarked)
# 2. Gereksiz ve Sızıntı Yapan Sütunları Silme
df_final <- df_model %>%
select(-PassengerId, -Name, -Ticket, -Cabin, -SibSp, -Parch,
-Survived_Cat, -Pclass_Cat, -FamilyGroup)
# 3. One-Hot Encoding (Algoritmaların ortak dili için şart)
dummies <- dummyVars(Survived ~ ., data = df_final)
df_encoded <- predict(dummies, newdata = df_final)
df_encoded <- as.data.frame(df_encoded)
df_encoded$Survived <- as.factor(df_final$Survived)
# DİKKAT: Scaling (Ölçeklendirme) işlemini burada YAPMIYORUZ.
# Data Leakage'ı önlemek için bunu model eğitim aşamasında (train fonksiyonu içinde) yapacağız.
# Sütun isimlerini temizle
colnames(df_encoded) <- make.names(colnames(df_encoded))
levels(df_encoded$Survived) <- c("Died", "Survived")
# İşlenmiş veriyi atayalım
df_processed <- df_encoded
🧐 Kodun ve Değişikliklerin Yorumu
Bu blokta yaptığımız kritik hamleler şunlardır:
-Neden SibSp ve Parch çıkarıldı?
Korelasyon matrisinde gördük ki bu iki değişken FamilySize ile %89 oranında ilişkili. Bunları tutmak modelde “gürültü” ve “çoklu bağlantı” yaratacaktı. FamilySize ve IsAlone (Yalnızlık etkisi) daha temiz bilgilerdir.
-Neden HasCabin eklendi?
EDA’da Cabin sütununun %77 oranında boş olduğunu gördük. Ancak kabin bilgisinin olması, yolcunun üst sınıf veya özel bir statüde olduğunu gösterebilir. Bu bilgiyi çöpe atmak yerine “Var/Yok” (1/0) sinyaline dönüştürdük.
-Neden One-Hot Encoding (Dummy Vars)?
Title sütununda “Mr”, “Mrs”, “Master” gibi değerler var. Bilgisayar metin anlamaz. Bunu şu hale getirdik: Title.Mr (1/0), Title.Mrs (1/0).
Özellikle Lojistik Regresyon ve k-NN algoritmaları bu matematiksel matrise muhtaçtır.
Artık verimiz, listedeki 8 algoritmanın tamamı tarafından (CatBoost dahil) sorunsuz işlenebilecek evrensel bir formata (df_processed) gelmiştir.
Verimiz analize hazır. Şimdi makine öğrenmesi sürecini başlatıyoruz. Ancak modellere geçmeden önce adil bir karşılaştırma ortamı (Arena) kurmalıyız.
Modelin başarısını, hiç görmediği veriler üzerindeki performansıyla ölçmeliyiz.
Neden Yapıyoruz?
-Train/Test: Eğer tüm veriyle eğitim yaparsak, model soruları ezberler (Overfitting). Sınavda (Test setinde) çakılır.
-10-Fold Cross Validation: Eğitim setini de kendi içinde 10 parçaya böleriz. Model 9 parçayla çalışır, 1 parçayla kendini dener. Bunu 10 kez tekrar eder. Bu sayede “Şans eseri iyi sonuç alma” riskini ortadan kaldırırız.
# Tekrarlanabilirlik için tohum (seed) belirleme
set.seed(123)
# --- KRİTİK DÜZELTME 1: Sınıf İsimleri ---
# Caret paketi, ROC hesabı için sınıf isimlerinin harfle başlamasını ister.
levels(df_processed$Survived) <- c("Died", "Survived")
# --- KRİTİK DÜZELTME 2: Sütun İsimleri (BU HATAYI ÇÖZER) ---
# rpart, xgboost gibi algoritmalar değişken isimlerinde özel karakter sevmez.
# make.names fonksiyonu tüm sütun isimlerini güvenli hale getirir.
colnames(df_processed) <- make.names(colnames(df_processed))
# -----------------------------------------------------------
# 1. Eğitim (%80) ve Test (%20) Ayrımı
train_index <- createDataPartition(df_processed$Survived, p = 0.8, list = FALSE)
train_set <- df_processed[train_index, ]
test_set <- df_processed[-train_index, ]
# 2. Çapraz Doğrulama (Cross-Validation) Ayarı
fit_control <- trainControl(method = "cv",
number = 10,
classProbs = TRUE,
summaryFunction = twoClassSummary)
# Kontrol
print("Sütun İsimleri Temizlendi:")
## [1] "Sütun İsimleri Temizlendi:"
print(head(colnames(train_set)))
## [1] "Pclass.1" "Pclass.2" "Pclass.3" "Sex.female" "Sex.male"
## [6] "Age"
Sınıflandırma problemlerinin “Merhaba Dünya”sıdır. Sonuçları olasılık (0 ile 1 arası) olarak verir.
-Tuning: Lojistik regresyon genellikle hiperparametre ayarı gerektirmez (Basit versiyonu). Doğrudan veriye en uygun “S” eğrisini çizer.
-Amaç: Bu model bizim “Taban Puanımız” (Baseline) olacak. Diğer karmaşık algoritmaların (XGBoost vb.) buna değip değmediğini bununla kıyaslayacağız.
set.seed(123)
# Lojistik Regresyon Eğitimi
model_logreg <- train(Survived ~ .,
data = train_set,
method = "glm",
family = "binomial",
trControl = fit_control,
preProcess = c("center", "scale"), # <-- YENİ EKLENEN KISIM (Otomatik Scaling)
metric = "ROC")
# Sonuçları Yazdır
print(model_logreg)
## Generalized Linear Model
##
## 714 samples
## 18 predictor
## 2 classes: 'Died', 'Survived'
##
## Pre-processing: centered (18), scaled (18)
## Resampling: Cross-Validated (10 fold)
## Summary of sample sizes: 643, 643, 642, 643, 643, 643, ...
## Resampling results:
##
## ROC Sens Spec
## 0.8734367 0.8772727 0.7705026
“Bana arkadaşını söyle, sana kim olduğunu söyleyeyim” mantığıyla çalışır. Yeni bir yolcunun hayatta kalıp kalmadığını tahmin etmek için, özellik uzayında ona en yakın k adet yolcuya bakar.
Tuning (Neden Gereklidir?):
k değeri: k çok küçükse (örn: 1), model gürültüye çok duyarlı olur (Overfitting).
k çok büyükse (örn: 100), model genelleme yapar ve detayları kaçırır (Underfitting).
Biz caret kütüphanesine “5 ile 25 arasındaki tek sayıları dene ve en iyisini bul” diyeceğiz.
set.seed(123)
# Tuning Grid (Denenecek Parametreler)
knn_grid <- expand.grid(k = seq(5, 25, by = 2))
# k-NN Eğitimi
model_knn <- train(Survived ~ .,
data = train_set,
method = "knn",
trControl = fit_control,
tuneGrid = knn_grid,
preProcess = c("center", "scale"), # <-- YENİ EKLENEN KISIM
metric = "ROC")
# En iyi k değeri hangisi çıktı?
plot(model_knn)
print(paste("En iyi k değeri:", model_knn$bestTune$k))
## [1] "En iyi k değeri: 21"
🧐 Grafik Analizi: Komşu Sayısı ve Model Kararlılığı (Revize) Bu grafik, sızıntı yapan değişkenlerin temizlenmesinden sonra modelin gerçek performansını göstermektedir. X ekseninde denenen k değerleri (Komşu Sayısı), Y ekseninde ise modelin başarısı (ROC) yer alır.
1. Genel Trend: Yükseliş ve Zirve
Grafik, k=5 değerinden başlayarak kademeli bir yükseliş trendi izlemektedir.
-Düşük k (5-9): Modelin başarısı 0.85 seviyelerinde başlıyor. Az sayıda komşuya bakmak, modelin gürültüye (noise) takılmasına ve kararsız kalmasına neden oluyor (Yüksek Varyans).
*-Yükseliş: Komşu sayısı arttıkça, model “Kalabalığın Bilgeliğinden” faydalanıyor ve genelleme yeteneği artıyor.
2. Zirve Noktası: k=21 Grafik en yüksek noktasına (ROC ≈ 0.871) k=21 değerinde ulaşıyor.
-Karar: caret paketi, modelimiz için en ideal komşu sayısını 21 olarak belirlemiştir.
-Anlamı: Bir yolcunun kaderini tahmin etmek için ona en çok benzeyen 21 kişiye bakmak, en doğru sonucu vermektedir. Bu sayı, Titanik gibi karmaşık sosyal grupların olduğu bir veri seti için makul bir kalabalıktır.
3. Kritik Düşüş: Aşırı Düzleştirme (Oversmoothing)
Zirve noktası olan k=21’den sonra (k=23 ve 25 ’te) grafiğin sert bir şekilde aşağı düştüğünü görüyoruz.
-Neden? Eğer çok fazla komşuya (25 ve üzeri) bakmaya başlarsanız, model farklı sınıflardaki insanları (örneğin 1. Sınıf ile 3. Sınıfı) aynı kefeye koymaya başlar. Sınırlar bulanıklaşır ve detaylar kaybolur. Buna “Aşırı Düzleştirme” denir. Modelin k=21’de durması bu yüzden kritiktir.
⚠️ Veri Sızıntısı Kontrolü (Başarılı) Önceki denemelerimizde (hatalı veri setiyle) ROC skorları 0.98 gibi gerçekdışı seviyelerdeydi.
-Mevcut Durum: Şu anki 0.87’lik ROC skoru, sızıntının (Leakage) başarıyla temizlendiğini kanıtlar.
-Yorum: Artık modelimiz “kopya çekmiyor”, gerçekten yaş, cinsiyet ve sınıf gibi değişkenleri yorumlayarak tahmin yapıyor. 0.87, Titanik literatüründe oldukça rekabetçi ve güvenilir bir skordur.
Karar ağaçları, veriyi “Evet/Hayır” sorularıyla böler. İnsan mantığına en yakın algoritmadır.
-Tuning: cp (Complexity Parameter - Karmaşıklık Parametresi). Ağacın ne kadar derinleşeceğini ve dallanacağını kontrol eder. Çok dallanırsa ezberler, az dallanırsa öğrenemez.
-Görsellik: Oluşan ağacı çizdireceğiz. Bu sayede modelin hangi kuralları (Örn: “Erkek mi? Evet -> Öldü”) koyduğunu göreceğiz.
library(rpart.plot) # Ağacı çizdirmek için
set.seed(123)
# Karar Ağacı Eğitimi (rpart)
model_cart <- train(Survived ~ .,
data = train_set,
method = "rpart",
trControl = fit_control,
tuneLength = 10, # 10 farklı cp değeri dene
preProcess = c("center", "scale"), # <-- YENİ EKLENEN KISIM
metric = "ROC")
# Sonuçlar
print(model_cart)
## CART
##
## 714 samples
## 18 predictor
## 2 classes: 'Died', 'Survived'
##
## Pre-processing: centered (18), scaled (18)
## Resampling: Cross-Validated (10 fold)
## Summary of sample sizes: 643, 643, 642, 643, 643, 643, ...
## Resampling results across tuning parameters:
##
## cp ROC Sens Spec
## 0.00000000 0.8569685 0.8772727 0.7480159
## 0.04987835 0.7811403 0.8250000 0.7335979
## 0.09975669 0.7722222 0.8000000 0.7444444
## 0.14963504 0.7722222 0.8000000 0.7444444
## 0.19951338 0.7722222 0.8000000 0.7444444
## 0.24939173 0.7722222 0.8000000 0.7444444
## 0.29927007 0.7722222 0.8000000 0.7444444
## 0.34914842 0.7722222 0.8000000 0.7444444
## 0.39902676 0.7722222 0.8000000 0.7444444
## 0.44890511 0.7082973 0.8431818 0.5734127
##
## ROC was used to select the optimal model using the largest value.
## The final value used for the model was cp = 0.
# Ağacın Görselleştirilmesi
# Bu grafik ders notunun en değerli parçalarından biridir.
rpart.plot(model_cart$finalModel,
box.palette = "RdBu", # Kırmızı: Öldü, Mavi: Kurtuldu
shadow.col = "gray",
nn = TRUE,
main = "Titanik Karar Ağacı: Kimler Yaşıyor?")
🧐 Grafik Yorumu: Karar Ağacının Mantık Haritası
Bu grafik, modelin Titanik yolcularını sınıflandırmak için kendi kendine oluşturduğu kurallar bütünüdür. Ağaç, tepeden başlar ve her düğümde bir “Evet/Hayır” sorusu sorarak yolcuları ayrıştırır.
Grafiği okurken şu 3 temel aşamaya dikkat etmeliyiz:
1. Kök Düğüm (En Tepe): “Varsayılan Durum”
Ağacın en tepesindeki kutuda “Died” (Öldü) yazmaktadır.
-Bu ne anlama gelir? Bu, modelin henüz hiçbir soru sormadan önceki tahminidir. Titanik’teki yolcuların %62’si hayatını kaybettiği için, rastgele seçilen bir yolcunun ölme ihtimali istatistiksel olarak daha yüksektir.
-Sayılar: Kutunun içindeki 0.38, hayatta kalma (Survived) olasılığıdır. 100% ise tüm verinin henüz burada olduğunu gösterir.
2. İlk ve En Önemli Ayrım: “Erkek misin?”
Ağacın ilk dallanma noktası Title.Mr >= -0.16 sorusudur.
-Veri Bilimi Çıkarımı: Model, yüzlerce değişken kombinasyonunu denemiş ve bilgi kazancı (Information Gain) en yüksek olan sorunun bu olduğuna karar vermiştir.
-Sol Dal (Evet - Mr): Eğer kişi yetişkin bir erkekse (Title = Mr), hayatta kalma olasılığı anında %15’e (0.15) düşmektedir. Model bu grubu “Yüksek Riskli” olarak işaretler.
-Sağ Dal (Hayır - Kadın/Çocuk/Diğer): Eğer kişi “Mr” değilse (yani Kadın, Çocuk, Doktor vb. ise), sağ tarafa geçer. Burada hayatta kalma olasılığı %71’e (0.71) fırlar. Etiket “Survived”a döner.
3. Derinlemesine Analiz ve İnce Nüanslar
A. Sol Kanat (Erkeklerin Kaderi): Para ve Kabin
Erkekler grubunda (Sol taraf), modelin ikinci sorusu Fare (Bilet Ücreti) üzerinedir.
Model, bilet ücreti çok düşük olanları (Fakir erkekler) en sola atar ve bu kutuda hayatta kalma şansı neredeyse sıfırdır. Biraz daha aşağıda HasCabin (Kabin Bilgisi) değişkenini görürüz. Bu, EDA aşamasında eksik verilerden türettiğimiz değişkenin işe yaradığını kanıtlar. Kabini belli olan (muhtemelen üst sınıf) erkeklerin şansı bir miktar artmaktadır.
B. Sağ Kanat (Kadın ve Çocukların Kaderi): Sınıf ve Aile
Kadın ve çocuk grubunda (Sağ taraf), modelin sorduğu kritik soru Pclass.3 (3. Sınıf mı?) sorusudur.
-Sınıf Etkisi: Eğer kadın/çocuksanız ama 3. Sınıfta seyahat ediyorsanız (grafikte yes yolu), hayatta kalma şansınız %49’a düşer.
-Aile Etkisi: 3. Sınıf kadın/çocuk grubunda FamilySize (Aile Büyüklüğü) devreye girer. Geniş ailelerin (Sağdaki FamilySize >= 1.6 ayrımı) hayatta kalma şansı düşüktür.
-En Güvenli Grup: Eğer kadın/çocuksanız ve 3. Sınıf değilseniz (yani 1. veya 2. Sınıfsanız), grafiğin en sağındaki kutuya ulaşırsınız. Burada hayatta kalma oranı %88 ile %95 arasındadır.
📝 Sonuç
Karar ağacı bize Titanik faciasının hiyerarşisini görselleştirmiştir:
Önce Cinsiyet (Kadınlar ve Çocuklar),
Sonra Sosyo-Ekonomik Statü (Sınıf ve Ücret),
Son olarak Aile Yapısı (Geniş ailelerin dezavantajı).
Bu yapı, EDA bölümünde yaptığımız görsel analizlerin makine öğrenmesi modeli tarafından da doğrulandığını göstermektedir.
Tek bir ağaç hata yapabilir, ama 500 ağaçtan oluşan bir “Orman” (Demokrasi) daha az hata yapar. Random Forest, Bagging (Bootstrap Aggregating) mantığını kullanır.
Mantık: Veriden rastgele örnekler alarak yüzlerce farklı ağaç kurar. Ayrıca her düğümde değişkenlerin sadece bir kısmını (mtry) görerek ağaçların birbirine benzemesini engeller (Korelasyonu düşürür). Sonuçta çoğunluğun oyuna (Voting) göre karar verir.
Tuning:
set.seed(123)
# Random Forest için Tuning Grid
# mtry: Her ağaç dalında kaç değişkene bakılsın?
# Genelde karekök(toplam değişken) civarı denenir.
rf_grid <- expand.grid(mtry = c(2, 5, 7, 10, 15))
# Random Forest Eğitimi
# Not: Veri setimiz küçük olduğu için ntree=500 (varsayılan) yeterlidir.
model_rf <- train(Survived ~ .,
data = train_set,
method = "rf",
trControl = fit_control,
tuneGrid = rf_grid,
metric = "ROC",
preProcess = c("center", "scale"), # <-- YENİ EKLENEN KISIM
verbose = FALSE)
# Sonuçlar
print(model_rf)
## Random Forest
##
## 714 samples
## 18 predictor
## 2 classes: 'Died', 'Survived'
##
## Pre-processing: centered (18), scaled (18)
## Resampling: Cross-Validated (10 fold)
## Summary of sample sizes: 643, 643, 642, 643, 643, 643, ...
## Resampling results across tuning parameters:
##
## mtry ROC Sens Spec
## 2 0.8764460 0.8954545 0.7482804
## 5 0.8870085 0.8954545 0.7411376
## 7 0.8849026 0.8795455 0.7628307
## 10 0.8806698 0.8636364 0.7624339
## 15 0.8784737 0.8568182 0.7588624
##
## ROC was used to select the optimal model using the largest value.
## The final value used for the model was mtry = 5.
plot(model_rf) # Tuning Grafiği (En iyi mtry hangisi?)
# EKSTRA: Değişken Önem Düzeyi (Feature Importance)
# Random Forest'ın en sevilen yanıdır. Hangi değişken hayati önem taşıyor?
rf_imp <- varImp(model_rf, scale = FALSE)
plot(rf_imp, top = 15, main = "Random Forest'a Göre En Önemli Değişkenler")
🧐 Grafik Yorumu 1: Hangi Değişkenler Kaderi Belirledi? (Feature Importance)
Birinci grafik (Çubuk Grafik), Random Forest modelinin yüzlerce ağaç kurduktan sonra ulaştığı “Önem Sıralaması”nı gösterir. Tek bir Karar Ağacında gördüğümüzden farklı ve çok ilginç bir tablo var:
Sürpriz Lider: Ücret (Fare) ve Yaş (Age)
- Karar ağacında en tepede “Mr” (Cinsiyet/Ünvan) vardı. Ancak burada Fare (Bilet Ücreti) ve Age (Yaş) listenin zirvesine oturdu.
-Neden? Tek bir ağaç, veriyi en hızlı bölen kategorik değişkeni (Erkek/Kadın) seçer. Ancak Random Forest, yüzlerce ağaçta detaylara iner. Fare ve Age değişkenleri sürekli (continuous) değişkenlerdir ve içinde çok fazla “ince detay” barındırır (Örn: 10£ ödeyenle 50£ ödeyen arasındaki fark). Model, karmaşık vakaları çözmek için bu sayısal detayların hayati olduğunu keşfetmiştir.
-Yorum: Zenginlik (Fare) ve Gençlik/Yaşlılık (Age), hayatta kalma denkleminde cinsiyet kadar, hatta bazen daha fazla bilgi taşımaktadır.
Vazgeçilmezler: Ünvan ve Cinsiyet
- Title.Mr, Sex.female ve Sex.male hemen ardından geliyor. Bu değişkenler hala “ana belirleyiciler”dir ancak Fare ve Age kadar varyasyon (çeşitlilik) sunmazlar.
Sosyal Faktörler:
- FamilySize (Aile Büyüklüğü) ve Pclass.3 (3. Sınıf Olmak) orta sıralarda yer alıyor. Bu, EDA aşamasında gördüğümüz “Kalabalık ailelerin ve alt sınıfın dezavantajı” hipotezini doğrular.
🧐 Grafik Yorumu 2: Ormanın Sıklığı Ne Olmalı? (Tuning Plot)
İkinci grafik (Çizgi Grafik), modelin “Her ağaç dalında kaç değişkene bakmalıyım?” (mtry) sorusuna verdiği cevaptır.
- Zirve Noktası: mtry = 5
Grafik, 5 değişkene bakıldığında başarının (ROC) zirveye (0.886 civarı) ulaştığını gösteriyor.
Teorik Kanıt: İstatistik literatüründe, sınıflandırma problemleri için ideal mtry değerinin, toplam değişken sayısının karekökü (karekök p) olduğu söylenir. Bizim yaklaşık 20-25 değişkenimiz var (Encoding sonrası).
karekök25 = 5 . Grafik, teoriyi mükemmel şekilde doğruluyor!
- Düşüşün Sebebi (Sağa Doğru Eğim)
mtry değeri 10 veya 15’e çıktığında başarı düşüyor.
Neden? Eğer her ağaç dalında değişkenlerin çoğunu (15 tanesini) gösterirsek, ağaçların hepsi birbirine benzemeye başlar (Hepsi en güçlü olan Fare veya Title.Mr değişkenini seçer).
Random Forest’ın gücü çeşitlilikten (Randomness) gelir. Değişken sayısını kısıtlı (5) tutmak, zayıf değişkenlerin de bazen söz sahibi olmasını sağlar ve bu da “kolektif zekayı” artırır.
-Sonuç: Modelimiz için en iyi parametre mtry = 5 olarak belirlenmiştir. Bu ayarla modelimiz, hem sayısal detayları (Fare, Age) hem de kategorik kuralları (Sex, Title) en dengeli şekilde harmanlamaktadır.
💡 Kod Notu: top = 15 Ne İşe Yarıyor?
Kod satırındaki plot(rf_imp, top = 15, …) ifadesi, R’a şu emri verir:
“Modelde kullanılan tüm değişkenleri önem sırasına diz, ancak grafikte bana sadece en önemli 15 tanesini göster.”
- Neden Yapıyoruz?
Veri hazırlığı (One-Hot Encoding) aşamasında Title.Mr, Title.Mrs, Embarked.S, Pclass.1 gibi birçok yeni sütun oluşturduk (Toplam değişken sayımız 20’nin üzerine çıktı).
Eğer top parametresini kullanmazsak; grafik aşağıya doğru uzayıp gider ve etkisi çok az olan (önemsiz) değişkenler grafiği kalabalıklaştırır, okunmasını zorlaştırır.
Bu sadece bir görselleştirme ayarıdır. Model arka planda hesaplama yaparken 15 tanesini değil, tüm değişkenleri kullanmaya devam eder. Biz sadece raporumuzda “Yıldız Oyuncuları” (En etkili faktörleri) öne çıkarıyoruz.
GBM, zayıf öğrenicileri (kısa ağaçları) bir araya getirerek güçlü bir tahminci oluşturur. Hataların üzerine giderek (Gradient Descent mantığıyla) ilerler.
Mantık: İlk ağaç bir tahmin yapar. İkinci ağaç, ilk ağacın hatalarına (Residuals) odaklanır. Üçüncü ağaç, ikincinin hatalarına odaklanır…
Tuning Parametreleri:
-n.trees: Toplam ağaç sayısı (Çok fazlası ezberlemeye yol açabilir).
-interaction.depth: Ağaçların derinliği (Genelde küçük tutulur, 1-10 arası).
-shrinkage: Öğrenme hızı (Adım büyüklüğü). Küçük olması (0.01 gibi) iyidir ama daha çok ağaç gerektirir.
-n.minobsinnode: Bir yapraktaki minimum gözlem sayısı.
set.seed(123)
# GBM Tuning Grid
# Bu parametre kombinasyonlarını deneyecek
gbm_grid <- expand.grid(interaction.depth = c(1, 3, 5),
n.trees = (1:10)*50, # 50, 100, ..., 500 ağaç
shrinkage = c(0.1, 0.01), # Öğrenme hızı
n.minobsinnode = 10)
# GBM Eğitimi
# verbose = FALSE: Eğitim sırasında ekrana sürekli yazı basmasını engeller
model_gbm <- train(Survived ~ .,
data = train_set,
method = "gbm",
trControl = fit_control,
tuneGrid = gbm_grid,
metric = "ROC",
preProcess = c("center", "scale"), # <-- YENİ EKLENEN KISIM
verbose = FALSE)
# Sonuçlar
print(model_gbm)
## Stochastic Gradient Boosting
##
## 714 samples
## 18 predictor
## 2 classes: 'Died', 'Survived'
##
## Pre-processing: centered (18), scaled (18)
## Resampling: Cross-Validated (10 fold)
## Summary of sample sizes: 643, 643, 642, 643, 643, 643, ...
## Resampling results across tuning parameters:
##
## shrinkage interaction.depth n.trees ROC Sens Spec
## 0.01 1 50 0.8428917 0.8545455 0.6932540
## 0.01 1 100 0.8570376 0.8545455 0.6969577
## 0.01 1 150 0.8585227 0.8522727 0.7153439
## 0.01 1 200 0.8649817 0.8477273 0.7226190
## 0.01 1 250 0.8659918 0.8454545 0.7154762
## 0.01 1 300 0.8672529 0.8750000 0.7265873
## 0.01 1 350 0.8671867 0.8795455 0.7265873
## 0.01 1 400 0.8707026 0.8818182 0.7338624
## 0.01 1 450 0.8721816 0.8840909 0.7302910
## 0.01 1 500 0.8733030 0.8840909 0.7302910
## 0.01 3 50 0.8693948 0.9795455 0.4890212
## 0.01 3 100 0.8691483 0.8863636 0.7083333
## 0.01 3 150 0.8722373 0.8840909 0.7339947
## 0.01 3 200 0.8785910 0.8840909 0.7374339
## 0.01 3 250 0.8795590 0.8886364 0.7447090
## 0.01 3 300 0.8808862 0.8886364 0.7410053
## 0.01 3 350 0.8824916 0.8840909 0.7447090
## 0.01 3 400 0.8827682 0.8886364 0.7410053
## 0.01 3 450 0.8832702 0.8909091 0.7410053
## 0.01 3 500 0.8840308 0.8863636 0.7485450
## 0.01 5 50 0.8730565 0.9636364 0.5329365
## 0.01 5 100 0.8725198 0.9227273 0.6716931
## 0.01 5 150 0.8791607 0.9227273 0.7224868
## 0.01 5 200 0.8790464 0.9181818 0.7261905
## 0.01 5 250 0.8811508 0.9045455 0.7335979
## 0.01 5 300 0.8836730 0.9022727 0.7374339
## 0.01 5 350 0.8858165 0.8954545 0.7411376
## 0.01 5 400 0.8867364 0.8977273 0.7448413
## 0.01 5 450 0.8875932 0.8909091 0.7519841
## 0.01 5 500 0.8869318 0.8931818 0.7556878
## 0.10 1 50 0.8751684 0.8795455 0.7265873
## 0.10 1 100 0.8758327 0.8704545 0.7595238
## 0.10 1 150 0.8794417 0.8704545 0.7559524
## 0.10 1 200 0.8783234 0.8659091 0.7559524
## 0.10 1 250 0.8786060 0.8659091 0.7559524
## 0.10 1 300 0.8800054 0.8659091 0.7634921
## 0.10 1 350 0.8817340 0.8772727 0.7596561
## 0.10 1 400 0.8815687 0.8704545 0.7706349
## 0.10 1 450 0.8825637 0.8727273 0.7559524
## 0.10 1 500 0.8810396 0.8704545 0.7559524
## 0.10 3 50 0.8829786 0.8840909 0.7630952
## 0.10 3 100 0.8818678 0.8886364 0.7410053
## 0.10 3 150 0.8861231 0.8863636 0.7484127
## 0.10 3 200 0.8854137 0.8954545 0.7592593
## 0.10 3 250 0.8822601 0.8931818 0.7410053
## 0.10 3 300 0.8836099 0.8931818 0.7410053
## 0.10 3 350 0.8866071 0.8931818 0.7518519
## 0.10 3 400 0.8865681 0.8886364 0.7517196
## 0.10 3 450 0.8843134 0.8977273 0.7517196
## 0.10 3 500 0.8856091 0.8863636 0.7588624
## 0.10 5 50 0.8872294 0.8886364 0.7739418
## 0.10 5 100 0.8900403 0.8954545 0.7517196
## 0.10 5 150 0.8870130 0.8931818 0.7591270
## 0.10 5 200 0.8862284 0.8909091 0.7628307
## 0.10 5 250 0.8888648 0.8886364 0.7518519
## 0.10 5 300 0.8883718 0.8750000 0.7555556
## 0.10 5 350 0.8843915 0.8750000 0.7481481
## 0.10 5 400 0.8821308 0.8704545 0.7554233
## 0.10 5 450 0.8796657 0.8659091 0.7517196
## 0.10 5 500 0.8816077 0.8750000 0.7735450
##
## Tuning parameter 'n.minobsinnode' was held constant at a value of 10
## ROC was used to select the optimal model using the largest value.
## The final values used for the model were n.trees = 100, interaction.depth =
## 5, shrinkage = 0.1 and n.minobsinnode = 10.
plot(model_gbm) # Hangi parametreler kazandı?
🧐 Grafik Analizi: “Kaplumbağa ile Tavşan”ın Yarışı
Bu grafik, modelin performansını (Y ekseni - ROC) üç farklı parametreye göre kıyaslar:
Ağaç Sayısı (X Ekseni): Model ne kadar süre çalıştı?
Öğrenme Hızı (Paneller):
Mavi (1): Çok basit ağaçlar (Kütük/Stump).
Yeşil (5): Karmaşık ve derin ağaçlar.
1. Sol Panel: Yavaş ve Kararlı (Shrinkage 0.01)
-Trend: Çizgilerin hepsi yukarı doğru tırmanıyor. 50 ağaçtan 500 ağaca gittikçe başarı (ROC) sürekli artıyor.
-Yorum: Öğrenme hızı düşük (0.01) olduğu için model küçük adımlarla ilerliyor. Hata yapma riski az ama hedefe ulaşmak için daha fazla ağaca (zaman) ihtiyacı var.
-Dikkat: 500. ağaca geldiğimizde bile çizgiler hala yukarı bakıyor. Yani modele “1000 ağaç kur” deseydik muhtemelen başarı daha da artacaktı. Henüz potansiyelinin zirvesine ulaşmadı.
-Kazanan: Yeşil Çizgi (Derinlik 5). Karmaşık ilişkileri yakalayabildiği için en yüksek skoru o veriyor.
2. Sağ Panel: Hızlı ve Tehlikeli (Shrinkage 0.10)
-Trend: Çizgiler çok yüksekten başlıyor (daha 50. ağaçta 0.88’leri görüyor) ancak sonra dalgalanıyor veya düşüşe geçiyor.
-Overfitting (Aşırı Öğrenme) Tehlikesi: Özellikle Yeşil Çizgiye (Derinlik 5) dikkat edin! 100-150 ağaç civarında zirve yapıyor (0.89), ancak sonra aşağı doğru düşmeye başlıyor.
-Yorum: Model o kadar hızlı öğreniyor ki, bir noktadan sonra verideki gürültüyü (noise) ezberlemeye başlıyor ve performansı düşüyor. Tavşan hızlı koştu ama çabuk yoruldu/hata yaptı.
3. Büyük Çıkarım (Grand Conclusion)
Bu grafik bize GBM ve Boosting algoritmaları için altın kuralı öğretir:
-Düşük Öğrenme Hızı (0.01) + Yüksek Ağaç Sayısı (500+): Genellikle en iyi ve en güvenilir (kararlı) sonucu verir (Sol panelin sonu).
-Yüksek Öğrenme Hızı (0.10): Hızlı sonuç verir ama doğru yerde durdurulmazsa (Early Stopping) ezberleme (Overfitting) riski taşır.
-Derinlik: Titanik veri seti karmaşık olduğu için (Yaş, Sınıf, Aile etkileşimleri), derinliği fazla olan ağaçlar (Depth 5 - Yeşil) sığ ağaçlara (Depth 1 - Mavi) göre her zaman daha iyi performans göstermiştir.
Modelin Tercihi: Muhtemelen caret paketi, Shrinkage 0.10, Depth 5 ve n.trees 100-150 civarındaki o zirve noktasını veya Shrinkage 0.01, Depth 5, n.trees 500 kombinasyonunu “En İyi Model” olarak seçecektir. Grafik, sol tarafın (düşük hızın) daha güvenilir bir yükseliş trendinde olduğunu kanıtlıyor.
Veri bilimi yarışmalarının (Kaggle) en popüler algoritmasıdır. GBM’in çok daha hızlı, optimize edilmiş ve “Regularization” (aşırı öğrenmeyi engelleme) yeteneği eklenmiş halidir.
Farkı Nedir? İşlemci gücünü çok verimli kullanır, eksik veriyi (biz doldurduk ama) yönetebilir ve budama (pruning) işlemlerini çok akıllıca yapar.
Tuning: XGBoost çok fazla parametreye sahiptir. Hepsini denemek günler sürebilir. Bu yüzden “Makul bir aralık” belirleyip grid search yapacağız.
set.seed(123)
# XGBoost Tuning Grid
# Bu grid biraz kalabalıktır çünkü XGBoost çok parametrelidir.
xgb_grid <- expand.grid(nrounds = c(100, 200), # Raunt (Ağaç) sayısı
max_depth = c(3, 5, 7), # Ağaç derinliği
eta = c(0.01, 0.1), # Öğrenme hızı (Learning Rate)
gamma = 0, # Bölünme için minimum kayıp düşüşü
colsample_bytree = c(0.7), # Her ağaçta kullanılacak değişken oranı
min_child_weight = 1, # Bir yapraktaki minimum ağırlık
subsample = c(0.8)) # Her ağaçta kullanılacak veri oranı
# XGBoost Eğitimi (xgbTree metodu)
# Not: Bu işlem bilgisayar hızına göre 1-2 dakika sürebilir.
model_xgb <- train(Survived ~ .,
data = train_set,
method = "xgbTree",
trControl = fit_control,
tuneGrid = xgb_grid,
metric = "ROC",
preProcess = c("center", "scale"), # <-- YENİ EKLENEN KISIM
verbosity = 0) # Sessiz mod
# Sonuçlar
print(model_xgb)
## eXtreme Gradient Boosting
##
## 714 samples
## 18 predictor
## 2 classes: 'Died', 'Survived'
##
## Pre-processing: centered (18), scaled (18)
## Resampling: Cross-Validated (10 fold)
## Summary of sample sizes: 643, 643, 642, 643, 643, 643, ...
## Resampling results across tuning parameters:
##
## eta max_depth nrounds ROC Sens Spec
## 0.01 3 100 0.8786451 0.8909091 0.7486772
## 0.01 3 200 0.8807480 0.8954545 0.7447090
## 0.01 5 100 0.8833499 0.9022727 0.7374339
## 0.01 5 200 0.8870280 0.9113636 0.7482804
## 0.01 7 100 0.8841435 0.9090909 0.7592593
## 0.01 7 200 0.8916546 0.9113636 0.7665344
## 0.10 3 100 0.8856331 0.8954545 0.7519841
## 0.10 3 200 0.8903199 0.8931818 0.7589947
## 0.10 5 100 0.8895593 0.9022727 0.7554233
## 0.10 5 200 0.8883267 0.8818182 0.7588624
## 0.10 7 100 0.8898028 0.8909091 0.7555556
## 0.10 7 200 0.8843464 0.8818182 0.7517196
##
## Tuning parameter 'gamma' was held constant at a value of 0
## Tuning
##
## Tuning parameter 'min_child_weight' was held constant at a value of 1
##
## Tuning parameter 'subsample' was held constant at a value of 0.8
## ROC was used to select the optimal model using the largest value.
## The final values used for the model were nrounds = 200, max_depth = 7, eta
## = 0.01, gamma = 0, colsample_bytree = 0.7, min_child_weight = 1 and
## subsample = 0.8.
# En iyi parametreleri görelim
print(model_xgb$bestTune)
## nrounds max_depth eta gamma colsample_bytree min_child_weight subsample
## 6 200 7 0.01 0 0.7 1 0.8
Microsoft tarafından geliştirilen, XGBoost’un en büyük rakibidir. “Yaprak odaklı büyüme” (Leaf-wise growth) stratejisi kullanır. Bu sayede genellikle daha hızlıdır ve büyük veri setlerinde çok etkilidir.
LightGBM, caret paketinin varsayılan listesinde olmadığı için, modele veriyi nasıl işlemesi gerektiğini öğreten özel bir tanımlama (Custom Method) yapıyoruz.
Tuning Parametreleri:
– num_leaves: Bir ağaçtaki maksimum yaprak sayısı (Model karmaşıklığını belirler).
- learning_rate: Öğrenme hızı.
- nrounds: İterasyon (Ağaç) sayısı.
library(lightgbm)
# --- CARET İÇİN DÜZELTİLMİŞ LIGHTGBM ENTEGRASYONU ---
lgbm_caret <- list(
type = "Classification",
library = "lightgbm",
loop = NULL,
parameters = data.frame(parameter = c("num_leaves", "learning_rate", "nrounds"),
class = rep("numeric", 3),
label = c("Num Leaves", "Learning Rate", "Iterations")),
grid = function(x, y, len = NULL, search = "grid") {
expand.grid(num_leaves = c(15, 31),
learning_rate = c(0.01, 0.1),
nrounds = c(100, 200))
},
fit = function(x, y, wts, param, lev, last, weights, classProbs, ...) {
# Y faktörünü 0 ve 1'e çeviriyoruz
label_num <- as.numeric(y) - 1
# LightGBM veri seti objesi
dtrain <- lgb.Dataset(data = as.matrix(x), label = label_num)
# Parametreler
params <- list(objective = "binary",
force_col_wise = TRUE,
num_leaves = param$num_leaves,
learning_rate = param$learning_rate,
verbose = -1)
# Eğitim
model <- lgb.train(params, dtrain, nrounds = param$nrounds)
# KİLİT NOKTA: Modeli ve seviyeleri bir liste içinde döndürüyoruz
list(booster = model, obsLevels = levels(y))
},
predict = function(modelFit, newdata, submodels = NULL) {
# Tahmin yaparken listenin içindeki booster'ı kullanıyoruz
preds <- predict(modelFit$booster, as.matrix(newdata))
res <- ifelse(preds >= 0.5, modelFit$obsLevels[2], modelFit$obsLevels[1])
factor(res, levels = modelFit$obsLevels)
},
prob = function(modelFit, newdata, submodels = NULL) {
# Olasılık döndürme
preds <- predict(modelFit$booster, as.matrix(newdata))
probs <- data.frame(1 - preds, preds)
colnames(probs) <- modelFit$obsLevels
return(probs)
}
)
# ---------------------------------------------
set.seed(123)
# LightGBM Eğitimi
model_lgbm <- train(Survived ~ .,
data = train_set,
method = lgbm_caret, # Arkadaşınızın gönderdiği metot
trControl = fit_control,
preProcess = c("center", "scale"), # <-- YENİ EKLENEN KISIM
metric = "ROC")
# Sonuçlar
print(model_lgbm)
## 714 samples
## 18 predictor
## 2 classes: 'Died', 'Survived'
##
## Pre-processing: centered (18), scaled (18)
## Resampling: Cross-Validated (10 fold)
## Summary of sample sizes: 643, 643, 642, 643, 643, 643, ...
## Resampling results across tuning parameters:
##
## num_leaves learning_rate nrounds ROC Sens Spec
## 15 0.01 100 0.8826344 0.9295455 0.6605820
## 15 0.01 200 0.8887085 0.9113636 0.7228836
## 15 0.10 100 0.8825667 0.8840909 0.7592593
## 15 0.10 200 0.8748828 0.8704545 0.7515873
## 31 0.01 100 0.8861276 0.9295455 0.6678571
## 31 0.01 200 0.8903965 0.9045455 0.7338624
## 31 0.10 100 0.8827652 0.8772727 0.7518519
## 31 0.10 200 0.8759921 0.8613636 0.7406085
##
## ROC was used to select the optimal model using the largest value.
## The final values used for the model were num_leaves = 31, learning_rate =
## 0.01 and nrounds = 200.
Yandex tarafından geliştirilen CatBoost, özellikle kategorik değişkenlerdeki başarısı ve overfitting’e karşı direnciyle bilinir. caret içinde olmadığı için özel entegrasyon (Custom Method) ile çalıştıracağız.
Bu projede, Lojistik Regresyon ve k-NN gibi algoritmaların matematiksel kısıtlamaları (sadece sayısal girdi kabul etmeleri) nedeniyle, tüm kategorik değişkenlere One-Hot Encoding işlemi uygulanmıştır.
Bilindiği üzere CatBoost (ve kısmen LightGBM), kategorik verileri ham halleriyle (String/Factor) işleme konusunda özelleşmiş ve bu alanda “Target Encoding” gibi gelişmiş teknikler kullanan algoritmalardır. Veriyi One-Hot formatına (0 ve 1’lere) çevirmek, CatBoost’un bu kendine has gücünü kullanmasını engellemektedir.
Ancak bu çalışmada; kod bütünlüğünü korumak ve tüm modelleri birebir aynı veri matrisi üzerinde, eşit şartlarda (ceteris paribus) karşılaştırabilmek amacıyla, CatBoost için ayrı bir veri seti hazırlanmamış, ortak veri seti kullanılmıştır. CatBoost’un performansının diğer modellere göre beklentinin altında kalması durumunda, bu yapısal tercihin etkisi göz önünde bulundurulmalıdır.
library(catboost)
# --- CARET İÇİN ÖZEL CATBOOST ENTEGRASYONU ---
catboost_caret <- list(
type = "Classification",
library = "catboost",
loop = NULL,
parameters = data.frame(parameter = c("depth", "learning_rate", "iterations"),
class = rep("numeric", 3),
label = c("Depth", "Learning Rate", "Iterations")),
grid = function(x, y, len = NULL, search = "grid") {
expand.grid(depth = c(4, 6),
learning_rate = c(0.01, 0.1),
iterations = c(100, 200))
},
fit = function(x, y, wts, param, lev, last, weights, classProbs, ...) {
# Veriyi CatBoost Havuzuna (Pool) Çevirme
# Y faktörünü 0-1 sayısala çevir (Died=0, Survived=1)
y_num <- as.numeric(y) - 1
train_pool <- catboost.load_pool(data = x, label = y_num)
# Parametreler
fit_params <- list(iterations = param$iterations,
learning_rate = param$learning_rate,
depth = param$depth,
loss_function = "Logloss", # İkili sınıflandırma için
logging_level = "Silent")
catboost.train(train_pool, params = fit_params)
},
predict = function(modelFit, newdata, submodels = NULL) {
# Tahmin (Olasılık döner)
pool <- catboost.load_pool(data = newdata)
preds <- catboost.predict(modelFit, pool, prediction_type = "Probability")
# 0.5 Eşiğine göre Sınıf Atama
ifelse(preds >= 0.5, "Survived", "Died")
},
prob = function(modelFit, newdata, submodels = NULL) {
# Olasılık Tablosu
pool <- catboost.load_pool(data = newdata)
preds <- catboost.predict(modelFit, pool, prediction_type = "Probability")
data.frame(Died = 1 - preds, Survived = preds)
}
)
# ---------------------------------------------
set.seed(123)
# CatBoost Eğitimi
model_catboost <- train(Survived ~ .,
data = train_set,
method = catboost_caret,
trControl = fit_control,
preProcess = c("center", "scale"), # <-- YENİ EKLENEN KISIM
metric = "ROC")
# Sonuçlar
print(model_catboost)
## 714 samples
## 18 predictor
## 2 classes: 'Died', 'Survived'
##
## Pre-processing: centered (18), scaled (18)
## Resampling: Cross-Validated (10 fold)
## Summary of sample sizes: 643, 643, 642, 643, 643, 643, ...
## Resampling results across tuning parameters:
##
## depth learning_rate iterations ROC Sens Spec
## 4 0.01 100 0.8704591 0.8909091 0.7447090
## 4 0.01 200 0.8774772 0.8909091 0.7556878
## 4 0.10 100 0.8782407 0.8727273 0.7521164
## 4 0.10 200 0.8871182 0.8909091 0.7588624
## 6 0.01 100 0.8724297 0.8909091 0.7555556
## 6 0.01 200 0.8719757 0.8909091 0.7484127
## 6 0.10 100 0.8788721 0.8750000 0.7374339
## 6 0.10 200 0.8863516 0.8931818 0.7734127
##
## ROC was used to select the optimal model using the largest value.
## The final values used for the model were depth = 4, learning_rate = 0.1
## and iterations = 200.
Projeyi tek bir dosyada topladık. Şimdi elimizdeki modelleri arenaya çıkarma ve şampiyonu belirleme zamanı.
İki aşamalı bir değerlendirme yapacağız:
-Antrenman Performansı: Modellerin eğitim sırasındaki (Cross-Validation) ROC skorları.
-Gerçek Sınav (Test Seti): Modellerin hiç görmediği %20’lik veri üzerindeki başarısı.
# 1. Modelleri Listeleme
model_list <- list(Logistic = model_logreg,
kNN = model_knn,
CART = model_cart,
RandomForest = model_rf,
GBM = model_gbm,
XGBoost = model_xgb,
LightGBM = model_lgbm,
CatBoost = model_catboost)
# 2. Resampling Sonuçlarını Toplama (CV Performansı)
results <- resamples(model_list)
# 3. İstatistiksel Özet
summary(results)
##
## Call:
## summary.resamples(object = results)
##
## Models: Logistic, kNN, CART, RandomForest, GBM, XGBoost, LightGBM, CatBoost
## Number of resamples: 10
##
## ROC
## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
## Logistic 0.8262987 0.8419838 0.8607955 0.8734367 0.9036195 0.9410774 0
## kNN 0.7929293 0.8403003 0.8714000 0.8708529 0.9082492 0.9330808 0
## CART 0.7723064 0.8532197 0.8583078 0.8569685 0.8671875 0.9292929 0
## RandomForest 0.8055556 0.8582589 0.8863636 0.8870085 0.9263468 0.9478114 0
## GBM 0.8207071 0.8539187 0.8932630 0.8900403 0.9231902 0.9461279 0
## XGBoost 0.8156566 0.8703328 0.8924513 0.8916546 0.9245581 0.9520202 0
## LightGBM 0.8316498 0.8462978 0.9029582 0.8903965 0.9281355 0.9457071 0
## CatBoost 0.8005051 0.8595779 0.8900162 0.8871182 0.9187710 0.9461279 0
##
## Sens
## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
## Logistic 0.7727273 0.8522727 0.8863636 0.8772727 0.9090909 0.9545455 0
## kNN 0.8181818 0.8920455 0.9090909 0.9181818 0.9659091 1.0000000 0
## CART 0.8409091 0.8465909 0.8636364 0.8772727 0.8863636 0.9545455 0
## RandomForest 0.8181818 0.8693182 0.8863636 0.8954545 0.9261364 0.9772727 0
## GBM 0.8181818 0.8636364 0.9090909 0.8954545 0.9318182 0.9545455 0
## XGBoost 0.8409091 0.8693182 0.9090909 0.9113636 0.9488636 0.9772727 0
## LightGBM 0.8181818 0.8693182 0.9204545 0.9045455 0.9488636 0.9545455 0
## CatBoost 0.8181818 0.8636364 0.8863636 0.8909091 0.9090909 0.9772727 0
##
## Spec
## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
## Logistic 0.6296296 0.7063492 0.7453704 0.7705026 0.8442460 0.9259259 0
## kNN 0.5714286 0.5962302 0.6481481 0.6679894 0.7251984 0.8148148 0
## CART 0.5925926 0.7129630 0.7638889 0.7480159 0.8075397 0.8214286 0
## RandomForest 0.6428571 0.6696429 0.7222222 0.7411376 0.8035714 0.8888889 0
## GBM 0.6428571 0.6851852 0.7638889 0.7517196 0.8148148 0.8214286 0
## XGBoost 0.6666667 0.7063492 0.7592593 0.7665344 0.8214286 0.8888889 0
## LightGBM 0.6296296 0.6428571 0.7268519 0.7338624 0.8197751 0.8518519 0
## CatBoost 0.6296296 0.6941138 0.7638889 0.7588624 0.8442460 0.8571429 0
# 4. Box Plot ile Karşılaştırma (Görsel Şölen)
bwplot(results, metric = "ROC", main = "Modellerin ROC Performansı Karşılaştırması")
# 5. TEST SETİ ÜZERİNDE FİNAL DOĞRULAMA
# En heyecanlı kısım: Modellerin gerçek başarısı ne?
# Sonuçları saklamak için boş bir tablo
final_scores <- data.frame(Model = character(),
Accuracy = numeric(),
ROC_AUC = numeric(),
stringsAsFactors = FALSE)
library(pROC) # AUC hesabı için
for(model_name in names(model_list)){
model <- model_list[[model_name]]
# Tahminler (Sınıf olarak: Died/Survived)
preds_class <- predict(model, newdata = test_set)
# Tahminler (Olasılık olarak: 0.1, 0.9 vb. ROC için gerekli)
# Bazı modellerde prob hesabı farklı olabilir, tryCatch ile güvene alıyoruz
preds_prob <- tryCatch({
predict(model, newdata = test_set, type = "prob")[, "Survived"]
}, error = function(e) return(NULL))
# Accuracy Hesabı
cm <- confusionMatrix(preds_class, test_set$Survived)
acc <- cm$overall["Accuracy"]
# AUC Hesabı (Eğer olasılık döndüyse)
auc_score <- NA
if(!is.null(preds_prob)){
roc_obj <- roc(response = test_set$Survived, predictor = preds_prob, quiet = TRUE)
auc_score <- auc(roc_obj)
}
# Listeye Ekle
final_scores[nrow(final_scores) + 1, ] <- list(model_name, acc, auc_score)
}
# 6. Şampiyonlar Ligi Tablosu
# Sonuçları Büyükten Küçüğe Sırala
final_scores <- final_scores %>% arrange(desc(ROC_AUC))
# Tabloyu Ekrana Bas
print(final_scores)
## Model Accuracy ROC_AUC
## 1 Logistic 0.8192090 0.8555720
## 2 CART 0.7853107 0.8548975
## 3 RandomForest 0.8248588 0.8544927
## 4 LightGBM 0.8135593 0.8536832
## 5 XGBoost 0.8079096 0.8534134
## 6 GBM 0.8079096 0.8488937
## 7 kNN 0.8192090 0.8457906
## 8 CatBoost 0.7966102 0.8373583
# En İyi Modelin Detaylı Confusion Matrix'i
best_model_name <- final_scores$Model[1]
print(paste("🏆 ŞAMPİYON MODEL:", best_model_name))
## [1] "🏆 ŞAMPİYON MODEL: Logistic"
best_model <- model_list[[best_model_name]]
confusionMatrix(predict(best_model, test_set), test_set$Survived)
## Confusion Matrix and Statistics
##
## Reference
## Prediction Died Survived
## Died 98 21
## Survived 11 47
##
## Accuracy : 0.8192
## 95% CI : (0.7545, 0.8729)
## No Information Rate : 0.6158
## P-Value [Acc > NIR] : 3.784e-09
##
## Kappa : 0.607
##
## Mcnemar's Test P-Value : 0.1116
##
## Sensitivity : 0.8991
## Specificity : 0.6912
## Pos Pred Value : 0.8235
## Neg Pred Value : 0.8103
## Prevalence : 0.6158
## Detection Rate : 0.5537
## Detection Prevalence : 0.6723
## Balanced Accuracy : 0.7951
##
## 'Positive' Class : Died
##
Aylarca sürebilecek bir veri bilimi projesini simüle ettik. Veriyi temizledik, görselleştirdik, mühendisliğini yaptık ve 8 farklı “Gladyatörü” arenaya sürdük.
Sonuçlar elimize ulaştığında, veri biliminin en altın kuralıyla yüzleştik.
🥇 Şampiyon: Lojistik Regresyon (Logistic Regression)
“Sadelik, karmaşıklığın en üst noktasıdır.” - Leonardo da Vinci
Lojistik Regresyon, 0.855 ROC AUC skoru ve %81.9 Accuracy ile XGBoost, LightGBM ve Random Forest gibi “Ağır Siklet” rakiplerini burun farkıyla geçerek şampiyon oldu.
🧐 Neden “En Basit” Model Kazandı?
Bu sonuç bizi tebessüm ettirse de, arkasında çok güçlü teknik sebepler yatıyor:
Mükemmel Veri Ön İşleme (The Power of Preprocessing):
Projenin başında yaptığımız Feature Engineering (Özellik Mühendisliği) o kadar etkili oldu ki (Title, FamilySize, HasCabin, Age atamaları), veri seti “Lineer Olarak Ayrıştırılabilir” (Linearly Separable) hale geldi. Veriyi temizleyip, doğru formatta sunduğumuzda, Lojistik Regresyonun basit bir “doğru çizmesi” (Decision Boundary) yetti. Karmaşık zikzaklar çizen ağaç modellerine (Boosting) gerek kalmadı.
Ders: İyi bir veri hazırlığı, en karmaşık algoritmadan daha değerlidir.
Veri Setinin Boyutu (Small Data):
Titanik veri seti (~891 satır) makine öğrenmesi standartlarına göre “küçük” bir veridir.
XGBoost, LightGBM ve CatBoost gibi modeller, milyonlarca satırlık verilerde ve karmaşık, gürültülü desenlerde parlar.
Küçük veride bu modeller bazen “Topla sinek avlamaya” benzer. Lojistik Regresyon ise az veriyle çok kararlı (stable) çalışır.
Overfitting (Aşırı Öğrenme) Riski:
Kutu Grafiğine (Box Plot) baktığımızda, LightGBM ve XGBoost’un medyan değerlerinin yüksek olduğunu ancak varyanslarının (kutu genişliği) da olduğunu görüyoruz. Bu modeller eğitim setini biraz fazla ezberlemeye çalışmış olabilir. Lojistik Regresyon ise genelleme yapma konusunda daha başarılı oldu.
📊 Detaylı Metrik Analiz
Confusion Matrix (Karmaşıklık Matrisi) bize şampiyonun röntgenini çekiyor:
Doğruluk (Accuracy - %81.9): Test setindeki her 100 yolcudan 82’sinin kaderini doğru tahmin ettik. Bu, Titanik literatüründe oldukça saygın bir skordur.
Duyarlılık (Sensitivity - 0.899): Modelimiz, ölenleri tespit etmede inanılmaz başarılı. Ölen her 100 kişiden 90’ını doğru bilmiş. (Referans sınıfı ‘Died’ olduğu için).
Seçicilik (Specificity - 0.691): Kurtulanları tespit etmede ise biraz daha zayıf kalmış (%69). Yani modelimiz biraz “kötümser”; bazı kurtulanları da ölecek sanmış (False Positive).
⚔️ Rakiplerin Durumu
-LightGBM ve XGBoost: Şampiyonu sadece binde birlik farklarla (0.853 vs 0.855) kaçırdılar. Bu onların kötü olduğunu değil, veri setinin sınırlarına ulaştığımızı gösterir. “Tavan” puana (Bayes Error Rate) çok yaklaştık.
-Random Forest: Her zamanki gibi en güvenilir liman. Hiçbir ayar yapmasanız bile (Out-of-the-box) her zaman ilk 3’e girer. Burada da 2. sırada yer aldı.
-CatBoost: Bu çalışmada performans olarak rakiplerinin biraz gerisinde kalmıştır. Raporun başında belirttiğimiz gibi; CatBoost’a ham kategorik veri yerine One-Hot Encoded veri sunmamız, algoritmanın en güçlü silahı olan kendi içindeki kategorik işleme (Native Categorical Handling) yeteneğini kısıtlamıştır. Bu sonuç, algoritmanın başarısızlığı değil, veri yapısı tercihinin (Lojistik Regresyon uyumu için yapılan dönüşümün) bir yan etkisidir.
🎓 Proje Kapanış Dersi
Bu çalışma bize şunu öğretti: Veri Bilimcisi, sadece kod yazan değil, verinin hikayesini kurgulayan kişidir.
Eğer biz Title (Ünvan) değişkenini çıkarmasaydık, FamilySize (Aile) grubunu oluşturmasaydık veya eksik yaşları (Age) ünvanlara göre doldurmasaydık; Lojistik Regresyon bu başarıyı gösteremezdi.
Zafer algoritmanın değil, veri hazırlığının (Data Preparation) zaferidir.