Project 02 · Tutorial

End-to-end: from CSV to predictive model.

A practical walk-through of building a churn predictor — the way I'd actually do it on a Monday morning. Six steps, no magic.

Tutorial walkthrough · coming soon

Pipeline

CSV
Clean
Features
Train
Evaluate
Deploy

Step 01

Load & inspect the data

Start with a single CSV. Skim shape, dtypes, and missingness before you write a single line of feature code. The first 10 minutes save the next 10 hours.

pythoncopy
import pandas as pd

df = pd.read_csv("subscribers.csv")
print(df.shape, df.dtypes)
df.describe(include="all").T

Step 02

Clean & standardize

Coerce dates, normalize categoricals, decide on a missing-value policy per column (not globally). Document every choice — future-you will need it.

pythoncopy
df["signup_date"] = pd.to_datetime(df["signup_date"])
df["plan"] = df["plan"].str.lower().str.strip()
df["mrr"] = df["mrr"].fillna(df["mrr"].median())

Step 03

Engineer features

Build behaviorally-meaningful features: recency, frequency, monetary, and trend deltas. Avoid leakage — features must only use data available before the prediction date.

pythoncopy
df["days_since_login"] = (snapshot - df["last_login"]).dt.days
df["sessions_7d"] = df.groupby("user_id")["session"].rolling("7D").sum()
df["mrr_delta_30d"] = df["mrr"] - df["mrr_lag30"]

Leakage check: every feature must be derivable from data available before the prediction snapshot. If you can't write the SQL for it as-of date X, you can't use it.

Step 04

Train a baseline

Start with logistic regression and a gradient-boosted tree. If the boosted model isn't comfortably beating logistic, you have a feature problem, not a model problem.

pythoncopy
from sklearn.model_selection import train_test_split
from sklearn.ensemble import GradientBoostingClassifier

X_train, X_test, y_train, y_test = train_test_split(
    X, y, stratify=y, test_size=0.2, random_state=42
)
model = GradientBoostingClassifier().fit(X_train, y_train)

Step 05

Evaluate honestly

Report AUC and calibration and lift at the operating threshold. A high AUC with terrible calibration is a footgun for any team using probabilities downstream.

pythoncopy
from sklearn.metrics import roc_auc_score, brier_score_loss

probs = model.predict_proba(X_test)[:, 1]
print("AUC:", roc_auc_score(y_test, probs))
print("Brier:", brier_score_loss(y_test, probs))

Step 06

Deploy & monitor

Ship scores to a warehouse table the product team can join on. Monitor input drift weekly and refit when feature distributions shift more than ~10%.

pythoncopy
scores = pd.DataFrame({"user_id": ids, "churn_prob": probs})
scores.to_gbq("analytics.churn_scores", if_exists="replace")