1 Giriş ve Amaç

Bu bütünleme projesinde Spaceship Titanic veri seti üzerinde çalışıyorum. Amaç, uzay gemisindeki yolcuların alternatif bir boyuta nakledilip nakledilmediğini (Transported) tahmin etmektir.

Proje Akışı:

Bu projede önce train ve test verilerini yükleyip eksik değerleri analiz edeceğim. Ardından verileri temizleyip tidymodels recipe ile ön işleme yapacağım. Train verisini yüzde 75 deneme_train ve yüzde 25 deneme_test olarak ayıracağım. Üç farklı makine öğrenmesi modeli (Lojistik Regresyon, Random Forest, XGBoost) kuracağım. Modelleri deneme_test setinde değerlendirip confusion matrix, accuracy, precision, recall ve F1-score hesaplayacağım. En iyi performansı gösteren modeli seçeceğim. Tüm train verisiyle final modelleri eğitip gerçek test setinde tahmin yapacağım. Son olarak Kaggle’a submission dosyalarını yükleyeceğim.


2 Gerekli Paketler

library(tidyverse)    # Veri manipülasyonu
library(tidymodels)   # Modelleme framework'ü
library(xgboost)      # XGBoost modeli
library(ranger)       # Random Forest
library(knitr)        # Tablo formatı
library(ggthemes)     # Grafik temaları
library(patchwork)    # Grafikleri birleştirmek için

3 Veri Setinin Yüklenmesi ve Tanıtımı

3.1 Ham Verilerin Yüklenmesi

train_raw <- read_csv("train.csv", show_col_types = FALSE)
test_raw <- read_csv("test.csv", show_col_types = FALSE)

3.2 Veri Setinin Yapısı

cat("=== TRAIN VERİSİ ===\n")
## === TRAIN VERİSİ ===
glimpse(train_raw)
## Rows: 8,693
## Columns: 14
## $ PassengerId  <chr> "0001_01", "0002_01", "0003_01", "0003_02", "0004_01", "0…
## $ HomePlanet   <chr> "Europa", "Earth", "Europa", "Europa", "Earth", "Earth", …
## $ CryoSleep    <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE, FA…
## $ Cabin        <chr> "B/0/P", "F/0/S", "A/0/S", "A/0/S", "F/1/S", "F/0/P", "F/…
## $ Destination  <chr> "TRAPPIST-1e", "TRAPPIST-1e", "TRAPPIST-1e", "TRAPPIST-1e…
## $ Age          <dbl> 39, 24, 58, 33, 16, 44, 26, 28, 35, 14, 34, 45, 32, 48, 2…
## $ VIP          <lgl> FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FA…
## $ RoomService  <dbl> 0, 109, 43, 0, 303, 0, 42, 0, 0, 0, 0, 39, 73, 719, 8, 32…
## $ FoodCourt    <dbl> 0, 9, 3576, 1283, 70, 483, 1539, 0, 785, 0, 0, 7295, 0, 1…
## $ ShoppingMall <dbl> 0, 25, 0, 371, 151, 0, 3, 0, 17, 0, NA, 589, 1123, 65, 12…
## $ Spa          <dbl> 0, 549, 6715, 3329, 565, 291, 0, 0, 216, 0, 0, 110, 0, 0,…
## $ VRDeck       <dbl> 0, 44, 49, 193, 2, 0, 0, NA, 0, 0, 0, 124, 113, 24, 7, 0,…
## $ Name         <chr> "Maham Ofracculy", "Juanna Vines", "Altark Susent", "Sola…
## $ Transported  <lgl> FALSE, TRUE, FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, …
cat("\n=== TEST VERİSİ ===\n")
## 
## === TEST VERİSİ ===
glimpse(test_raw)
## Rows: 4,277
## Columns: 13
## $ PassengerId  <chr> "0013_01", "0018_01", "0019_01", "0021_01", "0023_01", "0…
## $ HomePlanet   <chr> "Earth", "Earth", "Europa", "Europa", "Earth", "Earth", "…
## $ CryoSleep    <lgl> TRUE, FALSE, TRUE, FALSE, FALSE, FALSE, TRUE, TRUE, TRUE,…
## $ Cabin        <chr> "G/3/S", "F/4/S", "C/0/S", "C/1/S", "F/5/S", "F/7/P", "B/…
## $ Destination  <chr> "TRAPPIST-1e", "TRAPPIST-1e", "55 Cancri e", "TRAPPIST-1e…
## $ Age          <dbl> 27, 19, 31, 38, 20, 31, 21, 20, 23, 24, 19, 45, 44, 46, 2…
## $ VIP          <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, F…
## $ RoomService  <dbl> 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 339, 932, 0, 0, 0, 0, 2, 0…
## $ FoodCourt    <dbl> 0, 9, 0, 6652, 0, 1615, NA, 0, 0, 639, 3, 74, 1561, 0, 0,…
## $ ShoppingMall <dbl> 0, 0, 0, 0, 635, 263, 0, 0, 0, 0, 136, NA, 0, 0, 0, 0, 25…
## $ Spa          <dbl> 0, 2823, 0, 181, 0, 113, 0, 0, 0, 0, 237, 7, 14, 0, 1687,…
## $ VRDeck       <dbl> 0, 0, 0, 585, 0, 60, 0, 0, 0, 0, 0, 1010, 224, 0, 92, NA,…
## $ Name         <chr> "Nelly Carsoning", "Lerome Peckers", "Sabih Unhearfus", "…

Veri Seti Özeti:

  • Train: 8693 gözlem, 14 değişken (Transported dahil)
  • Test: 4277 gözlem, 13 değişken (Transported yok)

3.3 Değişken Açıklamaları

Değişken Açıklama Tür
PassengerId Yolcu kimlik numarası Karakter
HomePlanet Kalkış gezegeni (Europa, Earth, Mars) Kategorik
CryoSleep Dondurulmuş uyku modunda mı? Boolean
Cabin Kabin numarası (Deck/Num/Side formatında) Karakter
Destination Varış noktası gezegeni Kategorik
Age Yolcu yaşı Sayısal
VIP VIP servisi aldı mı? Boolean
RoomService Oda servisi harcaması (TL) Sayısal
FoodCourt Yemek alanı harcaması (TL) Sayısal
ShoppingMall Alışveriş merkezi harcaması (TL) Sayısal
Spa Spa harcaması (TL) Sayısal
VRDeck VR Deck harcaması (TL) Sayısal
Name Yolcu adı soyadı Karakter
Transported Alternatif boyuta nakledildi mi? (HEDEF) Boolean

4 Eksik Değer Analizi

4.1 Train Verisinde Eksik Değerler

train_na <- tibble(
  Degisken = names(train_raw),
  Eksik_Deger = colSums(is.na(train_raw)),
  Toplam_Gozlem = nrow(train_raw),
  Eksik_Oran = round(colSums(is.na(train_raw)) / nrow(train_raw) * 100, 2)
) %>%
  arrange(desc(Eksik_Deger))

kable(train_na, caption = "Train Verisinde Eksik Değer Dağılımı")
Train Verisinde Eksik Değer Dağılımı
Degisken Eksik_Deger Toplam_Gozlem Eksik_Oran
CryoSleep 217 8693 2.50
ShoppingMall 208 8693 2.39
VIP 203 8693 2.34
HomePlanet 201 8693 2.31
Name 200 8693 2.30
Cabin 199 8693 2.29
VRDeck 188 8693 2.16
FoodCourt 183 8693 2.11
Spa 183 8693 2.11
Destination 182 8693 2.09
RoomService 181 8693 2.08
Age 179 8693 2.06
PassengerId 0 8693 0.00
Transported 0 8693 0.00

4.1.1 Train Verisi: Eksik Değer Görselleştirmesi

train_na %>%
  filter(Eksik_Deger > 0) %>%
  ggplot(aes(x = reorder(Degisken, Eksik_Oran), y = Eksik_Oran)) +
  geom_col(fill = "#E74C3C", alpha = 0.8) +
  geom_text(aes(label = paste0(Eksik_Oran, "%")), hjust = -0.2, size = 3.5) +
  coord_flip() +
  labs(
    title = "Train Verisinde Eksik Değer Oranları",
    subtitle = paste0("Toplam ", sum(train_na$Eksik_Deger), " eksik değer mevcut"),
    x = "Değişken",
    y = "Eksik Değer Oranı (%)"
  ) +
  theme_minimal() +
  theme(
    plot.title = element_text(size = 14, face = "bold"),
    plot.subtitle = element_text(size = 11),
    axis.text = element_text(size = 10)
  ) +
  ylim(0, max(train_na$Eksik_Oran) * 1.15)

Train Verisi Eksik Değer Özeti:

  • En fazla eksik değer: CryoSleep (%2.5)
  • Toplam eksik hücre sayısı: 2324

4.2 Test Verisinde Eksik Değerler

test_na <- tibble(
  Degisken = names(test_raw),
  Eksik_Deger = colSums(is.na(test_raw)),
  Toplam_Gozlem = nrow(test_raw),
  Eksik_Oran = round(colSums(is.na(test_raw)) / nrow(test_raw) * 100, 2)
) %>%
  arrange(desc(Eksik_Deger))

kable(test_na, caption = "Test Verisinde Eksik Değer Dağılımı")
Test Verisinde Eksik Değer Dağılımı
Degisken Eksik_Deger Toplam_Gozlem Eksik_Oran
FoodCourt 106 4277 2.48
Spa 101 4277 2.36
Cabin 100 4277 2.34
ShoppingMall 98 4277 2.29
Name 94 4277 2.20
CryoSleep 93 4277 2.17
VIP 93 4277 2.17
Destination 92 4277 2.15
Age 91 4277 2.13
HomePlanet 87 4277 2.03
RoomService 82 4277 1.92
VRDeck 80 4277 1.87
PassengerId 0 4277 0.00

4.2.1 Test Verisi: Eksik Değer Görselleştirmesi

