Skip to main content

working-with-notebooks

Fixing and validating Jupyter notebooks in pymc-marketing. Use when notebooks fail CI, need arviz_plots 2.x migration fixes, or when source code changes require notebook output updates.

Ir a la instalación

Datos de origen

Repositorio
pymc-labs/pymc-marketing
Última actividad en el origen
28 de julio de 2026 a las 12:23
Idioma detectado de SKILL.md
inglés
Estrellas
1266
Forks
418

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
name
working-with-notebooks
description
Fixing and validating Jupyter notebooks in pymc-marketing. Use when notebooks fail CI, need arviz_plots 2.x migration fixes, or when source code changes require notebook output updates.
disable-model-invocation
false
# Working with Notebooks How to efficiently debug, fix, and validate notebooks in pymc-marketing. --- ## 1. The Mock Runner (fast local testing) The mock runner injects mocked PyMC sampling into notebooks, letting you check for runtime errors without real MCMC. See `scripts/run_notebooks/README.md`. ### Run a single notebook locally ```bash uv run python scripts/run_notebooks/runner.py --notebooks docs/source/notebooks/mmm/mmm_example.ipynb ``` ### Run multiple notebooks ```bash uv run python scripts/run_notebooks/runner.py --notebooks docs/source/notebooks/clv/sbg.ipynb docs/source/notebooks/mmm/mmm_example.ipynb ``` ### Run by category (CI-like) ```bash uv run python scripts/run_notebooks/runner.py --exclude-dirs mmm # non-MMM only uv run python scripts/run_notebooks/runner.py --exclude-dirs clv bass customer_choice general # MMM only ``` --- ## 2. Real Notebook Execution (for functional code changes) When source code changes affect model outputs, notebooks must be re-executed with real sampling to generate updated output cells. ### Recommended: jupyter execute --inplace ```bash # Clear stale outputs first uv run jupyter nbconvert --clear-output docs/source/notebooks/mmm/mmm_example.ipynb # Real execution with full MCMC uv run jupyter execute docs/source/notebooks/mmm/mmm_example.ipynb --inplace # Clean up auto-generated checkpoint dirs find docs/source/notebooks/mmm -name .ipynb_checkpoints -type d -exec rm -rf {} + ``` ### Alternative: jupyter nbconvert ```bash jupyter nbconvert --to notebook --execute --inplace docs/source/notebooks/mmm/mmm_example.ipynb ``` ### When to execute vs mock | Scenario | Tool | |---|---| | Notebook API migration (plot_dist, DataTree, kwarg changes) | Mock runner | | Source code changes (model fixes, new return types) | Real execution | | Before pushing ANY notebook change | Mock runner (minimum) | | Before pushing functional model changes | Both: real execution + mock runner | --- ## 3. Core Workflow (Iteration Loop) **CRITICAL: Never push until all targeted notebooks pass locally.** ``` 1. Identify failing notebooks (from CI or initial mock run) 2. FOR EACH notebook (one at a time): a. Run mock runner: python scripts/run_notebooks/runner.py --notebooks <path> b. Read the error (Cell In[X], exception type, traceback) c. Identify the pattern (see §4 below) d. Fix the notebook cell e. Repeat a-d until mock runner passes 3. Run ruff on changed files: ruff check --fix <files> ruff format <files> 4. Run pre-commit (or at minimum: ruff check + ruff format + mypy) 5. Push ONCE when all notebooks pass ``` --- ## 4. Common Notebook Failure Patterns ### B10: Bare DataArray/ndarray passed to plot_dist/plot_forest ```python # ❌ BROKEN — KeyError: 'posterior' or IndexError azp.plot_dist(my_dataarray) azp.plot_dist(my_numpy_array) # ✅ FIXED — wrap in Dataset azp.plot_dist(my_dataarray.to_dataset(name="var_name")) azp.plot_dist(xr.Dataset({"x": xr.DataArray(data, dims=["sample"])})) ``` ### B11: figsize → figure_kwargs ```python # ❌ — ValueError: extra keywords azp.plot_forest(idata, figsize=(8, 8)) # ✅ azp.plot_forest(idata, figure_kwargs={"figsize": (8, 8)}) ``` ### B12: rug=True removed ```python # ❌ — ValueError: extra keywords azp.plot_dist(idata, rug=True) # ✅ — just remove rug=True azp.plot_dist(idata) ``` ### B13: sample_dims mismatch ```python # ❌ — ValueError: sample_dims not found azp.plot_dist(xr.Dataset({"x": data}), sample_dims=["sample"]) # data has chain/draw # ✅ — remove sample_dims (auto-detect) or match data dims azp.plot_dist(xr.Dataset({"x": data})) # auto-detect for chain/draw data azp.plot_dist(xr.Dataset({"x": data}), sample_dims=["sample"]) # for pre-stacked data ``` ### B14: List-based plot_forest with model_names ```python # ❌ — TypeError: list indices must be integers azp.plot_forest([idata1, idata2], model_names=["A", "B"], ...) # ✅ — combine into single DataTree with model dim combined = xr.concat([ds1, ds2], dim="model").assign_coords(model=["A", "B"]) dt = xr.DataTree.from_dict({"/posterior": xr.DataTree(combined)}) azp.plot_forest(dt, ...) ``` ### B15/B16: Return type — PlotCollection, not axes ```python # ❌ — TypeError: 'PlotCollection' object is not subscriptable ax = azp.plot_forest(idata, ...); ax[0].set_title("...") # ✅ — extract axes from PlotCollection pc = azp.plot_forest(idata, ...) ax = pc.viz["/"]["figure"].values.item().axes[0] ax.set_title("...") ``` ### B17: compact=True removed ```python # ❌ — ValueError: extra keywords azp.plot_trace(idata, compact=True) # ✅ — remove azp.plot_trace(idata) ``` ### B18: group parameter for posterior_predictive ```python # ❌ — KeyError: 'posterior' azp.plot_dist(pp_idata) # pm.sample_posterior_predictive result # ✅ azp.plot_dist(pp_idata, group="posterior_predictive") ``` ### B20: Missing xarray import ```python # ❌ — NameError: name 'xr' is not defined # ✅ — add to imports cell: import xarray as xr ``` ### B22: hdi_prob → prob, ci_bounds → ci_bound ```python # ❌ az.hdi(data, hdi_prob=0.94) hdi.sel(ci_bounds="lower"), hdi.sel(ci_bounds="higher") # ✅ az.hdi(data, prob=0.94) hdi.sel(ci_bound="lower"), hdi.sel(ci_bound="upper") ``` ### B23: HDI integer indexing [0]/[1] → .sel(ci_bound=...) ```python # ❌ — IndexError hdi_result[0], hdi_result[1] # ✅ hdi_result.sel(ci_bound="lower"), hdi_result.sel(ci_bound="upper") ``` ### B24: az.plot_density removed ```python # ❌ az.plot_density(data, ...) # ✅ azp.plot_dist(data) ``` ### B25: prob= in plot_dist/plot_forest (not same as ci_prob=) ```python # ❌ — ValueError: extra keywords azp.plot_dist(idata, prob=1) # ✅ — prob= is NOT a valid kwarg for plot_dist/plot_forest azp.plot_dist(idata) # just remove prob= ``` --- ## 5. Identifying Failing Notebooks from CI The CI runs notebooks in batches on the `Test Notebooks` workflow: 1. Go to the PR checks tab on GitHub 2. Find entries like `notebooks (cvm, --notebooks clv)` with ❌ 3. Click through to the job log 4. Search for `Error running notebook:` — this gives the notebook name 5. Search for `Exception encountered at "In [X]":` — this gives the cell number Alternatively, find the run ID with `gh pr checks` or `gh run list`: ```bash # List recent notebook CI runs gh run list --branch chore/pymc6-migrate --workflow "Test Notebooks" --limit 5 # Get error details from a specific run gh run view <run_id> --log --job <job_id> | grep -A10 'Exception encountered' ``` --- ## 6. Pre-Push Checklist Before pushing notebook changes: 1. ☐ Mock runner passes for ALL changed notebooks 2. ☐ If source code changed: real execution passes for affected notebooks 3. ☐ `ruff check --fix <files> && ruff format <files>` on changed files 4. ☐ `ruff check docs/source/notebooks/ --exclude 'docs/source/notebooks/*/dev/*'` passes 5. ☐ `ruff format --check docs/source/notebooks/` shows no reformats needed 6. ☐ `pre-commit run --all-files` passes locally 7. ☐ Push ONCE --- ## 7. Real Execution for Release When re-executing notebooks for a release, use this full workflow per notebook. See `notebook-release-changes.md` for version-specific dependency bumps and API migrations. ### Per-Notebook Workflow ```bash # 1. Pre-flight smoke test (catches code errors fast) uv run python scripts/run_notebooks/runner.py --notebooks docs/source/notebooks/category/my_notebook.ipynb # 2. Clear stale outputs uv run jupyter nbconvert --clear-output docs/source/notebooks/category/my_notebook.ipynb # 3. Real execution with full MCMC sampling uv run jupyter execute docs/source/notebooks/category/my_notebook.ipynb --inplace # 4. Clean up auto-generated checkpoint dirs find docs/source/notebooks/category -name .ipynb_checkpoints -type d -exec rm -rf {} + # 5. Validate (check for errors + pre-commit) uv run python -c " import json with open('docs/source/notebooks/category/my_notebook.ipynb') as f: nb = json.load(f) for i, cell in enumerate(nb['cells']): for out in cell.get('outputs', []): if out.get('output_type') == 'error': print(f'ERROR in cell {i}: {out[\"evalue\"]}') " uv run pre-commit run --files docs/source/notebooks/category/my_notebook.ipynb ``` ### Fast Figure Iteration (Avoid Slow Notebook Re-Executions) Re-executing a full notebook just to tweak a plot is painfully slow (MCMC takes minutes). Instead, isolate the figure code into a standalone script in `sandbox/`: 1. **Extract** the figure cell into `sandbox/test_something.py` 2. **Mock data** with the same shape/dims as the real idata (use `rng = np.random.default_rng(42)` for reproducibility) 3. **Run the script** — no MCMC, no data loading, just the plot code 4. **Iterate** with the user until the output looks right 5. **Apply** the final pattern back to the notebook ```bash # 5-second iteration loop vs 10-minute notebook re-execution uv run python sandbox/test_figure.py ``` Tmp outputs in `sandbox/` are just for feedback, not kept. The point is speed: hours of notebook re-execution becomes seconds of isolated iteration. Used for `plot_forest` (delaxes, labels) and `plot_dist` (CI removal, legends).
Ver en GitHub