Customer Segmentation and Churn Prediction

Segment e-commerce customers with K-Means and predict churn with Random Forest.

Introduction

30 Second Summary

Every online store wants to know two things: which customers are their best, and which ones are about to leave. The companies that answer these questions keep their revenue growing while competitors lose customers silently.

In this project, you will build a customer intelligence pipeline that segments e-commerce customers into behavioral groups and predicts which ones are likely to churn. You will combine clustering and classification in a single notebook to demonstrate end-to-end machine learning thinking.

What You'll Build

A complete machine learning notebook in Google Colab that transforms 541,909 raw e-commerce transactions into customer segments and a churn prediction model.

By the end of this project, you'll have:

  • A cleaned, feature-engineered customer dataset with RFM (Recency, Frequency, Monetary) features derived from real e-commerce transactions.
  • A K-Means clustering model that segments customers into distinct behavioral groups, with Elbow and Silhouette visualizations.
  • A Random Forest churn classifier that uses cluster labels as an engineered feature, evaluated with ROC-AUC, confusion matrix, and feature importance plots.
  • Secret Mission: Quantify the exact performance uplift from adding cluster features to the classifier, proving the synergy between unsupervised and supervised learning.

Are there any prerequisites?

You should be comfortable with basic Python and have some familiarity with pandas DataFrames and the scikit-learn fit/predict pattern. No machine learning theory background is required.

Set Up and Load the Data

Every customer intelligence project starts with raw transaction data. Right now, you have over half a million purchase records sitting in a public dataset, but none of the patterns are visible yet.

In this step, you will set up your notebook environment, load the UCI Online Retail dataset, and clean it so only valid, meaningful transactions remain.

In this step, get ready to:
  • Import all libraries needed for clustering and classification.
  • Load the UCI Online Retail dataset into a pandas DataFrame.
  • Clean the raw data by removing invalid and cancelled transactions.
Open a new Google Colab notebook

Google Colab is a free, browser-based notebook environment that comes pre-installed with data science libraries like pandas and scikit-learn. You only need to install two small packages manually.

  • Open your browser and navigate to colab.research.google.com.
  • Sign in with your Google account if prompted.
  • Click New notebook to create a blank notebook.
  • Click the notebook title at the top left (it will say something like Untitled0.ipynb) and rename it to customer_intelligence.ipynb.

What is a Colab notebook?

A Colab notebook lets you write and run Python code in your browser, one cell at a time. Each cell can hold code or text. You run a cell by clicking the play button or pressing Shift+Enter.

Install packages and import libraries

Colab already has pandas, numpy, matplotlib, seaborn, and scikit-learn installed. You just need two additional packages: openpyxl (to read Excel files) and ucimlrepo (to fetch the dataset directly from the UCI repository).

  • In the first cell of your notebook, install the two packages by running this command:
!pip install ucimlrepo openpyxl

What does the exclamation mark do?

The ! prefix tells Colab to run a shell command instead of Python code. It is the equivalent of typing into a terminal.

  • Click the + Code button to add a new cell below.
  • Paste the following imports into the new cell and run it:
# =============================================================================
# E-Commerce Customer Intelligence: Segmentation + Churn Prediction
# =============================================================================

# --- Step 1: Setup and Data Loading ---

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.cluster import KMeans
from sklearn.ensemble import RandomForestClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import (
    classification_report,
    confusion_matrix,
    roc_auc_score,
    roc_curve,
    silhouette_score,
)

What are all these imports for?

pandas and numpy handle your data. matplotlib and seaborn create visualizations.

The sklearn imports include your clustering algorithm (KMeans), your classifier (RandomForestClassifier), preprocessing tools (StandardScaler), and evaluation metrics you will use later in the project.

  • Confirm the cell runs without errors. You should see no output (silence means success for imports).

Seeing an import error?

If you see a ModuleNotFoundError, make sure the !pip install cell ran first and completed without errors. Re-run it if needed, then re-run the imports cell.

Ask for help: Why am I getting a ModuleNotFoundError in Colab?

Fetch the UCI Online Retail dataset

The UCI Online Retail dataset contains 541,909 real e-commerce transactions from a UK-based retailer. You will pull it directly into your notebook using the ucimlrepo package.

  • Add a new code cell and load the dataset by running this code:
# Install and import ucimlrepo to fetch the dataset
# !pip install ucimlrepo openpyxl
from ucimlrepo import fetch_ucirepo

# Fetch UCI Online Retail dataset (ID: 352)
online_retail = fetch_ucirepo(id=352)
df = online_retail.data.original

print(f"Dataset shape: {df.shape}")
print(df.head())
print(df.dtypes)

What does this code do?

fetch_ucirepo(id=352) downloads the Online Retail dataset directly from the UCI Machine Learning Repository. The dataset ID 352 is the unique identifier for this specific dataset.

online_retail.data.original gives you the raw DataFrame with all 8 columns: InvoiceNo, StockCode, Description, Quantity, InvoiceDate, UnitPrice, CustomerID, and Country.

  • Run the cell. You should see output showing the dataset shape as (541909, 8), followed by the first 5 rows and column data types.

Download taking too long?

The dataset is a 22.6 MB Excel file. The first download can take 30-60 seconds depending on your internet connection. If it times out, re-run the cell.

Ask for help: My dataset download is failing in Colab.

Clean the raw transaction data

Raw transaction data always has noise. This dataset contains rows with missing customer IDs, cancelled orders, and invalid quantities. You need to remove these before building any models.

  • Add a new code cell and clean the data by running this code:
# Data cleaning
df = df.dropna(subset=["CustomerID"])
df = df[~df["InvoiceNo"].astype(str).str.startswith("C")]
df = df[df["Quantity"] > 0]
df = df[df["UnitPrice"] > 0]
df["TotalPrice"] = df["Quantity"] * df["UnitPrice"]

print(f"Cleaned dataset shape: {df.shape}")

What does each cleaning step do?

dropna(subset=["CustomerID"]) removes rows without a customer ID. You cannot build customer-level features without knowing who made the purchase.

The InvoiceNo filter removes cancelled transactions. In this dataset, invoice numbers starting with "C" indicate cancellations.

Filtering for Quantity > 0 and UnitPrice > 0 removes returns and free items. Finally, TotalPrice gives you the revenue per line item.

  • Run the cell. You should see the cleaned dataset shape printed, showing fewer rows than the original 541,909.

Shape didn't change?

Make sure you ran the cell that loads the data first. If you re-run the cleaning cell on already-cleaned data, the shape will barely change. Restart by running all cells from the top in order.

Ask for help: My cleaned dataset shape looks wrong.

✔️ Awesome, I've got everything!