test_na %>%
  filter(Eksik_Deger > 0) %>%
  ggplot(aes(x = reorder(Degisken, Eksik_Oran), y = Eksik_Oran)) +
  geom_col(fill = "#3498DB", alpha = 0.8) +
  geom_text(aes(label = paste0(Eksik_Oran, "%")), hjust = -0.2, size = 3.5) +
  coord_flip() +
  labs(
    title = "Test Verisinde Eksik Değer Oranları",
    subtitle = paste0("Toplam ", sum(test_na$Eksik_Deger), " eksik değer mevcut"),
    x = "Değişken",
    y = "Eksik Değer Oranı (%)"
  ) +
  theme_minimal() +
  theme(
    plot.title = element_text(size = 14, face = "bold"),
    plot.subtitle = element_text(size = 11),
    axis.text = element_text(size = 10)
  ) +
  ylim(0, max(test_na$Eksik_Oran) * 1.15)

Test Verisi Eksik Değer Özeti:

  • En fazla eksik değer: FoodCourt (%2.48)
  • Toplam eksik hücre sayısı: 1117

5 Veri Ön İşleme

5.1 Ön İşleme Adımları (Recipe Öncesi)

Recipe’de step_mutate olmadığı için bazı işlemleri önceden yapıyoruz:

# Train verisi için
train_raw <- train_raw %>%
  # Hedef değişkeni faktöre çevir (tidymodels için)
  mutate(Transported = factor(ifelse(Transported, "Yes", "No"), 
                               levels = c("No", "Yes"))) %>%
  # Cabin'i ayır (Deck/Num/Side formatı)
  separate(Cabin, into = c("Deck", "CabinNum", "Side"), 
           sep = "/", remove = FALSE, fill = "right") %>%
  mutate(
    # Düşük frekanslı Deck'leri birleştir
    Deck = ifelse(Deck %in% c("A", "D", "T"), "Other", Deck),
    Deck = as.factor(Deck),
    CabinNum = as.numeric(CabinNum),
    Side = as.factor(Side),
    # Diğer kategorik değişkenler
    HomePlanet = as.factor(HomePlanet),
    CryoSleep = as.factor(CryoSleep),
    Destination = as.factor(Destination),
    VIP = as.factor(VIP)
  )

# Test verisi için (aynı işlemler)
test_raw <- test_raw %>%
  separate(Cabin, into = c("Deck", "CabinNum", "Side"), 
           sep = "/", remove = FALSE, fill = "right") %>%
  mutate(
    Deck = ifelse(Deck %in% c("A", "D", "T"), "Other", Deck),
    Deck = as.factor(Deck),
    CabinNum = as.numeric(CabinNum),
    Side = as.factor(Side),
    HomePlanet = as.factor(HomePlanet),
    CryoSleep = as.factor(CryoSleep),
    Destination = as.factor(Destination),
    VIP = as.factor(VIP)
  )

Yapılan İşlemler:

Bu aşamada Transported değişkeni “Yes” ve “No” faktör seviyelerine çevrildi. Cabin değişkeni Deck, CabinNum ve Side olarak 3 sütuna ayrıldı. Nadir Deck değerleri (A, D, T) “Other” kategorisinde birleştirildi. Tüm kategorik değişkenler faktör tipine çevrildi.


6 Train–Test Ayrımı (%75 - %25)

set.seed(123)  # Tekrar üretilebilirlik için

# Stratified sampling (Transported değişkenine göre)
data_split <- initial_split(train_raw, prop = 0.75, strata = Transported)
deneme_train <- training(data_split)
deneme_test <- testing(data_split)

# Ayrım özeti
tibble(
  Veri_Seti = c("Deneme Train", "Deneme Test", "TOPLAM"),
  Satir_Sayisi = c(nrow(deneme_train), nrow(deneme_test), nrow(train_raw)),
  Oran = c("75%", "25%", "100%")
) %>% kable(caption = "Train-Test Ayrımı (Stratified Sampling)")
Train-Test Ayrımı (Stratified Sampling)
Veri_Seti Satir_Sayisi Oran
Deneme Train 6519 75%
Deneme Test 2174 25%
TOPLAM 8693 100%

Stratified Sampling Neden Önemli?

Hedef değişken (Transported) dengesiz dağılmış olabilir. Stratified sampling ile her iki sette de (deneme_train ve deneme_test) Transported’ın oranları korunur. Bu durum model değerlendirmesinin daha güvenilir olmasını sağlar.


7 Tidymodels Recipe (Veri Ön İşleme Pipeline)

Tüm veri ön işleme adımlarını tek bir recipe içinde yapıyoruz. Bu sayede işlemler tekrar üretilebilir ve hem train hem de test verilerine aynı şekilde uygulanır.

model_rec <- recipe(Transported ~ ., data = deneme_train) %>%
  # 1. Gereksiz değişkenleri çıkar (ID ve isim bilgisi model için gerekli değil)
  step_rm(PassengerId, Name, Cabin) %>%
  
  # 2. Karakter kolonları faktöre çevir (XGBoost için gerekli)
  step_string2factor(all_nominal_predictors()) %>%
  
  # 3. Eksik değerleri doldur
  #    - Kategorik değişkenler için: En sık görülen değer (mode)
  #    - Sayısal değişkenler için: Medyan
  step_impute_mode(all_nominal_predictors()) %>%
  step_impute_median(all_numeric_predictors()) %>%
  
  # 4. Kategorik değişkenleri dummy değişkenlere çevir (one-hot encoding)
  step_dummy(all_nominal_predictors()) %>%
  
  # 5. Sıfır varyanslı değişkenleri çıkar (sabit değerli kolonlar)
  step_zv(all_predictors())

