Data Science In Practice
Capstone Project: E-Commerce Marketing Analysis
Following Chapter 1's CRISP-DM structure exactly, on one realistic, end-to-end project. An e-commerce company's marketing team asks:
Jr Codex Data Science Notes
Level: Advanced Prerequisites: All of Modules 1–5, Chapter 1, Chapter 2 Time to complete: ~45 minutes
Table of Contents
- The Scenario
- Phase 1 — Business Understanding
- Phase 2 — Data Understanding
- Phase 3 — Data Preparation
- Phase 4 — Analysis (EDA + Statistics)
- Phase 5 — Experimentation
- Phase 6 — Visualization
- Phase 7 — Evaluation & Deployment
- Where to Go From Here
1. The Scenario
Following Chapter 1's CRISP-DM structure exactly, on one realistic, end-to-end project. An e-commerce company's marketing team asks:
"We ran a new email marketing campaign last quarter. Did it actually
increase purchases, and which customer segment responded best?
Should we roll it out company-wide?"
This single question will pull in a technique from nearly every module in this curriculum.
2. Phase 1 — Business Understanding
Applying Chapter 1, Section 3's discipline before touching any data:
Clarifying Questions & Answers
─────────────────────────────────────────
Q: What decision does this inform?
A: Whether to expand the campaign to ALL customers (a real budget decision)
Q: What does success look like?
A: A statistically AND practically significant increase in purchase
rate, for the campaign group vs a comparable non-campaign group
Q: Is there an existing hypothesis?
A: Yes — marketing believes the campaign increases purchases,
particularly among "lapsed" customers (no purchase in 90+ days)
Q: Who's the audience for the final result?
A: The VP of Marketing (needs a clear go/no-go recommendation)
and the marketing analytics team (needs the full methodology)
─────────────────────────────────────────
Refined, precise question: "Did customers who received the campaign email have a significantly higher purchase rate over the following 30 days than a comparable control group, and does this effect differ between lapsed and active customers?"
3. Phase 2 — Data Understanding
Following Module 3, Chapter 1's first-contact checklist:
import pandas as pd
df = pd.read_csv("campaign_data.csv")
print(df.shape) # (20000, 8)
print(df.head())
print(df.dtypes)
print(df.isna().sum())
print(df.duplicated().sum())
# customer_id, received_campaign (bool), days_since_last_purchase,
# purchased_within_30_days (bool), order_value, region, customer_tenure_days, age# Immediate first-contact observations
print(df["received_campaign"].value_counts()) # was this randomized? check the split
print(df.isna().mean() * 100) # age: 3.1% missing, order_value: 0% (0 for non-purchasers)Key early finding: received_campaign is close to a 50/50 split — consistent with proper randomization (Module 5, Chapter 1) rather than a self-selected opt-in, which is what makes a causal claim possible later.
4. Phase 3 — Data Preparation
Applying Module 2's cleaning and feature engineering directly:
# Handle missing age (Module 2, Ch.4) — small % missing, check for MAR pattern first
print(df.groupby(df["age"].isna())["purchased_within_30_days"].mean())
# No strong relationship found — safe to impute with median
df["age"] = df["age"].fillna(df["age"].median())
# Remove duplicates (Module 2, Ch.4)
df = df.drop_duplicates(subset=["customer_id"])
# Feature engineering (Module 2, Ch.5) — create the "lapsed customer" segment
df["is_lapsed"] = df["days_since_last_purchase"] > 90
# Standardize region text (Module 2, Ch.3)
df["region"] = df["region"].str.strip().str.title()
print(f"Final dataset: {len(df)} customers")
print(df["is_lapsed"].value_counts(normalize=True))5. Phase 4 — Analysis (EDA + Statistics)
Following Module 3's full EDA sequence:
import pandas as pd
# Univariate (Module 3, Ch.2)
print(df["order_value"].describe())
print(f"Skewness: {df['order_value'].skew():.2f}") # likely right-skewed, typical for order values
# Bivariate — does campaign group correlate with purchase rate? (Module 3, Ch.3)
print(df.groupby("received_campaign")["purchased_within_30_days"].mean())
# received_campaign
# False 0.082
# True 0.113
# Multivariate — does the effect DIFFER by lapsed status? (Module 3, Ch.3's Simpson's Paradox check)
print(df.groupby(["received_campaign", "is_lapsed"])["purchased_within_30_days"].mean())
# received_campaign is_lapsed
# False False 0.095
# True 0.061
# True False 0.112
# True 0.115 ← campaign effect is MUCH bigger for lapsed customers!Key finding: the campaign's effect is notably larger among lapsed customers (0.061 → 0.115, nearly doubling) than active customers (0.095 → 0.112) — exactly the segment-specific pattern the marketing team suspected, now confirmed with data rather than assumed.
6. Phase 5 — Experimentation
Formalizing the EDA finding with Module 5's proper statistical testing:
from statsmodels.stats.proportion import proportions_ztest, confint_proportions_2indep
import numpy as np
# Overall effect — two-proportion z-test (Module 5, Ch.1)
campaign = df[df["received_campaign"]]["purchased_within_30_days"]
control = df[~df["received_campaign"]]["purchased_within_30_days"]
count = np.array([campaign.sum(), control.sum()])
nobs = np.array([len(campaign), len(control)])
z_stat, p_value = proportions_ztest(count, nobs)
print(f"Overall effect p-value: {p_value:.5f}")
ci_low, ci_upp = confint_proportions_2indep(campaign.sum(), len(campaign), control.sum(), len(control))
print(f"95% CI for the difference: [{ci_low:.4f}, {ci_upp:.4f}]")
# Segment-specific test — lapsed customers only
lapsed_df = df[df["is_lapsed"]]
lapsed_campaign = lapsed_df[lapsed_df["received_campaign"]]["purchased_within_30_days"]
lapsed_control = lapsed_df[~lapsed_df["received_campaign"]]["purchased_within_30_days"]
count_l = np.array([lapsed_campaign.sum(), lapsed_control.sum()])
nobs_l = np.array([len(lapsed_campaign), len(lapsed_control)])
z_stat_l, p_value_l = proportions_ztest(count_l, nobs_l)
print(f"Lapsed-segment effect p-value: {p_value_l:.5f}")Applying Module 5, Chapter 3's rigor checks before trusting these results:
# Sample Ratio Mismatch check (Module 5, Ch.3)
from scipy.stats import chisquare
observed = df["received_campaign"].value_counts().values
expected = [len(df) * 0.5] * 2
_, srm_p = chisquare(observed, expected)
print(f"SRM check p-value: {srm_p:.3f}") # should be well above 0.01 — no red flag
# Effect size, not just significance (Module 5, Ch.2)
overall_lift = (campaign.mean() - control.mean()) / control.mean() * 100
lapsed_lift = (lapsed_campaign.mean() - lapsed_control.mean()) / lapsed_control.mean() * 100
print(f"Overall relative lift: {overall_lift:.1f}%")
print(f"Lapsed segment relative lift: {lapsed_lift:.1f}%")7. Phase 6 — Visualization
Applying Module 4's chart-selection and storytelling principles (Module 5, Chapter 5) together:
import matplotlib.pyplot as plt
import pandas as pd
summary = df.groupby(["is_lapsed", "received_campaign"])["purchased_within_30_days"].mean().unstack()
summary.index = ["Active Customers", "Lapsed Customers"]
summary.columns = ["No Campaign", "Received Campaign"]
fig, ax = plt.subplots(figsize=(8, 5))
summary.plot(kind="bar", ax=ax, color=["gray", "steelblue"])
ax.set_title("Campaign Effect Is Nearly 2x Larger for Lapsed Customers", fontweight="bold")
ax.set_ylabel("Purchase Rate")
ax.set_xticklabels(summary.index, rotation=0)
ax.legend(title="")
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
plt.tight_layout()The chart's title states the conclusion (Module 5, Chapter 5), not just "Purchase Rate by Group" — exactly the storytelling discipline covered there.
8. Phase 7 — Evaluation & Deployment
Evaluation — checking against the original business question from Phase 1:
Does this answer "should we roll out the campaign company-wide?"
─────────────────────────────────────────
✓ Statistically significant overall effect (p < 0.001)
✓ Practically meaningful lift, NOT just statistically significant
(overall +38% relative lift; lapsed segment +89% relative lift)
✓ Sample Ratio Mismatch check passed — randomization looks clean
✓ Segment-specific finding directly actionable: TARGET lapsed
customers specifically, rather than a blanket rollout
─────────────────────────────────────────
Deployment — delivered as a stakeholder-facing summary (Module 5, Chapter 5's structure):
HEADLINE
The campaign works — and works nearly twice as well on lapsed
customers. Recommend rolling out to ALL lapsed customers immediately,
and running a further-refined test on active customers before a
full company-wide expansion.
EVIDENCE
[Bar chart above] Purchase rate: lapsed customers went from 6.1%
to 11.5% (+89% relative lift) with the campaign; active customers
saw a smaller but still positive +18% relative lift.
95% CI for overall effect: [2.1pp, 3.9pp], p<0.001.
CAVEATS
Campaign group was properly randomized (SRM check passed). Order
value differences between groups were not statistically significant
— this analysis focused on PURCHASE RATE, not average order size.
RECOMMENDATION
1. Immediate: expand campaign to all lapsed customers
2. Next: run a dedicated follow-up test on active customers with
a modified message before full rollout
9. Where to Go From Here
This capstone walked through every phase of Chapter 1's CRISP-DM workflow, using a specific technique from every module in this curriculum:
Module → Technique Used in This Capstone
─────────────────────────────────────────
Module 1 (Statistics) → hypothesis testing framework, correlation caution
Module 2 (Wrangling) → cleaning, missing data, feature engineering (is_lapsed)
Module 3 (EDA) → univariate/bivariate/multivariate analysis, finding
the lapsed-segment pattern before formal testing
Module 4 (Visualization) → conclusion-titled, audience-appropriate chart
Module 5 (Experimentation) → proper A/B test analysis, SRM check, effect size,
storytelling structure
Module 6 (This module) → CRISP-DM structure end-to-end
─────────────────────────────────────────
You've now completed the entire Data Science curriculum. The natural next step is Module 7: Bridge to Machine Learning, which connects everything built here — especially the EDA and feature engineering skills — to building predictive models.
Jr Codex — 1-on-1 Personalized Coaching | Back to Module Index | Back to Data Science Index