Module 4 · Session 11 · 90 min · Python Lab

Session 11: AI & Machine Learning Fundamentals

CILO-2, CILO-3 · Analytics & Solution Design · Lab-based, Hands-on (Python) · Jupyter Notebook required

Learning Objectives

1. Machine Learning in Insurance — The Big Picture

Machine learning is not a magic wand. It is a set of statistical and computational techniques that find patterns in data and use those patterns to make predictions or decisions. In insurance, ML is valuable because the industry has exactly the characteristics that ML thrives on: large datasets, well-defined prediction problems, clear feedback loops, and significant financial impact from even small improvements in prediction accuracy.

1.1 Where ML Creates Value in Insurance

Insurance FunctionML ApplicationPrediction ProblemImpact of Improvement
UnderwritingRisk scoring, automated decision-makingPredict claim probability and expected loss for each applicantBetter risk selection = 5–15% loss ratio improvement
PricingElasticity modeling, dynamic pricingPredict willingness-to-pay and price sensitivity at individual level2–8% revenue uplift from optimized pricing
ClaimsSeverity prediction, triage, STPPredict ultimate claim size, complexity, and optimal routing5–10% claims leakage reduction
Fraud DetectionAnomaly detection, fraud classificationPredict probability that a claim is fraudulent10–25% improvement in fraud detection rate
Customer RetentionChurn prediction, next-best-actionPredict probability that a policyholder will lapse10–30% reduction in preventable churn
MarketingCustomer segmentation, lead scoringPredict which customers are most likely to buy20–40% improvement in marketing ROI

The impact ranges in the right-hand column are illustrative industry benchmarks drawn from McKinsey's AI-in-insurance research [4]; individual results depend heavily on data quality, product line, and implementation maturity.

1.2 The Insurance ML Maturity Curve

Most Indian insurers are at different stages of ML maturity. Understanding where an organisation sits on this curve is important for both internal strategy (what to invest in) and external analysis (what is a competitor capable of?):

Note on the percentages: the level shares (30% / 40% / 20% / 8% / 2%) are illustrative estimates intended to convey the rough distribution across the industry — they are not drawn from a single published survey. The framework itself (Descriptive → Diagnostic → Predictive → Prescriptive → Autonomous) follows Gartner's analytics maturity model [1]; real maturity data from McKinsey shows EMEA insurers' analytics-maturity scores span 18–85% [2], and Deloitte's global digital-maturity study is the complementary industry view [3]. See the References section at the end of this session.

📝
Note: An insurer does not need to be at Level 4 to benefit from ML. Most high-impact opportunities in Indian insurance are at Levels 2 and 3 — targeted fraud detection, automated motor underwriting, churn prediction. The ROI from moving from Level 1 to Level 2 is often higher than from Level 3 to Level 4, because the low-hanging fruit is larger. Do not be seduced by the vision of full autonomy if your organisation cannot reliably produce clean data at Level 1.

2. Supervised vs. Unsupervised Learning

All machine learning falls into two broad categories. The distinction is simple: supervised learning has a target variable to predict; unsupervised learning does not. Each category contains multiple algorithm types suited to different insurance problems.

DimensionSupervised LearningUnsupervised Learning
What you haveFeatures (input variables) + Labels (target variable to predict)Features only — no labels, no target
What the model doesLearns the relationship between features and labels, then predicts labels for new dataFinds hidden patterns, groups, or structures in the data without being told what to look for
RegressionPredict a continuous number. Example: "What will this claim cost?"N/A
ClassificationPredict a category. Example: "Will this policyholder file a claim (yes/no)?" or "What type of claim is this?"N/A
ClusteringN/AGroup similar items together. Example: "Segment policyholders into risk groups based on their characteristics."
Anomaly DetectionN/AIdentify unusual data points. Example: "Which claims look suspicious and should be investigated?"
Dimensionality ReductionN/ASimplify data while preserving information. Example: "Reduce 50 underwriting features to 10 composite features for easier modeling."
💡
Pro Tip: A common mistake beginners make is applying unsupervised learning when they actually have labels and should use supervised learning, or vice versa. The rule is: if you have historical data with known outcomes (a claim happened or didn't, fraud was confirmed or not), use supervised learning. If you want to explore the data structure without predefined categories, use unsupervised learning. In insurance, most business problems are supervised — but unsupervised techniques are invaluable for feature engineering (creating new variables for supervised models) and for detection of emerging patterns that do not have historical labels yet.

3. The ML Workflow

Every machine learning project in insurance follows the same six-step workflow. The details change — the features, the algorithm, the evaluation metric — but the structure is always the same. Mastering this workflow is more important than mastering any individual algorithm.

3.1 The Six-Step ML Workflow

┌─────────────────────────────────────────────────────────────────────┐
│                    THE ML WORKFLOW                                  │
│                                                                     │
│  ┌────────────┐   ┌────────────┐   ┌────────────┐   ┌────────────┐ │
│  │ 1. Problem  │   │ 2. Data    │   │ 3. Model   │   │ 4. Train   │ │
│  │ Definition  │──→│ Preparation│──→│ Selection  │──→│ & Validate │ │
│  └────────────┘   └────────────┘   └────────────┘   └────────────┘ │
│                                                         │          │
│  ┌────────────┐   ┌────────────┐                       │          │
│  │ 6. Deploy  │←──│ 5. Evaluate│◄───────────────────────┘          │
│  │ & Monitor  │   │            │                                    │
│  └────────────┘   └────────────┘                                    │
│                                                                     │
│  Feedback loop: Model predictions → Business decisions →           │
│  New data → Re-train → Better predictions                          │
└─────────────────────────────────────────────────────────────────────┘

3.2 The Scikit-Learn API Pattern

The Scikit-learn library provides a consistent API across all models. Once you learn this pattern, you can apply it to almost any algorithm with minimal code changes:

# The Universal Scikit-Learn Pattern:
#
# 1. Import the model class
# 2. Instantiate the model (with parameters)
# 3. Split data into training and test sets
# 4. Fit the model on the training data
# 5. Predict on the test data
# 6. Evaluate the predictions

import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score, accuracy_score

# 1. Import
from sklearn.linear_model import LinearRegression

# Toy data so this pattern runs standalone (Section 4 replaces this with real
# insurance data). X = one feature per row, y = the target value to predict.
X = np.array([[1], [2], [3], [4], [5], [6], [7], [8], [9], [10]])
y = np.array([2.9, 6.1, 8.8, 12.2, 15.1, 18.0, 20.9, 24.1, 27.0, 30.2])  # ≈ 3x + noise

# 2. Instantiate
model = LinearRegression()

# 3. Split
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size = 0.2, random_state = 0
)

# 4. Fit
model.fit(X_train, y_train)

# 5. Predict
y_pred = model.predict(X_test)

# 6. Evaluate
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
r2 = r2_score(y_test, y_pred)
print(f"RMSE: {rmse:.2f}")
print(f"R²: {r2:.3f}")

3.3 Train/Test Split — Why We Split

In insurance, the train/test split serves a crucial purpose: an ML model that performs well on the data it was trained on but poorly on new data is useless — and dangerous. The split simulates the real world: the model will be deployed on new policyholders and claims it has never seen. If it only learned the patterns in the training data (including its random noise), it will fail on new data. This is called overfitting, and we explore it in Section 7.

# Always split before training — never train on your test data!
# Common splits: 80/20 (large datasets), 70/30 (smaller), 60/20/20 (train/val/test)

X_train, X_test, y_train, y_test = train_test_split(
    X, y,                # X, y are the toy data defined in §3.2 above
    test_size=0.2,       # Hold back 20% for testing
    random_state = 0      # For reproducible results
)

# For classification (categorical target), add stratify=y to preserve class
# proportions in each split:
# X_train, X_test, y_train, y_test = train_test_split(
#     X, y, test_size = 0.2, random_state = 0, stratify = y
# )
Critical Warning: Never use test data for training. This is the single most common mistake in applied ML. If you train on your test data — even inadvertently (by looking at test set performance and then tweaking the model) — your reported accuracy will be optimistically biased. You will think your model is better than it really is. In insurance, this means: you deploy a model that performs worse than expected, underwriters lose trust, and the project fails. Use a separate validation set (train/val/test) if you need to iterate on model selection. Test data is sacred — it is used exactly once, at the very end.

4. Regression — Predicting Claim Amounts

Regression predicts a continuous numeric value. In insurance, the most common regression task is predicting claim amounts — given policyholder characteristics, what is the expected claim amount? This is used for: pricing (what premium should we charge?), reserving (how much money should we set aside?), and underwriting (which applications should we accept?).

🔗
How to run this section: the §4–§7 code blocks below form one continuous script split across the page — each block continues from the previous one, so copy them in order into the same Jupyter notebook cell sequence. Two data files are required (both provided in the lab folder InsuranceTech_code/data/ and on GitHub): insurance_cleaned.csv (claim-level — used by §4 regression) and insurance_policy_cleaned.csv (policy-level, includes no-claim policies — used by §5 classification and §6 clustering). A single complete runnable script is available as session_11_ml_fundamentals.py in InsuranceTech_code/: run venv/bin/python session_11_ml_fundamentals.py (requires scikit-learn). GitHub: insurance_cleaned.csv · insurance_policy_cleaned.csv · complete script.

4.1 Preparing Data for Regression

# Preparing Data for Regression

# Importing the libraries
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd

# Importing the dataset
dataset = pd.read_csv('data/insurance_cleaned.csv')

# Keep only claims with a known amount, and drop rows with missing features
dataset = dataset[dataset['claim_amount'].notna()].copy()
feature_cols = ['age', 'income', 'credit_score', 'premium', 'sum_assured', 'policy_type']
feature_cols = [c for c in feature_cols if c in dataset.columns]
dataset = dataset.dropna(subset=feature_cols)