# Recipe'yi hazırla
prep_rec <- prep(model_rec)

# Verileri dönüştür
train_baked <- bake(prep_rec, new_data = NULL)          # deneme_train için
test_baked <- bake(prep_rec, new_data = deneme_test)    # deneme_test için

7.1 Eksik Değer Kontrolü (Recipe Sonrası)

na_check <- tibble(
  Veri_Seti = c("Deneme Train (recipe sonrası)", 
                "Deneme Test (recipe sonrası)"),
  Toplam_NA = c(sum(is.na(train_baked)), 
                sum(is.na(test_baked))),
  Satir = c(nrow(train_baked), 
            nrow(test_baked)),
  Sutun = c(ncol(train_baked), 
            ncol(test_baked))
) 

kable(na_check, caption = "Recipe Sonrası Eksik Değer Kontrolü")
Recipe Sonrası Eksik Değer Kontrolü
Veri_Seti Toplam_NA Satir Sutun
Deneme Train (recipe sonrası) 0 6519 20
Deneme Test (recipe sonrası) 0 2174 20
if(sum(is.na(train_baked)) == 0 & sum(is.na(test_baked)) == 0) {
  cat("\nDoğrulama: Recipe sonrası her iki veri setinde de hiçbir eksik değer (NA) kalmamıştır.\n\n")
} else {
  cat("\nUYARI: Recipe sonrası hala eksik değer bulunmaktadır!\n\n")
}

Doğrulama: Recipe sonrası her iki veri setinde de hiçbir eksik değer (NA) kalmamıştır.


8 Makine Öğrenmesi Modelleri ve Teorik Açıklamalar

Bu bölümde üç farklı makine öğrenmesi yöntemi kullanacağım. Her model için önce teorik açıklama, ardından model kurulumu yer almaktadır.


8.1 Lojistik Regresyon

8.1.1 Teorik Açıklama

Lojistik Regresyon, binary (ikili) sınıflandırma problemleri için kullanılan parametrik bir istatistiksel yöntemdir. Doğrusal regresyondan farklı olarak, çıktıyı [0,1] aralığına sıkıştırmak için sigmoid (lojistik) fonksiyonu kullanır:

\[P(Y=1|X) = \frac{1}{1 + e^{-(\beta_0 + \beta_1X_1 + ... + \beta_pX_p)}}\]

Lojistik regresyonun temel avantajları arasında hızlı eğitim ve tahmin süreci, yorumlanabilir katsayılar (odds ratio’ya çevrilebilir), düşük overfitting riski ve baseline model olarak ideal olması yer alır. Ancak doğrusal olmayan ilişkileri yakalayamaması, feature engineering gerektirmesi ve karmaşık etkileşimleri modelleyememesi gibi sınırlamaları bulunmaktadır.

logistic_spec <- logistic_reg() %>%
  set_engine("glm") %>%
  set_mode("classification")

logistic_wf <- workflow() %>%
  add_recipe(model_rec) %>%
  add_model(logistic_spec)

logistic_fit <- fit(logistic_wf, deneme_train)

8.2 Random Forest

8.2.1 Teorik Açıklama

Random Forest, birden fazla karar ağacının (decision tree) tahminlerini birleştiren bir ensemble (topluluk öğrenmesi) yöntemidir. Temel prensibi şu şekilde işler: Her ağaç, train verisinden bootstrap sampling ile rastgele seçilmiş örneklerle eğitilir. Her düğümde rastgele seçilmiş özellikler arasından en iyisi kullanılır. Final tahmin, tüm ağaçların oylamasıyla yapılır (classification için majority voting).

Random Forest overfitting’e karşı dayanıklıdır (bagging sayesinde) ve doğrusal olmayan ilişkileri yakalayabilir. Feature importance sağlaması ve hiperparametre ayarına az duyarlı olması diğer avantajlarıdır. Ancak yorumlanabilirliği düşüktür, eğitim süresi uzun olabilir ve büyük veri setlerinde memory-intensive olabilir.

Bu modelde mtry parametresi 5 olarak ayarlanmıştır (her split’te rastgele seçilen değişken sayısı), trees parametresi 500’dür (toplam ağaç sayısı) ve min_n parametresi 10’dur (terminal düğümdeki minimum gözlem sayısı).

rf_spec <- rand_forest(
  mtry = 5,
  trees = 500,
  min_n = 10
) %>%
  set_engine("ranger") %>%
  set_mode("classification")

rf_wf <- workflow() %>%
  add_recipe(model_rec) %>%
  add_model(rf_spec)

rf_fit <- fit(rf_wf, deneme_train)

8.3 XGBoost (Extreme Gradient Boosting)

8.3.1 Teorik Açıklama

XGBoost, gradient boosting algoritmasının optimize edilmiş versiyonudur. Random Forest’tan farklı olarak ağaçları sıralı (sequential) şekilde eğitir. İlk ağaç veriyi öğrenir, ikinci ağaç ilk ağacın hatalarını düzeltmeye çalışır, her yeni ağaç önceki ağaçların hatalarını minimize etmeye odaklanır ve final tahmin tüm ağaçların ağırlıklı toplamıdır.

XGBoost state-of-the-art performans sağlar (Kaggle yarışmalarında sık kazanır), eksik değerleri otomatik işleyebilir, feature importance sağlar ve regularization sayesinde overfitting kontrolü sunar. Ancak hiperparametre ayarı kritik ve karmaşıktır, eğitim süresi uzun olabilir ve yorumlanabilirlik düşüktür.

Bu modelde trees parametresi 500 (boosting iteration sayısı), tree_depth parametresi 6 (ağaç derinliği, overfitting kontrolü için), learn_rate parametresi 0.1 (her ağacın katkısı) ve min_n parametresi 10 (leaf node’da minimum gözlem sayısı) olarak ayarlanmıştır.

xgb_spec <- boost_tree(
  trees = 500,
  tree_depth = 6,
  learn_rate = 0.1,
  min_n = 10
) %>%
  set_engine("xgboost") %>%
  set_mode("classification")

xgb_wf <- workflow() %>%
  add_recipe(model_rec) %>%
  add_model(xgb_spec)

xgb_fit <- fit(xgb_wf, deneme_train)

9 Model Değerlendirmesi (deneme_test üzerinde)

Şimdi kurduğumuz üç modeli deneme_test seti üzerinde değerlendireceğiz. Bu aşamada confusion matrix, accuracy, precision, recall, F1-score ve ROC-AUC gibi metrikleri hesaplayacağız.

9.1 Tahmin Yapma

# Tahminler (hem sınıf hem de olasılık)
logistic_pred <- predict(logistic_fit, deneme_test, type = "class") %>%
  bind_cols(predict(logistic_fit, deneme_test, type = "prob")) %>%
  bind_cols(select(deneme_test, Transported))

rf_pred <- predict(rf_fit, deneme_test, type = "class") %>%
  bind_cols(predict(rf_fit, deneme_test, type = "prob")) %>%
  bind_cols(select(deneme_test, Transported))

xgb_pred <- predict(xgb_fit, deneme_test, type = "class") %>%
  bind_cols(predict(xgb_fit, deneme_test, type = "prob")) %>%
  bind_cols(select(deneme_test, Transported))

9.2 Confusion Matrix (Karışıklık Matrisi)

9.2.1 Teorik Açıklama

Confusion Matrix, sınıflandırma modelinin gerçek ve tahmin edilen etiketleri karşılaştıran bir tablodur. Tabloda dört temel metrik yer alır: True Negative (TN) aslında negatif olan ve negatif tahmin edilen gözlemlerdir. True Positive (TP) aslında pozitif olan ve pozitif tahmin edilen gözlemlerdir. False Positive (FP) aslında negatif olan ancak yanlışlıkla pozitif tahmin edilen gözlemlerdir (Type I error). False Negative (FN) ise aslında pozitif olan ancak yanlışlıkla negatif tahmin edilen gözlemlerdir (Type II error).

Bu projede Transported değişkeni için “No” negatif sınıf, “Yes” pozitif sınıftır. Dolayısıyla TN yolcunun nakledilmediği doğru tahmin edildiğinde, TP yolcunun nakledildiği doğru tahmin edildiğinde, FP yolcunun nakledilmediği halde nakledildi denildiğinde, FN ise yolcunun nakledildiği halde nakledilmedi denildiğinde ortaya çıkar.

cm_logistic <- conf_mat(logistic_pred, truth = Transported, estimate = .pred_class)
cm_rf <- conf_mat(rf_pred, truth = Transported, estimate = .pred_class)
cm_xgb <- conf_mat(xgb_pred, truth = Transported, estimate = .pred_class)

9.2.2 Confusion Matrix Tabloları

Lojistik Regresyon:

cm_logistic
##           Truth
## Prediction  No Yes
##        No  841 194
##        Yes 238 901

Random Forest:

cm_rf
##           Truth
## Prediction  No Yes
##        No  856 179
##        Yes 223 916

XGBoost:

cm_xgb
##           Truth
## Prediction  No Yes
##        No  870 218
##        Yes 209 877

9.2.3 Confusion Matrix Görselleştirmeleri

library(patchwork)

# Lojistik için heatmap
p1 <- cm_logistic %>%
  autoplot(type = "heatmap") +
  labs(title = "Lojistik Regresyon - Confusion Matrix") +
  theme_minimal() +
  theme(plot.title = element_text(size = 12, face = "bold"))

# RF için heatmap
p2 <- cm_rf %>%
  autoplot(type = "heatmap") +
  labs(title = "Random Forest - Confusion Matrix") +
  theme_minimal() +
  theme(plot.title = element_text(size = 12, face = "bold"))

# XGBoost için heatmap
p3 <- cm_xgb %>%
  autoplot(type = "heatmap") +
  labs(title = "XGBoost - Confusion Matrix") +
  theme_minimal() +
  theme(plot.title = element_text(size = 12, face = "bold"))

# Grafikleri birleştir
p1 / p2 / p3


9.3 Performans Metrikleri

9.3.1 Teorik Açıklamalar

Aşağıda kullanılacak performans metriklerinin tanımları ve formülleri yer almaktadır:

Metrik Açıklama Formül Neden Kullanılır?
Accuracy Doğru tahminlerin toplam gözleme oranı (TP + TN) / (TP + TN + FP + FN) Genel doğruluk ölçüsü. Dengeli veri setlerinde kullanışlı.
Precision Pozitif tahmin edilenlerin gerçekten pozitif olma oranı TP / (TP + FP) False Positive’leri minimize etmek önemliyse (örn: spam tespiti)
Recall (Sensitivity) Gerçek pozitiflerin yakalanma oranı TP / (TP + FN) False Negative’leri minimize etmek önemliyse (örn: hastalık tespiti)
F1-Score Precision ve Recall’un harmonik ortalaması 2 × (Precision × Recall) / (Precision + Recall) Precision ve Recall arasında denge istiyorsak
ROC-AUC Sınıfları ayırma gücü (1’e yakın = daha iyi) ROC eğrisi altındaki alan Eşik değerinden bağımsız genel performans ölçüsü
hesapla_metrikler <- function(pred_df, model_adi) {
  tibble(
    Model = model_adi,
    Accuracy = accuracy(pred_df, truth = Transported, estimate = .pred_class)$.estimate,
    Precision = precision(pred_df, truth = Transported, estimate = .pred_class, 
                         event_level = "second")$.estimate,
    Recall = recall(pred_df, truth = Transported, estimate = .pred_class, 
                    event_level = "second")$.estimate,
    F1 = f_meas(pred_df, truth = Transported, estimate = .pred_class, 
                event_level = "second")$.estimate,
    ROC_AUC = roc_auc(pred_df, truth = Transported, .pred_Yes, 
                      event_level = "second")$.estimate
  )
}

metrikler <- bind_rows(
  hesapla_metrikler(logistic_pred, "Lojistik Regresyon"),
  hesapla_metrikler(rf_pred, "Random Forest"),
  hesapla_metrikler(xgb_pred, "XGBoost")
)

kable(metrikler, digits = 4, caption = "Model Performans Karşılaştırması (deneme_test)")
Model Performans Karşılaştırması (deneme_test)
Model Accuracy Precision Recall F1 ROC_AUC
Lojistik Regresyon 0.8013 0.7910 0.8228 0.8066 0.8864
Random Forest 0.8151 0.8042 0.8365 0.8201 0.9049
XGBoost 0.8036 0.8076 0.8009 0.8042 0.9049

9.3.2 En İyi Model

best_model <- metrikler %>%
  arrange(desc(ROC_AUC)) %>%
  slice(1) %>%
  pull(Model)

cat(paste0("\nEn yüksek ROC-AUC skoru: ", best_model, "\n\n"))

En yüksek ROC-AUC skoru: Random Forest


9.4 Tahmin Dağılımları

Model tahminlerinin dağılımını görselleştirerek her modelin kaç yolcuyu Transported olarak tahmin ettiğini görebiliriz.

pred_summary <- tibble(
  Model = rep(c("Lojistik Regresyon", "Random Forest", "XGBoost"), each = 2),
  Tahmin = rep(c("No", "Yes"), 3),
  Sayi = c(
    sum(logistic_pred$.pred_class == "No"),
    sum(logistic_pred$.pred_class == "Yes"),
    sum(rf_pred$.pred_class == "No"),
    sum(rf_pred$.pred_class == "Yes"),
    sum(xgb_pred$.pred_class == "No"),
    sum(xgb_pred$.pred_class == "Yes")
  )
)

ggplot(pred_summary, aes(x = Model, y = Sayi, fill = Tahmin)) +
  geom_col(position = "dodge", width = 0.7) +
  geom_text(aes(label = Sayi), 
            position = position_dodge(width = 0.7), 
            vjust = -0.5, size = 3.5) +
  labs(
    title = "Modellerin Tahmin Dağılımları (deneme_test)",
    subtitle = "Her model kaç yolcuyu Transported (Yes) veya Not Transported (No) olarak tahmin etti",
    y = "Yolcu Sayısı",
    x = "Model",
    fill = "Tahmin"
  ) +
  theme_minimal() +
  theme(
    plot.title = element_text(size = 13, face = "bold"),
    plot.subtitle = element_text(size = 10),
    legend.position = "bottom"
  ) +
  scale_fill_manual(values = c("No" = "#E74C3C", "Yes" = "#27AE60"))


9.5 Tahmin Olasılık Dağılımları

Modellerin tahmin olasılıklarının (probability) dağılımını inceleyerek model güvenini değerlendirebiliriz.

prob_data <- bind_rows(
  logistic_pred %>% 
    select(.pred_Yes) %>% 
    mutate(Model = "Lojistik Regresyon"),
  rf_pred %>% 
    select(.pred_Yes) %>% 
    mutate(Model = "Random Forest"),
  xgb_pred %>% 
    select(.pred_Yes) %>% 
    mutate(Model = "XGBoost")
)

ggplot(prob_data, aes(x = .pred_Yes, fill = Model)) +
  geom_histogram(bins = 50, alpha = 0.7, position = "identity") +
  facet_wrap(~Model, ncol = 1) +
  labs(
    title = "Modellerin Tahmin Olasılık Dağılımları",
    subtitle = "0'a yakın = Not Transported, 1'e yakın = Transported",
    x = "P(Transported = Yes)",
    y = "Frekans"
  ) +
  theme_minimal() +
  theme(
    plot.title = element_text(size = 13, face = "bold"),
    plot.subtitle = element_text(size = 10),
    legend.position = "none"
  ) +
  scale_fill_brewer(palette = "Set1")


9.6 Hata Analizi: Yanlış Tahmin Edilen Gözlemler

Modellerin hangi tür hataları yaptığını analiz ediyoruz.

hata_analizi <- tibble(
  Model = c("Lojistik Regresyon", "Random Forest", "XGBoost"),
  False_Positive = c(
    sum(cm_logistic$table[1, 2]),
    sum(cm_rf$table[1, 2]),
    sum(cm_xgb$table[1, 2])
  ),
  False_Negative = c(
    sum(cm_logistic$table[2, 1]),
    sum(cm_rf$table[2, 1]),
    sum(cm_xgb$table[2, 1])
  )
) %>%
  pivot_longer(cols = -Model, names_to = "Hata_Tipi", values_to = "Sayi")

ggplot(hata_analizi, aes(x = Model, y = Sayi, fill = Hata_Tipi)) +
  geom_col(position = "dodge", width = 0.7) +
  geom_text(aes(label = Sayi), 
            position = position_dodge(width = 0.7), 
            vjust = -0.5, size = 3.5) +
  labs(
    title = "Modellerin Hata Dağılımları",
    subtitle = "False Positive: Yanlış pozitif tahmin, False Negative: Yanlış negatif tahmin",
    y = "Hata Sayısı",
    x = "Model",
    fill = "Hata Tipi"
  ) +
  theme_minimal() +
  theme(
    plot.title = element_text(size = 13, face = "bold"),
    plot.subtitle = element_text(size = 10),
    legend.position = "bottom"
  ) +
  scale_fill_manual(
    values = c("False_Positive" = "#E67E22", "False_Negative" = "#9B59B6"),
    labels = c("False Negative", "False Positive")
  )


9.7 ROC Eğrileri

ROC (Receiver Operating Characteristic) Eğrisi, farklı eşik değerlerinde modelin True Positive Rate (Sensitivity) ve False Positive Rate (1 - Specificity) değerlerini gösterir. Eğri sol üst köşeye ne kadar yakınsa model o kadar iyidir. AUC (Area Under Curve) değeri 1’e yaklaştıkça model mükemmel performans gösterir, 0.5 ise rastgele tahmin anlamına gelir.

roc_logistic <- roc_curve(logistic_pred, truth = Transported, .pred_Yes, 
                          event_level = "second") %>%
  mutate(Model = "Lojistik Regresyon")

roc_rf <- roc_curve(rf_pred, truth = Transported, .pred_Yes, 
                    event_level = "second") %>%
  mutate(Model = "Random Forest")

roc_xgb <- roc_curve(xgb_pred, truth = Transported, .pred_Yes, 
                     event_level = "second") %>%
  mutate(Model = "XGBoost")

roc_all <- bind_rows(roc_logistic, roc_rf, roc_xgb)

ggplot(roc_all, aes(x = 1 - specificity, y = sensitivity, color = Model)) +
  geom_line(linewidth = 1.2) +
  geom_abline(linetype = "dashed", color = "gray50") +
  labs(
    title = "ROC Eğrileri Karşılaştırması",
    subtitle = "Yüksek AUC değeri daha iyi model performansını gösterir",
    x = "1 - Specificity (False Positive Rate)",
    y = "Sensitivity (True Positive Rate)"
  ) +
  theme_minimal() +
  theme(
    legend.position = "bottom",
    plot.title = element_text(size = 14, face = "bold"),
    plot.subtitle = element_text(size = 11)
  ) +
  scale_color_brewer(palette = "Set1")


9.8 Model Karşılaştırma Grafiği

metrikler_long <- metrikler %>%
  pivot_longer(cols = -Model, names_to = "Metrik", values_to = "Deger")

ggplot(metrikler_long, aes(x = Metrik, y = Deger, fill = Model)) +
  geom_col(position = "dodge", width = 0.7) +
  geom_text(aes(label = round(Deger, 3)), 
            position = position_dodge(width = 0.7), 
            vjust = -0.5, size = 3) +
  labs(
    title = "Model Performans Karşılaştırması",
    subtitle = "Tüm metrikler için yüksek değer daha iyidir (1'e yakın = ideal)",
    y = "Değer",
    x = "Performans Metriği"
  ) +
  theme_minimal() +
  theme(
    legend.position = "bottom",
    plot.title = element_text(size = 14, face = "bold")
  ) +
  scale_fill_brewer(palette = "Set2") +
  ylim(0, 1.05)


10 Final Modeller ve Kaggle Submission

Bu aşamada, tüm train verisiyle modelleri yeniden eğitip gerçek test setinde tahmin yapacağım.

10.1 Tüm Train Verisiyle Model Eğitimi

# Final recipe (tüm train verisi için)
final_rec <- recipe(Transported ~ ., data = train_raw) %>%
  step_rm(PassengerId, Name, Cabin) %>%
  step_string2factor(all_nominal_predictors()) %>%
  step_impute_mode(all_nominal_predictors()) %>%
  step_impute_median(all_numeric_predictors()) %>%
  step_dummy(all_nominal_predictors()) %>%
  step_zv(all_predictors())
# Lojistik Regresyon
logistic_wf_final <- workflow() %>%
  add_recipe(final_rec) %>%
  add_model(logistic_spec)
logistic_final <- fit(logistic_wf_final, train_raw)


# Random Forest
rf_wf_final <- workflow() %>%
  add_recipe(final_rec) %>%
  add_model(rf_spec)
rf_final <- fit(rf_wf_final, train_raw)


# XGBoost
xgb_wf_final <- workflow() %>%
  add_recipe(final_rec) %>%
  add_model(xgb_spec)
xgb_final <- fit(xgb_wf_final, train_raw)

10.2 Test Verisi Tahminleri ve Submission Dosyaları

# Test verisinde tahmin yap
pred_logistic_final <- predict(logistic_final, test_raw)
pred_rf_final <- predict(rf_final, test_raw)
pred_xgb_final <- predict(xgb_final, test_raw)

# Yes/No -> True/False formatına çevir (Kaggle formatı)
convert_pred <- function(pred) {
  ifelse(pred$.pred_class == "Yes", "True", "False")
}

# Submission dosyalarını oluştur
submission_logistic <- tibble(
  PassengerId = test_raw$PassengerId,
  Transported = convert_pred(pred_logistic_final)
)

submission_rf <- tibble(
  PassengerId = test_raw$PassengerId,
  Transported = convert_pred(pred_rf_final)
)

submission_xgb <- tibble(
  PassengerId = test_raw$PassengerId,
  Transported = convert_pred(pred_xgb_final)
)

# Dosyaları kaydet
write_csv(submission_logistic, "submission_logistic.csv")
write_csv(submission_rf, "submission_rf.csv")
write_csv(submission_xgb, "submission_xgb.csv")

Oluşturulan Dosyalar:

Submission aşamasında üç farklı dosya oluşturulmuştur: submission_logistic.csv, submission_rf.csv ve submission_xgb.csv dosyaları.

İlk 10 tahmin (örnek - XGBoost):

head(submission_xgb, 10) %>% kable()
PassengerId Transported
0013_01 True
0018_01 False
0019_01 True
0021_01 True
0023_01 True
0027_01 False
0029_01 True
0032_01 True
0032_02 True
0033_01 True

10.3 Kaggle Submission Sonuçları

Modellerin Kaggle platformuna yüklenmesi sonucunda elde edilen skorlar:

Kaggle Submission Sonuçları
Kaggle Submission Sonuçları

11 Sonuç ve Değerlendirme

11.1 Temel Bulgular

Bu projede Spaceship Titanic veri seti üzerinde üç farklı makine öğrenmesi modeli (Lojistik Regresyon, Random Forest, XGBoost) kurdum ve karşılaştırdım.

Model performansları incelendiğinde, deneme_test seti üzerinde üç model de birbirine yakın performans göstermiştir (Accuracy yaklaşık yüzde 79-80). Kaggle skorları açısından Random Forest en yüksek public leaderboard skorunu elde etmiştir (0.79962). XGBoost ikinci sırada yer almıştır (0.79424). Lojistik Regresyon ise 0.79167 ile üçüncü sırada kalmıştır.

Üç model arasındaki fark oldukça küçüktür (en iyi ve en kötü model arasında yalnızca 0.00795 puan fark vardır). Bununla birlikte Random Forest ve XGBoost gibi ensemble yöntemler lojistik regresyondan daha iyi performans göstermiştir. Bu durum veri setindeki karmaşık doğrusal olmayan ilişkilerin ensemble yöntemler tarafından daha iyi yakalandığını göstermektedir.

11.2 Veri Ön İşleme

Cabin değişkenini Deck, CabinNum ve Side olarak ayırmak feature engineering açısından faydalı olmuştur. Eksik değerler tidymodels recipe ile sistematik şekilde doldurulmuştur (kategorik değişkenler için mode, sayısal değişkenler için median). Recipe pattern sayesinde tüm preprocessing adımları tekrar üretilebilir hale gelmiştir.

11.3 Değerlendirme Metrikleri

Confusion Matrix ile modellerin hata türlerini (False Positive, False Negative) detaylı şekilde analiz ettim. ROC-AUC ile eşik değerinden bağımsız genel model performansını ölçtüm. F1-Score ile Precision ve Recall arasındaki dengeyi değerlendirdim. Görselleştirmeler sayesinde (ROC eğrileri ve metrik karşılaştırma grafikleri) modellerin performansını görsel olarak analiz ettim.

11.4 Öğrendiklerim

Tidymodels ekosistemi sayesinde recipe, workflow ve parsnip paketleri ile model kurma ve değerlendirme süreçleri tutarlı ve tekrar üretilebilir hale gelmiştir. Random Forest ve XGBoost gibi ensemble yöntemlerin kompleks ilişkileri yakalamada başarılı olduğunu gözlemledim. Model performansını optimize etmek için doğru hiperparametre kombinasyonunu bulmanın önemli olduğunu öğrendim. Gelecekte k-fold cross-validation kullanarak model değerlendirmesini daha robust hale getirebilirim.