Müşteri Kaybı (Churn) Tahmini: Modelden Arama Listesine

7.043 telekom müşterisinin verisiyle bir kayıp tahmini kuruyor; doğruluk oranının neden yanıltıcı olduğunu ve modeli "bu hafta kimi arayacağız?" sorusunun cevabına nasıl çevireceğimizi gösteriyorum.

  • Makine Öğrenmesi
  • İstatistik

Churn modeli kurmak zor değil. Zor olan, modelin ürettiği olasılığı "bu hafta kimi arayacağız?" sorusunun cevabına çevirmek. Bu yazıda 7.043 telekom müşterisinin verisiyle baştan sona bir kayıp tahmini kuruyorum — ve modelin doğruluk oranının neden yanıltıcı bir başarı ölçüsü olduğunu sayılarla gösteriyorum.

Veri, IBM'in yayımladığı ve churn çalışmalarında standart hâline gelmiş Telco Customer Churn seti: 21 sütun, müşteri başına tek satır, hedef değişken müşterinin ayrılıp ayrılmadığı.

“Bir churn modelinin değeri, kaç müşteriyi doğru sınıflandırdığında değil; sınırlı sayıda aramayla kaç kayıp müşteriye ulaşabildiğinde ortaya çıkar.”

1. Problem ve Taban Oran

Önce bilinmesi gereken tek sayı var: 7.043 müşterinin 1.869'u ayrılmış, yani kayıp oranı %26,5. Bu, bütün değerlendirmenin zeminini belirliyor.

Veri

7.043 müşteri

21 değişken, yinelenen müşteri kaydı yok.

Hedef

Churn (evet/hayır)

1.869 ayrılmış, 5.174 kalmış.

Taban oran

%73,5

"Hiç kimse ayrılmaz" diyen modelin doğruluğu.

Bölme

5.634 / 1.409

Tabakalı %80/%20; kayıp oranı iki sette de %26,5.

%73,5 rakamını aklında tut. Hiçbir şey öğrenmeyip her müşteriye "kalacak" diyen bir model bu doğruluğu elde eder. Dolayısıyla "modelimiz %75 doğrulukta" cümlesi, bu veride neredeyse hiçbir şey anlatmıyor. Bu yazıdaki en önemli sayı bu.

2. Kayıp Nerede Yoğunlaşıyor?

Model kurmadan önce veriye bakmak, modelin sonradan bulacağı şeyin çoğunu zaten gösteriyor. Dört kırılım yeterli.

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 kadar keskin bir ayrım nadirdir; modelin bu sütuna yüklenmesini şimdiden bekleyebiliriz.

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
İnternet hizmeti yok1.526%7,4

Üç tablo da aynı yöne işaret ediyor: bağlılığı zayıf, aylık ödemeli, destek almayan müşteri en riskli profil. Elektronik çekle ödeyenlerin kayıp oranının otomatik ödeme yapanların üç katı olması özellikle dikkat çekici — ödeme yöntemi burada bir niyet göstergesi gibi davranıyor.

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

İlk altı ay kritik: yeni müşterilerin yarısından fazlası gidiyor. Kıdem arttıkça risk tek yönlü düşüyor ve 49 ayı geçenlerde %9,5'e iniyor. Bu, elde tutma bütçesinin nereye gitmesi gerektiğini tek başına söylüyor.

3. "Eksik" Veri Gerçekten Eksik mi?

TotalCharges sütununda 11 boş hücre var. Refleks, medyanla doldurmak ya da o satırları atmak olur. Önce neden eksik olduklarına bakmak gerekiyor.

Eksikliğin deseni
df["TotalCharges"] = pd.to_numeric(
    df["TotalCharges"].replace(r"^\s*$", pd.NA, regex=True),
    errors="coerce")

df.loc[df["TotalCharges"].isna(), "tenure"].unique()
# array([0])  -> hepsinin kidemi 0 ay

On bir müşterinin tamamının kıdemi sıfır. Yani bu insanlar daha ilk faturalarını almamış yeni abonelikler; toplam ücretleri eksik değil, henüz yok. Medyanla doldurmak bu müşterilere var olmayan bir geçmiş yazmak olurdu.

Eksik veriyi doldurmadan önce desenine bak. Rastgele eksiklik ile yapısal eksiklik farklı şeylerdir. Burada eksiklik tek bir değişkenle (tenure = 0) tamamen açıklanıyor; doğru hamle ya sıfır yazmak ya da "yeni müşteri" bilgisini modele ayrı bir değişken olarak vermek. 11 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ı.

