Data Science

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:

JrCodex·7 min read

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

  1. The Scenario
  2. Phase 1 — Business Understanding
  3. Phase 2 — Data Understanding
  4. Phase 3 — Data Preparation
  5. Phase 4 — Analysis (EDA + Statistics)
  6. Phase 5 — Experimentation
  7. Phase 6 — Visualization
  8. Phase 7 — Evaluation & Deployment
  9. 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