Merhaba Veri Yolcusu! İkinci Durak: Modelleme Atölyesi

Dostlar, ilk durağımız olan ‘Keşif ve Analiz’de verinin bize fısıldadıklarını dinledik. En büyük derdimizin “Teslimat Gecikmesi” olduğunu ve müşterilerimizin ‘acil’ bir ‘bilgi’ ve ‘çözüm’ beklediğini gördük. Artık elimizde verinin mevcut durumunu gösteren bir harita var.

Şimdi atölyemize, yani ‘Modelleme’ durağına geçiyoruz. Burada artık fotoğraf çekmeyi bırakıp, geleceği öngören bir dürbün inşa etmeye çalışacağız. En kritik sorumuz şu: Hangi müşterilerin bizi terk etme riski taşıdığını önceden tahmin edebilir miyiz?

Hadi, atölye önlüklerimizi giyip işe koyulalım!

1. Adım: Eksik Hazine Haritası - Veri Setlerini Birleştirmek

Atölyeye girer girmez fark ettiğimiz ilk şey: Elimizdeki şikayet verisi tek başına kimin gittiğini, kimin kaldığını söylemiyor. Bu, bir tahmin modeli kurmak için yeterli değil.

Çözüm? Tıpkı bir dedektifin farklı ipuçlarını birleştirmesi gibi, biz de farklı bir kaynaktan gelen ikinci bir veri setine ihtiyaç duyuyoruz: musteri_bilgileri.csv. Bu set, müşterilerin üyelik süresi, aylık harcamaları ve en önemlisi terk_durumu (churn) bilgisini içeriyor.

Şimdi bu iki hazine haritasını birleştirelim!

# Gerekli kütüphaneleri yüklüyoruz
library(dplyr)

# Önceki bölümden gelen veriyi ve yeni müşteri verisini yüklüyoruz
# NOT: Bu dosyaların projenizin 'data' klasöründe olduğunu varsayıyoruz.
sikayetler <- read.csv("data/musteri_sikayetleri.csv")
musteri_bilgileri <- read.csv("data/musteri_bilgileri.csv")

# İki veri setini musteri_id üzerinden birleştiriyoruz
# Tıpkı Excel'deki VLOOKUP gibi!
birlesik_veri <- left_join(sikayetler, musteri_bilgileri, by = "musteri_id")

# Birleştirilmiş veriye hızlıca bir göz atalım
glimpse(birlesik_veri)
## Rows: 500
## Columns: 8
## $ sikayet_id         <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, …
## $ musteri_id         <int> 1049, 1065, 1074, 1146, 1122, 1049, 1128, 1047, 102…
## $ sikayet_tarihi     <chr> "2024-01-13", "2024-04-07", "2024-02-10", "2024-06-…
## $ sikayet_kategorisi <chr> "Teslimat Gecikmesi", "Ürün Hasarlı Geldi", "Ürün H…
## $ sikayet_metni      <chr> "Teslimat tarihi geçti ama ürünüm ortada yok.", "Ür…
## $ uyelik_suresi_ay   <int> 35, 33, 9, 20, 42, 35, 46, 4, 45, 29, 19, 46, 8, 27…
## $ aylik_harcama      <dbl> 299.56, 171.37, 105.49, 122.19, 188.13, 299.56, 468…
## $ terk_durumu        <int> 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, …

Harika! Artık tek bir veri setinde hem şikayet detayları hem de müşterinin bizi terk edip etmediği bilgisi (terk_durumu, 1=Terk Etti, 0=Kalmaya Devam Etti) var.

2. Adım: Ham Veriden Anlam Yaratmak - Özellik Mühendisliği

Modeller sayılarla konuşur. Onlara en anlamlı sayıları bizim vermemiz gerekir. Şu anki verimizde her şikayet ayrı bir satır. Modelin işini kolaylaştırmak için, her müşteriye özel bir özet çıkaralım. Mesela her müşterinin toplam şikayet sayısını hesaplayalım.

# Her müşterinin toplam şikayet sayısını ve diğer bilgilerini özetliyoruz
musteri_ozet <- birlesik_veri %>%
  group_by(musteri_id, uyelik_suresi_ay, aylik_harcama, terk_durumu) %>%
  summarise(toplam_sikayet_sayisi = n(), .groups = 'drop') 
  # n() fonksiyonu grup içindeki satırları sayar

