Müşteri Kaybı Tahmini: Modelden Arama ListesineCustomer Churn Prediction: From Model to Call List

7.043 telekom müşterisinin verisiyle bir kayıp modeli kurdum. Doğruluk oranı bu veride neredeyse hiçbir şey anlatmıyor; en riskli %10'luk dilim ise kaybedilen gelirin dörtte üçünü taşıyor.I built a churn model on 7,043 telecom customers. Accuracy says almost nothing on this data, while the riskiest 10% carries three quarters of the revenue being lost.

  • Python
  • scikit-learn
  • Sınıflandırma
  • Churn
  • Lift analizi

Abonelik iş modellerinde asıl sorun müşterinin ayrılması değil, ayrıldığını ancak ayrıldıktan sonra öğrenmek. Bu çalışmada 7.043 telekom müşterisinin verisiyle uçtan uca bir kayıp (churn) tahmini kurdum: kimin ayrılma riski taşıdığını, riski hangi davranışların açıkladığını ve sınırlı bir elde tutma kapasitesinin nereye yönlendirilmesi gerektiğini ölçtüm.

Projenin çıktısı bir olasılık listesi değil, öncelik sırasına dizilmiş bir arama listesi. Bunun neden farklı bir şey olduğunu da raporun ortasında somut sayılarla gösteriyorum: bu veride doğruluk oranı neredeyse hiçbir şey anlatmıyor.

Müşteri7.04321 değişken, yinelenen kayıt yok
Kayıp oranı%26,51.869 ayrılmış, 5.174 kalmış
ROC-AUC0,8455 katlı çapraz doğrulama, ±0,014
En riskli %10'da isabet2,85×rastgele aramaya kıyasla

Veri ve temizlik

Veri, IBM'in açık Telco Customer Churn seti: kurgusal bir telekom şirketinin müşteri kayıtları, churn literatüründe karşılaştırma amacıyla standart hâle gelmiş bir veri. Müşteri başına tek satır, 21 sütun; hedef değişken müşterinin aboneliğini sonlandırıp sonlandırmadığı. Raporda yer alan bütün sayılar bu dosyadan hesaplandı — sentetik veri üretilmedi, hiçbir metrik temsilî değil.

BulguKararGerekçe
TotalCharges metin okunuyor; 11 hücre boşlukSayısala çevrildiHam hâlinde sütunun tamamı kategorik sanılıyor
Eksik 11 kaydın tamamında tenure = 0Yapısal eksiklik; medyanla doldurulmadıHenüz faturalandırılmamış yeni abonelikler — değer eksik değil, henüz yok
Yinelenen customerIDKontrol edildi: yokAynı müşterinin iki kez sayılması metrikleri bozardı
customerID tahmin değişkeni değilModelden çıkarıldıKimlik alanları ezberleme ve sahte performans üretir

Eksik veriyi doldurmadan önce desenine bakmak gerekiyor. Burada eksiklik tek bir değişkenle tamamen açıklanıyor; medyan yazmak bu on bir müşteriye var olmayan bir geçmiş atfederdi. On bir satır 7.043'ün içinde kaybolur ama aynı desen büyük veride binlerce satır olsaydı karar ciddi biçimde önem kazanırdı.

Kayıp nerede yoğunlaşıyor?

Model kurmadan önce dört kırılım, modelin sonradan bulduğu yapının büyük kısmını zaten gösteriyor.

Sözleşme türüne göre kayıp oranı
  • Aydan aya3.875 müşteri%42,7
  • 1 yıllık1.473 müşteri%11,3
  • 2 yıllık1.695 müşteri%2,8

Aydan aya sözleşmeli müşterilerin kayıp oranı, iki yıllık sözleşmelilerin 15 katı. Tek bir değişkende bu keskinlikte bir ayrım nadirdir.

Müşteri kıdemine göre kayıp oranı
  • 0–6 ay1.481 müşteri%52,9
  • 7–12 ay705 müşteri%35,9
  • 13–24 ay1.024 müşteri%28,7
  • 25–48 ay1.594 müşteri%20,4
  • 49–72 ay2.239 müşteri%9,5
KırılımGrupMüşteriKayıp oranı
İnternet hizmetiFiber optik3.096%41,9
DSL2.421%19,0
Yok1.526%7,4
Ödeme yöntemiElektronik çek2.365%45,3
Posta çeki1.612%19,1
Banka transferi (otomatik)1.544%16,7
Kredi kartı (otomatik)1.522%15,2
Teknik destekYok3.473%41,6
Var2.044%15,2

Dört kırılım da aynı profili işaret ediyor: bağlılığı zayıf, aylık ödemeli, ek hizmet ve destek almayan, kıdemi düşük müşteri. Ödeme yönteminin bu kadar ayırt edici olması dikkat çekici — otomatik ödeme talimatı vermek, ilişkiyi sürdürme niyetinin göstergesi gibi davranıyor.

Yöntem ve veri sızıntısı

Sayısal değişkenler standartlaştırıldı, kategorikler one-hot kodlandı ve üç model aynı veri hattından geçti. Sınıf dengesizliği için üçünde de class_weight="balanced" kullanıldı: model azınlık sınıfını kaçırmayı daha pahalı sayıyor.

Bütün ön işleme adımları hat içinde
onisle = ColumnTransformer([
    ("num", Pipeline([("i", SimpleImputer(strategy="median")),
                      ("s", StandardScaler())]), sayisal),
    ("cat", Pipeline([("i", SimpleImputer(strategy="most_frequent")),
                      ("o", OneHotEncoder(handle_unknown="ignore"))]), kategorik),
])

X_tr, X_te, y_tr, y_te = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y)

Sızıntının en sinsi biçimi, ölçekleme ortalamasını veya doldurma medyanını tüm veri üzerinden hesaplamaktır; test setinin bilgisi böylece modele sızar ve rapor edilen performans gerçekte ulaşılamayacak kadar iyi çıkar. Bütün dönüşümleri Pipeline içine almak bunu yapısal olarak engelliyor: her katlamada parametreler yeniden ve yalnızca eğitim verisinden öğreniliyor.

Üç model, tek test seti

ModelDoğrulukKesinlikDuyarlılıkF1ROC-AUCPR-AUC5 katlı CV
Lojistik regresyon0,7380,5040,7830,6140,8410,6330,845 ± 0,014
Rastgele orman0,7590,5340,7430,6210,8370,6410,841 ± 0,013
Extra Trees0,7570,5320,7090,6080,8260,6260,830 ± 0,013
Taban oran0,735—0,000—0,5000,265—

Üç modelin çapraz doğrulanmış ROC-AUC'leri 0,830 ile 0,845 arasında; aradaki fark standart sapmaların mertebesinde. Model seçimi bu problemin zor kısmı değil. Yorumlanabilir olduğu ve en yüksek duyarlılığı verdiği için lojistik regresyon üretim adayı olarak seçildi.

Doğruluk neden kullanılamaz?

Müşterilerin %73,5'i kalıyor. "Hiç kimse ayrılmaz" diyen, hiçbir şey öğrenmemiş bir model %73,5 doğruluk alır. Lojistik regresyonun %73,8'i bunun yalnızca 0,3 puan üzerinde.

Doğruluğa bakan biri modeli işe yaramaz sanır. Oysa aynı model, ayrılacak müşterilerin %78,3'ünü yakalıyor: 581 müşteriyi işaretliyor, 293'ü gerçekten ayrılıyor, yalnızca 81 kayıp müşteri gözden kaçıyor. Doğruluğun düşük kalması bir kusur değil, bilinçli bir takas — class_weight modeli kaçırmamaya zorluyor, bedeli de yanlış alarm.

MetrikChurn'deki rolü
DoğrulukYanıltıcı. Dengesiz sınıfta taban oranı aşmak kolay; tek başına raporlanmamalı.
Duyarlılık (recall)Birincil. Kaçırılan her müşteri, geri kazanma şansı olmadan giden gelirdir.
Kesinlik (precision)Maliyet tarafı: düşük kesinlik, elde tutma bütçesinin boşa gitmesi demektir.
ROC-AUCEşikten bağımsız sıralama kalitesi; modelleri kıyaslamak için en uygun tek sayı.
PR-AUCAzınlık sınıfında denge. Taban oran %26,5; PR-AUC 0,633 bunun 2,4 katı.
İlk %N yakalamaİş metriği. Ekip kapasitesiyle doğrudan eşleşen tek ölçü.

Eşik de bir varsayım. 0,5 matematikten değil alışkanlıktan geliyor ve kapasiteye göre değişmeli: 0,3 eşiğinde kayıpların %92,8'i yakalanıyor ama 809 müşteri aranıyor; 0,7 eşiğinde 373 arama ile kayıpların %60,2'sine ulaşılıyor ve her iki aramadan biri isabetli oluyor. Üretimde bu eşik, eğitim verisinden ayrılan bir doğrulama kümesinde seçilmelidir — test setinde değil.

Kayıpla ilişkili etkenler

İki bağımsız yöntem aynı sıralamayı veriyor. Permütasyon öneminde tenure tek başına ROC-AUC'yi 0,152 puan düşürüyor; onu InternetService (0,043), Contract (0,040) ve MonthlyCharges (0,038) izliyor.

DeğişkenKatsayıOdds oranıYorum
Fiber optik internet+0,7052,02Kayıp bahis oranını iki katına çıkarıyor
Aydan aya sözleşme+0,6631,94Bağlayıcılığın olmaması riski neredeyse iki katına çıkarıyor
Elektronik çek+0,2391,27Otomatik ödemeye kıyasla belirgin biçimde riskli
Teknik destek yok+0,1641,18Destek paketi almamak riski artırıyor
Kıdem (standartlaştırılmış)−1,1240,33En güçlü koruyucu etki; bahis oranını üçte bire indiriyor
2 yıllık sözleşme−0,7810,46Uzun sözleşme riski yarıdan fazla azaltıyor

MonthlyCharges katsayısının negatif çıkması (odds oranı 0,52) ilk bakışta çelişki gibi görünüyor; tek başına bakıldığında pahalı paketlerde kayıp daha yüksek. Bu bir hata değil, koşullu etki: model zaten "fiber optik" bilgisine sahip ve pahalı olmanın riskini oradan okuyor. Fiber olduğu bilindikten sonra, yüksek aylık ücret daha kapsamlı ve daha bağlı bir aboneliğe işaret ediyor.

Risk grupları ve aksiyonlar

Model çıktısı olasılık eşiklerine göre üç gruba ayrıldı. Tablodaki kayıp oranları gerçekleşen değerlerdir; test setindeki 1.409 müşterinin gerçek sonuçlarından hesaplandı.

GrupOlasılıkMüşteriGerçekleşen kayıpOrt. aylık ücretOrt. kıdemRisk altındaki aylık gelir
Yüksek> 0,60489264 (%54,0)77,59 $15 ay20.358 $
Orta0,30 – 0,6032083 (%25,9)61,53 $26 ay4.882 $
Düşük< 0,3060027 (%4,5)54,45 $49 ay1.975 $

Gruplamanın işe yaramasının sebebi şu: yüksek risk grubu müşterilerin yalnızca %35'ini kapsıyor ama kaybedilen aylık gelirin %74,8'ini taşıyor. Üstelik bu grubun ortalama aylık ücreti (77,59 $) düşük risk grubundan (54,45 $) belirgin biçimde yüksek — kaybedilen müşteriler aynı zamanda daha değerli müşteriler.

Yüksek risk

Kişisel temas

Ortalama 15 aylık kıdem, aydan aya sözleşme, fiber optik, destek paketi yok. Elde tutma ekibince aranma; sözleşme yükseltme teklifi, ücretsiz teknik destek, fatura veya hız şikâyetinin çözülmesi. Ölçüm: kontrol grubuna kıyasla 90 günlük elde tutma farkı.

Orta risk

Otomatik ve düşük maliyetli

Risk yükseliyor ama henüz kritik değil; kıdem ortalama 26 ay. Otomatik ödeme talimatına geçiş teşviki, kullanılmayan özelliklere yönelik kısa rehberler, yenileme öncesi hatırlatma. Ölçüm: otomatik ödemeye geçiş oranı ve risk puanındaki değişim.

Düşük risk

Rahat bırak

Ortalama 49 aylık kıdem, gerçekleşen kayıp yalnızca %4,5. Olağan iletişim; aşırı kampanya temasından kaçınılmalı. Bu grup zaten kalıyor ve gereksiz temas ters etki yapabilir. Ölçüm: şikâyet sayısı ve uzun vadeli kayıp oranı.

Puandan arama listesine

Asıl soru şuydu: bu hafta kimi arayacağız? Cevabı doğruluk oranı değil, lift veriyor. Müşteriler risk puanına göre sıralanıp üstten belli bir yüzde alındığında o dilimde gerçekte kaç kayıp olduğuna bakılıyor.

Aranan dilimMüşteriDilimdeki kayıp oranıYakalanan kayıpLift
İlk %10140%75,7106 / 374 (%28,3)2,85×
İlk %20281%65,8185 / 374 (%49,5)2,48×
İlk %30422%57,6243 / 374 (%65,0)2,17×
İlk %50704%46,3326 / 374 (%87,2)1,74×

