ACTIVATE: Product Analytics
Playbook
A full-stack product analytics case study built on the Stack Exchange public data dump (stats.stackexchange.com). Covers the complete lifecycle a Product Data Analyst owns: activation funnel engineering, cohort retention matrices, feature adoption impact, RFM user segmentation, A/B test design and Bayesian analysis, and a data-backed product roadmap. All SQL runs inside DuckDB with Snowflake-compatible syntax, demonstrating real-world analytical SQL at scale without any external database.
Dataset & Coverage
10-Stage Analysis Pipeline
All Visualizations Explained
Predictive Modeling Deep Dives
Four end-to-end ML pipelines built on the same Stack Exchange dataset. The common thread is XGBoost tuned with Optuna TPE across 25-30 trials of 3-fold stratified cross-validation, evaluated with both ROC-AUC and Average Precision, with class imbalance handled directly through scale_pos_weight. NLP features come from TF-IDF on question tag strings, combined with dense numerical aggregates into a single feature matrix that XGBoost consumes natively without any dense conversion.
C=1.0, class_weight=balanced
max_iter=1000
Same feature matrix as XGB
30 trials, 3-fold StratifiedKFold
scale_pos_weight = neg/pos
eval_metric = auc
500 features, (1,2)-grams
min_df=5 to drop rare tags
Sparse + dense via hstack
hour_of_day, day_of_week
user_age_days, month_of_year
prior_accept_rate
Zero activity in last 90 days
REF_DATE = max post date
Two-window design, no leakage
30 trials, 3-fold StratifiedKFold
scale_pos_weight = neg/pos
SHAP TreeExplainer
Frequency: freq_30d, freq_90d
total_posts, total_comments
badge_count, days_since_badge
avg_post_score, account_age_days
log1p(reputation)
4 tiers:
Low 0-30% / Medium 30-50%
High 50-70% / Critical 70-100%
300 vocabulary features
Unigrams only, min_df=10
Custom token pattern for tags
Minimizes Frobenius norm ||V-WH||
W: document-topic matrix
H: topic-term matrix
Exhaustive search k = 5 to 20
Objective: reconstruction error
16 trials total
Assignment = argmax(W) per question
Acceptance rate per topic cluster
Volume vs acceptance scatter
First answer per question
Binary: hours_to_answer > 24
Window capped at 30 days
25 trials, 3-fold StratifiedKFold
scale_pos_weight = neg/pos
Tuned: depth, lr, subsample, colsample
300 features, (1,2)-grams, min_df=5
Sparse + 5 dense numerics via hstack
No dense conversion needed
hour_of_day, day_of_week
user_age_days
Engineering Deep Dives
The Stack Exchange data dump ships as a .7z archive containing
one XML file per table. Each <row> element carries all fields as
string attributes, so every column must be cast to its target type. Standard
CAST() in DuckDB throws a runtime error on empty strings;
TRY_CAST() returns NULL instead, which is the correct
semantic for optional XML attributes.
All five tables (Users, Posts, Comments, Badges, Votes) are registered as persistent DuckDB views after ingestion, allowing SQL across all tables in a single query without any Pandas cross-table merges in Python, keeping compute in the columnar engine.
DuckDB does not support the FILTER (WHERE ...) clause on
ordered-set aggregates like PERCENTILE_CONT. The workaround encodes the
predicate inside the ORDER BY expression as a CASE WHEN:
values outside the valid range return NULL, and PERCENTILE_CONT silently skips NULLs,
achieving an equivalent result. This pattern is used in the activation funnel and in the
feature adoption section.
The baseline D7 activation rate is computed directly from real data rather than assumed, ensuring the power calculation is grounded in actual platform behavior. Cohen's h (2 * |arcsin(sqrt(p2)) - arcsin(sqrt(p1))|) is used instead of a raw difference because it is the correct effect size measure for two proportions and remains stable at low base rates where Cohen's d breaks down.
The Bayesian layer uses a Beta(1 + conversions, 1 + non-conversions)
conjugate prior. 500K draws from scipy.stats.beta.rvs() yield
P(Treatment > Control), expected loss under a "keep control" decision, and the 95%
credible interval on posterior relative lift. The explicit SHIP/KILL requires all three
conditions simultaneously: p < 0.05,
positive relative lift, and P(T>C) > 0.95.
Each RFM dimension is ranked across all active users using NTILE(5) OVER (ORDER BY ...) with the correct sort direction: lower recency days = higher recency score, higher frequency = higher frequency score, higher reputation = higher magnitude score. Quintile 5 always means "best" on that dimension. The three scores are combined and mapped through a rule table to the five segment labels.
The scatter plot of Recency vs Frequency (Chart 06, bottom-left) serves as a sanity check that the rule-based segment boundaries produce visually coherent clusters in 2D space before those segments are used in any downstream analysis or stakeholder reporting.
Data-Backed Product Roadmap
Technical Architecture
- py7zr streaming archive extraction
- xml.etree.ElementTree row-level attribute parsing
- DuckDB in-process OLAP SQL engine
- TRY_CAST on all XML string columns
- Pandas for result materialization
- Five persistent DuckDB table views
- PERCENTILE_CONT WITHIN GROUP for P25/P50/P75
- NTILE(5) OVER (ORDER BY ...) for RFM quintiles
- DATEDIFF('month',...) for cohort period arithmetic
- Cohen's h effect size for two proportions
- statsmodels NormalIndPower for sample sizing
- scipy.stats.beta for Bayesian posteriors
- Dark-theme Matplotlib (custom color palette)
- Seaborn heatmap for cohort retention matrix
- Dual-axis charts for adoption vs retention
- Posterior density overlay (frequentist + Bayesian)
ax.table()with color-coded rows for roadmap- All outputs exported as PNG at 130 DPI
- TF-IDF (scikit-learn) on tag strings, bigrams
- scipy.sparse.hstack combines sparse TF-IDF + dense numerical features
- RFM aggregations: recency, freq_30d, freq_90d, badge windows
- log1p(reputation) for skew correction
- ParentId join to get first-answer timestamps per question
- XGBoost primary learner across all 3 supervised problems
- Optuna TPE sampler — 30/30/25 trials per study
- StratifiedKFold (3-fold) as CV objective
- scale_pos_weight for class imbalance (no oversampling)
- Logistic Regression baseline for P1
- NMF + Optuna GridSampler for topic count selection
- ROC-AUC and Average Precision per model
- Calibration curves (predicted vs actual positive rate)
- Precision-Recall curves for threshold selection
- SHAP TreeExplainer for churn model — beeswarm + bar
- 4-tier risk scoring: Low / Medium / High / Critical
- Model cards: approach, perf, and business use case per model