|
78 | 78 | "\n", |
79 | 79 | "This notebook is organized into the following sections:\n", |
80 | 80 | "\n", |
81 | | - "1. **Setup and Imports**: Load required packages\n", |
82 | | - "2. **Part I: Data Simulation**: Generate synthetic marketing data with realistic patterns\n", |
| 81 | + "* **Setup and Imports**: Load required packages\n", |
| 82 | + "* **Part I: Data Simulation**: Generate synthetic marketing data with realistic patterns\n", |
83 | 83 | " - Define time periods and intervention dates\n", |
84 | 84 | " - Create media spend data for multiple channels\n", |
85 | 85 | " - Apply marketing transformations (adstock and saturation)\n", |
86 | 86 | " - Construct the target variable (sales)\n", |
87 | 87 | " \n", |
88 | 88 | " *Note: If you already have your own data and just want to learn how to evaluate lift, you can skip directly to Part II.*\n", |
89 | 89 | "\n", |
90 | | - "3. **Part II: Interrupted Time Series Analysis**: Fit the ITS model and generate counterfactual predictions\n", |
91 | | - "4. **Part III: Calculating Lift Metrics**: \n", |
| 90 | + "* **Part II: Interrupted Time Series Analysis**: Fit the ITS model and generate counterfactual predictions\n", |
| 91 | + "* **Part III: Calculating Lift Metrics**: \n", |
92 | 92 | " - Absolute Lift (total incremental sales)\n", |
93 | 93 | " - Percentage Lift (% increase vs baseline)\n", |
94 | 94 | " - Return on Investment (ROI)\n", |
95 | | - "5. **Summary and Interpretation**: Business implications, assumptions, and next steps\n", |
| 95 | + "* **Summary and Interpretation**: Business implications, assumptions, and next steps\n", |
96 | 96 | "\n", |
97 | 97 | ":::\n" |
98 | 98 | ] |
|
374 | 374 | }, |
375 | 375 | { |
376 | 376 | "cell_type": "code", |
377 | | - "execution_count": 4, |
| 377 | + "execution_count": null, |
378 | 378 | "metadata": {}, |
379 | 379 | "outputs": [ |
380 | 380 | { |
|
418 | 418 | " ax.legend()\n", |
419 | 419 | " ax.grid(alpha=0.3)\n", |
420 | 420 | "\n", |
421 | | - "plt.tight_layout()\n", |
422 | | - "plt.show()" |
| 421 | + "plt.tight_layout()" |
423 | 422 | ] |
424 | 423 | }, |
425 | 424 | { |
|
619 | 618 | } |
620 | 619 | ], |
621 | 620 | "source": [ |
622 | | - "df_transformed.plot()" |
| 621 | + "df_transformed.plot();" |
623 | 622 | ] |
624 | 623 | }, |
625 | 624 | { |
|
633 | 632 | }, |
634 | 633 | { |
635 | 634 | "cell_type": "code", |
636 | | - "execution_count": 7, |
| 635 | + "execution_count": null, |
637 | 636 | "metadata": {}, |
638 | 637 | "outputs": [ |
639 | 638 | { |
|
816 | 815 | "print(f\"Std: {df['sales'].std():.2f}\")\n", |
817 | 816 | "print(f\"Min: {df['sales'].min():.2f}\")\n", |
818 | 817 | "print(f\"Max: {df['sales'].max():.2f}\")\n", |
819 | | - "print(\"\\nFirst few rows:\")\n", |
| 818 | + "\n", |
820 | 819 | "df.head()" |
821 | 820 | ] |
822 | 821 | }, |
|
925 | 924 | "\n", |
926 | 925 | "We'll fit a model that includes a time trend, monthly seasonality, and week-of-month effects to capture the baseline sales pattern.\n", |
927 | 926 | "\n", |
| 927 | + "#### Understanding the Model Formula\n", |
| 928 | + "\n", |
| 929 | + "The formula `\"sales ~ 1 + t + C(month) + C(week_of_month)\"` specifies a linear regression model with the following components:\n", |
| 930 | + "\n", |
| 931 | + "- **`1`**: Intercept term (baseline level of sales)\n", |
| 932 | + "- **`t`**: Linear time trend to capture long-term growth or decline\n", |
| 933 | + "- **`C(month)`**: Categorical variable for month of year (1-12)\n", |
| 934 | + " - Creates 11 dummy variables (with one month as reference)\n", |
| 935 | + " - Captures annual seasonality patterns (e.g., higher sales in summer, lower in winter)\n", |
| 936 | + "- **`C(week_of_month)`**: Categorical variable for week within month (1-5)\n", |
| 937 | + " - Creates 4 dummy variables (with week 1 as reference)\n", |
| 938 | + " - Captures within-month patterns (e.g., beginning vs end of month effects)\n", |
| 939 | + "\n", |
| 940 | + "This modeling approach is deliberately **simple and interpretable**. We're using categorical variables rather than smooth functions (like splines or Fourier terms) because:\n", |
| 941 | + "1. **Flexibility**: Each month and week can have its own level without constraining the pattern\n", |
| 942 | + "2. **Robustness**: Less prone to overfitting than complex non-linear models\n", |
| 943 | + "3. **Interpretability**: Easy to understand which time periods have higher/lower baseline sales\n", |
| 944 | + "\n", |
| 945 | + "The key assumption is that these baseline patterns (trend + seasonality) would have **continued unchanged** in the post-intervention period if the TV promo hadn't occurred. Any deviation from this pattern is attributed to the campaign.\n", |
| 946 | + "\n", |
928 | 947 | ":::{note}\n", |
929 | 948 | "The `random_seed` keyword argument for the PyMC sampler is not necessary. We use it here so that the results are reproducible.\n", |
930 | | - ":::\n" |
| 949 | + ":::" |
931 | 950 | ] |
932 | 951 | }, |
933 | 952 | { |
|
994 | 1013 | "cell_type": "markdown", |
995 | 1014 | "metadata": {}, |
996 | 1015 | "source": [ |
997 | | - "### Visualize the Results\n", |
998 | | - "\n", |
999 | | - "The plot shows three key panels:\n", |
1000 | | - "1. **Top**: Observed data (black) vs model fit and counterfactual prediction (blue)\n", |
1001 | | - "2. **Middle**: The causal impact at each time point (difference between observed and counterfactual)\n", |
1002 | | - "3. **Bottom**: Model coefficients with uncertainty\n" |
| 1016 | + "### Visualize the Results" |
1003 | 1017 | ] |
1004 | 1018 | }, |
1005 | 1019 | { |
1006 | 1020 | "cell_type": "code", |
1007 | | - "execution_count": 10, |
| 1021 | + "execution_count": null, |
1008 | 1022 | "metadata": {}, |
1009 | 1023 | "outputs": [ |
1010 | 1024 | { |
|
1101 | 1115 | }, |
1102 | 1116 | { |
1103 | 1117 | "cell_type": "code", |
1104 | | - "execution_count": 12, |
| 1118 | + "execution_count": null, |
1105 | 1119 | "metadata": {}, |
1106 | 1120 | "outputs": [ |
1107 | 1121 | { |
|
1150 | 1164 | " \"Total Absolute Lift from TV Promo Campaign\", fontsize=14, fontweight=\"bold\"\n", |
1151 | 1165 | ")\n", |
1152 | 1166 | "plt.tight_layout()\n", |
1153 | | - "plt.show()\n", |
1154 | 1167 | "\n", |
1155 | 1168 | "# Get summary statistics\n", |
1156 | 1169 | "absolute_lift_summary = az.summary(absolute_lift, kind=\"stats\")\n", |
|
1215 | 1228 | }, |
1216 | 1229 | { |
1217 | 1230 | "cell_type": "code", |
1218 | | - "execution_count": 17, |
| 1231 | + "execution_count": null, |
1219 | 1232 | "metadata": {}, |
1220 | 1233 | "outputs": [ |
1221 | 1234 | { |
|
1276 | 1289 | ")\n", |
1277 | 1290 | "ax.set_xlabel(\"Percentage Lift (%)\")\n", |
1278 | 1291 | "plt.tight_layout()\n", |
1279 | | - "plt.show()\n", |
1280 | 1292 | "\n", |
1281 | 1293 | "# Summary statistics\n", |
1282 | 1294 | "percentage_lift_summary = az.summary(avg_percentage_lift, kind=\"stats\")\n", |
|
1304 | 1316 | }, |
1305 | 1317 | { |
1306 | 1318 | "cell_type": "code", |
1307 | | - "execution_count": 18, |
| 1319 | + "execution_count": null, |
1308 | 1320 | "metadata": {}, |
1309 | 1321 | "outputs": [ |
1310 | 1322 | { |
|
1378 | 1390 | ")\n", |
1379 | 1391 | "ax.set_xlabel(\"ROI (%)\")\n", |
1380 | 1392 | "plt.tight_layout()\n", |
1381 | | - "plt.show()\n", |
1382 | 1393 | "\n", |
1383 | 1394 | "# Summary statistics\n", |
1384 | 1395 | "roi_summary = az.summary(roi, kind=\"stats\")\n", |
|
1475 | 1486 | "\n", |
1476 | 1487 | "The primary purpose of running lift tests is to generate experimental evidence that can improve your Media Mix Models. Here's how to use these results:\n", |
1477 | 1488 | "\n", |
1478 | | - "#### 1. **Feed Results into MMM Calibration**\n", |
| 1489 | + "#### 1. Feed Results into MMM Calibration\n", |
1479 | 1490 | "\n", |
1480 | 1491 | "The mean and standard deviation of the absolute lift (extracted above) can be directly integrated into MMM frameworks like PyMC-Marketing using the `add_lift_test_measurements()` method. This:\n", |
1481 | 1492 | "- Constrains the MMM's saturation curves to match observed experimental data\n", |
|
1484 | 1495 | "\n", |
1485 | 1496 | "See the [PyMC-Marketing lift test calibration documentation](https://www.pymc-marketing.io/en/latest/notebooks/mmm/mmm_lift_test.html) for implementation details.\n", |
1486 | 1497 | "\n", |
1487 | | - "#### 2. **Run Multiple Lift Tests**\n", |
| 1498 | + "#### 2. Run Multiple Lift Tests\n", |
1488 | 1499 | "\n", |
1489 | 1500 | "To get the most value from lift testing:\n", |
1490 | 1501 | "- Test **different channels** to calibrate each saturation curve\n", |
1491 | 1502 | "- Test at **different spend levels** to better define the curve shape\n", |
1492 | 1503 | "- Run tests in **different time periods** to capture seasonal variations\n", |
1493 | 1504 | "- Accumulate results over time to build a robust calibration dataset\n", |
1494 | 1505 | "\n", |
1495 | | - "#### 3. **Additional Extensions**\n", |
| 1506 | + "#### 3. Additional Extensions\n", |
1496 | 1507 | "\n", |
1497 | 1508 | "- **Incorporating Other Data**: Include additional predictors (weather, competitors, events) in the ITS model to improve counterfactual accuracy\n", |
1498 | 1509 | "- **Time-Varying Effects**: Examine `result.post_impact` at individual time points to understand how effects decay over time\n", |
|
1505 | 1516 | "source": [ |
1506 | 1517 | "## Conclusion\n", |
1507 | 1518 | "\n", |
1508 | | - "This notebook demonstrated how to **conduct lift tests using Interrupted Time Series analysis** when control groups are unavailable. The key takeaways are:\n", |
| 1519 | + "This notebook demonstrated how to conduct lift tests using Interrupted Time Series analysis when traditional control groups are unavailable. We showed how ITS can measure the causal impact of a national TV campaign, extracting the key statistical outputs (mean lift and standard deviation) that are essential for Media Mix Model calibration. Beyond these primary outputs, we also calculated percentage lift and ROI to provide immediate business insights for decision-makers.\n", |
1509 | 1520 | "\n", |
1510 | | - "### What We Accomplished\n", |
| 1521 | + "The importance of lift testing extends beyond measuring individual campaigns. Lift tests provide experimental evidence that validates whether your marketing actually works, separating signal from noise in your attribution models. When these experimental results are fed into MMM calibration procedures, they constrain the model's saturation curves to match real-world observations, dramatically improving parameter identifiability—especially for correlated channels where the model might otherwise struggle to distinguish their individual effects. This leads to more accurate attribution estimates and better-informed budget allocation decisions.\n", |
1511 | 1522 | "\n", |
1512 | | - "1. **Generated a Lift Test**: Used ITS to measure the causal impact of a national TV campaign\n", |
1513 | | - "2. **Extracted Key Metrics**: Calculated absolute lift with full uncertainty quantification (mean and standard deviation)\n", |
1514 | | - "3. **Prepared Outputs for MMM**: Showed how to format results for direct integration into Media Mix Models\n", |
1515 | | - "4. **Evaluated Business Impact**: Calculated percentage lift and ROI for immediate decision-making\n", |
1516 | | - "\n", |
1517 | | - "### Why This Matters\n", |
| 1523 | + ":::{admonition} The Complete Lift Testing Workflow\n", |
| 1524 | + ":class: tip\n", |
1518 | 1525 | "\n", |
1519 | | - "Lift tests provide **experimental evidence** that:\n", |
1520 | | - "- Validates whether your marketing actually works\n", |
1521 | | - "- Calibrates MMM models to improve accuracy\n", |
1522 | | - "- Informs budget allocation decisions with confidence intervals\n", |
1523 | | - "- Separates signal from noise in your attribution\n", |
| 1526 | + "For a complete marketing analytics workflow, lift testing fits into a three-step process:\n", |
1524 | 1527 | "\n", |
1525 | | - "### The Lift Testing Workflow\n", |
| 1528 | + "1. **Run lift tests** (this notebook) → Generate experimental evidence using ITS to measure the true causal impact of campaigns\n", |
| 1529 | + "2. **Calibrate your MMM** → Feed lift test results into your Media Mix Model using methods like PyMC-Marketing's `add_lift_test_measurements()` to constrain model parameters\n", |
| 1530 | + "3. **Optimize budget** → Use the calibrated MMM to allocate marketing spend across channels with confidence that the model reflects real-world effectiveness\n", |
1526 | 1531 | "\n", |
1527 | | - "For a complete marketing analytics workflow:\n", |
| 1532 | + "This approach bridges the gap between experimental causal inference and predictive modeling, combining the rigor of randomized experiments with the practical needs of ongoing attribution and optimization.\n", |
| 1533 | + ":::\n", |
1528 | 1534 | "\n", |
1529 | | - "1. **Run lift tests** (this notebook) → Generate experimental evidence using ITS\n", |
1530 | | - "2. **Calibrate your MMM** → Use lift test results to constrain model parameters\n", |
1531 | | - "3. **Optimize budget** → Use the calibrated MMM to allocate spend across channels\n", |
| 1535 | + "The method shown here is particularly valuable for national-level campaigns where geographic controls don't exist, but it can be extended in several ways. Multiple lift tests can be accumulated over time—testing different channels, different spend levels, and different time periods—to build a robust calibration dataset. The ITS model itself can be enhanced by incorporating additional predictors like weather, competitor activity, or special events to improve the counterfactual's accuracy. For organizations with multiple products or sub-markets, hierarchical Bayesian models can pool information across units while still estimating unit-specific lift.\n", |
1532 | 1536 | "\n", |
1533 | | - "This approach bridges the gap between experimental causal inference and predictive modeling, giving you both accuracy and actionable insights.\n", |
| 1537 | + "Ultimately, this approach represents a practical solution to one of marketing's most challenging problems: measuring incrementality when traditional experimental designs aren't feasible. By combining the strengths of time series analysis with Bayesian uncertainty quantification, we can generate the experimental evidence needed to build better attribution models and make more confident investment decisions.\n", |
| 1538 | + ":::\n", |
1534 | 1539 | "\n", |
1535 | 1540 | "### References\n", |
1536 | 1541 | "\n", |
|
0 commit comments