En riskli %10'u aradığında her dört müşteriden üçü gerçekten ayrılacak olan müşteri. Rastgele arama yapılsaydı bu oran %26,5 olurdu. Müşterilerin yarısını arayacak kapasite varsa kayıpların %87'sine ulaşılıyor.

Puandan aramaya
  1. Puanla
  2. Sırala
  3. Kapasiteyle
    Kes
  4. Kontrol Grubuyla
    Ölç

Dört adımın üçü teknik, biri değil. Model, ayrılmaya en yakın müşterileri seçiyor; bu müşterilere yapılan müdahalenin işe yaradığını varsaymak churn projelerinin en yaygın hatası. İşaretlenen müşterilerin rastgele seçilmiş bir bölümüne dokunmayıp iki grubu karşılaştırmak, artımlı etkiyi görmenin tek yolu.

Sınırlar

SınırlamaEtkisi
Veri kesitselHer müşteri için tek anlık görüntü var; kullanımdaki düşüş veya artan şikâyet gibi zaman içindeki davranış değişimi modellenemiyor. Gerçek bir sistemde zaman serisi özellikleri performansı belirgin biçimde artırır.
Kayıp tanımı veriliTahmin ufku seçilebilir değil; üretimde ufuk (ör. 30 gün) iş kararına göre belirlenmelidir.
Kurgusal şirket verisiIBM'in örnek veri seti gerçek bir operasyondan gelmiyor; bulgular yöntem gösterimi olarak güçlü, sektörel genelleme olarak sınırlı.
Nedensellik yokBütün bulgular ilişkisel. "Fiber optiği ucuzlatırsak kayıp azalır" gibi bir çıkarım bu veriyle desteklenemez.
Maliyet verisi yokTemas başına maliyet ve müşteri yaşam boyu değeri bilinmediği için eşik beklenen kâra göre optimize edilemedi; kapasite varsayımıyla ilerlendi.
Adalet denetimi yapılmadıModel çıktılarının demografik segmentler arasında dengeli davranıp davranmadığı ayrıca incelenmelidir.

Sonuç

Üç model de birbirine yakın çıktı: çapraz doğrulanmış ROC-AUC 0,830 ile 0,845 arasında. Model seçimi bu problemin zor kısmı değildi; zor kısım, çıkan olasılığı doğru soruya bağlamaktı.

Doğruluk oranına bakan biri modeli çöpe atardı — %73,8, hiçbir şey yapmayan modelin 0,3 puan üstünde. Lift tablosuna bakan biri ise şunu görüyor: en riskli %10'luk dilimde kayıp oranı %75,7, rastgele aramanın 2,85 katı isabet; yüksek risk grubu ise kaybedilen aylık gelirin dörtte üçünü taşıyor. Aynı model, aynı sayılar — farklı olan tek şey sorulan soru.

Analizin tamamı Python ve scikit-learn ile yapıldı; bütün betiklerde random_state=42 sabitlendi, aynı veriyle aynı sayılar üretilir. Veri seti: IBM Telco Customer Churn (7.043 müşteri, 21 değişken).

In subscription businesses the real problem is not that customers leave, but that you only find out after they have. In this project I built an end-to-end churn prediction on 7,043 telecom customers: who carries the risk of leaving, which behaviours explain that risk, and where a limited retention capacity should be pointed.

The output is not a list of probabilities but a ranked call list. Why that is a different thing is shown with concrete numbers in the middle of the report: on this data, accuracy says almost nothing.

Customers7,04321 variables, no duplicate records
Churn rate26.5%1,869 left, 5,174 stayed
ROC-AUC0.8455-fold cross-validation, ±0.014
Lift in riskiest 10%2.85×against calling at random

Data and cleaning

The data is IBM's open Telco Customer Churn set: customer records from a fictional telecom company, a standard benchmark in churn work. One row per customer, 21 columns; the target is whether the customer ended their subscription. Every number in this report was computed from that file — no synthetic data was generated and no metric is representative.

FindingDecisionReasoning
TotalCharges read as text; 11 cells blankConverted to numericLeft raw, the whole column is treated as categorical
All 11 missing records have tenure = 0Treated as structural; not median-imputedThese are new subscriptions not yet billed — the value is not missing, it does not exist yet
Duplicate customerIDChecked: noneCounting a customer twice would corrupt the metrics
customerID is not a predictorDropped from the modelIdentifier fields invite memorisation and fake performance

Look at the pattern before imputing. Here the missingness is fully explained by a single variable; writing a median would attribute a history these eleven customers never had. Eleven rows vanish inside 7,043, but the same pattern across thousands of rows would make this decision matter a great deal.

Where churn concentrates

Before any modelling, four breakdowns already reveal most of the structure the model later finds.

Churn rate by contract type
  • Month-to-month3,875 customers42.7%
  • One year1,473 customers11.3%
  • Two year1,695 customers2.8%

Month-to-month customers churn at 15 times the rate of two-year contracts. A split that sharp in a single variable is rare.

Churn rate by tenure
  • 0–6 months1,481 customers52.9%
  • 7–12 months705 customers35.9%
  • 13–24 months1,024 customers28.7%
  • 25–48 months1,594 customers20.4%
  • 49–72 months2,239 customers9.5%
BreakdownGroupCustomersChurn rate
Internet serviceFiber optic3,09641.9%
DSL2,42119.0%
None1,5267.4%
Payment methodElectronic check2,36545.3%
Mailed check1,61219.1%
Bank transfer (automatic)1,54416.7%
Credit card (automatic)1,52215.2%
Tech supportNo3,47341.6%
Yes2,04415.2%

All four point to the same profile: a loosely committed, month-to-month customer without add-on services or support and with little tenure. That payment method discriminates this strongly is striking — setting up automatic payment behaves like a signal of intent to stay.

Method and data leakage

Numeric variables were standardised, categoricals one-hot encoded, and three models run through the same pipeline. For the class imbalance all three used class_weight="balanced", which makes missing a churner more expensive.

All preprocessing inside the pipeline
preprocess = ColumnTransformer([
    ("num", Pipeline([("i", SimpleImputer(strategy="median")),
                      ("s", StandardScaler())]), numeric),
    ("cat", Pipeline([("i", SimpleImputer(strategy="most_frequent")),
                      ("o", OneHotEncoder(handle_unknown="ignore"))]), categorical),
])

X_tr, X_te, y_tr, y_te = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y)

The sneakiest form of leakage is computing a scaling mean or an imputation median over the whole dataset: the test set's information reaches the model and the reported performance comes out better than anything achievable in practice. Putting every transform inside a Pipeline prevents this structurally — in each fold the parameters are relearned from the training data alone.

Three models, one test set

ModelAccuracyPrecisionRecallF1ROC-AUCPR-AUC5-fold CV
Logistic regression0.7380.5040.7830.6140.8410.6330.845 ± 0.014
Random forest0.7590.5340.7430.6210.8370.6410.841 ± 0.013
Extra Trees0.7570.5320.7090.6080.8260.6260.830 ± 0.013
Base rate0.735—0.000—0.5000.265—

The three cross-validated ROC-AUCs sit between 0.830 and 0.845 — a gap on the order of the standard deviations. Model selection is not the hard part of this problem. Logistic regression was chosen as the production candidate for being interpretable and giving the highest recall.

Why accuracy cannot be used

73.5% of customers stay. A model that learns nothing and says "nobody leaves" scores 73.5% accuracy. Logistic regression's 73.8% is a mere 0.3 points above that.

Someone reading accuracy would call the model useless. Yet it catches 78.3% of the customers who actually leave: it flags 581 customers, 293 of them really churn, and only 81 churners slip through. The modest accuracy is not a flaw but a deliberate trade — class_weight pushes the model not to miss, and false alarms are the price.

MetricRole in churn
AccuracyMisleading. With imbalanced classes the base rate is easy to beat; never report it alone.
RecallPrimary. Every missed customer is revenue lost with no chance to win it back.
PrecisionThe cost side: low precision means the retention budget is spent on nothing.
ROC-AUCThreshold-free ranking quality; the best single number for comparing models.
PR-AUCBalance in the minority class. The base rate is 26.5%; PR-AUC of 0.633 is 2.4 times that.
Top-N captureThe business metric. The only measure that maps directly onto team capacity.

The threshold is an assumption too. The 0.5 cut-off comes from habit, not mathematics, and should follow capacity: at 0.3, 92.8% of churn is caught but 809 customers are called; at 0.7, 373 calls reach 60.2% of churners and roughly every second call lands. In production that threshold belongs on a validation split carved out of the training data — not on the test set.

What drives churn

Two independent methods give the same ordering. In permutation importance, tenure alone costs 0.152 of ROC-AUC, followed by InternetService (0.043), Contract (0.040) and MonthlyCharges (0.038).

VariableCoefficientOdds ratioReading
Fiber optic internet+0.7052.02Doubles the odds of churning
Month-to-month contract+0.6631.94No commitment nearly doubles the risk
Electronic check+0.2391.27Clearly riskier than automatic payment
No tech support+0.1641.18Going without a support package raises risk
Tenure (standardised)−1.1240.33The strongest protective effect; cuts the odds to a third
Two-year contract−0.7810.46A long contract more than halves the risk

The negative coefficient on MonthlyCharges (odds ratio 0.52) looks like a contradiction at first, since churn is higher among expensive plans on their own. It is not an error but a conditional effect: the model already knows about "fiber optic" and reads the risk of being expensive from there. Given fiber, a higher monthly bill points to a broader, more committed subscription.

Risk groups and actions

The model's output was cut into three groups by probability. The churn rates below are realised values, computed from the actual outcomes of the 1,409 test customers.

GroupProbabilityCustomersRealised churnAvg. monthly chargeAvg. tenureMonthly revenue at risk
High> 0.60489264 (54.0%)$77.5915 months$20,358
Medium0.30 – 0.6032083 (25.9%)$61.5326 months$4,882
Low< 0.3060027 (4.5%)$54.4549 months$1,975

The grouping works because the high-risk band covers only 35% of customers yet carries 74.8% of the monthly revenue that was lost. Its average monthly charge ($77.59) is also clearly higher than the low-risk group's ($54.45) — the customers being lost are also the more valuable ones.

High risk

Personal contact

Average tenure 15 months, month-to-month contract, fiber optic, no support package. A call from the retention team; a contract upgrade offer, free tech support, or resolution of a billing or speed complaint. Measure: 90-day retention difference against a control group.

Medium risk

Automated and low-cost

Risk is rising but not yet critical; average tenure 26 months. Incentives to switch to automatic payment, short guides for unused features, a reminder before renewal. Measure: switch rate to automatic payment and change in risk score.

Low risk

Leave them alone

Average tenure 49 months, realised churn only 4.5%. Normal communication; avoid heavy campaign contact. This group is already staying and needless contact can backfire. Measure: complaint volume and long-term churn.

From score to call list

The original question was: who do we call this week? Accuracy does not answer it; lift does. Sort customers by risk score, take the top slice, and count how many in it actually churned.

Slice calledCustomersChurn rate in sliceChurners capturedLift
Top 10%14075.7%106 / 374 (28.3%)2.85×
Top 20%28165.8%185 / 374 (49.5%)2.48×
Top 30%42257.6%243 / 374 (65.0%)2.17×
Top 50%70446.3%326 / 374 (87.2%)1.74×

Call the riskiest 10% and three out of every four are genuinely about to leave. Calling at random would hit 26.5%. With capacity to call half the base, 87% of the churn is reached.

From score to call
  1. Score
  2. Rank
  3. Cut by
    Capacity
  4. Measure vs
    Control

Three of the four steps are technical and one is not. The model picks the customers closest to leaving; assuming that an intervention on them works is the most common mistake in churn projects. Leaving a randomly chosen share of the flagged group untouched and comparing the two is the only way to see the incremental effect.

Limitations

LimitationEffect
The data is cross-sectionalOne snapshot per customer; behavioural change over time — falling usage, rising complaints — cannot be modelled. In a real system, time-series features would lift performance considerably.
The churn definition is givenThe prediction horizon is not selectable; in production it (e.g. 30 days) should follow the business decision.
Fictional company dataIBM's sample set does not come from a real operation; the findings are strong as a demonstration of method, limited as a sector generalisation.
No causalityAll findings are associational. "Cheaper fiber would reduce churn" cannot be supported by this data.
No cost dataWithout contact cost and customer lifetime value, the threshold could not be optimised for expected profit; a capacity assumption was used instead.
No fairness auditWhether the model behaves evenly across demographic segments needs separate examination.

Conclusion

All three models landed close together: cross-validated ROC-AUC between 0.830 and 0.845. Model selection was not the hard part of this problem; attaching the resulting probability to the right question was.

Someone reading accuracy would have thrown the model away — 73.8%, 0.3 points above doing nothing. Someone reading the lift table sees this: in the riskiest 10% the churn rate is 75.7%, 2.85 times better than calling at random, and the high-risk group carries three quarters of the monthly revenue lost. Same model, same numbers — the only difference is the question being asked.

The whole analysis was done in Python with scikit-learn; random_state=42 is fixed in every script, so the same data produces the same numbers. Dataset: IBM Telco Customer Churn (7,043 customers, 21 variables).