Session 22: Customer Retention & Churn Analytics
Learning Objectives
- Calculate the economic impact of insurance churn — why improving retention by 5% can increase enterprise value by 25%+
- Engineer churn prediction features using policy tenure, premium changes, claims history, and customer demographics
- Train an XGBoost churn classifier and evaluate using precision-recall on a highly imbalanced dataset
- Design a retention strategy that targets the "persuadable" segment and calculate retention ROI
- Build a KNIME workflow for automated churn scoring and prioritised retention list generation
1. The Economics of Insurance Churn
Churn — policyholders not renewing their insurance policies — is the single most important driver of long-term insurance profitability. The cost of acquiring a new customer is 5–10× the cost of retaining an existing one, and a retained customer's profitability increases over time as acquisition costs are recovered and cross-sell revenue accumulates. In life insurance, where a policy's value is back-loaded (acquisition costs are recovered over 3–5 years), improving retention by even a few percentage points can double the value of new business written.
1.1 The Cost of Churn — A Numerical Example
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split, cross_val_score, StratifiedKFold
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import (classification_report, confusion_matrix, roc_auc_score,
precision_recall_curve, average_precision_score)
from sklearn.linear_model import LogisticRegression
import warnings
warnings.filterwarnings('ignore')
# Churn economics simulation
acquisition_cost = 2500 # Cost to acquire one new customer (₹)
annual_premium = 8500 # Average annual premium per customer
loss_ratio = 0.68 # Loss ratio
variable_expense_ratio = 0.15 # Variable expenses (claims + commissions + service)
profit_margin = 1 - loss_ratio - variable_expense_ratio # ~17%
annual_profit = annual_premium * profit_margin
retention_rates = [0.50, 0.60, 0.70, 0.75, 0.80, 0.85, 0.90]
years = 10
print("=" * 75)
print("CHURN ECONOMICS — IMPACT OF RETENTION ON CUSTOMER VALUE")
print("=" * 75)
print(f"Assumptions:")
print(f" Annual premium: ₹{annual_premium:,.0f}")
print(f" Profit margin: {profit_margin*100:.0f}%")
print(f" Annual profit/customer: ₹{annual_profit:,.0f}")
print(f" Acquisition cost: ₹{acquisition_cost:,.0f}")
print(f"\nCustomer Lifetime Value by Retention Rate:")
print(f"{'Retention':12s} {'Avg Tenure':12s} {'CLV':15s} {'CLV w/o acq':15s} {'Value Lift':15s}")
print("-" * 69)
baseline_clv = 0
for ret in retention_rates:
tenure_years = 1 / (1 - ret) if ret < 1 else years
# CLV = Σ (profit per year × retention_prob) − acquisition cost
clv = 0
retention_prob = 1.0
for yr in range(1, int(min(tenure_years, years)) + 1):
if yr > 1:
retention_prob *= ret
clv += annual_profit * retention_prob
clv -= acquisition_cost
clv_w_o_acq = clv + acquisition_cost # CLV ignoring acquisition cost
if ret == 0.75:
baseline_clv = clw
value_lift = (clv - baseline_clv) / basseline_clv * 100 if basseline_clv != 0 else 0
print(f"{ret*100:>5.0f}% {tenure_years:>5.1f} years ₹{clv:>7,.0f} ₹{clv_w_o_acq:>7,.0f} {'+' if value_lift > 0 else ''}{value_lift:>5.1f}%")
print(f"\nKey insight: Improving retention from 75% to 85% increases CLV by")
print(f"approximately 80% — because the customer stays twice as long and")
print(f"the acquisition cost is amortised over more years.")
2. Defining and Measuring Churn
Before we can predict churn, we must define it precisely. In insurance, "churn" is not a single event — it is the policyholder's failure to renew their policy when it expires. The operational definition of churn depends on the line of business and the policy term. For annual policies (most general insurance), a policy is "churned" if it has not been renewed within 60 days of its expiry date. For multi-year life insurance, churn is measured by "persistency" — the percentage of policies still in force at specific milestones (13 months, 25 months, 37 months, 61 months post-sale).
2.1 Churn Metrics Framework
| Metric | Definition | Best For | Typical Values (India) |
|---|---|---|---|
| Churn Rate | % of policies not renewed at expiry | General insurance — annual policies | 15–30% motor, 20–35% health, 10–20% term life |
| Persistency (13th month) | % of life policies in force at 13 months | Life insurance — standard regulatory metric | 65–75% LIC, 75–85% private life insurers |
| Persistency (61st month) | % of life policies in force at 61 months | Life insurance — true measure of long-term retention | 40–55% LIC, 50–65% private life insurers |
| Net Promoter Score (NPS) | Would you recommend us? (0–10 scale) | Leading indicator of future churn | 25–60 for Indian insurers |
| Time-to-churn | Average tenure of churning customers | Identifying when churn risk is highest | Mode: after first year (60% of churn occurs between 1st and 2nd renewal) |
2.2 Creating the Churn Label
# Load the insurance dataset
df = pd.read_csv('data/insurance_cleaned.csv')
# Create policy-level data with churn label
policy_data = df.groupby(['policy_id', 'customer_id']).agg(
age=('age', 'first'),
income=('income', 'first'),
credit_score=('credit_score', 'first'),
gender=('gender', 'first'),
location=('location', 'first'),
occupation=('occupation', 'first'),
premium=('premium', 'first'),
sum_assured=('sum_assured', 'first'),
policy_type=('policy_type', 'first'),
channel=('channel', 'first'),
tenure_months=('tenure_months', 'first'),
start_date=('start_date', 'first'),
end_date=('end_date', 'first') if 'end_date' in df.columns else ('start_date', 'first'),
claim_count=('claim_id', 'count'),
total_claim_amount=('claim_amount', 'sum'),
fraud_flag=('fraud_flag', 'max')
).reset_index()
# Create the churn label
# For this dataset, we simulate churn based on:
# 1. Whether the customer has a claim (fewer claims = higher churn)
# 2. Whether credit score is below a threshold
# 3. Random variation to create realistic distribution
np.random.seed(42)
# Base churn factors
no_claim_factor = (policy_data['claim_count'] == 0).astype(float) * 0.15 # 15% baseline churn for non-claimants
low_credit_factor = (policy_data['credit_score'] < 650).astype(float) * 0.10 # Additional churn risk
premium_factor = np.clip(policy_data['premium'] / policy_data['premium'].median() - 0.5, 0, 0.10) # Higher premium = higher churn
# Combined churn probability
churn_prob = 0.10 + no_claim_factor + low_credit_factor + premium_factor + np.random.uniform(-0.05, 0.05, len(policy_data))
churn_prob = np.clip(churn_prob, 0.01, 0.60)
policy_data['churn_prob'] = churn_prob
policy_data['churned'] = (np.random.uniform(0, 1, len(policy_data)) < churn_prob).astype(int)
# Class balance
churn_rate = policy_data['churned'].mean() * 100
print(f"Policy-level dataset: {len(policy_data):,} policies")
print(f"Churn rate: {churn_rate:.1f}%")
print(f" Retained (0): {(policy_data['churned']==0).sum():,}")
print(f" Churned (1): {(policy_data['churned']==1).sum():,}")
print(f" Imbalance: {1/churn_rate*100:.1f}:1")
# Analyse churn by policy type
print(f"\nChurn rate by policy type:")
for ptype in policy_data['policy_type'].unique():
subset = policy_data[policy_data['policy_type'] == ptype]
rate = subset['churned'].mean() * 100
print(f" {ptype:15s}: {rate:.1f}% churn rate ({len(subset):,} policies)")
3. Feature Engineering for Churn
Churn prediction features fall into three categories: policy behaviour (premium changes, tenure, claims), customer characteristics (demographics, credit, channel preference), and interaction features (combinations that capture specific risk patterns). The best churn models use features from all three categories.
3.1 Creating Churn Features
# ==========================================
# CHURN FEATURE ENGINEERING
# ==========================================
# 1. Policy Behaviour Features
# ─────────────────────────
# Tenure: how long has the customer been with the insurer?
policy_data['tenure_years'] = policy_data['tenure_months'] / 12
# Premium change from previous year (simulated: compare premium to mean of same policy type)
mean_premium = policy_data.groupby('policy_type')['premium'].transform('mean')
policy_data['premium_vs_mean'] = (policy_data['premium'] - mean_premium) / mean_premium
# Has the customer ever filed a claim?
policy_data['had_claim'] = (policy_data['claim_count'] > 0).astype(int)
# Claim frequency per year of tenure
policy_data['claims_per_year'] = policy_data['claim_count'] / (policy_data['tenure_years'] + 0.5)
# Is the claim amount high relative to premium?
policy_data['claim_to_premium_ratio'] = policy_data['total_claim_amount'] / (policy_data['premium'] + 1)
# 2. Demographics & Customer Profile
# ────────────────────────────────
# Age group
policy_data['senior'] = (policy_data['age'] >= 60).astype(int)
policy_data['young'] = (policy_data['age'] <= 30).astype(int)
# Credit tier
policy_data['credit_tier'] = pd.cut(policy_data['credit_score'],
bins=[0, 600, 700, 800, 900],
labels=['Low', 'Fair', 'Good', 'Excellent'])
# Income relative to peers
median_income = policy_data['income'].median()
policy_data['income_above_median'] = (policy_data['income'] > median_income).astype(int)
# 3. Interaction Features
# ────────────────────
# Interaction: young + no claim (likely to churn — young customers with no claims
# may feel insurance is unnecessary)
policy_data['young_no_claim'] = (policy_data['young'] & (policy_data['claim_count'] == 0)).astype(int)
# Interaction: high premium + no claim (dissatisfaction — paying high premium for nothing)
policy_data['high_prem_no_claim'] = (
(policy_data['premium_vs_mean'] > 0.2) & (policy_data['claim_count'] == 0)
).astype(int)
# Interaction: low credit + senior (most vulnerable segment)
policy_data['low_credit_senior'] = (
(policy_data['credit_tier'] == 'Low') & (policy_data['senior'] == 1)
).astype(int)
# Interaction: digital channel × tenure < 1 year (early digital segment)
policy_data['new_digital'] = (
(policy_data['channel'].isin(['Online', 'Aggregator'])) &
(policy_data['tenure_years'] < 1)
).astype(int)
new_features = ['tenure_years', 'premium_vs_mean', 'had_claim', 'claims_per_year',
'claim_to_premium_ratio', 'senior', 'young', 'credit_tier',
'income_above_median', 'young_no_claim', 'high_prem_no_claim',
'low_credit_senior', 'new_digital',
'churned' # skip — this is our target
]
print("Featured created — checking for data quality:")
for feat in new_features:
if feat in policy_data.columns:
nulls = policy_data[feat].isna().sum()
if nulls > 0:
policy_data[feat] = policy_data[feat].fillna(policy_data[feat].mode()[0] if
policy_data[feat].dtype == 'object' else policy_data[feat].median())
print(f" ✓ {feat:25s} — {nulls} nulls filled")
# Count total features after one-hot encoding
feature_cols = [c for c in policy_data.columns if c not in ['policy_id', 'customer_id',
'start_date', 'end_date', 'churn_prob', 'churned']]
print(f"\nTotal features: {len(feature_cols)} (including categoricals for encoding)")
4. Churn Prediction Model
Churn prediction is a binary classification problem with class imbalance (typically 70–85% retained vs. 15–30% churned). As with fraud detection, accuracy is a misleading metric — a model that predicts "everyone stays" achieves 70–85% accuracy but catches zero churn. We evaluate churn models on precision, recall, and particularly the ability to rank customers by churn probability so that retention efforts target the highest-risk customers first.
4.1 Training the Churn Classifier
# Define features and target
X = policy_data[feature_cols]
y = policy_data['churned']
# Identify column types
cat_cols_churn = X.select_dtypes(include=['object', 'category']).columns.tolist()
num_cols_churn = X.select_dtypes(include=[np.number]).columns.tolist()
# Preprocessor
preprocessor_churn = ColumnTransformer(
transformers=[
('num', StandardScaler(), num_cols_churn),
('cat', OneHotEncoder(drop='first', handle_unknown='ignore', sparse_output=False), cat_cols_churn)
])
# Split
X_train_c, X_test_c, y_train_c, y_test_c = train_test_split(
X, y, test_size=0.25, random_state=42, stratify=y
)
# XGBoost with scale_pos_weight
import xgboost as xgb
churn_ratio = (y_train_c == 0).sum() / (y_train_c == 1).sum()
xgb_churn = xgb.XGBClassifier(
n_estimators=200, max_depth=5, learning_rate=0.05,
scale_pos_weight=churn_ratio,
subsample=0.8, colsample_bytree=0.8,
reg_alpha=0.1, reg_lambda=1.0,
random_state=42, n_jobs=-1, eval_metric='auc'
)
churn_pipeline = Pipeline(steps=[
('preprocessor', preprocessor_churn),
('classifier', xgb_churn)
])
churn_pipeline.fit(X_train_c, y_train_c)
# Predict
y_prob_churn = churn_pipeline.predict_proba(X_test_c)[:, 1]
y_pred_churn = (y_prob_churn >= 0.30).astype(int) # Lower threshold to catch more churn
# Evaluate
print("=" * 60)
print("CHURN PREDICTION MODEL — XGBoost Results")
print("=" * 60)
print(f"Churn ratio (class_weight): {churn_ratio:.1f}:1")
print(f"\nClassification Report (threshold = 0.30):")
print(classification_report(y_test_c, y_pred_churn, target_names=['Retained', 'Churned']))
roc_auc = roc_auc_score(y_test_c, y_prob_churn)
pr_auc = average_precision_score(y_test_c, y_prob_churn)
print(f"ROC-AUC: {roc_auc:.3f}")
print(f"PR-AUC: {pr_auc:.3f}")
# At this lower threshold, we catch more churners (higher recall)
# but at the cost of more false positives (wasted retention effort)
cm = confusion_matrix(y_test_c, y_pred_churn)
print(f"\nConfusion Matrix:")
print(f" Predicted Retained Predicted Churned")
print(f"Actual Retained {cm[0,0]:>6,d} {cm[0,1]:>6,d}")
print(f"Actual Churned {cm[1,0]:>6,d} {cm[1,1]:>6,d}")
print(f"\nAt threshold = 0.30:")
print(f" Recall (churn caught): {cm[1,1]/(cm[1,0]+cm[1,1])*100:.0f}%")
print(f" Precision (efficiency): {cm[1,1]/(cm[0,1]+cm[1,1])*100:.0f}%")
print(f" Would contact: {cm[0,1]+cm[1,1]:,} customers")
4.2 Finding the Optimal Threshold — Cost-Based
# Cost-benefit analysis for threshold selection
retention_offer_cost = 200 # Cost of a retention offer (discount, call centre, etc.)
clv_retained = annual_profit / (1 - 0.75) # CLV of a retained customer at 75% retention
clv_saved_if_prevent_churn = clv_retained * 0.30 # Assume 30% of churners can be saved with intervention
thresholds = np.arange(0.05, 0.65, 0.05)
results = []
for threshold in thresholds:
pred = (y_prob_churn >= threshold).astype(int)
cm = confusion_matrix(y_test_c, pred)
tp = cm[1, 1] # Correctly identified churners — intervened, some retained
fp = cm[0, 1] # False positives — cost of unnecessary retention offer
# Value: customers saved × CLV saved per customer
customers_saved = tp * 0.30 # 30% of contacted churners are persuaded to stay
value_saved = customers_saved * clv_saved_if_prevent_churn
# Cost: contact everyone predicted to churn
total_contact_cost = (tp + fp) * retention_offer_cost
net_value = value_saved - total_contact_cost
results.append({'threshold': threshold, 'net_value': net_value,
'customers_contacted': tp + fp, 'customers_saved': customers_saved})
results_df = pd.DataFrame(results)
optimal = results_df.loc[results_df['net_value'].idxmax()]
print(f"{'Threshold':12s} {'Contacted':12s} {'Saved':12s} {'Net Value':15s}")
print("-" * 51)
for _, row in results_df.iterrows():
marker = '← OPTIMAL' if row['threshold'] == optimal['threshold'] else ''
print(f"{row['threshold']:.2f} {row['customers_contacted']:>5,.0f} {row['customers_saved']:>5,.0f} ₹{row['net_value']/1e5:.1f}L {marker}")
print(f"\nOptimal threshold: {optimal['threshold']:.2f}")
print(f"At this threshold:")
print(f" Contact {optimal['customers_contacted']:,} customers with retention offer")
print(f" Expected to save {optimal['customers_saved']:.0f} customers")
print(f" Net value: ₹{optimal['net_value']:,.0f}")
print(f" ROI: {(optimal['net_value'] / (optimal['customers_contacted'] * retention_offer_cost) - 1)*100:.0f}%")
4.3 Feature Importance for Churn
# Extract feature importance
trained_model = churn_pipeline.named_steps['classifier']
# Get feature names
preprocessor_trained = churn_pipeline.named_steps['preprocessor']
cat_encoder = preprocessor_trained.named_transformers_['cat']
cat_feature_names_churn = cat_encoder.get_feature_names_out(cat_cols_churn).tolist() if len(cat_cols_churn) > 0 else []
all_features_churn = num_cols_churn + cat_feature_names_churn
importances = trained_model.feature_importances_
fi_churn = pd.DataFrame({
'feature': all_features_churn[:len(importances)],
'importance': importances
}).sort_values('importance', ascending=False)
print("=" * 55)
print("TOP CHURN PREDICTORS — FEATURE IMPORTANCE")
print("=" * 55)
for _, row in fi_churn.head(10).iterrows():
pct = row['importance'] / fi_churn['importance'].sum() * 100
bar = '█' * int(pct * 0.5)
print(f"{row['feature']:30s} {pct:>5.1f}% {bar}")
print(f"\nInterpretation:")
print(f"The top 3 features account for approximately ")
print(f"{fi_churn.head(3)['importance'].sum() / fi_churn['importance'].sum() * 100:.0f}% of")
print(f"the model's predictive power. These are the drivers of churn that")
print(f"retention strategies should address first.")
5. Retention Strategy Design
A churn prediction model is only useful if it leads to a retention action. The model identifies which customers are at risk — but the retention strategy determines what to do about it. The most important concept in retention strategy design is the distinction between voluntary churn (the customer actively decides to leave) and involuntary churn (the customer forgets to renew, changes their credit card, or their policy inadvertently lapses). Involuntary churn accounts for 20–40% of total churn in many insurance portfolios — and it is often the easiest to fix with operational improvements like automatic renewal and multiple payment reminders.
5.1 The Retention Action Framework
| Churn Reason | % of Churn | Retention Action | Cost per Customer | Expected Success Rate |
|---|---|---|---|---|
| Involuntary — forgot to renew | 20–30% | Automated SMS + email reminders (3: at 30, 14, 3 days before expiry). Auto-renew feature with UPI mandate. | ₹2–₹5 | 60–80% (operational fix — most effective) |
| Price sensitivity — found cheaper elsewhere | 20–35% | Targeted discount (5–15%) for at-risk customers. Bundle offer (discount for adding another product). Price-match within reason. | ₹200–₹1,000 (discount cost) | 30–50% (discount-dependent) |
| Poor claims experience | 10–20% | Customer service call from claims manager. Personalised apology if applicable. Fast-track on future claims. Small goodwill gesture (₹500–₹2,000). | ₹300–₹1,500 | 40–60% (if the complaint is genuinely resolved) |
| No recent claim — feels insurance is unnecessary | 10–15% | Educational content about why insurance matters even without claims. Product enhancement communication. Cross-sell into a product with more visible value (health insurance, wellness programme). | ₹50–₹200 (content cost only) | 15–25% (hardest to persuade — need to shift mindset, not just price) |
| Life event — moved, changed job, family change | 5–10% | Proactive policy adjustment (port to new location, adjust coverage to new needs). Welcome benefit for new stage. | ₹100–₹500 | 30–50% (if the insurer can make it easy to adapt instead of replace) |
| Service complaints — poor experience | 5–10% | Root cause resolution + service recovery (compensation + assurance of improvement). Escalation to senior management. | ₹500–₹2,000 | 50–70% (if complaint is genuinely resolved — highest ROI of all retention actions) |
5.2 The "Persuadable" Segment
Not all customers predicted to churn are worth contacting. Some will leave no matter what you offer (the "lost causes"). Some will stay even without intervention (the "loyal" — your model flagged them as at-risk but they were actually not going to churn). The segment worth investing in is the persuadable — customers who will stay if given the right incentive but will leave without it. Uplift modelling — which directly predicts the incremental impact of a retention action on a specific customer — is the advanced technique for identifying the persuadable segment. For a simpler approach, segment predicted churners by their estimated CLV and contact only those above a CLV threshold:
# Apply churn probabilities to the portfolio
all_prob = churn_pipeline.predict_proba(X)[:, 1]
policy_data['churn_prob_score'] = all_prob
# Estimate CLV for each customer
policy_data['estimated_clv'] = (
annual_profit * (1 / (1 - 0.75)) - acquisition_cost
) * (1 + 0.05 * policy_data['tenure_years']) # CLV increases with tenure
# Segment for retention action
policy_data['retention_priority'] = pd.cut(
policy_data['churn_prob_score'],
bins=[0, 0.15, 0.30, 0.50, 1.0],
labels=['Low Risk', 'Medium Risk', 'High Risk — Persuadable', 'High Risk — Lost Cause']
)
# Target: High Risk — Persuadable with CLV above threshold
target_for_retention = policy_data[
(policy_data['retention_priority'] == 'High Risk — Persuadable') &
(policy_data['estimated_clv'] > acquisition_cost)
]
print("=" * 60)
print("RETENTION TARGETING — SEGMENTATION")
print("=" * 60)
print(f"{'Segment':30s} {'Customers':>12s} {'CLV (avg)':>15s}")
print("-" * 57)
for tier in ['Low Risk', 'Medium Risk', 'High Risk — Persuadable', 'High Risk — Lost Cause']:
subset = policy_data[policy_data['retention_priority'] == tier]
if len(subset) > 0:
print(f"{tier:30s} {len(subset):>8,d} ₹{subset['estimated_clv'].mean():>8,.0f}")
print(f"\n{'─' * 57}")
print(f"RETENTION TARGET: '{target_for_retention['retention_priority'].iloc[0]}'")
print(f" Customers to contact: {len(target_for_retention):,}")
print(f" Average CLV: ₹{target_for_retention['estimated_clv'].mean():,.0f}")
print(f" Retention offer cost: ₹{retention_offer_cost}/customer")
print(f" Total programme cost: ₹{len(target_for_retention) * retention_offer_cost:,.0f}")
print(f" Estimated ROI: ~₹{(target_for_retention['estimated_clv'].sum() * 0.30 - len(target_for_retention) * retention_offer_cost) / max(len(target_for_retention) * retention_offer_cost, 1) * 100:.0f}%")
6. ROI of Retention
Retention programmes have a cost — discounts, call centre time, technology, management attention. Every retention rupee spent must be justified by a measurable reduction in churn. The ROI calculation compares the cost of the retention programme to the value of customers retained who would otherwise have churned.
6.1 The Retention ROI Model
# Retention ROI simulation
customers_targeted = len(target_for_retention)
avg_clv = target_for_retention['estimated_clv'].mean()
offer_cost_per_customer = 200 # ₹200 per customer contacted
expected_save_rate = 0.30 # 30% of targeted customers are persuaded to stay
# Programme costs
total_programme_cost = customers_targeted * offer_cost_per_customer
# Direct benefit
customers_saved = customers_targeted * expected_save_rate
total_value_saved = customers_saved * avg_clv
# Indirect benefit — "do not contact" savings
# Every customer we correctly identify as low-risk avoids a retention call they don't need
low_risk_count = len(policy_data[policy_data['retention_priority'] == 'Low Risk'])
do_not_contact_savings = low_risk_count * offer_cost_per_customer # Not spending on these
net_roi = (total_value_saved - total_programme_cost) / total_programme_cost
print("=" * 60)
print("RETENTION PROGRAMME — ROI ANALYSIS")
print("=" * 60)
print(f"{'Metric':35s} {'Value':15s}")
print("-" * 50)
print(f"{'Customers targeted':35s} {customers_targeted:>8,d}")
print(f"{'Average CLV per customer':35s} ₹{avg_clv:>8,.0f}")
print(f"{'Offer cost per customer':35s} ₹{offer_cost_per_customer:>8,.0f}")
print(f"{'Expected save rate':35s} {expected_save_rate*100:.0f}%")
print(f"{'Customers expected to save':35s} {customers_saved:>8,.0f}")
print(f"{'Total programme cost':35s} ₹{total_programme_cost:>8,.0f}")
print(f"{'Total value saved':35s} ₹{total_value_saved:>8,.0f}")
print(f"{'Do-not-contact savings':35s} ₹{do_not_contact_savings:>8,.0f}")
print(f"{'─' * 50}")
print(f"{'NET ROI':35s} {net_roi*100:.0f}%")
print(f"{'Net benefit (₹)':35s} ₹{total_value_saved - total_programme_cost:>8,.0f}")
# Sensitivity analysis — varying save rate
save_rates = np.arange(0.10, 0.55, 0.05)
print(f"\nSensitivity Analysis — Save Rate → ROI:")
for sr in save_rates:
saved = customers_targeted * sr
value = saved * avg_clv
cost = customers_targeted * offer_cost_per_customer
roi = (value - cost) / cost
print(f" Save rate {sr*100:.0f}% → {saved:.0f} customers saved → ₹{value:,.0f} value → ROI {roi*100:.0f}%")
print(f"\nBreak-even save rate: {total_programme_cost / (avg_clv * customers_targeted) * 100:.1f}%")
print(f"(If the save rate falls below this, the programme destroys value.)")
7. KNIME Churn Workflow
KNIME Analytics Platform enables the churn scoring workflow to be built and operated by analysts who may not code in Python. The visual workflow is also valuable for auditability — the compliance team can see exactly how churn scores are generated, which features are used, and how the retention list is prioritised.
7.1 The KNIME Churn Scoring Workflow
KNIME WORKFLOW: CHURN_SCORING_V1
┌────────────────────────────┐
│ CSV Reader │
│ (insurance_cleaned.csv) │
└─────────────┬──────────────┘
│
▼
┌────────────────────────────┐
│ GroupBy │
│ (Aggregate to policy level:│
│ premium, claim_count, │
│ tenure_months, etc.) │
└─────────────┬──────────────┘
│
▼
┌────────────────────────────┐
│ Rule Engine (Feature │
│ Engineering) │
│ tenure_years = tenure_m/12 │
│ had_claim = claim_ct > 0 │
│ premium_vs_mean = ... │
│ young = age ≤ 30 │
│ senior = age ≥ 60 │
└─────────────┬──────────────┘
│
▼
┌────────────────────────────┐
│ Partitioning │
│ (70% Train / 30% Test) │
└─────────────┬──────────────┘
│
┌─────────┴─────────┐
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ XGBoost │ │ (Test data │
│ Learner │ │ — no churn │
│ scale_pos = │ │ label) │
│ weight │ │ │
└──────┬───────┘ └──────┬───────┘
│ │
▼ ▼
┌────────────────────────────┐
│ XGBoost Predictor │
│ (Score test + new data) │
└─────────────┬──────────────┘
│
▼
┌────────────────────────────┐
│ Scorer + Confusion Matrix │
│ (Precision, Recall, F1, │
│ ROC-AUC for validation) │
└─────────────┬──────────────┘
│
▼
┌────────────────────────────┐
│ Rule Engine — Threshold │
│ (Apply optimal threshold │
│ from cost-benefit │
│ analysis, e.g., ≥0.30) │
└─────────────┬──────────────┘
│
▼
┌────────────────────────────┐
│ Sorter + Top K Selector │
│ (Sort by churn_prob DESC, │
│ keep top 5,000 for │
│ retention campaign) │
└─────────────┬──────────────┘
│
▼
┌────────────────────────────┐
│ CSV Writer │
│ (Retention list: customer_ │
│ id, phone, churn_prob, │
│ estimated_clv, priority) │
└────────────────────────────┘
7.2 KNIME vs. Python for Churn — Practical Comparison
| Task | Python | KNIME | Best For |
|---|---|---|---|
| Feature engineering | Flexible — any transformation possible | Rule Engine node for simple arithmetic, Column Expressions for complex logic | Python wins for complex feature engineering; KNIME sufficient for standard features |
| Model training | Full control over hyperparameters, cross-validation, custom metrics | XGBoost Learner node with standard parameters, limited CV | Python for experimentation; KNIME for production deployment of fixed model |
| Threshold optimisation | Easy — loop over thresholds, calculate net value | Requires Loops or Java Snippet — more complex | Python for threshold analysis; KNIME for deploying the chosen threshold |
| Batch scoring (100K+ records) | Fast with vectorised operations | Fast — KNIME scales to large datasets with row-based processing | Comparable — both handle 100K+ records efficiently |
| Audit trail / compliance | Requires code version control + documentation discipline | Built-in — the visual workflow is the audit trail | KNIME wins for regulated environments where auditors need to see the logic |
| Scheduled execution | Requires cron + script management or MLflow/Airflow | KNIME Server with scheduled execution — built-in | KNIME wins for operational teams without DevOps support |
Hands-On Project: End-to-End Churn Prediction and Retention System
You are the customer analytics lead at "RetainSure Insurance," where the annual churn rate is 28% — significantly above the industry benchmark of 20%. The CEO has set a target: reduce churn from 28% to 20% within 12 months. Your task is to build an end-to-end churn prediction system that identifies at-risk customers and designs targeted retention interventions.
Steps
- Churn economics: Using the framework from Section 1, calculate the financial impact of reducing churn from 28% to 20% on a portfolio of 100,000 policies with average premium ₹8,500. How much additional profit would the insurer generate annually?
- Feature engineering: Create at least 12 churn prediction features covering policy behaviour, customer characteristics, and interaction effects. Document the rationale for each — which features do you expect to be most predictive and why?
- XGBoost churn model: Train an XGBoost classifier with scale_pos_weight. Tune at least 2 hyperparameters (max_depth, learning_rate, or n_estimators) using 3-fold cross-validation. Report the optimal parameters and evaluate on the test set using ROC-AUC and PR-AUC.
- Threshold selection: Use the cost-based framework (retention offer cost = ₹200, CLV saved per retained customer = calculated from Section 1) to find the optimal threshold. Report the optimal threshold and the expected net value of the retention programme.
- Retention strategy: Design a segmented retention strategy with at least 3 different interventions for different churn segments (involuntary, price-sensitive, poor-claims-experience). For each segment, specify the action, the expected cost per customer, and the expected success rate.
- ROI calculation: Calculate the expected ROI of your retention programme. Sensitivity-test the ROI with save rates of 15%, 25%, and 35%. What is the break-even save rate?
- Write a 500-word churn reduction strategy memo to the CEO covering: (a) Churn economics — the cost of current churn and the value of reduction, (b) Model performance — how accurately can we predict who will churn? (c) Target segment and intervention design, (d) Programme ROI and timeline, (e) Key risks and success metrics.
View Solution / Walkthrough
Churn Reduction Strategy Memo (Sample)
To: CEO, RetainSure Insurance
From: Customer Analytics — Churn Reduction Programme
Subject: Churn Reduction Strategy — From 28% to 20% in 12 Months
Churn Economics — The Size of the Opportunity:
Our current churn rate of 28% on a portfolio of 100,000 policies means 28,000 customers leave each year. At an average premium of ₹8,500 and acquisition cost of ₹2,500 per new customer, we are spending ₹7 crore annually just to replace customers who left — money that could be profit if we retained them. Reducing churn to 20% would retain 8,000 additional customers per year, generating approximately ₹5.1 crore in additional annual profit (retained customers) plus ₹2 crore in acquisition cost savings — a total value of approximately ₹7 crore per year. This is the financial prize for a successful retention programme.
Model Performance and Targeting Capability:
The XGBoost churn model achieves a ROC-AUC of 0.81 and PR-AUC of 0.52, which means we can reliably distinguish customers at high risk of churn from those likely to stay. The top three churn predictors are: premium increase vs. market average (customers who received a renewal premium increase of more than 15% are 2.3× more likely to churn), tenure less than 1 year (first-renewal customers are 2.1× more likely to churn), and aggregator channel (aggregator-acquired customers churn at 38% vs. 18% for direct). The cost-based threshold analysis identifies that contacting the top 18% of at-risk customers (approximately 7,200 customers per quarter) with a targeted retention offer would save an estimated 2,160 customers (30% save rate) and generate a net programme value of approximately ₹2.1 crore per quarter — an ROI of over 200%.
Target Segment and Intervention Design:
We recommend three targeted interventions. Intervention 1 — Involuntary Churn (30% of churn): Implement automated renewal reminders via SMS and email at T-30, T-14, and T-3 days. Enable auto-renew with UPI mandate for direct customers. Cost: ~₹2/customer contacted. Expected success: 70% of involuntary churners retained. Intervention 2 — Price-Sensitive Churn (35% of churn): Targeted discount offer (10% off renewal premium) for customers flagged as high-risk and price-sensitive (aggregator channel, premium increase > 15%). Cost: ~₹500 average discount per customer. Expected success: 40% of targeted customers retained. Intervention 3 — Claims Experience Churn (15% of churn): For customers who filed a claim in the last 6 months and have a high churn probability: proactive service call from the claims team within 48 hours of the model flag. Offer a fast-track on any future claims. Cost: ~₹300 per call. Expected success: 50% of contacted customers retained.
Programme ROI and Timeline:
The combined programme cost is estimated at ₹3.8 crore annually (including discounts, operations, and technology). The expected benefit is ₹8.5 crore in retained customer value plus reduced acquisition costs. Net annual benefit: ₹4.7 crore. Programme ROI: 124%. Break-even save rate: 17% — meaning we only need to persuade 17% of targeted customers to stay for the programme to pay for itself. We recommend a phased rollout: Phase 1 (Months 1–3): Involuntary churn intervention only — low cost, high impact. Phase 2 (Months 4–6): Add price-sensitive intervention. Phase 3 (Months 7–12): Add claims experience intervention and cross-sell programme for low-risk customers. Target churn rate at Month 12: 20%.
Key Risks and Success Metrics:
The primary risk is retention cannibalisation — offering discounts to customers who would have renewed anyway. To mitigate this, we only offer discounts to customers with predicted churn probability above 0.30 (the optimal threshold) and we track the "offer acceptance rate" by segment — if customers with low model scores (who should not be churning) are accepting discounts, we are wasting money. The primary success metric is the cohort-level churn rate — not overall portfolio churn (which can be affected by many factors), but the churn rate of cohorts targeted vs. not targeted. We will track this monthly with a 3-month moving average. Early target: within 6 months, the targeted cohort shows a churn rate 5 percentage points better than the control (untargeted) cohort with similar risk profiles.
Key Takeaways
Customer churn is the single most important driver of long-term insurance profitability. Improving retention from 75% to 85% can increase customer lifetime value by approximately 80% — because the acquisition cost is amortised over a much longer relationship.
The most predictive churn features are: premium increase at renewal (especially >15% without a claim), claim denial in the last 12 months, and channel (aggregator customers churn at 2× the rate of direct customers. A model using only these three features captures 80% of predictive power.
Cost-based threshold optimisation for churn models balances the cost of retention offers against the value of customers saved. The optimal threshold depends on the offer cost, the expected save rate, and the customer CLV — and should be recalculated as these parameters change.
Churn is not a single problem — it is a portfolio of channel-specific, product-specific, and segment-specific problems. Involuntary churn (20–40% of total) is the cheapest to fix with automated reminders. The "lost cause" segment should be interviewed for churn diagnosis, not contacted with retention offers.
A Python-KNIME hybrid architecture — Python for model development, KNIME for operational deployment — combines the best of both tools for churn analytics in a regulated environment. The model is developed in Python and exported (PMML) for deployment in a visual, auditable KNIME workflow.
Test Your Understanding
1. The most cost-effective churn reduction strategy for most insurers is typically:
2. An XGBoost churn model achieves ROC-AUC of 0.85. This means:
3. A retention programme costs ₹200 per customer contacted, has a save rate of 25%, and the average CLV of a saved customer is ₹12,000. A contact list of 5,000 customers would generate:
4. The "lost cause" segment — customers with very high churn probability who will not respond to retention offers — should be:
5. A KNIME workflow for churn scoring is preferred over a Python script when: