HomeProjectsCertificationsContact
pandas

Telecom Customer Churn Analysis

pandas-driven exploratory analysis of 7,043 telecom customers to isolate the contract types, payment methods, and service gaps that predict churn, and what retention should target first.

DatasetIBM Telco Customer Churn (public)
Scale7,043 customers · 21 features · 26.5% churn rate
ToolsPython · pandas · NumPy · Matplotlib · Jupyter
View code & data on GitHub ↗

The Problem

A telecom operator loses roughly a quarter of its customer base per year. Acquiring a replacement customer costs 5 to 7 times more than retaining an existing one, so the business question is precise: which customer attributes actually separate churners from stayers, and where should a limited retention budget be aimed?

The IBM Telco dataset mirrors a real CRM extract: demographics, subscribed services, contract and billing attributes, and a churn label. It also includes the classic real-world data quality trap of TotalCharges stored as text with blank strings for brand-new customers.

7,043customers profiled
26.5%baseline churn rate
42.7%month-to-month churn
3xchurn without tech support

Approach

01

Data cleaning & type repair

Coerced TotalCharges from object to numeric (11 blank rows for zero-tenure customers), standardized 'No internet service' responses, and encoded the churn label, documenting every transformation in the notebook.

02

Univariate churn baselines

Established the 26.5% global churn rate, then computed churn by each categorical feature with groupby aggregations to rank features by lift over baseline.

03

Tenure cohort analysis

Bucketed customers into tenure bands with pd.cut (0-12, 13-24, 25-48, 49-72 months) to expose how dramatically churn concentrates in the first year.

04

Cross-feature interaction pivots

Built pivot tables crossing contract type with payment method and internet service with support add-ons, revealing compounding risk combinations far above any single-feature signal.

05

Revenue-at-risk quantification

Weighted churn probability by monthly charges per segment to translate percentages into dollars, reframing the analysis from 'who churns' to 'which churn is expensive'.

The Work

Representative excerpts from the analysis: the queries and transformations doing the heavy lifting. The full runnable project lives in the GitHub repo.

Cleaning + churn rate by featurepython
import pandas as pd
import numpy as np

df = pd.read_csv("telco_churn.csv")

# Classic trap: TotalCharges is object with blanks for tenure=0
df["TotalCharges"] = pd.to_numeric(df["TotalCharges"], errors="coerce")
df["TotalCharges"] = df["TotalCharges"].fillna(0)

df["churn_flag"] = (df["Churn"] == "Yes").astype(int)

def churn_by(col: str) -> pd.DataFrame:
    out = (
        df.groupby(col)
          .agg(customers=("customerID", "count"),
               churn_rate=("churn_flag", "mean"),
               avg_monthly=("MonthlyCharges", "mean"))
          .assign(churn_rate=lambda x: (x.churn_rate * 100).round(1),
                  avg_monthly=lambda x: x.avg_monthly.round(2))
          .sort_values("churn_rate", ascending=False)
    )
    return out

for col in ["Contract", "PaymentMethod", "InternetService", "TechSupport"]:
    print(f"\n=== {col} ===")
    print(churn_by(col))
Tenure cohorts and revenue at riskpython
bins   = [0, 12, 24, 48, 72]
labels = ["0-12 mo", "13-24 mo", "25-48 mo", "49-72 mo"]
df["tenure_band"] = pd.cut(df["tenure"], bins=bins, labels=labels,
                           include_lowest=True)

cohort = (
    df.groupby("tenure_band", observed=True)
      .agg(customers=("customerID", "count"),
           churn_rate=("churn_flag", "mean"),
           monthly_revenue=("MonthlyCharges", "sum"))
)
cohort["revenue_at_risk"] = (
    cohort["churn_rate"] * cohort["monthly_revenue"]
).round(0)
cohort["churn_rate"] = (cohort["churn_rate"] * 100).round(1)

# Compounding risk: contract x payment method
risk_matrix = df.pivot_table(
    index="Contract", columns="PaymentMethod",
    values="churn_flag", aggfunc="mean"
).mul(100).round(1)

print(cohort)
print(risk_matrix)

What the Data Shows

Churn rate by contract type

% of customers churned

Month-to-month
42.7%
One year
11.3%
Two year
2.8%

Contract length is the single strongest churn predictor in the dataset. Payment method stacks on top: month-to-month plus electronic check exceeds 55%.

Key Findings

Month-to-month contracts churn at 42.7% versus 2.8% for two-year contracts. Contract type is by far the strongest single predictor in the dataset.

Electronic check payers churn at 45.3%, roughly 2.5 times the rate of customers on automatic bank transfer or credit card payments.

Fiber optic customers churn at 41.9% vs 19.0% for DSL despite paying premium prices, a strong signal of a price and perceived-quality mismatch rather than a service-tier upsell win.

Customers without TechSupport or OnlineSecurity add-ons churn at roughly 3 times the rate of those who have them.

First-year customers (0-12 months tenure) churn at 47.4%, and the month-to-month plus electronic check plus fiber combination exceeds 55%, the single highest-risk profile identified.

Churners actually pay more on average ($74/mo vs $61/mo), so the company is disproportionately losing its higher-revenue customers.

Recommendations

Attack the 90-day window

Nearly half of first-year customers leave. A structured onboarding and check-in program in the first 3 months targets the steepest part of the churn curve.

Migrate month-to-month to 1-year terms

A modest discount for a 12-month commitment moves customers from a 42.7% to an 11.3% churn pool. The math works even at a 15% price concession.

Bundle tech support into fiber plans

Fiber's 41.9% churn paired with the 3x effect of missing support add-ons suggests bundling support would directly address the perceived-value gap.

Nudge electronic check users to auto-pay

A one-time credit for switching to auto-pay targets the 45.3% churn payment cohort and reduces involuntary churn from failed payments.