Great. Double check you have three code cells that all ran without errors: the pip install, the imports, and the data fetch plus cleaning.

ⓧ I'd like to double check the full code

Here is the complete code for this step. Each block below should be in its own cell in your Colab notebook.

# =============================================================================
# E-Commerce Customer Intelligence: Segmentation + Churn Prediction
# =============================================================================

# --- Step 1: Setup and Data Loading ---

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.cluster import KMeans
from sklearn.ensemble import RandomForestClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import (
    classification_report,
    confusion_matrix,
    roc_auc_score,
    roc_curve,
    silhouette_score,
)

# Install and import ucimlrepo to fetch the dataset
# !pip install ucimlrepo openpyxl
from ucimlrepo import fetch_ucirepo

# Fetch UCI Online Retail dataset (ID: 352)
online_retail = fetch_ucirepo(id=352)
df = online_retail.data.original

print(f"Dataset shape: {df.shape}")
print(df.head())
print(df.dtypes)

# Data cleaning
df = df.dropna(subset=["CustomerID"])
df = df[~df["InvoiceNo"].astype(str).str.startswith("C")]
df = df[df["Quantity"] > 0]
df = df[df["UnitPrice"] > 0]
df["TotalPrice"] = df["Quantity"] * df["UnitPrice"]

print(f"Cleaned dataset shape: {df.shape}")

Your environment is set up and your data is clean. Next up, you will engineer customer-level features using RFM analysis to prepare the data for clustering.

Engineer Customer RFM Features

Your cleaned dataset is ready with valid transactions and a TotalPrice column. Now you need to transform those half-million individual transactions into a single row per customer.

Machine learning models cannot learn from raw transaction logs directly. You need to aggregate them into meaningful customer-level features. The standard framework for this in customer analytics is RFM analysis (Recency, Frequency, Monetary). In this step, you will build an RFM table and define a churn target for classification.

In this step, get ready to:
  • Aggregate raw transactions into a customer-level table with RFM features.
  • Add derived features like AverageOrderValue.
  • Define a churn target variable for later classification.
Define the reference date and aggregate to customer level

Each customer's behavior needs to be summarized as a fixed set of numbers. RFM captures three dimensions: how recently a customer purchased (Recency), how often they purchased (Frequency), and how much they spent (Monetary).

What is RFM analysis?

RFM is a standard customer segmentation framework used in marketing analytics. It distills purchase history into three numbers per customer.

  • Recency measures how many days have passed since the customer's last purchase. Lower recency means the customer is more engaged.
  • Frequency counts the number of distinct orders. Higher frequency signals loyalty.
  • Monetary sums total spend. Higher monetary value means the customer is more valuable.
  • Create a new code cell in your notebook and add the following code to define a reference date and aggregate transactions by customer:
# --- Step 2: Feature Engineering - Build Customer RFM Table ---

# Define reference date (one day after last transaction)
reference_date = df["InvoiceDate"].max() + pd.Timedelta(days=1)

# Aggregate to customer level
customer_df = df.groupby("CustomerID").agg(
    Recency=("InvoiceDate", lambda x: (reference_date - x.max()).days),
    Frequency=("InvoiceNo", "nunique"),
    Monetary=("TotalPrice", "sum"),
    n_products=("StockCode", "nunique"),
    n_days=("InvoiceDate", lambda x: x.dt.date.nunique()),
).reset_index()

What does this code do?

  • reference_date is set to one day after the latest transaction in the dataset. This gives every customer a consistent "today" to measure recency against.
  • groupby("CustomerID").agg(...) collapses all transactions for each customer into a single row with named aggregation.
  • n_products counts unique stock codes purchased, and n_days counts unique shopping days. These extra features will help the classifier later.
  • You will not see any output yet. The next cell adds a derived feature and prints the table summary.
Add derived features and inspect the table

Average order value reveals whether a customer makes many small purchases or fewer large ones. This provides signal beyond raw monetary totals.

  • Create a new code cell and add the following code to compute AverageOrderValue and inspect the customer table:
# Additional features
customer_df["AverageOrderValue"] = customer_df["Monetary"] / customer_df["Frequency"]

print(f"Customer table shape: {customer_df.shape}")
print(customer_df.describe())

What does this code do?

  • AverageOrderValue divides total spend by number of orders. A customer who spent $500 across 10 orders has a very different profile than one who spent $500 in a single order.
  • customer_df.describe() prints summary statistics (mean, std, min, max, quartiles) for every numeric column so you can spot outliers and confirm the features look reasonable.
  • Run the cell. You should see the customer table shape printed (around 4,300 rows and 7 columns) followed by a statistical summary of each feature.

Seeing a KeyError or empty DataFrame?

  • Make sure you ran the Step 1 cleaning cells first. The customer_df depends on the cleaned df with TotalPrice already created.
  • Check that reference_date was defined in the previous cell. If you combined cells, ensure the reference date assignment comes before the groupby.

Help me debug my customer_df aggregation

Define the churn target variable

Classification models need a target label. You will define churn based on a simple heuristic: if a customer has not purchased in over 90 days, they are considered churned.

Why 90 days as the churn threshold?

The dataset spans December 2010 to December 2011. A 90-day window is a common heuristic in e-commerce analytics. It gives customers enough time to return without being so generous that the model has nothing to predict.

In a real business context, this threshold would be tuned based on the company's purchase cycle. For this portfolio project, 90 days produces a meaningful class split.

  • Create a new code cell and add the following code to label customers as churned or active:
# Define churn target: churned if Recency > 90 days
customer_df["Churned"] = (customer_df["Recency"] > 90).astype(int)
print(f"\nChurn distribution:\n{customer_df['Churned'].value_counts()}")
print(f"Churn rate: {customer_df['Churned'].mean():.2%}")

What does this code do?

  • (customer_df["Recency"] > 90).astype(int) creates a binary column where 1 means churned (last purchase was more than 90 days ago) and 0 means active.
  • value_counts() shows how many customers fall into each class. This lets you check for class imbalance before training a classifier.
  • The churn rate percentage tells you the proportion of customers labeled as churned. You will use this Churned column as the target variable for your Random Forest model in a later step.
  • Run the cell. You should see the churn distribution (counts for 0 and 1) and a churn rate percentage printed below the cell.

Churn rate looks unexpected?

  • If the churn rate is 0% or 100%, check that your cleaning step did not accidentally filter out all recent or all old transactions.
  • Confirm customer_df["Recency"].describe() shows a range of values. If the max is below 90, your reference date calculation may be off.

Help me understand my churn distribution

✔️ Awesome, I've got everything!

Great. Double check you have saved your notebook.

ⓧ I'd like to double check the full code

# =============================================================================
# E-Commerce Customer Intelligence: Segmentation + Churn Prediction
# =============================================================================

