End-to-End Project: Customer Churn Prediction

Lesson 25 of 26

Ab tak jo seekha, sab ek project me jodte hain. Ek telecom company chahti hai ki pehle se pata chale kaunse customers company chhodne wale hain (churn), taaki unhe time pe offer diya ja sake.

Step 1: Data

Practice ke liye humne 1000 customers ka realistic dataset generate kiya hai (kuch values jaan-boojh ke missing rakhi hain):

Example

Python
import numpy as np
import pandas as pd

rng = np.random.default_rng(42)
n = 1000

contract = rng.choice(["Month-to-month", "One year", "Two year"], size=n, p=[0.55, 0.25, 0.20])
internet = rng.choice(["Fiber", "DSL", "None"], size=n, p=[0.45, 0.40, 0.15])
tenure = rng.integers(1, 73, size=n)                       # kitne mahine se customer hai
monthly = rng.normal(70, 25, size=n).clip(20, 150).round(2)
support_calls = rng.poisson(1.5, size=n)

logit = (-1.0 + 1.6 * (contract == "Month-to-month") - 1.2 * (contract == "Two year")
         + 0.6 * (internet == "Fiber") - 0.04 * tenure
         + 0.015 * (monthly - 70) + 0.35 * support_calls)
churn = (rng.random(n) < 1 / (1 + np.exp(-logit))).astype(int)

df = pd.DataFrame({
    "tenure_months": tenure,
    "monthly_charges": monthly,
    "contract": contract,
    "internet": internet,
    "support_calls": support_calls,
    "churn": churn,
})
df.loc[rng.choice(n, 40, replace=False), "monthly_charges"] = np.nan
df.loc[rng.choice(n, 25, replace=False), "internet"] = np.nan

pd.set_option("display.max_columns", None)   # saare columns dikhao
pd.set_option("display.width", 120)
print(df.head())
print(df.shape)

Output

Plain Text
   tenure_months  monthly_charges        contract internet  support_calls  churn
0             23            60.60        One year    Fiber              2      0
1             61            58.55  Month-to-month      NaN              0      0
2              3              NaN        Two year    Fiber              1      0
3             33            90.48        One year      NaN              2      1
4             58            56.62  Month-to-month      DSL              1      0
(1000, 6)

Step 2: EDA

Example

Python
print("Churn rate:", round(df["churn"].mean(), 3))
print()
print(df.isnull().sum())
print()
print(df.groupby("contract")["churn"].mean().round(3).sort_values(ascending=False))
print()
print(df.groupby("churn")[["tenure_months", "support_calls"]].mean().round(2))

Output

Plain Text
Churn rate: 0.33

tenure_months       0
monthly_charges    40
contract            0
internet           25
support_calls       0
churn               0
dtype: int64

contract
Month-to-month    0.459
One year          0.244
Two year          0.086
Name: churn, dtype: float64

       tenure_months  support_calls
churn
0              40.06           1.31
1              26.82           1.68

Pehli insights: overall churn rate lagbhag 33% hai. Month-to-month contract wale sabse zyada chhodte hain (46%), jabki Two year wale sabse kam (9%). Churn karne wale customers naye hain (kam tenure) aur support ko zyada call karte hain.

Example

Python
import matplotlib.pyplot as plt

fig, axes = plt.subplots(1, 2, figsize=(10, 4))
df.groupby("contract")["churn"].mean().plot(kind="bar", ax=axes[0], color="#f97316", rot=0)
axes[0].set_title("Contract ke hisaab se churn rate")
df.boxplot(column="tenure_months", by="churn", ax=axes[1], grid=False)
axes[1].set_title("Tenure: churn (1) vs stay (0)")
plt.suptitle("")
plt.tight_layout()
plt.show()

Contract wise churn rate aur tenure boxplot

Step 3: Train-test split

Example

Python
from sklearn.model_selection import train_test_split

X = df.drop(columns="churn")
y = df["churn"]

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)
print(X_train.shape, X_test.shape)

Output

Plain Text
(800, 5) (200, 5)

Step 4: Preprocessing — ColumnTransformer

Number aur category columns ko alag treatment chahiye. ColumnTransformer dono ko ek saath sambhalta hai:

  • Numbers: missing → median, phir StandardScaler
  • Categories: missing → sabse common value, phir OneHotEncoder

Example

Python
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler

numeric = ["tenure_months", "monthly_charges", "support_calls"]
categorical = ["contract", "internet"]

preprocess = ColumnTransformer([
    ("num", Pipeline([("impute", SimpleImputer(strategy="median")),
                      ("scale", StandardScaler())]), numeric),
    ("cat", Pipeline([("impute", SimpleImputer(strategy="most_frequent")),
                      ("onehot", OneHotEncoder(handle_unknown="ignore"))]), categorical),
])

Step 5: Models ki tulna (cross-validation)

Example

Python
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score

models = {
    "Logistic Regression": LogisticRegression(max_iter=1000),
    "Random Forest": RandomForestClassifier(n_estimators=300, max_depth=6, random_state=42),
}

scores = {}
for name, estimator in models.items():
    pipe = Pipeline([("prep", preprocess), ("model", estimator)])
    cv = cross_val_score(pipe, X_train, y_train, cv=5, scoring="roc_auc")
    scores[name] = cv.mean()
    print(f"{name}: ROC-AUC = {cv.mean():.3f} ± {cv.std():.3f}")

best_name = max(scores, key=scores.get)
print("\nBest model:", best_name)

Output

Plain Text
Logistic Regression: ROC-AUC = 0.801 ± 0.052
Random Forest: ROC-AUC = 0.791 ± 0.052

Best model: Logistic Regression

Step 6: Final model aur test evaluation

Best model ko poore training data pe train karke, pehli baar test data pe check karte hain:

Example

Python
from sklearn.metrics import classification_report, roc_auc_score

final_model = Pipeline([("prep", preprocess), ("model", models[best_name])])
final_model.fit(X_train, y_train)

y_pred = final_model.predict(X_test)
y_proba = final_model.predict_proba(X_test)[:, 1]

print(classification_report(y_test, y_pred, target_names=["stay", "churn"]))
print("Test ROC-AUC:", round(roc_auc_score(y_test, y_proba), 3))

Output

Plain Text
              precision    recall  f1-score   support

        stay       0.81      0.85      0.83       134
       churn       0.66      0.59      0.62        66

    accuracy                           0.77       200
   macro avg       0.73      0.72      0.73       200
weighted avg       0.76      0.77      0.76       200

Test ROC-AUC: 0.797

Step 7: Churn ki wajah samajhna

Business ko sirf prediction nahi, wajah bhi chahiye. Logistic Regression ke coefficients se dikhta hai ki kaunsi cheez churn ka risk badhati hai (+) ya ghataati hai (−):

Example

Python
explain = Pipeline([("prep", preprocess), ("model", LogisticRegression(max_iter=1000))])
explain.fit(X_train, y_train)

names = explain.named_steps["prep"].get_feature_names_out()
coefs = pd.Series(explain.named_steps["model"].coef_[0], index=names)
print(coefs.sort_values(ascending=False).round(2))

Output

Plain Text
cat__contract_Month-to-month    1.26
num__monthly_charges            0.40
num__support_calls              0.40
cat__internet_Fiber             0.30
cat__internet_DSL               0.00
cat__contract_One year         -0.12
cat__internet_None             -0.30
num__tenure_months             -0.87
cat__contract_Two year         -1.14
dtype: float64

Step 8: Naye customer pe prediction

Example

Python
new_customer = pd.DataFrame([{
    "tenure_months": 3,
    "monthly_charges": 95.0,
    "contract": "Month-to-month",
    "internet": "Fiber",
    "support_calls": 4,
}])
risk = final_model.predict_proba(new_customer)[0, 1]
print(f"Churn risk: {risk:.0%}")

Output

Plain Text
Churn risk: 93%

Model ko save karke baad me app ya API me use kar sakte hain:

Example

Python
import joblib

joblib.dump(final_model, "churn_model.pkl")      # save
model = joblib.load("churn_model.pkl")           # baad me load

Step 9: Business recommendations

  • Month-to-month customers ko 1-2 saal ke contract pe discount ka offer do — ye sabse bada lever hai.
  • Jo customer 3+ baar support ko call kare, uske liye priority support ya follow-up call.
  • Pehle kuch mahine sabse risky hain — naye customers ke liye onboarding offers.
  • Har mahine model se top 10% risky customers ki list retention team ko bhejo.

Ye poora flow — problem → EDA → preprocessing → model comparison → evaluation → insights — har Data Science project ka template hai. Isko apne portfolio ke liye kisi real dataset (jaise Kaggle ka Telco Churn) pe dohraiye.