# Yeni özet verimize göz atalım
glimpse(musteri_ozet)
## Rows: 146
## Columns: 5
## $ musteri_id            <int> 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, …
## $ uyelik_suresi_ay      <int> 37, 1, 25, 10, 36, 18, 47, 24, 7, 36, 25, 37, 46…
## $ aylik_harcama         <dbl> 402.85, 107.99, 108.09, 82.51, 73.91, 289.34, 10…
## $ terk_durumu           <int> 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, …
## $ toplam_sikayet_sayisi <int> 4, 4, 7, 1, 4, 4, 1, 3, 3, 6, 3, 5, 3, 3, 3, 2, …

İşte bu! Artık her müşteri için tek bir satırımız var ve bu satırda “çok şikayet eden daha mı çabuk gider?” hipotezini test etmemizi sağlayacak toplam_sikayet_sayisi adında güçlü bir özelliğimiz var.

3. Adım: Göreve Uygun Modeli Seçmek - Lojistik Regresyon

Müşterileri iki kategoriden birine (“terk etti” / “terk etmedi”) ayırmaya çalıştığımız için bu bir sınıflandırma problemidir. Bu görev için, sonuçları kolayca yorumlanabilen Lojistik Regresyon modelini kullanacağız. Çünkü bu model, bir müşterinin terk etme olasılığını hesaplamak için harika bir başlangıç noktasıdır.

Dostlara Bir Not:

Arkadaşlar, makine öğrenmesi derya deniz bir konu. Onlarca farklı ve güçlü algoritma var. Biz bu seride, konunun özünü en anlaşılır şekilde kavramak için bir başlangıç yapıyoruz. Lojistik Regresyon, sınıflandırma problemlerini anlamak için en temel, yorumlaması en kolay ve en harika başlangıç noktasıdır. Onu, bir müzik aleti öğrenirken ilk öğrendiğimiz temel notalar gibi düşünebilirsiniz. Merak etmeyin, ilerleyen paylaşımlarımızda hem regresyon (sayısal tahmin) hem de sınıflandırma tekniklerine çok daha derinlemesine dalacağımız seriler de hazırlayacağız. Şimdilik, gelin bu ilk melodimizi birlikte başarıyla çalalım!

4. Adım: Adil Bir Sınav - Veriyi Eğitim ve Test Olarak Ayırmak

Modelimize hem ders notlarını verip hem de sınavda aynı notlardan soru sormak, onun gerçekten öğrenip öğrenmediğini göstermez. Bu yüzden adil bir sınav yapacağız. Verimizin bir kısmıyla (%80) modeli “eğitecek”, kalan kısmıyla da (%20) “test edeceğiz”.

# Makine öğrenmesi için standart olan 'caret' kütüphanesini yüklüyoruz
library(caret)

# Tekrarlanabilir sonuçlar için bir başlangıç noktası belirliyoruz
set.seed(123) 

# createDataPartition ile terk_durumu oranını koruyarak 
# %80'lik eğitim seti için indeksler oluşturuyoruz
indeksler <- createDataPartition(musteri_ozet$terk_durumu, p = 0.8, list = FALSE)

# Oluşturulan indekslere göre eğitim setini seçiyoruz
egitim_seti <- musteri_ozet[indeksler, ]

# Geriye kalanları (indekslerde olmayanları) test seti olarak alıyoruz
test_seti <- musteri_ozet[-indeksler, ]

5. Adım: Geleceği İnşa Etmek - Modelin Kurulması ve Yorumlanması

Şimdi eğitim setimizi kullanarak geleceği tahmin edecek modelimizi inşa etme zamanı!

# Lojistik Regresyon modelini kuruyoruz
# terk_durumu'nu diğer değişkenlere göre tahmin etmeye çalışıyoruz
model <- glm(terk_durumu ~ toplam_sikayet_sayisi + uyelik_suresi_ay + aylik_harcama, 
             data = egitim_seti, 
             family = "binomial") # Sınıflandırma için "binomial" ailesini seçiyoruz

# Modelin özetini inceleyelim
summary(model)
## 
## Call:
## glm(formula = terk_durumu ~ toplam_sikayet_sayisi + uyelik_suresi_ay + 
##     aylik_harcama, family = "binomial", data = egitim_seti)
## 
## Coefficients:
##                        Estimate Std. Error z value Pr(>|z|)    
## (Intercept)           -0.504635   0.761992  -0.662    0.508    
## toplam_sikayet_sayisi  0.644381   0.158996   4.053 5.06e-05 ***
## uyelik_suresi_ay      -0.069209   0.017569  -3.939 8.17e-05 ***
## aylik_harcama         -0.001114   0.001816  -0.613    0.540    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 158.41  on 116  degrees of freedom
## Residual deviance: 120.05  on 113  degrees of freedom
## AIC: 128.05
## 
## Number of Fisher Scoring iterations: 4

Hikayeyi Okumak: Modelin özetini istatistiksel terimlere boğulmadan yorumlayalım:

İşte bu, veriyle kanıtlanmış içgörüdür!

Adım 6: Karne Günü - Modelin Notlarını Kırıyoruz

Peki inşa ettiğimiz bu “tahmin dürbünü” ne kadar net gösteriyor? Bunu anlamak için modelimizi daha önce hiç görmediği test seti ile sınava sokalım. Önce, modelimizin tahminleri ile gerçek sonuçları karşılaştıran meşhur Karışıklık Matrisi’ni (Confusion Matrix) oluşturalım ve ardından tüm kritik notlarını hesaplayalım.

# Test seti üzerinde tahminler yapıyoruz
tahmin_olasikliklari <- predict(model, newdata = test_seti, type = "response")

# Olasılıkları 0.5 eşiğine göre "Terk Etti" (1) veya "Etmedi" (0) olarak sınıflandırıyoruz
tahmin_siniflari <- ifelse(tahmin_olasikliklari > 0.5, 1, 0)

# Karışıklık Matrisi'ni oluşturalım (Hata önlemek için faktör seviyeleriyle)
karne <- table(Gercek = factor(test_seti$terk_durumu, levels=c(0,1)), 
               Tahmin = factor(tahmin_siniflari, levels=c(0,1)))
print("Karne (0.5 Eşiği ile):")
## [1] "Karne (0.5 Eşiği ile):"
print(karne)
##       Tahmin
## Gercek  0  1
##      0 12  2
##      1  3 12
# Şimdi bu karneden temel notlarımızı hesaplayalım
dogruluk <- sum(diag(karne)) / sum(karne)       # Accuracy
kesinlik <- karne[2,2] / sum(karne[,2])         # Precision: TP / (TP + FP)
duyarlilik <- karne[2,2] / sum(karne[2,])       # Recall: TP / (TP + FN)
ozgulluk <- karne[1,1] / sum(karne[1,])         # Specificity: TN / (TN + FP)

# Sonuçları tablo gibi daha okunaklı sunalım
metrikler <- data.frame(
  Metrik = c("Doğruluk (Accuracy)", "Kesinlik (Precision)", "Duyarlılık (Recall)", 
             "Özgüllük (Specificity)"),
  Deger = c(dogruluk, kesinlik, duyarlilik, ozgulluk)
)
print("Model Performans Metrikleri:")
## [1] "Model Performans Metrikleri:"
print(knitr::kable(metrikler, digits = 3))
## 
## 
## |Metrik                 | Deger|
## |:----------------------|-----:|
## |Doğruluk (Accuracy)    | 0.828|
## |Kesinlik (Precision)   | 0.857|
## |Duyarlılık (Recall)    | 0.800|
## |Özgüllük (Specificity) | 0.857|

Karneyi Yorumlayalım:

Dostlar, bu tablo bize en baştan itibaren çok daha dengeli ve güçlü bir modelin resmini çiziyor.

Sonuç: Modelimiz, standart haliyle bile iş hedefimize oldukça iyi hizmet eden, dengeli ve güçlü bir başlangıç noktası sunuyor. Peki, bu dengeyi stratejik olarak daha da iyileştirebilir miyiz?

7. Adım: Dürbünü Stratejik Olarak Ayarlamak - İyi’den Mükemmel’e

Modelimiz, standart 0.5 eşiğiyle bile iş hedefimiz olan kayıp önleme konusunda oldukça başarılı (%80 Recall). Peki, bu dengeyi stratejik olarak daha da iyileştirebilir miyiz?

Şirket stratejimiz, “ne pahasına olursa olsun müşteri kaybetmemek” üzerine kurulu olabilir. Veya tam tersi, “pazarlama bütçesini en verimli şekilde kullanmak” öncelikli olabilir. Elimizdeki bu tablo, bir veri bilimcinin teknik bir analisti stratejik bir danışmana dönüştürdüğü yerdir. Gelin farklı senaryoları inceleyelim:

# Farklı eşik değerleri için metrikleri saklayacak bir data frame oluşturalım
esik_degerleri <- seq(0.25, 0.6, by = 0.05)
sonuclar_df <- data.frame(Esik=numeric(), Dogruluk=numeric(), Kesinlik=numeric(), 
                          Duyarlilik=numeric(), Ozgulluk=numeric())

for (esik in esik_degerleri) {
  tahminler <- ifelse(tahmin_olasikliklari > esik, 1, 0)
  karne <- table(Gercek = factor(test_seti$terk_durumu, levels=c(0,1)), 
                 Tahmin = factor(tahminler, levels=c(0,1)))
  
  # Tüm metrikleri hesapla
  dogruluk <- sum(diag(karne)) / sum(karne)
  kesinlik <- karne[2,2] / sum(karne[,2])
  duyarlilik <- karne[2,2] / sum(karne[2,])
  ozgulluk <- karne[1,1] / sum(karne[1,])
  
  sonuclar_df <- rbind(sonuclar_df, data.frame(Esik=esik, Dogruluk=dogruluk, 
                Kesinlik=kesinlik, Duyarlilik=duyarlilik, Ozgulluk=ozgulluk))
}
sonuclar_df[is.na(sonuclar_df)] <- 0

print("Farklı Eşik Değerleri için Metriklerin Değişimi:")
## [1] "Farklı Eşik Değerleri için Metriklerin Değişimi:"
print(knitr::kable(sonuclar_df, digits = 3))
## 
## 
## | Esik| Dogruluk| Kesinlik| Duyarlilik| Ozgulluk|
## |----:|--------:|--------:|----------:|--------:|
## | 0.25|    0.690|    0.636|      0.933|    0.429|
## | 0.30|    0.724|    0.667|      0.933|    0.500|
## | 0.35|    0.793|    0.737|      0.933|    0.643|
## | 0.40|    0.828|    0.812|      0.867|    0.786|
## | 0.45|    0.828|    0.812|      0.867|    0.786|
## | 0.50|    0.828|    0.857|      0.800|    0.857|
## | 0.55|    0.828|    0.857|      0.800|    0.857|
## | 0.60|    0.793|    0.846|      0.733|    0.857|

Stratejik Seçim:

Bu tablo, bize farklı iş stratejileri için en uygun ayarı seçme imkanı tanıyor.

Senaryo 1: Dengeli Performans (Mevcut Durum - Eşik 0.50)

Mevcut ayarımızda Duyarlılık (Recall) %80, Kesinlik (Precision) ise %85.7 seviyesinde. Bu, her iki metrik arasında harika bir denge kurduğumuz anlamına gelir. Eğer hem müşteri kaybını önlemek hem de pazarlama bütçesini verimli kullanmak eşit derecede önemliyse, bu ayar zaten çok iyi bir başlangıç noktasıdır.

Senaryo 2: Agresif Kayıp Önleme (Recall Odaklı - Eşik 0.35)

Eğer şirket için tek bir müşteriyi bile kaybetmenin maliyeti çok yüksekse, hedefimiz Recall’ı maksimize etmektir. Eşiği 0.35’e çektiğimizde:

Duyarlılık (Recall) %93.3’e fırlıyor! Neredeyse terk edecek tüm müşterileri yakalıyoruz. Bu, kayıp önleme açısından bir zaferdir. Bedeli: Kesinlik (Precision) %73.7’ye düşüyor. Yani artık “riskli” diye etiketlediğimiz her 4 müşteriden 1’den fazlası aslında kalacak olan müşteri. Bu, pazarlama bütçesinin bir kısmının yanlış hedeflere gideceği anlamına gelir.

Senaryo 3: Stratejik Tatlı Nokta (Dengeli İyileştirme - Eşik 0.40)

Peki, Recall’ı anlamlı bir şekilde artırırken, Kesinlik’ten çok fazla ödün vermeyeceğimiz bir “tatlı nokta” var mı? Tabloya göre kesinlikle var! Eşiği 0.40’a çektiğimizde:

Duyarlılık (Recall) %80’den %86.7’ye yükseliyor. Bu, ana hedefimiz için kayda değer bir iyileşme. Kesinlik (Precision) ise %85.7’den sadece %81.2’ye düşüyor. Bu, kazancımıza kıyasla çok makul bir kayıp.