# --- Step 1: Setup and Data Loading ---

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.cluster import KMeans
from sklearn.ensemble import RandomForestClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import (
    classification_report,
    confusion_matrix,
    roc_auc_score,
    roc_curve,
    silhouette_score,
)

# Install and import ucimlrepo to fetch the dataset
# !pip install ucimlrepo openpyxl
from ucimlrepo import fetch_ucirepo

# Fetch UCI Online Retail dataset (ID: 352)
online_retail = fetch_ucirepo(id=352)
df = online_retail.data.original

print(f"Dataset shape: {df.shape}")
print(df.head())
print(df.dtypes)

# Data cleaning
df = df.dropna(subset=["CustomerID"])
df = df[~df["InvoiceNo"].astype(str).str.startswith("C")]
df = df[df["Quantity"] > 0]
df = df[df["UnitPrice"] > 0]
df["TotalPrice"] = df["Quantity"] * df["UnitPrice"]

print(f"Cleaned dataset shape: {df.shape}")

# --- Step 2: Feature Engineering - Build Customer RFM Table ---

# Define reference date (one day after last transaction)
reference_date = df["InvoiceDate"].max() + pd.Timedelta(days=1)

# Aggregate to customer level
customer_df = df.groupby("CustomerID").agg(
    Recency=("InvoiceDate", lambda x: (reference_date - x.max()).days),
    Frequency=("InvoiceNo", "nunique"),
    Monetary=("TotalPrice", "sum"),
    n_products=("StockCode", "nunique"),
    n_days=("InvoiceDate", lambda x: x.dt.date.nunique()),
).reset_index()

# Additional features
customer_df["AverageOrderValue"] = customer_df["Monetary"] / customer_df["Frequency"]

print(f"Customer table shape: {customer_df.shape}")
print(customer_df.describe())

# Define churn target: churned if Recency > 90 days
customer_df["Churned"] = (customer_df["Recency"] > 90).astype(int)
print(f"\nChurn distribution:\n{customer_df['Churned'].value_counts()}")
print(f"Churn rate: {customer_df['Churned'].mean():.2%}")

Your customer table is ready with RFM features and a churn label. Next up, you will use these features to segment customers into behavioral clusters with K-Means.

Segment Customers with K-Means

You now have a clean customer-level dataset with RFM features and a churn label. The next question is: are all customers the same, or do natural behavioral groups exist in the data?

In this step, you will use K-Means clustering to segment customers into distinct groups based on their purchasing behavior. These cluster labels will later become a powerful feature for your churn classifier.

In this step, get ready to:
  • Standardize RFM features and evaluate cluster counts from 2 to 8.
  • Select the optimal number of clusters and fit the final K-Means model.
  • Visualize and interpret the customer segments.
Standardize features and evaluate cluster counts

K-Means uses distance to assign points to clusters. If one feature (like Monetary) has values in the thousands while another (like Frequency) sits in single digits, the high-magnitude feature dominates the clustering. Standardizing puts all features on the same scale.

  • Create a new code cell in your Colab notebook. Add a markdown cell above it with the text # --- Step 3: Customer Segmentation with K-Means ---.
  • Extract and standardize the RFM features by pasting this code into the new code cell:
# Standardize RFM features for clustering
rfm_features = customer_df[["Recency", "Frequency", "Monetary"]]
scaler = StandardScaler()
rfm_scaled = scaler.fit_transform(rfm_features)

What does this code do?

rfm_features extracts the three columns that define customer purchasing behavior: how recently they bought, how often, and how much they spent.

StandardScaler transforms each column so it has a mean of 0 and a standard deviation of 1. This ensures no single feature dominates the distance calculations in K-Means.

Now you need to figure out how many clusters to use. Two standard methods help: the Elbow method (looking for where inertia stops dropping sharply) and Silhouette analysis (measuring how well-separated clusters are).

  • Add a new code cell. Run K-Means for k=2 through k=8, collecting both inertia and silhouette scores, by pasting this code:
# Find optimal k using Elbow method and Silhouette scores
inertias = []
silhouette_scores = []
k_range = range(2, 9)

for k in k_range:
    kmeans = KMeans(n_clusters=k, random_state=42, n_init="auto")
    labels = kmeans.fit_predict(rfm_scaled)
    inertias.append(kmeans.inertia_)
    silhouette_scores.append(silhouette_score(rfm_scaled, labels))

What does this code do?

The loop tries every value of k from 2 to 8. For each k, it fits a K-Means model and records two metrics.

  • Inertia measures how tight each cluster is (lower is better). You look for the "elbow" where adding more clusters stops helping much.
  • Silhouette score ranges from -1 to 1. Higher values mean clusters are dense and well-separated from each other.
  • n_init="auto" tells scikit-learn to automatically choose how many times to run K-Means with different starting positions.
  • Add another code cell. Plot both metrics side by side by pasting this code:
# Plot Elbow curve
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

axes[0].plot(k_range, inertias, "bo-")
axes[0].set_xlabel("Number of Clusters (k)")
axes[0].set_ylabel("Inertia")
axes[0].set_title("Elbow Method")

axes[1].plot(k_range, silhouette_scores, "ro-")
axes[1].set_xlabel("Number of Clusters (k)")
axes[1].set_ylabel("Silhouette Score")
axes[1].set_title("Silhouette Analysis")

plt.tight_layout()
plt.show()

What does this code do?

plt.subplots(1, 2) creates two charts side by side. The left chart shows inertia dropping as k increases (look for the elbow bend). The right chart shows silhouette scores (look for the peak).

  • Run all three cells. You should see two plots side by side: one with a downward curve (Elbow) and one with varying scores (Silhouette).

Not seeing the plots?

Make sure you ran the cells in order. The plotting cell depends on inertias and silhouette_scores being populated by the loop cell above it.

Help me debug my K-Means Elbow and Silhouette plots in Google Colab

Select optimal k and fit the final model

Looking at your plots, you should see the elbow bending around k=4, and the silhouette score is relatively strong there. For RFM data, k=4 typically produces interpretable segments: loyal high-spenders, regular customers, occasional buyers, and at-risk churners.

  • Add a new code cell. Fit the final K-Means model with k=4 and assign cluster labels by pasting this code:
# Select optimal k (typically k=4 works well for RFM data)
optimal_k = 4
kmeans_final = KMeans(n_clusters=optimal_k, random_state=42, n_init="auto")
customer_df["Cluster"] = kmeans_final.fit_predict(rfm_scaled)

print(f"\nFinal silhouette score (k={optimal_k}): "
      f"{silhouette_score(rfm_scaled, customer_df['Cluster']):.3f}")

What does this code do?

kmeans_final.fit_predict(rfm_scaled) fits the model and returns a cluster label (0, 1, 2, or 3) for each customer. These labels get stored directly in customer_df["Cluster"] so they are ready to use as a feature later.

The final silhouette score confirms how well the four clusters separate the data. A score above 0.3 on real-world data is considered reasonable.

  • Run the cell. You should see the silhouette score printed below the cell.

Seeing a NameError for rfm_scaled?

This means the standardization cell was not run. Go back and run the cell containing scaler.fit_transform(rfm_features) first, then re-run this cell.

Help me fix a NameError in my K-Means clustering notebook

Visualize and interpret cluster profiles

Numbers alone do not tell the story. A bar chart showing the average RFM values per cluster makes it immediately clear which group is your best customers and which group is slipping away.

  • Add a new code cell. Create the cluster profile visualization by pasting this code:
# Visualize cluster profiles
cluster_summary = customer_df.groupby("Cluster")[["Recency", "Frequency", "Monetary"]].mean()
print("\nCluster Profiles (Mean RFM values):")
print(cluster_summary)

cluster_summary.plot(kind="bar", figsize=(10, 6))
plt.title("Average RFM Values per Customer Segment")
plt.xlabel("Cluster")
plt.ylabel("Mean Value")
plt.legend(title="Feature")
plt.tight_layout()
plt.show()

What does this code do?

cluster_summary groups all customers by their cluster label and computes the average Recency, Frequency, and Monetary for each group. This gives you the "profile" of each segment.

The bar chart makes it easy to spot patterns visually. For example, a cluster with low Recency and high Frequency is your loyal base. A cluster with very high Recency and low Frequency represents at-risk customers who have not purchased recently.

  • Run the cell. You should see a printed table of mean RFM values per cluster followed by a grouped bar chart.

Bar chart bars look unreadable?

If Monetary values are much larger than Recency and Frequency, the bars for the smaller features will appear tiny. This is expected. The printed table gives you the exact numbers to compare.

Help me understand my cluster profile bar chart

  • Add a markdown cell below the bar chart. Write 2-3 sentences interpreting each cluster based on the printed table.

Interpreting your clusters

Look at the mean values for each cluster. Here are common patterns in RFM data:

  • Low Recency + High Frequency + High Monetary = loyal high-value customers.
  • High Recency + Low Frequency + Low Monetary = at-risk churning customers.
  • Low Recency + Low Frequency + Low Monetary = new or occasional buyers.
  • Medium values across all three = regular mid-tier customers.

Your exact cluster numbers (0, 1, 2, 3) may map differently. Use the printed table to match each cluster ID to these archetypes.

✔️ Awesome, I've got everything!

Great. Make sure you have added a markdown cell interpreting your clusters before moving on.

ⓧ I'd like to double check the full code

Here is the complete code from Step 1 through Step 3. Compare it against your notebook cells:

# =============================================================================
# E-Commerce Customer Intelligence: Segmentation + Churn Prediction
# =============================================================================

# --- Step 1: Setup and Data Loading ---

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.cluster import KMeans
from sklearn.ensemble import RandomForestClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import (
    classification_report,
    confusion_matrix,
    roc_auc_score,
    roc_curve,
    silhouette_score,
)

# Install and import ucimlrepo to fetch the dataset
# !pip install ucimlrepo openpyxl
from ucimlrepo import fetch_ucirepo

# Fetch UCI Online Retail dataset (ID: 352)
online_retail = fetch_ucirepo(id=352)
df = online_retail.data.original

print(f"Dataset shape: {df.shape}")
print(df.head())
print(df.dtypes)

# Data cleaning
df = df.dropna(subset=["CustomerID"])
df = df[~df["InvoiceNo"].astype(str).str.startswith("C")]
df = df[df["Quantity"] > 0]
df = df[df["UnitPrice"] > 0]
df["TotalPrice"] = df["Quantity"] * df["UnitPrice"]

print(f"Cleaned dataset shape: {df.shape}")

# --- Step 2: Feature Engineering - Build Customer RFM Table ---

# Define reference date (one day after last transaction)
reference_date = df["InvoiceDate"].max() + pd.Timedelta(days=1)

# Aggregate to customer level
customer_df = df.groupby("CustomerID").agg(
    Recency=("InvoiceDate", lambda x: (reference_date - x.max()).days),
    Frequency=("InvoiceNo", "nunique"),
    Monetary=("TotalPrice", "sum"),
    n_products=("StockCode", "nunique"),
    n_days=("InvoiceDate", lambda x: x.dt.date.nunique()),
).reset_index()

# Additional features
customer_df["AverageOrderValue"] = customer_df["Monetary"] / customer_df["Frequency"]

print(f"Customer table shape: {customer_df.shape}")
print(customer_df.describe())

# Define churn target: churned if Recency > 90 days
customer_df["Churned"] = (customer_df["Recency"] > 90).astype(int)
print(f"\nChurn distribution:\n{customer_df['Churned'].value_counts()}")
print(f"Churn rate: {customer_df['Churned'].mean():.2%}")

# --- Step 3: Customer Segmentation with K-Means ---

# Standardize RFM features for clustering
rfm_features = customer_df[["Recency", "Frequency", "Monetary"]]
scaler = StandardScaler()
rfm_scaled = scaler.fit_transform(rfm_features)

# Find optimal k using Elbow method and Silhouette scores
inertias = []
silhouette_scores = []
k_range = range(2, 9)

for k in k_range:
    kmeans = KMeans(n_clusters=k, random_state=42, n_init="auto")
    labels = kmeans.fit_predict(rfm_scaled)
    inertias.append(kmeans.inertia_)
    silhouette_scores.append(silhouette_score(rfm_scaled, labels))

# Plot Elbow curve
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

axes[0].plot(k_range, inertias, "bo-")
axes[0].set_xlabel("Number of Clusters (k)")
axes[0].set_ylabel("Inertia")
axes[0].set_title("Elbow Method")

axes[1].plot(k_range, silhouette_scores, "ro-")
axes[1].set_xlabel("Number of Clusters (k)")
axes[1].set_ylabel("Silhouette Score")
axes[1].set_title("Silhouette Analysis")

plt.tight_layout()
plt.show()

# Select optimal k (typically k=4 works well for RFM data)
optimal_k = 4
kmeans_final = KMeans(n_clusters=optimal_k, random_state=42, n_init="auto")
customer_df["Cluster"] = kmeans_final.fit_predict(rfm_scaled)

print(f"\nFinal silhouette score (k={optimal_k}): "
      f"{silhouette_score(rfm_scaled, customer_df['Cluster']):.3f}")

# Visualize cluster profiles
cluster_summary = customer_df.groupby("Cluster")[["Recency", "Frequency", "Monetary"]].mean()
print("\nCluster Profiles (Mean RFM values):")
print(cluster_summary)

cluster_summary.plot(kind="bar", figsize=(10, 6))
plt.title("Average RFM Values per Customer Segment")
plt.xlabel("Cluster")
plt.ylabel("Mean Value")
plt.legend(title="Feature")
plt.tight_layout()
plt.show()

Your customers are now segmented into four distinct behavioral groups. Next up, you will build a Random Forest classifier that uses these cluster labels as one of its features to predict which customers are likely to churn.

Build the Churn Classifier

Your customers are now grouped into distinct behavioral segments. The next question is: which of these customers are likely to leave?

In this step, you will train a Random Forest classifier to predict customer churn. The key insight is that you will feed the cluster labels from the previous step as a feature into the classifier. This is where unsupervised and supervised learning combine to produce stronger predictions than either could alone.

In this step, get ready to:
  • Prepare features (including cluster labels) and split the data into training and test sets.
  • Train a Random Forest classifier on your customer data.
  • Evaluate the model with a classification report, confusion matrix, and ROC curve.
Prepare features and split the data

Before training a model, you need to define which columns are inputs (features) and which column is the output (target). You will also split the data so the model trains on one portion and gets evaluated on unseen data.

  • Create a new code cell in your Colab notebook and add the following code to define your features and split the data:
# --- Step 4: Churn Classification with Random Forest ---

# Prepare features and target
feature_cols = ["Recency", "Frequency", "Monetary", "AverageOrderValue",
                "n_products", "n_days", "Cluster"]
X = customer_df[feature_cols]
y = customer_df["Churned"]

# Train/test split
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)

What does this code do?

feature_cols lists all seven input columns. Notice that Cluster is included here. This is the engineered feature from your K-Means model that gives the classifier extra behavioral context about each customer.

train_test_split holds back 20% of the data for testing. The stratify=y argument ensures both the training and test sets have the same proportion of churned vs. active customers. random_state=42 makes the split reproducible.

  • Run the cell by pressing **Shift+Enter**.
  • No output is expected here. The variables are now defined and ready for training.

Seeing a KeyError?

If you see KeyError: 'Cluster', make sure you ran the K-Means cell from the previous step. The Cluster column only exists after you assign cluster labels to customer_df.

Help me fix a KeyError when preparing my features for churn classification.

Train the Random Forest model

A Random Forest builds many decision trees on random subsets of your data, then combines their votes for the final prediction. This makes it robust against overfitting and gives you built-in feature importances later.

  • In a new code cell, train the model and generate predictions by adding this code:
# Train Random Forest
rf_model = RandomForestClassifier(n_estimators=100, random_state=42)
rf_model.fit(X_train, y_train)

# Predictions
y_pred = rf_model.predict(X_test)
y_proba = rf_model.predict_proba(X_test)[:, 1]

What does this code do?

RandomForestClassifier(n_estimators=100) creates a forest of 100 decision trees. More trees generally mean better predictions at the cost of slightly longer training time.

y_pred holds the hard predictions (0 or 1) for each test customer. y_proba holds the probability of churn (a value between 0 and 1). You need both: hard predictions for the confusion matrix, and probabilities for the ROC curve.

  • Run the cell. Training should complete in a few seconds with no output.

Cell running for too long?

If the cell takes more than 30 seconds, your Colab runtime may have disconnected. Check the top-right connection indicator. Click **Reconnect** if needed, then re-run all cells from the top.

Help me troubleshoot my Random Forest taking too long to train in Colab.

Evaluate model performance

A model is only as good as its evaluation. You will generate three views of performance: a text-based classification report, a visual confusion matrix, and an ROC-AUC curve.

  • In a new code cell, print the classification report and ROC-AUC score by adding this code:
# Evaluation
print("\nClassification Report:")
print(classification_report(y_test, y_pred))

roc_auc = roc_auc_score(y_test, y_proba)
print(f"ROC-AUC Score: {roc_auc:.4f}")

What does this code do?

classification_report prints precision, recall, and F1-score for both classes (Active and Churned). These tell you how well the model identifies each group.

roc_auc_score measures how well the model ranks churned customers higher than active ones. A score of 1.0 is perfect, 0.5 is no better than random guessing.

  • Run the cell. You should see a table with precision, recall, and F1-score columns, plus a ROC-AUC score printed below it.

Seeing very low scores?

If your ROC-AUC is below 0.6, double-check that you used y_proba (probabilities) for the ROC-AUC calculation, not y_pred (hard labels). Also confirm your Churned column was defined correctly in the previous step (Recency > 90).

Help me understand why my churn model has low performance.

  • In a new code cell, plot the confusion matrix as a heatmap by adding this code:
# Confusion matrix heatmap
cm = confusion_matrix(y_test, y_pred)
plt.figure(figsize=(8, 6))
sns.heatmap(cm, annot=True, fmt="d", cmap="Blues",
            xticklabels=["Active", "Churned"],
            yticklabels=["Active", "Churned"])
plt.title("Confusion Matrix")
plt.xlabel("Predicted")
plt.ylabel("Actual")
plt.tight_layout()
plt.show()

How do I read a confusion matrix?

The top-left cell shows correctly predicted active customers. The bottom-right shows correctly predicted churned customers. Off-diagonal cells are mistakes.

For a churn model, the bottom-left (missed churners) is the most expensive mistake. A customer who was going to leave but was predicted as active gets no retention intervention.

  • Run the cell. You should see a blue heatmap with four cells, each showing the count of predictions in that category.
  • In a new code cell, plot the ROC curve by adding this code:
# ROC curve
fpr, tpr, _ = roc_curve(y_test, y_proba)
plt.figure(figsize=(8, 6))
plt.plot(fpr, tpr, label=f"Random Forest (AUC = {roc_auc:.3f})")
plt.plot([0, 1], [0, 1], "k--", label="Random Baseline")
plt.xlabel("False Positive Rate")
plt.ylabel("True Positive Rate")
plt.title("ROC Curve - Churn Prediction")
plt.legend()
plt.tight_layout()
plt.show()

What is a ROC curve?

The ROC curve plots the trade-off between catching true churners (True Positive Rate) and falsely flagging active customers as churners (False Positive Rate). The dashed diagonal is the "random guess" baseline.

The further your curve bows toward the top-left corner, the better your model. The area under the curve (AUC) condenses this into a single number.

  • Run the cell. You should see a smooth curve above the dashed diagonal, with your model's AUC displayed in the legend.

ROC curve looks like a straight line?

If your ROC curve overlaps the dashed baseline, you may have passed y_pred instead of y_proba to the roc_curve function. Hard labels (0 and 1) produce a jagged 3-point line. Probabilities produce the smooth curve you expect.

Help me fix my ROC curve plot that looks wrong.

✔️ Awesome, I've got everything!

Great. Make sure all your cells are saved in Colab (they auto-save, but you can press **Ctrl+S** to be safe).

ⓧ I'd like to double check the full code

Here is all the code your notebook should contain up to this point. Compare it cell by cell with what you have.

# =============================================================================
# E-Commerce Customer Intelligence: Segmentation + Churn Prediction
# =============================================================================

# --- Step 1: Setup and Data Loading ---

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.cluster import KMeans
from sklearn.ensemble import RandomForestClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import (
    classification_report,
    confusion_matrix,
    roc_auc_score,
    roc_curve,
    silhouette_score,
)

# Install and import ucimlrepo to fetch the dataset
# !pip install ucimlrepo openpyxl
from ucimlrepo import fetch_ucirepo

# Fetch UCI Online Retail dataset (ID: 352)
online_retail = fetch_ucirepo(id=352)
df = online_retail.data.original

print(f"Dataset shape: {df.shape}")
print(df.head())
print(df.dtypes)

# Data cleaning
df = df.dropna(subset=["CustomerID"])
df = df[~df["InvoiceNo"].astype(str).str.startswith("C")]
df = df[df["Quantity"] > 0]
df = df[df["UnitPrice"] > 0]
df["TotalPrice"] = df["Quantity"] * df["UnitPrice"]

print(f"Cleaned dataset shape: {df.shape}")

# --- Step 2: Feature Engineering - Build Customer RFM Table ---

# Define reference date (one day after last transaction)
reference_date = df["InvoiceDate"].max() + pd.Timedelta(days=1)

# Aggregate to customer level
customer_df = df.groupby("CustomerID").agg(
    Recency=("InvoiceDate", lambda x: (reference_date - x.max()).days),
    Frequency=("InvoiceNo", "nunique"),
    Monetary=("TotalPrice", "sum"),
    n_products=("StockCode", "nunique"),
    n_days=("InvoiceDate", lambda x: x.dt.date.nunique()),
).reset_index()

# Additional features
customer_df["AverageOrderValue"] = customer_df["Monetary"] / customer_df["Frequency"]

print(f"Customer table shape: {customer_df.shape}")
print(customer_df.describe())

# Define churn target: churned if Recency > 90 days
customer_df["Churned"] = (customer_df["Recency"] > 90).astype(int)
print(f"\nChurn distribution:\n{customer_df['Churned'].value_counts()}")
print(f"Churn rate: {customer_df['Churned'].mean():.2%}")

# --- Step 3: Customer Segmentation with K-Means ---

# Standardize RFM features for clustering
rfm_features = customer_df[["Recency", "Frequency", "Monetary"]]
scaler = StandardScaler()
rfm_scaled = scaler.fit_transform(rfm_features)

# Find optimal k using Elbow method and Silhouette scores
inertias = []
silhouette_scores = []
k_range = range(2, 9)

for k in k_range:
    kmeans = KMeans(n_clusters=k, random_state=42, n_init="auto")
    labels = kmeans.fit_predict(rfm_scaled)
    inertias.append(kmeans.inertia_)
    silhouette_scores.append(silhouette_score(rfm_scaled, labels))

# Plot Elbow curve
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

axes[0].plot(k_range, inertias, "bo-")
axes[0].set_xlabel("Number of Clusters (k)")
axes[0].set_ylabel("Inertia")
axes[0].set_title("Elbow Method")

axes[1].plot(k_range, silhouette_scores, "ro-")
axes[1].set_xlabel("Number of Clusters (k)")
axes[1].set_ylabel("Silhouette Score")
axes[1].set_title("Silhouette Analysis")

plt.tight_layout()
plt.show()

# Select optimal k (typically k=4 works well for RFM data)
optimal_k = 4
kmeans_final = KMeans(n_clusters=optimal_k, random_state=42, n_init="auto")
customer_df["Cluster"] = kmeans_final.fit_predict(rfm_scaled)

print(f"\nFinal silhouette score (k={optimal_k}): "
      f"{silhouette_score(rfm_scaled, customer_df['Cluster']):.3f}")

# Visualize cluster profiles
cluster_summary = customer_df.groupby("Cluster")[["Recency", "Frequency", "Monetary"]].mean()
print("\nCluster Profiles (Mean RFM values):")
print(cluster_summary)

cluster_summary.plot(kind="bar", figsize=(10, 6))
plt.title("Average RFM Values per Customer Segment")
plt.xlabel("Cluster")
plt.ylabel("Mean Value")
plt.legend(title="Feature")
plt.tight_layout()
plt.show()

# --- Step 4: Churn Classification with Random Forest ---

# Prepare features and target
feature_cols = ["Recency", "Frequency", "Monetary", "AverageOrderValue",
                "n_products", "n_days", "Cluster"]
X = customer_df[feature_cols]
y = customer_df["Churned"]

# Train/test split
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)

# Train Random Forest
rf_model = RandomForestClassifier(n_estimators=100, random_state=42)
rf_model.fit(X_train, y_train)

# Predictions
y_pred = rf_model.predict(X_test)
y_proba = rf_model.predict_proba(X_test)[:, 1]

# Evaluation
print("\nClassification Report:")
print(classification_report(y_test, y_pred))

roc_auc = roc_auc_score(y_test, y_proba)
print(f"ROC-AUC Score: {roc_auc:.4f}")

# Confusion matrix heatmap
cm = confusion_matrix(y_test, y_pred)
plt.figure(figsize=(8, 6))
sns.heatmap(cm, annot=True, fmt="d", cmap="Blues",
            xticklabels=["Active", "Churned"],
            yticklabels=["Active", "Churned"])
plt.title("Confusion Matrix")
plt.xlabel("Predicted")
plt.ylabel("Actual")
plt.tight_layout()
plt.show()

# ROC curve
fpr, tpr, _ = roc_curve(y_test, y_proba)
plt.figure(figsize=(8, 6))
plt.plot(fpr, tpr, label=f"Random Forest (AUC = {roc_auc:.3f})")
plt.plot([0, 1], [0, 1], "k--", label="Random Baseline")
plt.xlabel("False Positive Rate")
plt.ylabel("True Positive Rate")
plt.title("ROC Curve - Churn Prediction")
plt.legend()
plt.tight_layout()
plt.show()

Your churn classifier is trained and evaluated. Next, you will dig into feature importances to understand which customer behaviors drive churn predictions the most.

Interpret Features and Summarize Findings

Your churn classifier is trained and evaluated. You know it works well from the ROC-AUC score and confusion matrix.

But a model that makes good predictions is only half the story. Hiring managers and business stakeholders want to know why the model predicts what it does. In this step, you will extract feature importances to understand what drives churn, then document your entire project so the notebook is ready to push to GitHub.

In this step, get ready to:
  • Plot feature importances to reveal what drives churn prediction.
  • Add markdown cells interpreting results and recommending business actions.
  • Write a Project Summary section that recruiters can scan in 30 seconds.
Extract and plot feature importances

Random Forest models track how much each feature contributes to accurate predictions. This is called feature importance. Plotting it reveals which customer behaviors are the strongest churn signals.

  • Add a new code cell below your ROC curve cell and paste the following code:
# --- Step 5: Feature Importance and Interpretation ---

# Feature importance plot
importances = rf_model.feature_importances_
feature_importance_df = pd.DataFrame({
    "Feature": feature_cols,
    "Importance": importances
}).sort_values("Importance", ascending=True)

plt.figure(figsize=(10, 6))
plt.barh(feature_importance_df["Feature"], feature_importance_df["Importance"])
plt.xlabel("Feature Importance")
plt.title("Random Forest Feature Importances - Churn Prediction")
plt.tight_layout()
plt.show()

print("\nFeature Importances (sorted):")
print(feature_importance_df.sort_values("Importance", ascending=False).to_string(index=False))

What does this code do?

The rf_model.feature_importances_ attribute returns an array showing how much each feature contributed to accurate predictions. Higher values mean the model relied on that feature more heavily when splitting decision trees.

Sorting ascending and using plt.barh() creates a horizontal bar chart where the most important features appear at the top. This is a standard visualization in ML portfolios because it communicates model behavior at a glance.

  • Run the cell.
  • You should see a horizontal bar chart with 7 features ranked by importance. The printed table below it shows the exact importance values sorted from highest to lowest.

Not seeing the chart?

Make sure you ran the Step 4 cells first. This code depends on rf_model and feature_cols from the previous step.

If you see a NameError, use **Runtime** then **Run all** to execute all cells from the top.

Help me debug my feature importance plot

Take a look at where Cluster ranks in the chart. This tells you how much value your K-Means segmentation added to the churn prediction model.

Add markdown cells interpreting the results

A notebook full of code with no written explanation looks like a homework assignment. Adding markdown interpretation cells transforms it into a professional analysis that proves you understand the business implications of your model.

  • Click the + Text button in the toolbar to add a new markdown cell below your feature importance code cell.
  • In this markdown cell, write 3-5 sentences covering:

What should I write?

Write in your own words based on what you observe. For example, if Recency dominates, explain why that makes intuitive sense. If Cluster ranks in the middle, note that it adds predictive signal beyond the raw features alone.

This is your analysis. There is no single correct answer. The goal is showing that you can connect model outputs to business meaning.

  • Add another markdown cell below the first one.
  • In this cell, write business action recommendations for each cluster. For example:

Use your cluster profiles

Refer back to the cluster profile bar chart from Step 3. The mean Recency, Frequency, and Monetary values for each cluster tell you what kind of customer belongs to each group. Match your business recommendations to those profiles.

Write the Project Summary section

When a recruiter opens your notebook on GitHub, they scroll to the bottom first. A clear summary section lets them understand your entire project in 30 seconds without reading every code cell.

  • Add a final markdown cell at the very bottom of your notebook.
  • Title it ## Project Summary using markdown heading syntax.
  • Include the following sections in this cell:

Why does the summary matter?

Recruiters and hiring managers reviewing GitHub portfolios spend less than a minute per notebook. A structured summary at the bottom proves you can communicate results clearly. It also shows that you think about projects holistically, not just as code exercises.

  • Review your complete notebook from top to bottom. Confirm that each section has at least one markdown cell providing context.

✔️ Awesome, I've got everything!

Great. Double check you have saved your notebook. Your feature importance chart and markdown cells should all be visible.

ⓧ I'd like to double check the full code

Here is the complete Python code your notebook should contain up through Step 5:

# =============================================================================
# E-Commerce Customer Intelligence: Segmentation + Churn Prediction
# =============================================================================

# --- Step 1: Setup and Data Loading ---

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.cluster import KMeans
from sklearn.ensemble import RandomForestClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import (
    classification_report,
    confusion_matrix,
    roc_auc_score,
    roc_curve,
    silhouette_score,
)

# Install and import ucimlrepo to fetch the dataset
# !pip install ucimlrepo openpyxl
from ucimlrepo import fetch_ucirepo

# Fetch UCI Online Retail dataset (ID: 352)
online_retail = fetch_ucirepo(id=352)
df = online_retail.data.original

print(f"Dataset shape: {df.shape}")
print(df.head())
print(df.dtypes)

# Data cleaning
df = df.dropna(subset=["CustomerID"])
df = df[~df["InvoiceNo"].astype(str).str.startswith("C")]
df = df[df["Quantity"] > 0]
df = df[df["UnitPrice"] > 0]
df["TotalPrice"] = df["Quantity"] * df["UnitPrice"]

print(f"Cleaned dataset shape: {df.shape}")

# --- Step 2: Feature Engineering - Build Customer RFM Table ---

# Define reference date (one day after last transaction)
reference_date = df["InvoiceDate"].max() + pd.Timedelta(days=1)

# Aggregate to customer level
customer_df = df.groupby("CustomerID").agg(
    Recency=("InvoiceDate", lambda x: (reference_date - x.max()).days),
    Frequency=("InvoiceNo", "nunique"),
    Monetary=("TotalPrice", "sum"),
    n_products=("StockCode", "nunique"),
    n_days=("InvoiceDate", lambda x: x.dt.date.nunique()),
).reset_index()

# Additional features
customer_df["AverageOrderValue"] = customer_df["Monetary"] / customer_df["Frequency"]

print(f"Customer table shape: {customer_df.shape}")
print(customer_df.describe())

# Define churn target: churned if Recency > 90 days
customer_df["Churned"] = (customer_df["Recency"] > 90).astype(int)
print(f"\nChurn distribution:\n{customer_df['Churned'].value_counts()}")
print(f"Churn rate: {customer_df['Churned'].mean():.2%}")

# --- Step 3: Customer Segmentation with K-Means ---

# Standardize RFM features for clustering
rfm_features = customer_df[["Recency", "Frequency", "Monetary"]]
scaler = StandardScaler()
rfm_scaled = scaler.fit_transform(rfm_features)

# Find optimal k using Elbow method and Silhouette scores
inertias = []
silhouette_scores = []
k_range = range(2, 9)

for k in k_range:
    kmeans = KMeans(n_clusters=k, random_state=42, n_init="auto")
    labels = kmeans.fit_predict(rfm_scaled)
    inertias.append(kmeans.inertia_)
    silhouette_scores.append(silhouette_score(rfm_scaled, labels))

# Plot Elbow curve
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

axes[0].plot(k_range, inertias, "bo-")
axes[0].set_xlabel("Number of Clusters (k)")
axes[0].set_ylabel("Inertia")
axes[0].set_title("Elbow Method")

axes[1].plot(k_range, silhouette_scores, "ro-")
axes[1].set_xlabel("Number of Clusters (k)")
axes[1].set_ylabel("Silhouette Score")
axes[1].set_title("Silhouette Analysis")

plt.tight_layout()
plt.show()

# Select optimal k (typically k=4 works well for RFM data)
optimal_k = 4
kmeans_final = KMeans(n_clusters=optimal_k, random_state=42, n_init="auto")
customer_df["Cluster"] = kmeans_final.fit_predict(rfm_scaled)

print(f"\nFinal silhouette score (k={optimal_k}): "
      f"{silhouette_score(rfm_scaled, customer_df['Cluster']):.3f}")

# Visualize cluster profiles
cluster_summary = customer_df.groupby("Cluster")[["Recency", "Frequency", "Monetary"]].mean()
print("\nCluster Profiles (Mean RFM values):")
print(cluster_summary)

cluster_summary.plot(kind="bar", figsize=(10, 6))
plt.title("Average RFM Values per Customer Segment")
plt.xlabel("Cluster")
plt.ylabel("Mean Value")
plt.legend(title="Feature")
plt.tight_layout()
plt.show()

# --- Step 4: Churn Classification with Random Forest ---

# Prepare features and target
feature_cols = ["Recency", "Frequency", "Monetary", "AverageOrderValue",
                "n_products", "n_days", "Cluster"]
X = customer_df[feature_cols]
y = customer_df["Churned"]

# Train/test split
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)

# Train Random Forest
rf_model = RandomForestClassifier(n_estimators=100, random_state=42)
rf_model.fit(X_train, y_train)

# Predictions
y_pred = rf_model.predict(X_test)
y_proba = rf_model.predict_proba(X_test)[:, 1]

# Evaluation
print("\nClassification Report:")
print(classification_report(y_test, y_pred))

roc_auc = roc_auc_score(y_test, y_proba)
print(f"ROC-AUC Score: {roc_auc:.4f}")

# Confusion matrix heatmap
cm = confusion_matrix(y_test, y_pred)
plt.figure(figsize=(8, 6))
sns.heatmap(cm, annot=True, fmt="d", cmap="Blues",
            xticklabels=["Active", "Churned"],
            yticklabels=["Active", "Churned"])
plt.title("Confusion Matrix")
plt.xlabel("Predicted")
plt.ylabel("Actual")
plt.tight_layout()
plt.show()

# ROC curve
fpr, tpr, _ = roc_curve(y_test, y_proba)
plt.figure(figsize=(8, 6))
plt.plot(fpr, tpr, label=f"Random Forest (AUC = {roc_auc:.3f})")
plt.plot([0, 1], [0, 1], "k--", label="Random Baseline")
plt.xlabel("False Positive Rate")
plt.ylabel("True Positive Rate")
plt.title("ROC Curve - Churn Prediction")
plt.legend()
plt.tight_layout()
plt.show()

# --- Step 5: Feature Importance and Interpretation ---

# Feature importance plot
importances = rf_model.feature_importances_
feature_importance_df = pd.DataFrame({
    "Feature": feature_cols,
    "Importance": importances
}).sort_values("Importance", ascending=True)

plt.figure(figsize=(10, 6))
plt.barh(feature_importance_df["Feature"], feature_importance_df["Importance"])
plt.xlabel("Feature Importance")
plt.title("Random Forest Feature Importances - Churn Prediction")
plt.tight_layout()
plt.show()

print("\nFeature Importances (sorted):")
print(feature_importance_df.sort_values("Importance", ascending=False).to_string(index=False))

Your notebook is now portfolio-ready. You have working models, clear visualizations, and written interpretation that demonstrates end-to-end ML thinking. Next up is an optional Secret Mission where you can quantify exactly how much value the clustering step added to your classifier.

Secret mission

Quantify the Clustering Uplift

Your churn classifier uses the Cluster label as a feature. But how much does that cluster feature actually help? In this mission, you'll design a controlled experiment to measure the exact performance difference between a model with clusters and one without.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely in Google Colab, so there are no ongoing costs or cloud resources to worry about. Your notebook is auto-saved to your Google Drive.

Decide whether to keep your notebook in Google Drive, download a local copy, or delete it entirely.

Resources you used:

  • Google Colab notebook (auto-saved to Google Drive).
  • UCI Online Retail dataset (loaded into Colab runtime memory, not persisted).

Keep everything running

No action needed. Choose this if you plan to revisit the notebook, share it, or push it to GitHub for your portfolio.

  • Your notebook remains in Google Drive under the Colab Notebooks folder.
  • The Colab runtime disconnects automatically after inactivity. Re-running the cells will reload the dataset and retrain the models.
  • To share, click Share in the top right corner of your Colab notebook, or push the .ipynb file to a GitHub repository.

Pause - I'll come back to this later

Disconnect the Colab runtime to free up resources. Your notebook file stays in Google Drive.

  • In your Colab notebook, click Runtime in the top menu bar.
  • Click Disconnect and delete runtime.
  • This frees up the allocated GPU/CPU. Your notebook code and outputs remain saved in Google Drive.
  • When you return, click Runtime then Run all to re-execute everything from scratch.

Delete - I don't want to use this again

Remove the notebook from your Google Drive entirely.

  • In your Colab notebook, click File in the top menu bar.
  • Click Move to trash.
  • To permanently delete it, open Google Drive, navigate to Trash, right-click the notebook file, and select Delete forever.
  • The Colab runtime is ephemeral and cleans itself up automatically. No other resources remain.

That's a Wrap!

That's a Wrap!

Nice work! You just built a complete customer intelligence pipeline that transforms raw e-commerce transactions into actionable business insights using both unsupervised and supervised machine learning.

You've learned how to:

  • Transformed 541,909 raw transactions into a customer-level dataset with engineered RFM features (Recency, Frequency, Monetary) and defined a churn target variable.
  • Segmented customers into behavioral clusters using K-Means with Elbow method and Silhouette analysis to select the optimal number of clusters.
  • Built a Random Forest churn classifier that uses cluster labels as an engineered feature, evaluated with ROC-AUC, a confusion matrix, and a feature importance chart.
  • Secret Mission: Quantified the exact performance uplift from adding cluster features to the classifier, proving the synergy between unsupervised and supervised learning.

Ready to quiz yourself?