4. Üç Model, Tek Test Seti

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

Veri hattı ve modeller
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)

for ad, model in modeller.items():
    p = Pipeline([("prep", onisle), ("model", model)]).fit(X_tr, y_tr)
    olasilik = p.predict_proba(X_te)[:, 1]
ModelDoğrulukKesinlikDuyarlılıkF1ROC-AUCPR-AUCBrier
Lojistik regresyon0,7380,5040,7830,6140,8410,6330,169
Rastgele orman0,7590,5340,7430,6210,8370,6410,158
Extra Trees0,7570,5320,7090,6080,8260,6260,161
Taban oran0,735—0,000—0,5000,265—

Tablonun en çarpıcı satırı en alttaki. Lojistik regresyonun %73,8'lik doğruluğu, hiçbir şey yapmayan modelin %73,5'inden yalnızca 0,3 puan yüksek. Doğruluğa bakarak bu modeli değerlendiren biri "işe yaramıyor" sonucuna varırdı. Oysa ROC-AUC 0,841 ve duyarlılık 0,783 — model ayrılacak müşterilerin dörtte üçünden fazlasını yakalıyor.

Neden böyle oluyor? class_weight="balanced" modeli kayıp müşterileri kaçırmamaya zorluyor; bunun bedeli de yanlış alarm. Model 581 müşteriye "ayrılacak" diyor, 293'ü gerçekten ayrılıyor. Doğruluk düşüyor çünkü 288 müşteri boşuna işaretleniyor — ama 374 kayıp müşterinin 293'ü yakalanıyor. Bu bir hata değil, bilinçli bir takas.

5. Eşik Bir Varsayımdır

Yukarıdaki bütün metrikler tek bir karardan doğuyor: olasılık 0,5'i geçerse "ayrılacak". Bu eşik matematikten değil alışkanlıktan geliyor. Değiştirince model değişmiyor, yalnızca olasılığı karara çevirme biçimi değişiyor.

EşikDoğrulukDuyarlılıkKesinlikF1İşaretlenen müşteri
0,30,6530,9280,4290,587809
0,40,7010,8660,4660,606695
0,50,7380,7830,5040,614581
0,60,7620,7060,5400,612489
0,70,7890,6020,6030,602373

Eşiği seçen şey istatistik değil, ekip kapasitesi ve maliyet. 1.409 müşterilik bir listede ayda 800 müşteriyi arayabilecek bir ekip varsa 0,3 eşiği kayıpların %92,8'ini kapsar. Ayda 400 aramalık kapasite varsa 0,7 eşiği daha gerçekçi; bu kez kayıpların %60,2'si yakalanır ama her iki aramadan biri isabetli olur.

Eşiği test setinde seçme. Yukarıdaki tablo bir ders, bir yöntem değil. Eşiği test verisine bakarak ayarlarsan o set artık "görülmemiş veri" olmaktan çıkar. Doğrusu, eşiği eğitim setinden ayrılan bir doğrulama kümesinde veya çapraz doğrulamayla seçmek — eşik de diğerleri gibi bir hiperparametredir.

6. Model Ne Söylüyor?

Lojistik regresyonun en güzel yanı okunabilir olması. Katsayıları odds oranına çevirince her değişkenin yönü ve büyüklüğü doğrudan görünüyor.

DeğişkenKatsayıOdds oranıYorum
Fiber optik internet+0,7052,02Kayıp olasılığının bahis oranını iki katına çıkarıyor.
Aydan aya sözleşme+0,6631,94Keşifte gördüğümüz en güçlü ayrım burada da karşımızda.
Elektronik çek+0,2391,27Otomatik ödemeye göre belirgin biçimde riskli.
Teknik destek yok+0,1641,18Destek almamak riski artırıyor.
Kıdem (standartlaştırılmış)−1,1240,33En güçlü koruyucu etki: bir standart sapma kıdem, 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.

Permütasyon önemi de aynı sıralamayı veriyor: 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. Yani modelin bildiği her şeyin dörtte üçü dört değişkende toplanıyor.