# Separate features and target (X = features, y = claim amount to predict)
X = dataset[feature_cols]          # keep as DataFrame so ColumnTransformer can use column names
y = dataset['claim_amount'].values

# Identify categorical columns (to be one-hot encoded below)
categorical_cols = dataset[feature_cols].select_dtypes(include=['object']).columns.tolist()
numeric_cols = dataset[feature_cols].select_dtypes(include=[np.number]).columns.tolist()

print(f"Numeric features: {numeric_cols}")
print(f"Categorical features: {categorical_cols}")
print(f"Total observations: {len(dataset):,}")

# Encoding categorical data
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder
ct = ColumnTransformer(transformers=[('encoder', OneHotEncoder(drop='first'), categorical_cols)], remainder='passthrough')
X = np.array(ct.fit_transform(X))

4.2 Building the Regression Pipeline

# Splitting the dataset into the Training set and Test set
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 0)

# Feature Scaling
from sklearn.preprocessing import StandardScaler
sc = StandardScaler()
X_train = sc.fit_transform(X_train)
X_test = sc.transform(X_test)

# Training the Multiple Linear Regression model on the Training set
from sklearn.linear_model import LinearRegression
regressor = LinearRegression()
regressor.fit(X_train, y_train)

# Predicting the Test set results
y_pred = regressor.predict(X_test)
np.set_printoptions(precision=2)
print(np.concatenate((y_pred.reshape(len(y_pred),1), y_test.reshape(len(y_test),1)),1))

# Evaluating the model
from sklearn.metrics import mean_squared_error, r2_score, mean_absolute_error
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
mae = mean_absolute_error(y_test, y_pred)
r2 = r2_score(y_test, y_pred)

print("=" * 50)
print("CLAIM AMOUNT REGRESSION RESULTS")
print("=" * 50)
print(f"RMSE:  ₹{rmse:,.0f}")
print(f"MAE:   ₹{mae:,.0f}")
print(f"R²:    {r2:.3f}")
print(f"  (Context: average claim = ₹{y_test.mean():,.0f})")
print(f"  The prediction error represents {mae/y_test.mean()*100:.1f}% of the average claim")

4.3 Interpreting Coefficients

# Interpreting the coefficients
# After one-hot encoding, the feature names come from the ColumnTransformer
feature_names = ct.get_feature_names_out()

coeff_df = pd.DataFrame({
    'feature': feature_names,
    'coefficient': regressor.coef_
}).sort_values('coefficient', key=abs, ascending=False)

print("Top 10 Most Influential Features (by absolute coefficient):")
print("=" * 60)
print(f"{'Feature':35s} {'Coefficient':>15s} {'Direction':>12s}")
print("-" * 60)
for _, row in coeff_df.head(10).iterrows():
    direction = "Increases claim" if row['coefficient'] > 0 else "Decreases claim"
    print(f"{row['feature']:35s} {row['coefficient']:>+12,.0f}   {direction}")

print(f"\n{'-' * 60}")
print(f"Intercept: {regressor.intercept_:,.0f}")
print(f"\nNote: because the features were standardised (mean 0, std 1), the")
print(f"coefficients are comparable in RELATIVE magnitude (which feature matters")
print(f"most), not in original rupee units. Categorical coefficients are relative")
print(f"to the baseline (dropped) category.")

4.4 Diagnostic Plot

import matplotlib.pyplot as plt

fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# LEFT: Actual vs Predicted
axes[0].scatter(y_test, y_pred, alpha=0.3, s=10, color='#6c5ce7')
axes[0].plot([y_test.min(), y_test.max()],
             [y_test.min(), y_test.max()],
             'r--', linewidth=2, label='Perfect Prediction')
axes[0].set_xlabel('Actual Claim Amount')
axes[0].set_ylabel('Predicted Claim Amount')
axes[0].set_title('Regression: Actual vs. Predicted', fontweight='bold')
axes[0].legend()
axes[0].spines['top'].set_visible(False)
axes[0].spines['right'].set_visible(False)

# RIGHT: Residuals
residuals = y_test - y_pred
axes[1].scatter(y_pred, residuals, alpha=0.3, s=10, color='#00d2d3')
axes[1].axhline(y=0, color='red', linestyle='--', linewidth=1.5)
axes[1].set_xlabel('Predicted Claim Amount')
axes[1].set_ylabel('Residual (Actual − Predicted)')
axes[1].set_title('Residual Plot', fontweight='bold')
axes[1].spines['top'].set_visible(False)
axes[1].spines['right'].set_visible(False)

plt.tight_layout()
plt.show()

print("Residual analysis:")
print(f"  Mean residual: {residuals.mean():.0f} (should be close to 0)")
print(f"  Std of residuals: {residuals.std():.0f}")
print(f"  Are residuals roughly evenly distributed above and below zero?")
print(f"  If the residual plot shows a pattern (cone shape, curve), the linear model is inadequate.")
🌎
Real World: In practice, claim amount prediction is rarely done with simple linear regression because claim distributions are heavily right-skewed. A better approach (which you will see in Session 21) is to predict the logarithm of the claim amount, which transforms the skewed distribution into a roughly normal one. Or use a quantile regression to predict different points in the distribution (e.g., the 50th percentile for "typical" claims and the 95th percentile for tail risk). Linear regression on untransformed claim amounts is a learning exercise — the foundation for understanding more sophisticated models later.

5. Classification — Predicting Claim Probability

Classification predicts a category. In insurance, the most common classification task is predicting whether a policyholder will file a claim (binary: yes or no). This is used for: underwriting (should we accept this risk?), renewal targeting (who is most likely to claim?), and fraud triage (is this claim suspicious?).

5.1 Preparing Data for Classification

# Preparing Data for Classification

# Importing the libraries
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd

# Importing the dataset (policy-level: one row per policy, including no-claim policies)
dataset = pd.read_csv('data/insurance_policy_cleaned.csv')

# Check class balance
claim_rate = dataset['has_claim'].mean()
print(f"Claim rate: {claim_rate*100:.1f}%")
print(f"  Claim filed (1): {(dataset['has_claim'] == 1).sum():,}")
print(f"  No claim (0):    {(dataset['has_claim'] == 0).sum():,}")

# Define features and target, and drop rows with missing values
class_features = ['premium', 'age', 'income', 'credit_score', 'sum_assured', 'policy_type']
class_features = [c for c in class_features if c in dataset.columns]
dataset = dataset.dropna(subset=class_features)

X = dataset[class_features]         # keep as DataFrame so ColumnTransformer can use column names
y = dataset['has_claim'].values

# Encoding categorical data
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder
categorical_cols = dataset[class_features].select_dtypes(include=['object']).columns.tolist()
ct = ColumnTransformer(transformers=[('encoder', OneHotEncoder(drop='first'), categorical_cols)], remainder='passthrough')
X = np.array(ct.fit_transform(X))

5.2 Building the Classification Pipeline

# Splitting the dataset into the Training set and Test set
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 0, stratify = y)

# Feature Scaling
from sklearn.preprocessing import StandardScaler
sc = StandardScaler()
X_train = sc.fit_transform(X_train)
X_test = sc.transform(X_test)

# Training the Logistic Regression model on the Training set
from sklearn.linear_model import LogisticRegression
classifier = LogisticRegression(random_state = 0, class_weight = 'balanced')
classifier.fit(X_train, y_train)

# Predicting the Test set results
y_pred = classifier.predict(X_test)
print(np.concatenate((y_pred.reshape(len(y_pred),1), y_test.reshape(len(y_test),1)),1))

# Making the Confusion Matrix
from sklearn.metrics import confusion_matrix, accuracy_score
cm = confusion_matrix(y_test, y_pred)
print(cm)
print(accuracy_score(y_test, y_pred))

# Evaluating with precision, recall, and ROC-AUC
from sklearn.metrics import precision_score, recall_score, f1_score, roc_auc_score
y_prob = classifier.predict_proba(X_test)[:, 1]
print("=" * 60)
print("CLAIM PROBABILITY CLASSIFICATION RESULTS")
print("=" * 60)
print(f"Accuracy:  {accuracy_score(y_test, y_pred):.3f}")
print(f"Precision: {precision_score(y_test, y_pred):.3f}")
print(f"Recall:    {recall_score(y_test, y_pred):.3f}")
print(f"F1 Score:  {f1_score(y_test, y_pred):.3f}")
print(f"ROC-AUC:   {roc_auc_score(y_test, y_prob):.3f}")

5.3 Why Accuracy is Not Enough

In a dataset where only 12% of policyholders file a claim, a naive model that predicts "No Claim" for everyone achieves 88% accuracy — but it is completely useless. This is the class imbalance problem that pervades insurance ML. Accuracy alone is dangerously misleading.

For insurance classification problems, the evaluation metric depends on the business context:

5.4 ROC Curve

# Plot ROC curve
from sklearn.metrics import roc_curve, roc_auc_score
fpr, tpr, thresholds = roc_curve(y_test, y_prob)
auc = roc_auc_score(y_test, y_prob)

fig, ax = plt.subplots(figsize=(8, 6))
ax.plot(fpr, tpr, color='#6c5ce7', linewidth=2.5,
        label=f'ROC Curve (AUC = {auc:.3f})')
ax.plot([0, 1], [0, 1], 'r--', linewidth=1.5, label='Random Classifier (AUC = 0.5)')
ax.fill_between(fpr, tpr, alpha=0.15, color='#6c5ce7')
ax.set_xlabel('False Positive Rate (1 − Specificity)')
ax.set_ylabel('True Positive Rate (Recall)')
ax.set_title('ROC Curve — Claim Prediction Model', fontweight='bold')
ax.legend(loc='lower right')
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)

