Etiket: veri analizi

  • Banka Kampanyası Verisi: Kim Vadeli Mevduat Açar?

    Banka Kampanyası Verisi: Kim Vadeli Mevduat Açar?

    Bir banka, müşterilerini telefonla arayıp vadeli mevduat hesabı açmalarını teklif ediyor. On kişiyi arasın, dokuzu “hayır” diyor. Peki kimi aramalı? Bu hafta sonu projesinde gerçek bir banka kampanyasının verisiyle bu soruya cevap arıyoruz.

    Yol boyunca veri biliminin üç klasik tuzağına da basacağız: gizli eksik değerler, yanıltıcı bir başarı ölçüsü ve modelin gelecekten haber alması.

    Tüm kodlar Google Colab’da çalışıyor. Colab tarayıcıda açılıyor ve kodu Google’ın sunucularında çalıştırıyor; bu projede kullandığımız pandas, scikit-learn ve matplotlib orada hazır geliyor. Yani bilgisayarına hiçbir şey kurman gerekmiyor, bir Google hesabı yeterli: Colab’da aç

    Veri seti

    Veri, bir Portekiz bankasının 2008-2010 yılları arasındaki telefonla pazarlama kampanyalarından geliyor ve UCI Machine Learning Repository’de CC BY 4.0 lisansıyla paylaşılıyor. Her satır anonim bir müşteriyle yapılan bir görüşme; isim ya da kimlik bilgisi yok.

    Sütunlar dört gruba ayrılıyor:

    • Müşteri: yaş, meslek, medeni durum, eğitim, kredi durumu
    • Bu kampanyadaki görüşme: ay, gün, iletişim türü, görüşme süresi, kaçıncı arama olduğu
    • Geçmiş: önceki kampanyada aranıp aranmadığı ve sonucu
    • Ekonomi: istihdam, enflasyon endeksi, faiz gibi o döneme ait göstergeler

    Hedef sütun y: müşteri vadeli mevduat açtı mı (yes), açmadı mı (no)?

    Veriyi indirmek

    UCI dosyayı iç içe iki zip olarak veriyor. Diske tek tek açmak yerine bellekte açıyoruz:

    import io
    import os
    import zipfile
    import numpy as np
    import pandas as pd
    import requests
    DOSYA = "bank-additional-full.csv"
    URL = "https://archive.ics.uci.edu/static/public/222/bank+marketing.zip"
    def veriyi_indir():
    dis = zipfile.ZipFile(io.BytesIO(requests.get(URL, timeout=60).content))
    ic_adi = [a for a in dis.namelist() if a.endswith("bank-additional.zip")][0]
    ic = zipfile.ZipFile(io.BytesIO(dis.read(ic_adi)))
    csv_adi = [a for a in ic.namelist() if a.endswith(DOSYA)][0]
    with open(DOSYA, "wb") as f:
    f.write(ic.read(csv_adi))
    if not os.path.exists(DOSYA):
    veriyi_indir()
    df = pd.read_csv(DOSYA, sep=";")
    print(df.shape)
    (41188, 21)

    sep=";" kısmına dikkat et. Bu dosyada ayırıcı virgül değil, noktalı virgül. Vermezsen pandas her satırı tek bir uzun sütun sanır.

    İlk bakış: on kişiden biri

    print("Tekrarlanan satır:", df.duplicated().sum())
    df = df.drop_duplicates().reset_index(drop=True)
    df["hedef"] = (df["y"] == "yes").astype(int)
    print(f"Vadeli mevduat açanların oranı: %{df['hedef'].mean() * 100:.1f}")
    Tekrarlanan satır: 12
    Vadeli mevduat açanların oranı: %11.3

    12 tekrar eden satırı sildikten sonra elimizde 41.176 görüşme kalıyor ve bunların yalnızca yüzde 11,3’ü “evet” ile bitmiş. Bir sınıfın diğerinden çok daha az olduğu bu duruma dengesiz sınıf deniyor. Aklında tut, birazdan canımızı sıkacak.

    Gizli eksikler: unknown ve 999

    df.isna().sum() bu veride her sütun için sıfır gösterir. Ama bu, eksik veri olmadığı anlamına gelmiyor. Eksikler iki kılıkta saklanıyor:

    • Kategorik sütunlarda "unknown" yazısı. En çok default sütununda (kredi borcunu ödememiş mi?): 8.596 satır, yani neredeyse her beş kişiden biri.
    • pdays sütununda 999 değeri. Müşterinin daha önce hiç aranmadığı anlamına geliyor ve satırların yüzde 96’sında bu değer var.

    Bu satırları silmiyoruz. “Bu soruya cevap vermemiş olmak” da bir bilgi; unknown kendi başına bir kategori olarak kalıyor. 999’u ise ortalamaya katılırsa sonucu saçma bir yere çekecek sıradan bir sayı gibi değil, “hiç” anlamında okuyoruz.

    Yeni bir veri setine başlarken isna() ile yetinme. Her kategorik sütunun value_counts() çıktısına bir kez göz at; eksikler genelde orada saklanır.

    Keşif: hangi grupta evet oranı yüksek?

    Aynı hesabı birçok sütun için yapacağımızdan küçük bir fonksiyon yazıyoruz:

    def oran_tablosu(sutun, sira=None):
    t = df.groupby(sutun, observed=True)["hedef"].agg(gorusme="size", evet_orani="mean")
    if sira is not None:
    t = t.reindex(sira)
    t["evet_orani"] = (t["evet_orani"] * 100).round(1)
    return t
    oran_tablosu("poutcome")
    Önceki kampanyanın sonucuGörüşmeEvet oranı
    Başarılı (evet demiş)1.373%65,1
    Başarısız4.252%14,2
    Daha önce aranmamış35.551%8,8

    Geçen sefer “evet” diyen müşterilerin üçte ikisi bu sefer de evet demiş. Tek bir sütun, ortalamanın neredeyse altı katı bir oran gösteriyor.

    Aynı fonksiyonla baktığımız diğer gruplardan öne çıkanlar:

    • İletişim türü: Cep telefonundan aranan müşterilerde oran %14,7, sabit hattan arananlarda %5,2.
    • Yaş: 66 yaş ve üstünde %46,9, 17-25 arasında %21,0. Aradaki yaş grupları %8-15 arasında kalıyor. Ama 66+ grubunda sadece 618 görüşme var.
    • Arama sayısı: İlk aramada oran %13,0; altı ve daha fazla aranmış kişilerde %5,5. Aynı müşteriyi ısrarla aramak işe yaramıyor gibi.

    Ay tablosu ve korelasyon tuzağı

    AYLAR = ["mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"]
    oran_tablosu("month", AYLAR)

    Mart, eylül, ekim ve aralıkta evet oranı %44 ile %51 arasında. Mayıs’ta ise %6,4. “Demek ki martı beklemek lazım” demek çok cazip.

    Ama görüşme sayılarına bak: mayısta 13.767 görüşme var, aralıkta 182. Az aranan aylarda banka belki de zaten ilgili olduğunu bildiği müşterileri aradı. Bu tablo ayın sonucu değiştirdiğini göstermiyor; sadece ikisinin birlikte hareket ettiğini gösteriyor. Korelasyon, nedensellik değildir.

    duration tuzağı: veri sızıntısı

    duration, görüşmenin saniye cinsinden süresi:

    df.groupby("y")["duration"].median()
    y
    no 164.0
    yes 449.0

    Evet diyenlerle görüşme, ortanca değere göre neredeyse üç kat uzun sürmüş. Doğal: ikna olan müşteriyle hesap açmanın detaylarını konuşuyorsun. Bu sütun modele girerse sonucu çok iyi tahmin eder.

    Sorun şu: bankanın asıl sorusu “kimi arayalım?” ve bu soru arama yapılmadan önce soruluyor. O anda görüşmenin ne kadar süreceğini bilemezsin. Tahmin anında elinde olmayacak bir bilgiyi modele vermeye veri sızıntısı (data leakage) deniyor. Veri setinin kendi açıklaması da gerçekçi bir model için bu sütunun çıkarılmasını öneriyor. Biz de çıkarıyoruz.

    Model: lojistik regresyon

    from sklearn.compose import ColumnTransformer
    from sklearn.linear_model import LogisticRegression
    from sklearn.model_selection import train_test_split
    from sklearn.pipeline import Pipeline
    from sklearn.preprocessing import OneHotEncoder, StandardScaler
    CIKAR = ["y", "hedef", "duration", "yas_grubu", "arama_sayisi"]
    X = df.drop(columns=CIKAR)
    y = df["hedef"]
    X_egitim, X_test, y_egitim, y_test = train_test_split(
    X, y, test_size=0.25, stratify=y, random_state=42)
    kategorik = X.select_dtypes(exclude="number").columns.tolist()
    sayisal = X.select_dtypes(include="number").columns.tolist()
    def model_kur(kategorik, sayisal):
    hazirlik = ColumnTransformer([
    ("kategorik", OneHotEncoder(handle_unknown="ignore"), kategorik),
    ("sayisal", StandardScaler(), sayisal),
    ])
    return Pipeline([
    ("hazirlik", hazirlik),
    ("model", LogisticRegression(max_iter=2000, class_weight="balanced")),
    ])
    model = model_kur(kategorik, sayisal)
    model.fit(X_egitim, y_egitim)
    olasilik = model.predict_proba(X_test)[:, 1]
    tahmin = (olasilik >= 0.5).astype(int)

    (yas_grubu ve arama_sayisi, keşif bölümünde tablolar için eklediğimiz yardımcı sütunlar; not defterinde oluşturuluyorlar.)

    Satır satır ne yaptık:

    • stratify=y: Eğitim ve test setinde evet oranı aynı kalsın. Dengesiz veride bunu unutursan test setine şans eseri çok az “evet” düşebilir.
    • OneHotEncoder: “meslek = öğrenci” gibi metin değerlerini 0-1 sütunlarına çevirir. Model sadece sayılarla çalışır.
    • StandardScaler: Yaş (17-98) ile faiz oranı (0-5) gibi farklı ölçekteki sayıları aynı ölçeğe getirir.
    • class_weight="balanced": Az olan “evet” sınıfındaki hataları daha ağır cezalandır. Bunu yazmazsan model, hemen herkese “hayır” demenin kolay yolunu seçer.
    • Pipeline: Hazırlık ve modeli tek bir nesnede toplar. Böylece test verisine eğitimde öğrenilen dönüşümün aynısı uygulanır.

    Accuracy neden yanıltır?

    Hiçbir şey öğrenmemiş, herkese “hayır” diyen bir model düşün:

    from sklearn.metrics import accuracy_score
    herkese_hayir = np.zeros(len(y_test), dtype=int)
    print(f"Herkese 'hayır' diyen model accuracy: %{accuracy_score(y_test, herkese_hayir) * 100:.1f}")
    print(f"Bizim modelin accuracy'si: %{accuracy_score(y_test, tahmin) * 100:.1f}")
    Herkese 'hayır' diyen model accuracy: %88.7
    Bizim modelin accuracy'si: %83.3

    Hiçbir şey öğrenmemiş model daha “başarılı” görünüyor. Oysa bankaya tek bir müşteri kazandırmaz. Dengesiz veride accuracy, çoğunluk sınıfını ne kadar iyi tahmin ettiğini ölçer; bizi ilgilendiren azınlık sınıfı ise kaybolur.

    Bu yüzden başka ölçülere bakıyoruz. Test setindeki 10.294 görüşmede bizim model:

    Sayı
    Doğru yakalanan “evet”742
    Kaçırılan “evet”418
    Boşa aranan (evet tahmin edildi, hayır dedi)1.300
    Doğru “hayır”7.834
    • Recall (duyarlılık): Gerçekten evet diyenlerin yüzde kaçını yakaladık? 742 / 1.160 = %64
    • Precision (kesinlik): Evet dediğimiz kişilerin yüzde kaçı gerçekten evet dedi? 742 / 2.042 = %36
    • ROC-AUC: Model, rastgele seçilmiş bir “evet”i rastgele seçilmiş bir “hayır”ın önüne ne sıklıkla koyuyor? 0,5 yazı tura, 1 kusursuz. Bizim model: 0,80

    Precision’ın %36 olması kötü gibi görünebilir. Ama rastgele arasan bu oran %11 olurdu; model aynı emekle üç kattan fazla müşteri buluyor.

    Asıl soru: kimi önce aramalı?

    Banka herkesi arayamaz. Modelin verdiği olasılığa göre müşterileri sıralayıp onluk dilimlere ayıralım. 1. dilim, modelin en umutlu bulduğu yüzde 10.

    sirali = pd.DataFrame({"gercek": y_test.to_numpy(), "olasilik": olasilik})
    sirali = sirali.sort_values("olasilik", ascending=False).reset_index(drop=True)
    sirali["dilim"] = sirali.index * 10 // len(sirali) + 1
    dilimler = sirali.groupby("dilim")["gercek"].agg(kisi="size", evet="sum", evet_orani="mean")
    Modelin sıralamasına göre 10 müşteri diliminde evet oranı. 1. dilimde %50, 2. dilimde %23; geri kalan dilimler %3 ile %10 arasında. Rastgele aramada oran %11.

    Modelin en umutlu bulduğu yüzde 10’luk dilimde 1.030 kişiden 510’u evet demiş: yaklaşık her iki kişiden biri. Bu dilim, test setindeki tüm evetlerin yüzde 44’ünü tek başına yakalıyor. Yani banka, listenin sadece onda birini arayarak müşterilerin neredeyse yarısına ulaşabilir.

    Bir model, her şeyi doğru tahmin etmek zorunda değil. Bazen tek işi doğru sıralamaktır.

    Sızıntılı model ne kadar iyi görünürdü?

    Aynı modeli bir de duration dahil kurduğumuzda (not defterinin 10. bölümü):

    duration olmadan ROC-AUC: 0.802
    duration ile ROC-AUC: 0.940

    0,94 çok daha parlak duruyor ve bir sunumda insanları kolayca etkiler. Ama gerçek hayatta kullanılamaz. Bir model fazla iyi çıktığında ilk soracağın soru şu olmalı: Bu bilgi, tahmin anında elimde olacak mı?

    Bu projeden kalanlar

    • Eksik veri her zaman NaN olarak gelmez; unknown ve 999 gibi kılıklara bak.
    • Grup oranlarına bakarken görüşme sayısını da yaz. Küçük gruplardaki uç oranlar akıl karıştırır.
    • Dengesiz veride accuracy’ye güvenme; recall, precision ve ROC-AUC’ye bak.
    • Modeli işin sorusuna göre değerlendir. Burada soru “kimi önce arayalım?” olduğu için sıralama kalitesi asıl ölçü.
    • “Fazla iyi” sonuçlarda veri sızıntısından şüphelen.

    Kendin dene

    1. class_weight="balanced" parametresini sil. Recall ve precision nasıl değişti?
    2. Eşiği 0,5 yerine 0,3 ve 0,7 yap. Yakalanan ve boşa aranan kişi sayıları nasıl değişiyor?
    3. LogisticRegression yerine RandomForestClassifier dene. ROC-AUC artıyor mu?
    4. education sütunu için oran_tablosu çalıştır. Ne görüyorsun?

    Kaynak ve lisans

    Moro, S., Cortez, P. ve Rita, P. (2014). A Data-Driven Approach to Predict the Success of Bank Telemarketing. Decision Support Systems, 62, 22-31. Veri: UCI Machine Learning Repository, Bank Marketing, CC BY 4.0.

  • R Nedir, Python Varken Neden R? (R Dersi 1)

    R Nedir, Python Varken Neden R? (R Dersi 1)

    Blogda Python serisi devam ederken bugün yanına yeni bir seri ekliyoruz: R dersleri. Her salı ve perşembe bir R dersi yayımlanacak; Python dersleri de her zamanki gibi sürecek.

    İlk soru belli: Python bu kadar yaygınken neden bir dil daha? Bu derste R’ın ne olduğunu, nerede kullanıldığını ve Python’dan nerede ayrıldığını anlatacağım. Bir de ilk R kodlarını yazacağız, böylece farkı kendi gözünle göreceksin.

    R nedir?

    R, istatistiksel hesaplama ve veri analizi için geliştirilmiş, ücretsiz ve açık kaynaklı bir programlama dili. 1990’larda Yeni Zelanda’daki Auckland Üniversitesi’nde iki istatistikçi, Ross Ihaka ve Robert Gentleman tarafından başlatıldı. Adı da kısmen iki yaratıcısının isimlerinin baş harfinden geliyor.

    Bu köken R’ı anlamanın anahtarı. Python her işe yarayan genel amaçlı bir dil olarak doğdu; veri analizi sonradan eklenen kütüphanelerle geldi. R ise doğrudan istatistikçiler için, istatistikçiler tarafından tasarlandı. Ortalama, regresyon, hipotez testi gibi işler dilin içine gömülü.

    R’ın gücünün büyük kısmı CRAN (Comprehensive R Archive Network) adı verilen paket deposundan geliyor. Dünyanın dört bir yanındaki araştırmacıların yazdığı çok sayıda paket burada; yeni bir istatistik yöntemi çoğu zaman ilk R paketi olarak yayımlanıyor.

    R nerelerde kullanılıyor?

    • Akademik araştırma: Tez ve makalelerdeki istatistiksel analizler.
    • Sağlık ve ilaç: Biyoistatistik, klinik araştırmalar, epidemiyoloji.
    • Finans ve risk: Zaman serileri, risk modelleri.
    • Anket ve sosyal bilimler: Anket verisinin analizi ve raporlanması.
    • Veri görselleştirme: ggplot2 paketi, veri görselleştirmenin en bilinen araçlarından biri.
    • Raporlama: Kod, grafik ve yazıyı tek bir belgede birleştiren R Markdown ve Quarto.

    İlk R kodların

    Python derslerinden tanıdığın not örneğiyle başlayalım:

    notlar <- c(70, 85, 40, 90, 45)
    mean(notlar) # 66
    notlar + 5 # 75 90 45 95 50
    notlar[notlar >= 50] # 70 85 90

    İlk satırda iki yeni şey var:

    • <- R’da atama işareti, Python’daki = gibi. = de çalışıyor ama R topluluğunda gelenek <-.
    • c() birden fazla değeri bir araya getirip bir vektör oluşturuyor. Vektör R’ın temel veri yapısı; R Dersi 4’te ayrıntılı bakacağız.

    Asıl ilginç olan ikinci ve üçüncü satırlar. notlar + 5 bütün notlara tek seferde 5 ekliyor. Python’da aynı iş için bir döngü ya da liste üreteci yazman gerekirdi:

    # Python'da
    notlar = [70, 85, 40, 90, 45]
    print([n + 5 for n in notlar])
    print([n for n in notlar if n >= 50])

    R’da işlemler varsayılan olarak vektörün bütün elemanlarına uygulanıyor. Buna vektörizasyon deniyor ve R’da kod yazmanın doğal yolu bu.

    Dikkat: R saymaya 1’den başlar

    notlar[1] # 70, yani ilk eleman

    Python’da ilk eleman notlar[0] idi. R’da ise notlar[1]. Python’dan gelenlerin ilk haftalarda en çok takıldığı yer burası; notlar[0] yazarsan R hata vermez, boş bir sonuç döndürür.

    Tek satırda özet

    summary(notlar)
       Min. 1st Qu.  Median    Mean 3rd Qu.    Max.
         40      45      70      66      85      90

    En küçük, en büyük, ortalama, medyan ve çeyrekler tek komutla. Bu, R’ın istatistik için tasarlandığının küçük ama güzel bir örneği.

    Tablolarla çalışmak: data.frame

    R’da tablo verisi için hazır bir yapı var: data.frame. Python’da bunun için pandas kütüphanesini yüklemen gerekiyor; R’da dilin içinde geliyor.

    ogrenciler <- data.frame(
    ad = c("Ayşe", "Mert", "Zeynep", "Can", "Elif"),
    bolum = c("Bilgisayar", "Elektrik", "Bilgisayar", "Elektrik", "Bilgisayar"),
    notu = notlar
    )
    ogrenciler[ogrenciler$notu >= 50, ] # geçen öğrenciler
    aggregate(notu ~ bolum, data = ogrenciler, FUN = mean)
           bolum     notu
    1 Bilgisayar 51.66667
    2   Elektrik 87.50000

    $ işareti tablodan bir sütun seçiyor: ogrenciler$notu. Son satırdaki notu ~ bolum ifadesi R’ın formül yazımı; “notu bölüme göre” diye okunuyor. Bu yazımı birazdan bir kez daha göreceğiz.

    R’ın asıl parladığı yer: istatistiksel modeller

    R, içinde hazır veri setleriyle geliyor. Bunlardan biri mtcars: 32 arabanın ağırlık, motor gücü ve yakıt tüketimi gibi bilgileri. “Araba ağırlaştıkça yakıt verimi nasıl değişiyor?” sorusunu tek satırla cevaplayabiliriz:

    model <- lm(mpg ~ wt, data = mtcars)
    coef(model)
    (Intercept) wt
    37.29 -5.34

    lm doğrusal regresyon (linear model) kuruyor. Sonuç şunu söylüyor: Bu veri setinde arabaların ağırlığı her 1000 pound arttığında yakıt verimi galon başına ortalama 5,34 mil düşüyor. summary(model) yazarsan da modelin istatistiksel olarak anlamlı olup olmadığını gösteren tam bir rapor alıyorsun.

    Python’da aynı işi yapabilirsin, ama önce birkaç kütüphane yüklemen ve veriyi hazırlaman gerekiyor. R’da regresyon, t-testi, varyans analizi gibi yöntemlerin hepsi dilin bir parçası.

    Python ile R yan yana

    PythonR
    DoğuşuGenel amaçlı programlama diliİstatistik ve veri analizi dili
    Güçlü olduğu yerYazılım geliştirme, otomasyon, makine öğrenimi, yapay zekaİstatistiksel modeller, akademik analiz, görselleştirme, raporlama
    Tablo verisipandas kütüphanesiyledata.frame dilin içinde
    İlk elemanliste[0]vektor[1]
    Atama=<-
    Tipik kullanıcıYazılımcı, veri bilimci, ML mühendisiİstatistikçi, araştırmacı, veri analisti

    Peki hangisini öğrenmeli?

    Bu bir “ya o ya bu” sorusu değil. İki dil çoğu zaman aynı ekipte, hatta aynı projede birlikte kullanılıyor.

    • Yazılım geliştirme, otomasyon ya da yapay zeka hedefliyorsan Python’la devam et; R’ı sonra ekleyebilirsin.
    • İstatistik, akademi, sağlık araştırmaları ya da anket analizi gibi alanlara yöneliyorsan R seni çok hızlandırır.
    • Veri analisti olmak istiyorsan ikisini de tanımak CV’nde fark yaratır. Hedeflediğin ilanları oku, hangi dilin istendiğine bak.

    Bir dili iyi öğrendikten sonra ikincisi çok daha kolay geliyor. Değişken, döngü, fonksiyon, tablo gibi kavramlar aynı; değişen sadece yazım. Bu yüzden R serisini Python serisiyle paralel götürüyoruz: Python’da öğrendiğin bir kavramın R’daki karşılığını görmek ikisini de pekiştirecek.

    R’ı nerede çalıştıracağım?

    Üç yol var, ikisi kurulum gerektirmiyor:

    • Google Colab: Python dersleri için kullandığımız Colab’da R da çalışıyor. Yeni bir not defteri aç, çalışma zamanı türünü R olarak değiştir.
    • Posit Cloud: RStudio’nun tarayıcıda çalışan sürümü. Ücretsiz bir hesapla başlayabilirsin.
    • R ve RStudio kurulumu: Kendi bilgisayarında çalışmak için. R Dersi 2’de adım adım anlatıyorum.

    Bu dersteki kodları şimdiden Colab’da deneyebilirsin.

    Bu derste ne öğrendik?

    • R, istatistikçiler tarafından istatistik için tasarlanmış, ücretsiz ve açık kaynaklı bir dil.
    • R’da atama <- ile yapılır, vektörler c() ile kurulur ve saymaya 1’den başlanır.
    • İşlemler bütün vektöre birden uygulanır: vektörizasyon.
    • Tablo verisi için data.frame, istatistiksel modeller için lm gibi fonksiyonlar dilin içinde.
    • Python ile R rakip değil; hedefine göre biri ya da ikisi.

    Sıradaki ders

    R Dersi 2’de R ve RStudio’yu kendi bilgisayarına kuruyoruz.

    Devamını oku