Phase 1 is the foundation of the capstone — you cannot engineer good features from data you do not understand, and you cannot clean data you have not inspected. In this phase you will profile every column, identify and document data-quality issues, understand the distribution of the target and its relationship with the most important raw features, and produce a written quality report that will guide your decisions in Phases 2 and 3. The motto of Phase 1 is observe before you act: every decision in the subsequent phases should be traceable to a specific finding here.
Step 1 — Load and Initial Profile
Load the CricketStream dataset, inspect its schema, check dtypes, count rows and columns, and compute basic summary statistics. Flag any immediate concerns — columns with unexpected dtypes, surprising ranges, or names suggesting redundancy. This initial scan takes ten minutes and prevents hours of wasted work on bad assumptions.
import pandas as pd
import numpy as np
df = pd.read_csv("data/cricketstream_churn.csv", parse_dates=["snapshot_date","signup_date"])
print(f"Shape: {df.shape}")
print(f"\nDtypes:\n{df.dtypes}")
print(f"\nFirst 3 rows:\n{df.head(3)}")
print(f"\nBasic stats:\n{df.describe(include='all').T}")
print(f"\nTarget distribution:\n{df['churned'].value_counts(normalize=True).round(3)}")
Step 2 — Missing Value and Variance Audit
For every column compute the missing-value rate and the variance. Flag columns with more than twenty percent missing as requiring a strategy decision, and flag near-constant columns (variance below a threshold) for removal. Document the mechanism of missingness for the highest-missing columns — are they MCAR, MAR, or MNAR? — because the mechanism drives the imputation choice in Phase 2.
# Missing value audit
missing = df.isnull().mean().sort_values(ascending=False)
print("Missing rates (all columns):")
print(missing[missing > 0].round(3))
# Near-constant / low-variance columns (numeric only)
numeric_cols = df.select_dtypes(include="number").columns
variances = df[numeric_cols].var()
near_const = variances[variances < 0.01]
print(f"\nNear-constant numeric columns (var < 0.01):\n{near_const}")
# Missingness indicator: is missingness correlated with churn?
for col in missing[missing > 0].index:
miss_flag = df[col].isnull().astype(int)
corr = miss_flag.corr(df["churned"])
if abs(corr) > 0.1:
print(f" WARNING: missingness in '{col}' correlates {corr:.2f} with churn -> MAR/MNAR")
Step 3 — Distribution Analysis
Plot and summarise the distribution of each numeric feature, focusing on skewness and outliers. For weekly_watch_mins and total_sessions compute the skewness statistic and identify the percentile where the long tail begins. For categorical features compute value-count distributions and flag any high-cardinality or rare-category columns. These findings directly inform the transform decisions in Phase 2.
from scipy import stats
numeric_cols = df.select_dtypes(include="number").drop(columns=["churned"]).columns
print("Skewness of numeric features:")
for col in numeric_cols:
sk = stats.skew(df[col].dropna())
pct95 = df[col].quantile(0.95)
print(f" {col:35}: skew={sk:+.2f}, 95th pct={pct95:.1f}")
print("\nCategorical value counts:")
cat_cols = df.select_dtypes(include="object").columns
for col in cat_cols:
vc = df[col].value_counts()
print(f" {col}: {len(vc)} unique, rarest={vc.min()} ({vc.idxmin()})")
print("\nTarget correlation with raw numeric features (absolute, top-5):")
corrs = df[numeric_cols].corrwith(df["churned"]).abs().sort_values(ascending=False)
print(corrs.head(5).round(3))
Step 4 — Temporal Structure Check
Check that the dataset's time structure is intact: verify that snapshot_date spans the expected range, that there are no future-dated snapshots, and that the thirty-day churn window correctly precedes each snapshot. Plot the churn rate over time to detect any temporal drift that would affect the train-test split strategy. A time-ordered dataset with stable churn rate is the ideal; temporal drift requires stratification in the split.
print("Temporal structure audit:")
print(f" snapshot_date range: {df['snapshot_date'].min()} to {df['snapshot_date'].max()}")
print(f" tenure_days range: {df['tenure_days'].min()} to {df['tenure_days'].max()}")
# Churn rate over time (monthly)
df["snapshot_month"] = df["snapshot_date"].dt.to_period("M")
monthly_churn = df.groupby("snapshot_month")["churned"].mean()
print("\nMonthly churn rate (should be stable for clean train/test split):")
print(monthly_churn.round(3))
# Proposed time-ordered split: last 20% of snapshots as test
split_date = df["snapshot_date"].quantile(0.8)
train_n = (df["snapshot_date"] <= split_date).sum()
test_n = (df["snapshot_date"] > split_date).sum()
print(f"\nProposed split: train={train_n} rows (before {split_date.date()}), "
f"test={test_n} rows (after)")
Step 5 — Quality Report
Compile your findings into a structured quality report: list every data-quality issue found, the columns affected, the severity, and the planned remediation in Phase 2. Include the near-constant column to be dropped, the columns requiring imputation and the chosen strategy, the skewed columns requiring transformation, and any temporal anomalies. This report is a Phase 2 input document — treat it as an instruction list for yourself.
quality_report = {
"near_constant_cols": [], # fill from near_const above
"high_missing_cols": {}, # col -> (rate, mechanism, strategy)
"skewed_cols": {}, # col -> (skew, planned_transform)
"unseen_cat_risk": [], # categoricals with rare values
"temporal_drift": False, # set True if monthly churn varies > 0.05
"split_date": str(split_date.date()) if hasattr(split_date,"date") else str(split_date),
"notes": []
}
print("Quality Report Template (fill with your findings):")
import json
print(json.dumps(quality_report, indent=2))
print("\nThis structured report is your Phase 2 instruction list.")
print("Every cleaning and transform decision in Phase 2 must trace back to a finding here.")
Warning: Do not start Phase 2 until the quality report is complete. Jumping to feature engineering before understanding the data produces features built on wrong assumptions — imputing with the mean when the mechanism is MNAR, transforming a column that was already near-normal, or missing the near-constant column that will corrupt your variance threshold. The quality report is the map; Phase 2 without it is navigating blind.
- Phase 1 is pure observation: profile every column, audit missingness and variance, analyse distributions, and check temporal structure.
- Document every quality issue with severity and planned remediation before touching any features.
- Flag near-constant columns for removal, high-missing columns for imputation strategy decisions, and skewed columns for transforms.
- Check temporal structure: snapshot-date range, churn-rate stability over time, and the proposed time-ordered split boundary.
- Every Phase 2 decision must trace to a specific Phase 1 finding — the quality report is the link between observation and action.