# Find the optimal threshold (Youden's J statistic)
j_scores = tpr - fpr
best_idx = np.argmax(j_scores)
best_threshold = thresholds[best_idx]
ax.annotate(f'Optimal threshold: {best_threshold:.2f}',
            xy=(fpr[best_idx], tpr[best_idx]),
            xytext=(fpr[best_idx] + 0.15, tpr[best_idx] - 0.1),
            arrowprops=dict(arrowstyle='->', color='green'),
            fontsize=10, color='green')
ax.plot(fpr[best_idx], tpr[best_idx], 'go', markersize=10)

plt.tight_layout()
plt.show()

print(f"\nOptimal threshold (Youden's J): {best_threshold:.3f}")
print(f"  At this threshold: Sensitivity = {tpr[best_idx]:.3f}, Specificity = {1-fpr[best_idx]:.3f}")
print(f"\nInterpretation: A model with AUC = {auc:.3f} can distinguish")
print(f"claim-filers from non-claim-filers {auc*100:.1f}% of the time.")
print(f"This is {'strong' if auc > 0.85 else 'moderate' if auc > 0.7 else 'weak'} predictive power.")
💡
Pro Tip: When communicating classification model performance to business stakeholders, do not lead with precision, recall, or AUC. Lead with business impact. "Our model identifies 65% of fraudulent claims before payment, which would save ₹12 crore annually" is a statement the business will act on. "Our model has a recall of 0.65 and precision of 0.40" is a statement that will get you a blank stare. Always translate ML metrics into business outcomes before presenting to non-technical stakeholders.

6. Clustering — Segmenting Policyholders

Clustering is an unsupervised learning technique that groups similar data points together. In insurance, clustering is used for customer segmentation, risk profiling, fraud detection (unusual clusters), and product design (identifying underserved segments). Unlike regression and classification, clustering does not use a target variable — it finds structure in the data purely from the features.

6.1 When to Cluster

Use clustering when you want to discover groups in your data that you did not know existed. Common insurance applications:

6.2 K-Means Clustering in Python

# K-Means Clustering

# Importing the libraries
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd

# Importing the dataset (policy-level)
dataset = pd.read_csv('data/insurance_policy_cleaned.csv')

# Building a customer-level profile (one row per customer, including no-claim customers)
customer_profile = dataset.groupby('customer_id').agg(
    age = ('age', 'first'),
    income = ('income', 'first'),
    credit_score = ('credit_score', 'first'),
    total_premium = ('premium', 'sum'),
    total_claims = ('total_claim_amount', 'sum'),
    claim_count = ('n_claims', 'sum'),
    policy_count = ('policy_id', 'nunique')
).reset_index()

# Adding derived features
customer_profile['loss_ratio'] = (customer_profile['total_claims'] / customer_profile['total_premium']).fillna(0)
customer_profile['claims_per_policy'] = customer_profile['claim_count'] / customer_profile['policy_count']

# Selecting the features for clustering, and dropping rows with missing values
clust_cols = ['age', 'income', 'credit_score', 'total_premium', 'loss_ratio', 'claims_per_policy', 'policy_count']
clust_cols = [c for c in clust_cols if c in customer_profile.columns]
customer_profile = customer_profile.dropna(subset=clust_cols)
X = customer_profile[clust_cols].values

# Feature Scaling (K-means is distance-based, so scaling is critical)
from sklearn.preprocessing import StandardScaler
sc = StandardScaler()
X = sc.fit_transform(X)

# Using the elbow method to find the optimal number of clusters
from sklearn.cluster import KMeans
wcss = []
for i in range(1, 11):
    kmeans = KMeans(n_clusters = i, init = 'k-means++', random_state = 42)
    kmeans.fit(X)
    wcss.append(kmeans.inertia_)
plt.plot(range(1, 11), wcss)
plt.title('The Elbow Method')
plt.xlabel('Number of clusters')
plt.ylabel('WCSS')
plt.show()

6.3 Profiling the Clusters

# Training the K-Means model on the dataset
kmeans = KMeans(n_clusters = 4, init = 'k-means++', random_state = 42)
y_kmeans = kmeans.fit_predict(X)

# Profiling the clusters
customer_profile['cluster'] = y_kmeans
cluster_profile = customer_profile.groupby('cluster').agg(
    count = ('customer_id', 'count'),
    avg_age = ('age', 'mean'),
    avg_income = ('income', 'mean'),
    avg_credit = ('credit_score', 'mean'),
    avg_premium = ('total_premium', 'mean'),
    avg_loss_ratio = ('loss_ratio', 'mean'),
    avg_claims_per_policy = ('claims_per_policy', 'mean'),
    avg_policies = ('policy_count', 'mean')
).round(1)

# Sort by cluster size for readability
cluster_profile = cluster_profile.sort_values('count', ascending=False)

print("=" * 90)
print("CUSTOMER SEGMENTATION — CLUSTER PROFILES")
print("=" * 90)
print(cluster_profile.to_string())

