100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Data Analysis & Feature Engineering
55 minintermediate

Practice — Feature Engineering for Churn Prediction

What You'll Build

In this exercise you will build a comprehensive feature-engineering module for a churn-prediction problem, applying every technique from this module to transform raw subscriber records into a rich, leakage-free feature set. The scenario is a cricket-streaming service predicting which subscribers will cancel, framed around viewing behaviour, engagement, and account history. You will create domain-driven ratios and aggregations, extract datetime features from activity timestamps, build targeted interaction terms, transform skewed engagement metrics, and extract text features from support-ticket messages — all constructed to respect the arrow of time and avoid leakage. This consolidates the entire feature-engineering module into one tool that mirrors how a data scientist turns raw behavioural data into the predictive features that make churn detectable, with leakage prevention designed in throughout.

Analogy🏏Cricket
🏏 Think of it like cricket: Building this pipeline is like a head analyst codifying the entire pre-match preparation routine into a single reusable playbook — pitch inspection, opposition profiling, matchup analysis, and threat assessment — that can be run before any match against any opponent. Just as the playbook turns scattered preparation habits into one repeatable, complete routine, your EDA pipeline turns scattered exploration steps into one repeatable engine. Just as a good playbook ensures no aspect of preparation is forgotten before any match, your pipeline ensures no aspect of exploration is skipped on any dataset. The insight is that codifying the full exploration workflow into a reusable tool is what makes thorough EDA fast, consistent, and complete every time.

Prerequisites

  • Python 3.10 or later with NumPy, pandas, and scikit-learn installed.
  • Mastery of domain-driven feature creation and leakage prevention from Lesson 13.
  • Command of datetime extraction from Lesson 14 and polynomial/interaction features from Lesson 15.
  • Understanding of distribution transforms from Lesson 16 and text feature extraction from Lesson 17.
  • Familiarity with the time-aware, leakage-free construction discipline throughout.

Setup & Project Structure

You will create a project with a feature-engineering module containing reusable functions and a script that applies them to the churn dataset. Separating the reusable engineering functions from the dataset-specific script lets you apply the same techniques to any future behavioural data. Install the dependencies into a virtual environment and seed any randomness for reproducibility. The module will organise features by type — ratios, aggregations, datetime, interactions, transforms, and text — each function constructed to use only information available before the prediction point.

Analogy🏏Cricket
🏏 Think of it like cricket: a smart side doesn't build a brand-new practice routine for every opponent — it develops a reusable set of net drills and fitness protocols that can be pointed at any upcoming team, then layers opponent-specific prep on top. That is exactly why you separate a reusable EDA engine module, holding your general analysis functions, from the dataset-specific script that aims them at the housing data: build the engine once and you can explore any future dataset with it, just as a well-designed net session works against any tour. Just as a coach fixes the bowling-machine settings and pitch so today's session can be repeated identically tomorrow, you install dependencies into an isolated virtual environment and seed every source of randomness — including the outlier detection — so the whole analysis is reproducible. Just as a structured training ground keeps drills organised and repeatable, a clean project structure keeps your engine and script cleanly divided. The payoff: a disciplined setup you can reuse and trust match after match.
python
# Create the project
mkdir cricket_churn_features && cd cricket_churn_features
python -m venv venv
source venv/bin/activate          # Windows: venv\Scripts\activate
pip install numpy pandas scikit-learn

# Project files
touch feature_engineering.py run_features.py

# Verify
python -c "import numpy, pandas, sklearn; print('Feature engineering stack ready')"

Step 1 — Foundation

Step 1 builds the foundation: domain-driven ratio and aggregation features capturing subscriber engagement and behaviour. This is the foundation because these behavioural features are typically the strongest churn predictors — declining engagement and falling usage ratios are the clearest early-warning signs — and they must be constructed with leakage prevention from the start. You will create efficiency ratios and time-aware activity aggregations that summarise each subscriber's behaviour using only their history before the prediction point.

Analogy🏏Cricket
🏏 Think of it like cricket: Step 1 is like the opening pitch-and-conditions inspection — establishing the basic facts of the playing surface before any tactical planning. Just as the pitch inspection must come first because every tactic depends on the conditions, the structure and quality checks must come first because every analysis depends on understanding them. Just as a misread pitch ruins the game plan, missed type errors or missingness ruin the analysis. The insight is that the foundational structure-and-quality inspection is the bedrock the whole exploration stands on.
python
# feature_engineering.py
import pandas as pd
import numpy as np

def behavioural_features(df):
    """Domain-driven engagement ratios and time-aware activity trends."""
    df = df.copy()
    # RATIO: engagement efficiency (watch time per session)
    df["mins_per_session"] = df["total_watch_mins"] / df["sessions"].replace(0, np.nan)
    # RATIO: how much of their plan they actually use
    df["plan_utilisation"] = df["total_watch_mins"] / df["plan_mins_included"]
    # AGGREGATION (time-aware): trend in recent vs earlier activity
    df = df.sort_values(["subscriber_id", "week"])
    df["activity_last4"] = (df.groupby("subscriber_id")["weekly_mins"]
                            .transform(lambda s: s.shift(1).rolling(4, min_periods=1).mean()))
    df["activity_prev4"] = (df.groupby("subscriber_id")["weekly_mins"]
                            .transform(lambda s: s.shift(5).rolling(4, min_periods=1).mean()))
    # Declining engagement = strong churn signal
    df["engagement_trend"] = df["activity_last4"] - df["activity_prev4"]
    return df

Step 2 — Core Logic

Step 2 adds datetime and interaction features, capturing temporal patterns and conditional effects. This is the analytical core because timing — recency of last activity, day-of-week patterns, account tenure — and interactions between behaviour and account type often carry decisive churn signal that the raw fields miss. You will extract time-since and calendar features from activity timestamps and build targeted interaction terms encoding domain knowledge about which factor combinations drive churn.

Analogy🏏Cricket
🏏 Think of it like cricket: Step 2 is like profiling every single player in both squads with the metrics appropriate to their role — batting stats for batsmen, bowling figures for bowlers — so each is understood individually before matchups are considered. Just as each player gets the right kind of profile, each variable gets a type-appropriate summary. Just as profiling every player ensures none is overlooked before the matchup analysis, profiling every variable ensures none is skipped before relationship analysis. The insight is that complete, type-appropriate univariate profiling is the analytical core that everything downstream builds on.
python
# feature_engineering.py (continued)
def temporal_and_interaction_features(df):
    """Datetime extraction and domain-targeted interactions."""
    df = df.copy()
    df["last_active"] = pd.to_datetime(df["last_active"])
    df["snapshot"] = pd.to_datetime(df["snapshot_date"])
    # TIME-SINCE: recency of last activity (strong churn signal)
    df["days_since_active"] = (df["snapshot"] - df["last_active"]).dt.days
    # TENURE: how long they have been a subscriber
    df["signup"] = pd.to_datetime(df["signup_date"])
    df["tenure_days"] = (df["snapshot"] - df["signup"]).dt.days
    # CALENDAR: was last activity on a weekend (casual viewers churn differently)?
    df["last_active_weekend"] = (df["last_active"].dt.dayofweek >= 5).astype(int)
    # INTERACTION (domain): low utilisation hurts MORE on expensive premium plans
    df["util_x_premium"] = df["plan_utilisation"] * (df["plan_tier"] == "premium").astype(int)
    return df

Step 3 — Integration & Enhancement

Step 3 integrates distribution transforms and text feature extraction, then assembles the full leakage-safe feature set. You will log-transform the heavily skewed engagement metrics and extract TF-IDF features from support-ticket text, then combine all feature families into one engineering function. This integration completes the module by adding the distribution and text dimensions and unifying everything into a single coherent transform, with the text vectoriser fit on training data only to prevent leakage.

Analogy🏏Cricket
🏏 Think of it like cricket: Step 3 is like completing the preparation by analysing matchups between players, flagging anomalous performances, and turning the whole survey into a concrete game plan with specific instructions. Just as the matchup analysis and threat flags feed into an actionable plan, the bivariate and outlier analysis feed into the feature-engineering plan. Just as scattered observations are useless without a plan, EDA findings are useless without being made actionable. The insight is that integrating relationships and anomalies and converting everything into a concrete plan is what completes the pipeline and delivers EDA's real value.
python
# feature_engineering.py (continued)
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer

def transform_skewed(df, cols):
    """Log-transform heavily skewed engagement metrics (log1p handles zeros)."""
    df = df.copy()
    for col in cols:
        df[f"{col}_log"] = np.log1p(df[col])
    return df

def fit_text_features(train_text, max_features=50):
    """Fit TF-IDF on TRAINING support tickets only (prevents leakage)."""
    tfidf = TfidfVectorizer(stop_words="english", max_features=max_features,
                            ngram_range=(1, 2))
    tfidf.fit(train_text)
    return tfidf

def engineer_all(df, tfidf=None):
    """Full pipeline: behavioural -> temporal/interaction -> transforms -> text."""
    df = behavioural_features(df)
    df = temporal_and_interaction_features(df)
    df = transform_skewed(df, ["total_watch_mins", "sessions"])
    if tfidf is not None:
        text_feats = tfidf.transform(df["last_ticket_text"].fillna(""))
        text_df = pd.DataFrame(text_feats.toarray(),
                               columns=[f"txt_{w}" for w in tfidf.get_feature_names_out()],
                               index=df.index)
        df = pd.concat([df, text_df], axis=1)
    return df

Step 4 — Testing & Verification

Now you will run the full feature-engineering module on the churn dataset and verify it produces a rich, leakage-free feature set. Split the data respecting time order, fit the text vectoriser on training tickets only, engineer features for both splits, and confirm that the engineered features are present, that no feature uses future information, and that the resulting feature set improves a churn model over the raw fields. A richer, leakage-free feature set that improves prediction verifies the module works end to end.

Analogy🏏Cricket
🏏 Think of it like cricket: before the real series you play a full dress-rehearsal warm-up match to confirm all your preparation actually holds up under match conditions — and that is exactly what running the complete EDA pipeline on the housing data does. Just as you glance at the scoreboard to check it reads a sensible total and the right number of players are accounted for, you confirm the structure and quality checks report sensible shapes and plausible missingness. Just as you verify each player is listed in their correct role — batsman, bowler, keeper — you check the univariate profiles correctly classify and summarise every variable. Just as a warm-up reveals which opposition threats correlate most with danger, the relationship analysis should surface the features most strongly tied to valuation, and the outlier detection should flag genuinely reasonable freak cases, not nonsense. And just as you review the footage afterward to be sure nothing looked broken, you verify the whole output is coherent and actionable. The payoff: you trust the pipeline before it matters.
python
# run_features.py
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import roc_auc_score
from feature_engineering import engineer_all, fit_text_features

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

# TIME-ORDERED split: train on earlier snapshots, test on later (no future leakage)
df = df.sort_values("snapshot_date")
split = int(len(df) * 0.75)
train, test = df.iloc[:split].copy(), df.iloc[split:].copy()

# Fit text vectoriser on TRAIN tickets only
tfidf = fit_text_features(train["last_ticket_text"].fillna(""))

# Engineer features for both splits using the SAME fitted vectoriser
train_fe = engineer_all(train, tfidf)
test_fe = engineer_all(test, tfidf)

feature_cols = ["mins_per_session", "plan_utilisation", "engagement_trend",
                "days_since_active", "tenure_days", "util_x_premium",
                "total_watch_mins_log"] + [c for c in train_fe if c.startswith("txt_")]
feature_cols = [c for c in feature_cols if c in train_fe.columns]

model = RandomForestClassifier(random_state=42)
model.fit(train_fe[feature_cols].fillna(0), train_fe["churned"])
auc = roc_auc_score(test_fe["churned"], model.predict_proba(test_fe[feature_cols].fillna(0))[:,1])
print(f"Churn model AUC with engineered features: {auc:.3f}")
print("All features use only pre-snapshot information -> no leakage.")

Warning: In churn feature engineering, the deadliest mistake is leakage through features that peek past the prediction snapshot — an activity aggregation that includes post-snapshot behaviour, or a text vectoriser fit on the full dataset including future tickets. Such features make the model look brilliant in testing and fail completely in production, where the future is genuinely unknown. Construct every aggregation and time-since feature using only pre-snapshot data, fit all transformers on training data only, and validate with a time-ordered split, treating leakage as the default risk to disprove for every feature.

Extension Challenge: Extend the module to add a feature capturing the acceleration of engagement decline — the change in the engagement trend itself — since a sharply accelerating drop is a stronger churn signal than a steady one. As a stretch goal, add interaction features between the text-derived complaint topics and account tenure, since the same complaint may predict churn differently for new versus long-standing subscribers, and produce a feature-importance report showing which engineered features most drive the churn predictions, validating that the domain-driven features earn their place.

  • Churn feature engineering fuses behavioural ratios, aggregations, datetime, interaction, transform, and text features into one predictive set.
  • Engagement ratios and time-aware activity trends are typically the strongest churn signals, built leakage-free from the start.
  • Time-since features like days-since-last-activity and account tenure capture the recency effects that often dominate churn.
  • Targeted interactions encode domain knowledge about which factor combinations drive churn, like utilisation interacting with plan tier.
  • Log transforms tame skewed engagement metrics, and TF-IDF extracts signal from support-ticket text, fit on training only.
  • Every feature must use only pre-snapshot information, and validation must use a time-ordered split to prevent future leakage.
  • Leakage is the default risk to disprove for every feature, since a leaked feature looks brilliant in testing and fails in production.
Lesson 18 of 35
0% complete