Dikkat çeken bir çelişki: MonthlyCharges'ın katsayısı negatif (odds oranı 0,52) — oysa tek başına bakıldığında pahalı paketlerde kayıp daha yüksek. Çelişki değil, koşullu etki: model zaten "fiber optik" değişkenini biliyor ve pahalı olmanın riskini oradan okuyor. Fiber olduğu bilindikten sonra, aylık ücretin yüksek olması daha kapsamlı ve daha bağlı bir müşteriye işaret ediyor. Çok değişkenli bir modelde katsayılar "diğerleri sabitken" okunur; tek değişkenli sezgiyle doğrudan karşılaştırılamaz.

7. Modeli İş Değerine Çevirmek

Asıl soru şuydu: bu hafta kimi arayacağız? Cevabı doğruluk oranı değil, lift veriyor. Müşterileri risk puanına göre sıralayıp üstten belli bir yüzdeyi aldığımızda o dilimde gerçekte kaç kayıp olduğuna bakıyoruz.

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×

Bu tablo doğruluk tablosundan çok daha kullanışlı. En riskli %10'u aradığında her dört müşteriden üçü gerçekten ayrılacak olan müşteri. Rastgele arama yapsan bu oran %26,5 olurdu; model aramanın isabetini 2,85 katına çıkarıyor. Müşterilerin yarısını arayacak kapasiten varsa kayıpların %87'sine ulaşıyorsun.

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

Dört adımın üçü teknik, biri değil. Puanlama ve sıralama modelin işi; listeyi nerede keseceğini ekibin haftalık kapasitesi belirliyor. Dördüncü adım ise tamamen tasarım meselesi: işaretlenen müşterilerin rastgele seçilmiş bir bölümüne hiç dokunmamak.

Dördüncü adım pazarlık konusu değil. Yüksek riskli müşterilere yapılan müdahalenin işe yaradığını varsaymak, churn projelerinin en yaygın hatası. Model zaten ayrılmaya en yakın müşterileri seçiyor; onlara yapılan aramanın bir kısmı kaçınılmaz olarak boşa gidecek, bazıları ise müşteriyi ayrılmayı düşünmeye itebilecek. Artımlı etkiyi görmenin tek yolu, işaretlenenlerin rastgele bir bölümünü dokunmadan bırakıp iki grubu karşılaştırmak. Bunun nasıl kurulacağını A/B testi yazısında ele almıştım.

8. Sık Yapılan Hatalar

HataNeden problem?Daha iyi yaklaşım
Doğruluk oranını raporlamakTaban oran %73,5; model hiçbir şey öğrenmeden bunu yakalar.ROC-AUC, PR-AUC ve ilk %N yakalama oranını birlikte vermek.
0,5 eşiğini sorgulamamakEşik, kapasiteye ve maliyete göre değişmesi gereken bir karar.Eşiği doğrulama setinde, iş kısıtına göre seçmek.
Eksikleri desenine bakmadan doldurmakYapısal eksikliğe medyan yazmak veriyi bozar.Eksikliğin hangi değişkenle açıklandığını kontrol etmek.
Katsayıyı nedensellik saymakFiber optik kaybı "yapmıyor"; onunla birlikte anılıyor.İlişkiyi tarif etmek, nedeni deneyle sınamak.
Modeli bir kez kurup bırakmakMüşteri davranışı ve kampanyalar değişir; model eskir.Performansı ve girdi dağılımlarını düzenli izlemek.
Tek bir bölünmeye güvenmek1.409 gözlemlik test setinde sayılar tohuma göre oynar.Tekrarlı çapraz doğrulamayla aralık raporlamak.

Sonuç

Bu veride üç model de birbirine yakın çıktı: ROC-AUC 0,826 ile 0,841 arasında. Yani model seçimi bu problemin zor kısmı değil. Zor kısım, çıkan olasılığı doğru soruya bağlamak.

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, yani rastgele aramanın 2,85 katı isabet. Aynı model, aynı sayılar; farklı olan tek şey sorulan soru.

“Model kimin ayrılacağını söyler. Kimi arayacağını söyleyen şey, modelin çıktısıyla ekibin kapasitesini birleştiren karardır.”

İlgili yazılar: Sınıflandırma metriklerinin ayrıntısını Confusion Matrix yazısında, lojistik regresyonun matematiğini Lojistik Regresyon yazısında, müdahalenin etkisini ölçmeyi ise A/B Testi yazısında ele aldım. Veri seti: IBM Telco Customer Churn (7.043 müşteri, 21 değişken).

Building a churn model is not the hard part. The hard part is turning the probability it produces into an answer to "who do we call this week?" In this post I build a churn prediction end to end on 7,043 telecom customers — and show with numbers why the model's accuracy is a misleading measure of success.

The data is IBM's Telco Customer Churn set, a standard in churn work: 21 columns, one row per customer, and a target of whether the customer left.

“A churn model's value does not show up in how many customers it classifies correctly, but in how many departing customers it can reach with a limited number of calls.”

1. The Problem and the Base Rate

One number has to come first: 1,869 of the 7,043 customers churned, a rate of 26.5%. That number sets the floor for every evaluation that follows.

Data

7,043 customers

21 variables, no duplicate customer records.

Target

Churn (yes/no)

1,869 left, 5,174 stayed.

Base rate

73.5%

The accuracy of a model that says "nobody leaves".

Split

5,634 / 1,409

Stratified 80/20; churn rate is 26.5% in both sets.

Hold on to that 73.5%. A model that learns nothing and calls every customer a stayer achieves exactly that accuracy. So the sentence "our model is 75% accurate" says almost nothing on this data. It is the most important number in this post.

2. Where Does Churn Concentrate?

Looking at the data before modelling already reveals most of what the model will later find. Four breakdowns are enough.

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; we can already expect the model to lean on this column.

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%
No internet service1,5267.4%

All three tables point the same way: the riskiest profile is a loosely committed, month-to-month customer without support. That customers paying by electronic check churn at three times the rate of those on automatic payment is especially striking — payment method behaves here like a proxy for intent.

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%

The first six months are critical: more than half of new customers leave. Risk falls monotonically with tenure, down to 9.5% past 49 months. That alone says where a retention budget should go.

3. Is "Missing" Data Really Missing?

The TotalCharges column has 11 blank cells. The reflex is to fill them with the median or drop those rows. The better first move is to ask why they are empty.

The pattern behind the gaps
df["TotalCharges"] = pd.to_numeric(
    df["TotalCharges"].replace(r"^\s*$", pd.NA, regex=True),
    errors="coerce")

df.loc[df["TotalCharges"].isna(), "tenure"].unique()
# array([0])  -> every one of them has zero tenure

All eleven customers have a tenure of zero. These are brand-new subscriptions that have not been billed yet; their total charges are not missing, they do not exist yet. Filling them with the median would invent a history these customers never had.

Look at the pattern before imputing. Random missingness and structural missingness are different problems. Here the gaps are fully explained by a single variable (tenure = 0); the right move is either a zero or a separate "new customer" flag for the model. Eleven rows vanish inside 7,043 — but the same pattern across thousands of rows in a larger dataset would make this decision matter a great deal.

4. Three Models, One Test Set

Numeric variables are standardised, categoricals one-hot encoded, and three models run through the same pipeline. For the class imbalance I use class_weight="balanced", which makes the model treat the minority class as more expensive to miss.

Pipeline and models
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)

for name, model in models.items():
    p = Pipeline([("prep", preprocess), ("model", model)]).fit(X_tr, y_tr)
    probability = p.predict_proba(X_te)[:, 1]
ModelAccuracyPrecisionRecallF1ROC-AUCPR-AUCBrier
Logistic regression0.7380.5040.7830.6140.8410.6330.169
Random forest0.7590.5340.7430.6210.8370.6410.158
Extra Trees0.7570.5320.7090.6080.8260.6260.161
Base rate0.735—0.000—0.5000.265—

The most telling row is the last one. Logistic regression's 73.8% accuracy is just 0.3 points above the 73.5% of a model that does nothing. Anyone judging this model by accuracy would conclude it does not work. Yet its ROC-AUC is 0.841 and its recall 0.783 — the model catches more than three quarters of the customers who actually leave.

Why does that happen? class_weight="balanced" pushes the model not to miss churners, and the price is false alarms. The model flags 581 customers as leaving; 293 of them actually do. Accuracy drops because 288 customers are flagged for nothing — but 293 of the 374 real churners are caught. That is not a flaw, it is a deliberate trade.

5. The Threshold Is an Assumption

Every metric above rests on one decision: call it churn when the probability passes 0.5. That cut-off comes from habit, not from the mathematics. Changing it does not change the model, only how its probabilities become decisions.

ThresholdAccuracyRecallPrecisionF1Customers flagged
0.30.6530.9280.4290.587809
0.40.7010.8660.4660.606695
0.50.7380.7830.5040.614581
0.60.7620.7060.5400.612489
0.70.7890.6020.6030.602373

What picks the threshold is not statistics but team capacity and cost. On a list of 1,409 customers, a team able to call 800 a month covers 92.8% of the churn at a threshold of 0.3. With capacity for 400 calls, 0.7 is the realistic choice: 60.2% of churners are caught, but roughly every second call lands on a real one.

Do not pick the threshold on the test set. The table above is a lesson, not a method. Tune the threshold by looking at the test data and that set stops being unseen. The right move is to choose it on a validation split carved out of the training data, or by cross-validation — a threshold is a hyperparameter like any other.

6. What Does the Model Say?

The best thing about logistic regression is that it can be read. Converting its coefficients to odds ratios makes each variable's direction and size visible directly.

VariableCoefficientOdds ratioReading
Fiber optic internet+0.7052.02Doubles the odds of churning.
Month-to-month contract+0.6631.94The sharpest split from the exploration shows up here too.
Electronic check+0.2391.27Clearly riskier than automatic payment.
No tech support+0.1641.18Going without support raises the risk.
Tenure (standardised)−1.1240.33The strongest protective effect: one standard deviation of tenure cuts the odds to a third.
Two-year contract−0.7810.46A long contract more than halves the risk.

Permutation importance gives the same ordering: tenure alone costs 0.152 of ROC-AUC when shuffled, followed by InternetService (0.043), Contract (0.040) and MonthlyCharges (0.038). Three quarters of what the model knows sits in four variables.

A contradiction worth noticing: the coefficient on MonthlyCharges is negative (odds ratio 0.52) — yet on its own, churn is higher among expensive plans. This is not a contradiction but a conditional effect: the model already knows about "fiber optic" and reads the risk of being expensive from there. Given that a customer is on fiber, a higher monthly bill points to a broader, more committed subscription. In a multivariate model coefficients are read "holding the others constant"; they cannot be compared directly with single-variable intuition.

7. Turning the Model Into Business Value

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 look at how many of them actually churned.

SliceCustomersChurn 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×

This table is far more useful than the accuracy one. Call the riskiest 10% and three out of every four customers are genuinely about to leave. Calling at random would hit 26.5%; the model makes each call 2.85 times more likely to land. With capacity to call half the base, you reach 87% of the churn.

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. Scoring and ranking are the model's job; where the list gets cut is set by the team's weekly capacity. The fourth step is purely a matter of design: leaving a randomly chosen share of flagged customers untouched.

The fourth step is not negotiable. Assuming that an intervention on high-risk customers works is the most common mistake in churn projects. The model is already picking the customers closest to leaving; some of those calls will inevitably be wasted, and a few may even push a customer into thinking about leaving. The only way to see the incremental effect is to leave a random portion of the flagged group alone and compare. How to set that up is covered in the A/B testing post.

8. Common Mistakes

MistakeWhy it is a problemBetter approach
Reporting accuracyThe base rate is 73.5%; a model reaches it without learning anything.Report ROC-AUC, PR-AUC and top-N capture together.
Never questioning the 0.5 cut-offThe threshold is a decision that should follow capacity and cost.Choose it on a validation set against the business constraint.
Imputing without checking the patternWriting a median over structural missingness corrupts the data.Check which variable explains the missingness.
Reading a coefficient as causationFiber optic does not "cause" churn; it travels with it.Describe the association; test the cause with an experiment.
Building the model once and walking awayCustomer behaviour and campaigns change; models go stale.Monitor performance and input distributions on a schedule.
Trusting a single splitOn a 1,409-row test set the numbers move with the seed.Report a range from repeated cross-validation.

Conclusion

All three models landed close together on this data: ROC-AUC between 0.826 and 0.841. Model selection is not the hard part of this problem. The hard part is attaching the resulting probability to the right question.

Someone looking at accuracy would throw the model away — 73.8%, a mere 0.3 points above doing nothing. Someone looking at the lift table sees this instead: in the riskiest 10%, the churn rate is 75.7%, 2.85 times better than calling at random. Same model, same numbers; the only difference is the question being asked.

“The model tells you who is going to leave. What tells you who to call is the decision that joins the model's output to the team's capacity.”

Related posts: classification metrics are covered in Confusion Matrix, the mathematics of logistic regression in Logistic Regression, and measuring the effect of an intervention in A/B Testing. Dataset: IBM Telco Customer Churn (7,043 customers, 21 variables).