# Give each cluster a meaningful name based on its profile
print(f"\n{'='*90}")
print("BUSINESS INTERPRETATION:")
print(f"{'='*90}")
for cluster_id in cluster_profile.index:
    row = cluster_profile.loc[cluster_id]
    share = row['count'] / cluster_profile['count'].sum() * 100
    # Generate a descriptive label
    if row['avg_loss_ratio'] < 0.3 and row['avg_income'] > cluster_profile['avg_income'].median():
        label = "Low-Risk, High-Value"
    elif row['avg_loss_ratio'] > 0.6:
        label = "High-Risk — Manage Carefully"
    elif row['avg_claims_per_policy'] < 0.1 and row['avg_policies'] > 2:
        label = "Loyal, Low-Claim — ⭐ Protect"
    elif row['avg_age'] > 50:
        label = "Senior Segment — Growing"
    else:
        label = "Standard Risk — Optimize Service"
    print(f"  Cluster {cluster_id} ({share:.0f}% of customers): {label}")
    print(f"    Age: {row['avg_age']:.0f}, Income: ₹{row['avg_income']:,.0f}, Credit: {row['avg_credit']:.0f}")
    print(f"    Loss Ratio: {row['avg_loss_ratio']*100:.1f}%, Policies: {row['avg_policies']:.1f}, Claims/Policy: {row['avg_claims_per_policy']:.2f}")
    print()
📝
Note: Clustering results are not "true" in the sense that a classification label is true. Different K values produce different segmentations, all of which may be valid for different business purposes. K=4 might give you the right segmentation for marketing (four customer types with different needs). K=8 might give you a segmentation that reveals risk patterns useful for underwriting. The "right" K is the one that produces actionable insights for your specific business problem. Always evaluate clustering output against business utility, not against a statistical gold standard.

7. Overfitting, Underfitting, and Cross-Validation

A model that memorizes the training data but fails on new data is overfitted. A model that is too simple to capture the patterns in the data is underfitted. Managing this tradeoff is the central challenge of applied machine learning — and it is especially acute in insurance, where the cost of a wrong prediction (an underpriced risk, a missed fraud) can be measured in crores.

7.1 Overfitting vs. Underfitting

DimensionUnderfittingOverfitting
What happensModel is too simple to capture the underlying pattern in the dataModel learns the training data too well, including noise and random fluctuations
Training performancePoor (high training error)Excellent (very low training error)
Test performancePoor (high test error)Poor (high test error — the gap between train and test is large)
Visual signatureLine through data does not capture the trendLine curves wildly to pass through every point
In insuranceUsing a flat premium for all policyholders regardless of risk factorsA pricing model that assigns a unique premium to each of 10,000 policyholders based on noise, not signal
FixUse a more complex model; engineer better features; reduce regularizationSimplify the model; use regularization; get more training data; reduce feature count

7.2 Detecting Overfitting

# Detecting Overfitting (continues from the regression model in Section 4)

# Re-load and re-prepare the regression data so this section is self-contained
# (Sections 5-6 redefined X and y, so re-establish the regression context here).
import numpy as np
import pandas as pd
dataset = pd.read_csv('data/insurance_cleaned.csv')
dataset = dataset[dataset['claim_amount'].notna()].copy()
feature_cols = ['age', 'income', 'credit_score', 'premium', 'sum_assured', 'policy_type']
feature_cols = [c for c in feature_cols if c in dataset.columns]
dataset = dataset.dropna(subset=feature_cols)
X = dataset[feature_cols]          # keep as DataFrame so ColumnTransformer can use column names
y = dataset['claim_amount'].values
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder, StandardScaler
ct = ColumnTransformer(transformers=[('encoder', OneHotEncoder(drop='first'), ['policy_type'])], remainder='passthrough')
X = np.array(ct.fit_transform(X))
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 0)
sc = StandardScaler()
X_train = sc.fit_transform(X_train)
X_test = sc.transform(X_test)
from sklearn.linear_model import LinearRegression
regressor = LinearRegression()
regressor.fit(X_train, y_train)

# Training a deliberately over-complex model (deep decision tree) on the Training set
from sklearn.tree import DecisionTreeRegressor
overfit_tree = DecisionTreeRegressor(max_depth = None, min_samples_leaf = 1, random_state = 0)
overfit_tree.fit(X_train, y_train)

# Evaluating the overfit model
train_score = overfit_tree.score(X_train, y_train)
test_score = overfit_tree.score(X_test, y_test)

print("OVERFITTING DEMONSTRATION — Decision Tree (no depth limit)")
print("=" * 55)
print(f"  Training R²:  {train_score:.4f}  {'(unrealistically high — memorized training data)' if train_score > 0.95 else ''}")
print(f"  Test R²:      {test_score:.4f}")
print(f"  Gap:          {train_score - test_score:.4f}")
print()
if train_score > 0.95:
    print("  ⚠ This model has memorized the training data. It will perform")
    print("    poorly on new policyholders. The training R² is misleadingly high.")
    print()
    print("  Solution: Limit tree depth, increase min_samples_leaf, or use cross-validation.")
else:
    print("  The model shows modest overfitting. Consider regularization.")

# Training a regularized model (limited depth) to avoid overfitting
regularized_tree = DecisionTreeRegressor(max_depth = 5, min_samples_leaf = 20, random_state = 0)
regularized_tree.fit(X_train, y_train)
train_score_r = regularized_tree.score(X_train, y_train)
test_score_r = regularized_tree.score(X_test, y_test)

print(f"\nREGULARIZED DECISION TREE (max_depth=5, min_samples_leaf=20)")
print(f"  Training R²:  {train_score_r:.4f}")
print(f"  Test R²:      {test_score_r:.4f}")
print(f"  Gap:          {train_score_r - test_score_r:.4f}")
print(f"\nThe regularized model has a SMALLER gap between train and test,")
print(f"which means it will generalize better to new data.")

7.3 Cross-Validation

Cross-validation evaluates model performance on multiple train/test splits, providing a more reliable estimate of how the model will perform on new data. It reduces the variance of the performance estimate — a single train/test split may be lucky or unlucky depending on which observations ended up in the test set.

# Cross-Validation

from sklearn.model_selection import cross_val_score, KFold

# Set up 5-fold cross-validation
cv = KFold(n_splits = 5, shuffle = True, random_state = 0)

# Evaluate the regression model with cross-validation
cv_scores = cross_val_score(regressor, X, y, cv = cv, scoring = 'r2')

print("CROSS-VALIDATION RESULTS (5-Fold)")
print("=" * 40)
print(f"  Fold 1 R²: {cv_scores[0]:.3f}")
print(f"  Fold 2 R²: {cv_scores[1]:.3f}")
print(f"  Fold 3 R²: {cv_scores[2]:.3f}")
print(f"  Fold 4 R²: {cv_scores[3]:.3f}")
print(f"  Fold 5 R²: {cv_scores[4]:.3f}")
print(f"  ─────────────────────────")
print(f"  Mean R²:   {cv_scores.mean():.3f}")
print(f"  Std Dev:   {cv_scores.std():.3f}")
print(f"\nIf the standard deviation is large (>0.1), the model's performance")
print(f"varies significantly across data splits — investigate stability.")
print(f"If mean R² is close to the single-test R² from our 80/20 split,")
print(f"we can be more confident in the performance estimate.")
💡
Pro Tip: In insurance ML, use stratified cross-validation when working with classification problems (fraud, churn, claim prediction). Stratified CV preserves the class proportion in each fold — so if 2% of claims are fraudulent, each fold will also have approximately 2% fraudulent claims. Without stratification, some folds may end up with zero fraud cases, and the model cannot learn anything useful from that fold. For regression, standard K-Fold CV is usually sufficient, but consider shuffling the data first if it is ordered by time (policies sold later are different from policies sold earlier).
📈
Financial & Risk Management Implications — Overfitting is a reserving risk: For Finance-specialization students, "overfitting" is the same concept as model risk in banking and credit risk — a model that performs well in-sample but fails out-of-sample. In insurance, an overfit claims-severity model will under-reserve: IBNR and case reserves are set from the model's predictions, and a model that memorised noise will produce wrong central estimates, feeding directly into reserve adequacy and reported loss ratios. This is why actuarial and credit-risk model governance both mandate out-of-sample / back-testing — cross-validation is the ML name for exactly that discipline. A Finance student should read every "train/test gap" here as a proxy for the out-of-sample validation a model risk committee would require before signing off a model for capital or reserving purposes.

Hands-On Project: Three ML Models on One Insurance Dataset

You are an insurance analytics intern and your manager wants you to demonstrate three fundamental ML techniques — regression, classification, and clustering — on the company's insurance dataset. Use the cleaned dataset from Session 05. Build all three models. Present a single-page summary comparing what each model taught you about the business.

Steps

  1. Data preparation: Load the cleaned dataset. For regression and classification, use the policy-level aggregation (one row per policy). For clustering, use the customer-level aggregation (one row per customer).
  2. Regression model: Predict total claim amount per policy. Use features: premium, age, income, credit_score, policy_type, sum_assured. Evaluate using RMSE, MAE, and R². Interpret the coefficients — which factor most increases claim amount?
  3. Classification model: Predict whether a policy will have at least one claim. Use the same features. Evaluate using the full classification report: accuracy, precision, recall, F1, ROC-AUC. Plot the ROC curve. Report the optimal threshold.
  4. Clustering model: Segment customers into 3–5 groups using K-means on features: age, income, credit_score, total_premium, loss_ratio, claims_per_policy. Profile each cluster with a descriptive business label. Recommend one action per cluster.
  5. Summary table: Create a single-page table comparing the three models with columns: Model Type, Business Question, Key Metric, Performance, Actionable Insight (one sentence).
View Solution / Walkthrough

Summary Table (Sample Output)

