Skip to content

Commit d6151ec

Browse files
committed
same basic structure for each of the modelling sections
1 parent 1130aa6 commit d6151ec

File tree

1 file changed

+83
-28
lines changed

1 file changed

+83
-28
lines changed

docs/source/notebooks/graded_intervention_time_series_single_channel_ols.ipynb

Lines changed: 83 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -212,8 +212,6 @@
212212
"cell_type": "markdown",
213213
"metadata": {},
214214
"source": [
215-
"## 1. Generate Simulated Data\n",
216-
"\n",
217215
"We'll simulate weekly water consumption data for a catchment area in a **dry climate** over 3 years with:\n",
218216
"- **Baseline drivers**: temperature (seasonal) and rainfall (very low, with drought periods)\n",
219217
"- **Responsive policy**: public communications intensity that activates only during sustained drought\n",
@@ -517,12 +515,14 @@
517515
"cell_type": "markdown",
518516
"metadata": {},
519517
"source": [
520-
"## 2. Model Fitting\n",
518+
"## Modelling with HAC\n",
521519
"\n",
522520
"Fitting a transfer function model involves finding both the optimal transform parameters and the regression coefficients. This is accomplished through a nested optimization procedure. In the outer loop, the algorithm searches for the best saturation and adstock parameters—either by exhaustively evaluating all combinations on a discrete grid, or by using continuous optimization to search more efficiently through the parameter space. For each candidate set of transform parameters, the inner loop applies these transformations to the raw treatment variable and fits a regression model (OLS or ARIMAX) to the data. The root mean squared error (RMSE) of each fitted model is computed, and the parameter combination that minimizes this error is selected.\n",
523521
"\n",
524522
"This nested approach is computationally tractable because ordinary least squares has a closed-form solution based on matrix operations, making each individual model fit very fast. When using grid search with, say, 10 values for each of 3 parameters, the algorithm evaluates 1,000 model fits, which typically completes in under a second. Continuous optimization via gradient-based methods can be even faster, though it may settle on local optima rather than finding the global best. For models with ARIMAX error structures, each fit requires numerical optimization and takes longer, but the overall approach remains the same.\n",
525523
"\n",
524+
"### Fit Model\n",
525+
"\n",
526526
"Let's fit a model using grid search to estimate the transform parameters. We'll use a coarse grid for speed in this demonstration, though you can make it finer for production use to achieve more precise parameter estimates.\n"
527527
]
528528
},
@@ -578,7 +578,7 @@
578578
"cell_type": "markdown",
579579
"metadata": {},
580580
"source": [
581-
"## 3. Visualize Estimated vs True Transform Parameters\n",
581+
"### Visualize Estimated vs True Transform Parameters\n",
582582
"\n",
583583
"Since we know the true parameters used to generate the data, we can compare the estimated transforms to the true transforms. This helps us assess **parameter recovery** - how well the estimation procedure identifies the true data-generating process.\n",
584584
"\n",
@@ -665,10 +665,6 @@
665665
"cell_type": "markdown",
666666
"metadata": {},
667667
"source": [
668-
"## 4. Model Methods and Diagnostics\n",
669-
"\n",
670-
"Now that we have a fitted model with estimated transforms, let's explore the available methods for analysis and diagnostics.\n",
671-
"\n",
672668
"### Model Summary\n",
673669
"\n",
674670
"View the fitted model coefficients and their **HAC standard errors** (robust to autocorrelation and heteroskedasticity):\n"
@@ -1080,7 +1076,7 @@
10801076
},
10811077
{
10821078
"cell_type": "code",
1083-
"execution_count": 13,
1079+
"execution_count": null,
10841080
"metadata": {},
10851081
"outputs": [
10861082
{
@@ -1102,20 +1098,22 @@
11021098
" scale=0.0, # Zero out all communications (no policy counterfactual)\n",
11031099
")\n",
11041100
"\n",
1101+
"# Visualize the counterfactual analysis\n",
1102+
"fig, ax = result_estimated.plot_effect(effect_result)\n",
1103+
"plt.show()\n",
1104+
"\n",
11051105
"print(\n",
1106-
" f\"Total water saved by communications policy: {-effect_result['total_effect']:.0f} ML\"\n",
1107-
")\n",
1108-
"print(f\"Average weekly savings: {-effect_result['mean_effect']:.0f} ML/week\")\n",
1109-
"print(\n",
1110-
" f\"Percentage reduction: {-100 * effect_result['mean_effect'] / df['water_consumption'].mean():.1f}%\"\n",
1106+
" f\"\\nThe communications policy saved approximately {-effect_result['total_effect']:.0f} ML of water \"\n",
1107+
" f\"over the 3-year period, representing a {-100 * effect_result['mean_effect'] / df['water_consumption'].mean():.1f}% \"\n",
1108+
" f\"reduction in average consumption.\"\n",
11111109
")"
11121110
]
11131111
},
11141112
{
11151113
"cell_type": "markdown",
11161114
"metadata": {},
11171115
"source": [
1118-
"## 5. Alternative Error Model: ARIMAX\n",
1116+
"## Modelling with ARIMAX\n",
11191117
"\n",
11201118
"So far we've used **HAC (Newey-West) standard errors**, which provide robust inference without requiring us to specify the autocorrelation structure. This is the recommended default approach.\n",
11211119
"\n",
@@ -1143,7 +1141,7 @@
11431141
"cell_type": "markdown",
11441142
"metadata": {},
11451143
"source": [
1146-
"### Fit Model with ARIMAX Errors\n",
1144+
"### Fit Model\n",
11471145
"\n",
11481146
"Since we generated the data with AR(2) errors (`rho1=0.5`, `rho2=0.2`), the true error structure is ARIMA(2,0,0). For demonstration purposes, we'll fit an ARIMA(1,0,0) model, which is a slight misspecification. This shows how ARIMAX still performs reasonably well even when the order is not perfectly matched. In practice, you would use ACF/PACF plots to guide ARIMA order selection:\n"
11491147
]
@@ -1192,9 +1190,73 @@
11921190
")"
11931191
]
11941192
},
1193+
{
1194+
"cell_type": "markdown",
1195+
"metadata": {},
1196+
"source": [
1197+
"### Visualize Estimated vs True Transform Parameters\n",
1198+
"\n",
1199+
"Since we know the true parameters used to generate the data, we can compare the estimated transforms to the true transforms. This helps us assess **parameter recovery** - how well the estimation procedure identifies the true data-generating process.\n",
1200+
"\n",
1201+
"We'll visualize:\n",
1202+
"1. **Saturation curves**: How raw communication intensity gets transformed by saturation\n",
1203+
"2. **Adstock weights**: How effects carry over across weeks\n"
1204+
]
1205+
},
1206+
{
1207+
"cell_type": "code",
1208+
"execution_count": null,
1209+
"metadata": {},
1210+
"outputs": [],
1211+
"source": [
1212+
"# Create true transform objects (parameters used for data generation)\n",
1213+
"true_saturation = cp.LogisticSaturation(lam=0.5)\n",
1214+
"true_adstock = cp.GeometricAdstock(half_life=1.5, l_max=8, normalize=True)\n",
1215+
"\n",
1216+
"# Plot estimated transforms with comparison to true transforms\n",
1217+
"fig, ax = result_arimax.plot_transforms(\n",
1218+
" true_saturation=true_saturation, true_adstock=true_adstock, x_range=(0, 10)\n",
1219+
")\n",
1220+
"plt.show()\n",
1221+
"\n",
1222+
"# Parameter Recovery Assessment\n",
1223+
"true_params = true_saturation.get_params()\n",
1224+
"est_params = result_arimax.treatments[0].saturation.get_params()\n",
1225+
"true_adstock_params = true_adstock.get_params()\n",
1226+
"est_adstock_params = result_arimax.treatments[0].adstock.get_params()\n",
1227+
"\n",
1228+
"print(\"\\nParameter Recovery Assessment:\")\n",
1229+
"print(f\"Saturation - lam error: {abs(est_params['lam'] - true_params['lam']):.2f}\")\n",
1230+
"print(\n",
1231+
" f\"Adstock - half_life error: {abs(est_adstock_params['half_life'] - true_adstock_params['half_life']):.2f} weeks\"\n",
1232+
")"
1233+
]
1234+
},
1235+
{
1236+
"cell_type": "markdown",
1237+
"metadata": {},
1238+
"source": [
1239+
"**Interpretation:**\n",
1240+
"\n",
1241+
"- **Saturation curve** (left): Shows how raw communication intensity (0-10) gets transformed by diminishing returns. The curve flattens at higher intensities, meaning the 10th message has much less impact than the 1st.\n",
1242+
"\n",
1243+
"- **Adstock weights** (right): Shows how a communication \"impulse\" at week 0 affects water consumption over the following weeks. The bars show the relative contribution of each lag.\n",
1244+
"\n",
1245+
"- **Parameter recovery**: In this simulated example with known ground truth, we can assess how well the estimation recovered the true parameters. The ARIMAX model should recover similar transform parameters as the HAC model, since both use the same estimation procedure for transforms.\n"
1246+
]
1247+
},
1248+
{
1249+
"cell_type": "markdown",
1250+
"metadata": {},
1251+
"source": [
1252+
"### Model Summary\n",
1253+
"\n",
1254+
"View the fitted model coefficients and their standard errors. Note the ARIMA order is displayed:\n"
1255+
]
1256+
},
11951257
{
11961258
"cell_type": "code",
1197-
"execution_count": 15,
1259+
"execution_count": null,
11981260
"metadata": {},
11991261
"outputs": [
12001262
{
@@ -1224,7 +1286,6 @@
12241286
}
12251287
],
12261288
"source": [
1227-
"# View summary - note the ARIMA order is displayed\n",
12281289
"result_arimax.summary(round_to=2)"
12291290
]
12301291
},
@@ -1448,7 +1509,7 @@
14481509
},
14491510
{
14501511
"cell_type": "code",
1451-
"execution_count": 20,
1512+
"execution_count": null,
14521513
"metadata": {},
14531514
"outputs": [
14541515
{
@@ -1476,15 +1537,9 @@
14761537
}
14771538
],
14781539
"source": [
1479-
"# Visualize the counterfactual analysis\n",
1480-
"fig, ax = result_estimated.plot_effect(effect_result)\n",
1481-
"plt.show()\n",
1482-
"\n",
1483-
"print(\n",
1484-
" f\"\\nThe communications policy saved approximately {-effect_result['total_effect']:.0f} ML of water \"\n",
1485-
" f\"over the 3-year period, representing a {-100 * effect_result['mean_effect'] / df['water_consumption'].mean():.1f}% \"\n",
1486-
" f\"reduction in average consumption.\"\n",
1487-
")"
1540+
"# Note: This cell was removed - the HAC counterfactual visualization\n",
1541+
"# is now properly located in the HAC section above\n",
1542+
"pass"
14881543
]
14891544
},
14901545
{

0 commit comments

Comments
 (0)