Bu tamamen bir iş kararıdır. Ancak bu analiz, 0.40 eşiğinin, ana hedefimiz olan kayıp önlemede bize önemli bir avantaj sağlarken, pazarlama verimliliğini de yüksek seviyede tutan en stratejik nokta olduğunu gösteriyor. Biz de iş hedefimiz doğrultusunda bu bilinçli adımı atarak modelimizi standart bir modelden, stratejik bir araca dönüştürmüş oluyoruz.

Dostlara Bir Not: Peki Sadece Lojistik Regresyon mu?

Dostlar, bu noktada aklınıza şu soru gelmiş olabilir: “Bu işin tek yolu Lojistik Regresyon mu? Ya daha iyi sonuç veren başka bir yöntem varsa?”

Bu harika bir soru ve sizi daha iyi bir veri bilimci yapacak olan merak tam olarak budur! Makine öğrenmesi, içinde Karar Ağaçları (Decision Trees), Rastgele Orman (Random Forest), Destek Vektör Makineleri (SVM) gibi onlarca farklı ve güçlü aletin bulunduğu zengin bir alet çantası gibidir.

Gerçek bir projede, genellikle birden fazla modeli dener, onları birbiriyle yarıştırır ve problemimize en iyi çözümü sunan “şampiyon modeli” seçeriz. Hatta bu daha gelişmiş modellerin, bizim yaptığımız eşik ayarından çok daha karmaşık ve etkili “hyperparameter tuning” (ince ayar) imkanları vardır.

Peki neden biz en temel olanıyla başladık?

Çünkü bu serideki amacımız, bir gökdelen inşa etmeden önce sağlam bir temel atmaktır. Lojistik Regresyon; bir modelin nasıl kurulduğunu, katsayıların nasıl yorumlandığını, olasılıkların nasıl tahmin edildiğini ve performans metriklerinin iş diline nasıl çevrildiğini anlamak için en mükemmel başlangıç noktasıdır. Attığımız bu temel üzerine, ileride çok daha karmaşık yapıları güvenle inşa edeceğiz.

Merak etmeyin, gelecek maceralarımızda o güçlü aletleri de tek tek çantamızdan çıkarıp ne zaman ve nasıl kullanıldıklarını birlikte keşfedeceğiz!

İkinci Duraktan Notlar ve Ufuktaki Yeni Rota

Bu önemli duraktan ayrılırken, yol haritamıza sadece bir model değil, paha biçilmez dersler ve stratejik kazanımlar ekledik:

Dengeli Bir Başlangıç: Sadece doğru araçları (caret) kullanarak bile, en baştan itibaren iş hedeflerimize oldukça iyi hizmet eden, güçlü ve dengeli bir model prototipi inşa ettik.

Metriklerin Ötesini Okumak: Modelin karnesini sadece “Doğruluk” notuna bakarak değil, iş hedefimiz için en kritik olan Duyarlılık (Recall) gibi derin metrikleri yorumlayarak analiz ettik.

Stratejik Kalibrasyon: Standart bir modelle yetinmedik. Teknik bir aracı, farklı iş senaryolarını (kayıp önleme vs. bütçe verimliliği) değerlendirerek stratejik bir iş aracına dönüştürdük. Elimizdeki dürbünü, tam olarak hedefimize nişan alacak şekilde bilinçli olarak kalibre ettik!

Artık elimizde sadece çalışan bir kod parçası değil; test edilmiş, iş mantığıyla optimize edilmiş ve bir sonraki adıma hazır, akıllı bir prototip var.

Ancak bu akıllı prototip, şimdilik sadece bizim bilgisayarımızdaki bu R dosyasının içinde yaşıyor. Peki, bu bir defalık analizi, her gün kendi kendine çalışan, sonuçlarını ilgili departmanlara gönderen sağlam bir ‘motora’ nasıl dönüştürürüz?

İşte bu sorunun cevabını, yolculuğumuzun üçüncü ve en teknik durağı olan Kodlama ve Uygulama atölyesinde bulacağız. Bir sonraki bölümde, bu akıllı planları ve prototipleri, güvenilir ve otomatize edilmiş bir makineye dönüştürmek için tekrar buluşalım!

Veriyle kalın, lütfen takipte kalın.