Model TypeBusiness QuestionKey MetricPerformanceActionable Insight
Linear Regression What claim amount should we expect from a policy? R² ≈ 0.25–0.40 Premium and sum assured are the strongest predictors of claim amount — but the model explains less than half of variance, indicating that claim amounts are driven by factors we are not capturing (loss severity randomness, external conditions).
Logistic Regression Will this policy generate a claim? ROC-AUC AUC ≈ 0.65–0.75 The model has moderate discriminatory power. In our synthetic portfolio, credit score is the most influential feature — policyholders with credit scores below 650 are 40% more likely to file a claim (a dataset-derived lab output, not an external industry fact). Consider a credit-score based pricing adjustment.
K-Means Clustering What natural customer segments exist in our portfolio? Silhouette Score / Cluster Profiles 4 distinct clusters identified In our synthetic portfolio, K-Means found four clusters: Cluster 0 (32%): Young, low-income, low-premium — high loss ratio. Cluster 1 (28%): Mid-age, mid-income, medium premium — standard loss ratio. Cluster 2 (24%): Older, high-income, multi-policy — low loss ratio, highly profitable (⭐). Cluster 3 (16%): High-income, high-premium, high loss ratio — investigate for over-utilisation or price inadequacy. (All percentages are this lab's own output, not industry-wide findings.)

Key Business Insights

  1. Regression says: Claim amount is hard to predict with policy-level data alone. The unexplained variance (noise) is large — which is normal for insurance. But the model still adds value by identifying which policies are "high expected severity" vs. "low expected severity" at portfolio level.
  2. Classification says: Claim frequency is moderately predictable. The credit score feature is the most important single predictor. This supports using credit score as a rating factor (subject to regulatory approval and ethical review) and suggests implementing a "credit-score-aware" underwriting filter.
  3. Clustering says: The portfolio contains distinct customer segments with significantly different economics. Cluster 2 (older, wealthy, multi-policy, low claims) is the "golden segment" — the insurer should identify and protect these customers with loyalty programmes, premium discounts for loyalty, and cross-sell offers. Cluster 3 (high-premium, high-claims) needs underwriting review — are these customers over-utilising their coverage, or is the product mispriced for this segment?

The overarching insight: Combining all three ML techniques produces a richer business understanding than any one alone. Regression and classification tell you what drives outcomes (predictive). Clustering tells you how customers naturally group (descriptive). Together, they provide the evidence base for underwriting, pricing, and retention strategies.

Key Takeaways

1

Machine learning creates value across the insurance value chain — underwriting, pricing, claims, fraud, retention, and marketing. Most Indian insurers are at Level 1–2 of ML maturity, meaning the highest-ROI opportunities are in moving from descriptive to predictive analytics.

2

Supervised learning (regression + classification) requires labelled historical outcomes. Unsupervised learning (clustering) finds hidden patterns without labels. The Scikit-learn API is consistent across all models: import, instantiate, fit, predict, evaluate.

3

Regression predicts continuous values (claim amount). Classification predicts categories (claim/no-claim). For imbalanced insurance data, accuracy is misleading — use precision, recall, F1, and ROC-AUC instead.

4

Clustering with K-means reveals natural customer segments. The "right" number of clusters depends on business utility, not a statistical rule. A cluster with a low loss ratio and multi-policy tenure is the insurer's most valuable segment — protect it.

5

Managing the overfitting-underfitting tradeoff is the central challenge of insurance ML. Cross-validation provides a more reliable performance estimate than a single train/test split. Never use test data for training — test data is used exactly once, at the very end.

References

  1. Gartner, "Data and Analytics Maturity Score," Gartner, Inc. [Online]. Available: https://www.gartner.com/en/data-analytics/research/data-analytics-maturity-score
  2. McKinsey & Company, "On the brink: Realizing the value of analytics in insurance," McKinsey & Company. [Online]. Available: https://www.mckinsey.com/industries/financial-services/our-insights/on-the-brink-realizing-the-value-of-analytics-in-insurance
  3. Deloitte, "Digital Insurance Maturity 2025," Deloitte. [Online]. Available: https://www.deloitte.com/ce/en/related-content/digital-insurance-maturity-2025.html
  4. McKinsey & Company, "The future of AI in the insurance industry," 2025. [Online]. Available: https://www.mckinsey.com/industries/financial-services/our-insights/the-future-of-ai-in-the-insurance-industry

Further reading (Task G5): the applied-research anchor for this session's finance framing is McKinsey [4]. Flag for instructor review: a peer-reviewed Journal of Risk and Insurance / Geneva Papers article on analytics-maturity or ML-adoption business value was not verified during this pass and should be added before publication.

Test Your Understanding

1. An insurer wants to predict the exact claim amount (in rupees) that a policyholder will file. This is a:

2. A fraud detection model has 98% accuracy but achieves this by predicting "Not Fraud" for 100% of cases in a dataset where 2% of claims are fraudulent. The problem is:

3. A linear regression model achieves R² = 0.72 on the training set but R² = 0.18 on the test set. The most likely diagnosis is:

4. K-means clustering is classified as unsupervised learning because:

5. A model has ROC-AUC of 0.85. This means: