Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
680bb1e
Merge branch 'main' of https://github.com/CDCgov/PyRenew
cdc-mitzimorris Sep 15, 2025
2cb876b
update
cdc-mitzimorris Sep 18, 2025
60db8df
Merge branch 'main' of github-bf06:CDCgov/PyRenew
cdc-mitzimorris Sep 22, 2025
32a5314
Merge branch 'main' of github-bf06:CDCgov/PyRenew
cdc-mitzimorris Oct 5, 2025
d6213f2
Merge branch 'main' of github-bf06:CDCgov/PyRenew
cdc-mitzimorris Oct 8, 2025
96f27c9
Merge branch 'main' of github-bf06:CDCgov/PyRenew
cdc-mitzimorris Nov 17, 2025
1cb6fa2
Merge branch 'main' of github-bf06:CDCgov/PyRenew
cdc-mitzimorris Nov 24, 2025
f62e1e4
Merge branch 'main' of github-bf06:CDCgov/PyRenew
cdc-mitzimorris Dec 4, 2025
0c6785d
Merge branch 'main' of github-bf06:CDCgov/PyRenew
cdc-mitzimorris Dec 22, 2025
1ee62b9
Merge branch 'main' of github-bf06:CDCgov/PyRenew
cdc-mitzimorris Jan 29, 2026
0629461
Merge branch 'main' of github-bf06:CDCgov/PyRenew
cdc-mitzimorris Feb 4, 2026
efeadee
Merge branch 'main' of github-bf06:CDCgov/PyRenew
cdc-mitzimorris Feb 5, 2026
371ba98
Merge branch 'main' of github-bf06:CDCgov/PyRenew
cdc-mitzimorris Feb 5, 2026
0304bed
Merge branch 'main' of github-bf06:CDCgov/PyRenew
cdc-mitzimorris Feb 6, 2026
ffeea65
Merge branch 'main' of github-bf06:CDCgov/PyRenew
cdc-mitzimorris Feb 9, 2026
50e7261
Merge branch 'main' of github-bf06:CDCgov/PyRenew
cdc-mitzimorris Feb 9, 2026
dae6af8
Merge branch 'main' of github-bf06:CDCgov/PyRenew
cdc-mitzimorris Feb 10, 2026
5cb3097
Merge branch 'main' of github-bf06:CDCgov/PyRenew
cdc-mitzimorris Feb 11, 2026
1d80ccc
Merge branch 'main' of github-bf06:CDCgov/PyRenew
cdc-mitzimorris Feb 11, 2026
e73b401
Merge branch 'main' of github-bf06:CDCgov/PyRenew
cdc-mitzimorris Feb 12, 2026
b1473b5
Merge branch 'main' of github-bf06:CDCgov/PyRenew
cdc-mitzimorris Feb 18, 2026
0b929b5
Merge branch 'main' of github-bf06:CDCgov/PyRenew
cdc-mitzimorris Feb 18, 2026
3ee00a7
Merge branch 'main' of github-bf06:CDCgov/PyRenew
cdc-mitzimorris Feb 24, 2026
307982a
Merge branch 'main' of github-bf06:CDCgov/PyRenew
cdc-mitzimorris Feb 24, 2026
b862bc6
Merge branch 'main' of github-bf06:CDCgov/PyRenew
cdc-mitzimorris Feb 26, 2026
2c665a5
Merge branch 'main' of github-bf06:CDCgov/PyRenew
cdc-mitzimorris Mar 11, 2026
60d6458
Merge branch 'main' of github-bf06:CDCgov/PyRenew
cdc-mitzimorris Mar 12, 2026
ec8c464
Merge branch 'main' of github-bf06:CDCgov/PyRenew
cdc-mitzimorris Mar 19, 2026
c018bf7
Merge branch 'main' of github-bf06:CDCgov/PyRenew
cdc-mitzimorris Mar 24, 2026
d0207dd
Merge branch 'main' of github-bf06:CDCgov/PyRenew
cdc-mitzimorris Apr 4, 2026
f3c706a
Merge branch 'main' of github-bf06:CDCgov/PyRenew
cdc-mitzimorris Apr 9, 2026
684c6c5
Merge branch 'main' of github-bf06:CDCgov/PyRenew
cdc-mitzimorris Apr 10, 2026
ca2454f
Merge branch 'main' of github-bf06:CDCgov/PyRenew
cdc-mitzimorris Apr 13, 2026
0f38afc
merge
cdc-mitzimorris Apr 14, 2026
d8e7a57
Merge branch 'main' of github-bf06:CDCgov/PyRenew
cdc-mitzimorris Apr 16, 2026
084c4e7
updated observation processes for aggregated count data; unit tests p…
cdc-mitzimorris Apr 21, 2026
4065842
add stepwise temporal process and unit tests
cdc-mitzimorris Apr 21, 2026
f06d98d
updated builder, added integration test for agg hosp data, all unit t…
cdc-mitzimorris Apr 21, 2026
ae61442
integration test
cdc-mitzimorris Apr 21, 2026
613c325
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 21, 2026
eb3bc70
changes per co-pilot code review
cdc-mitzimorris Apr 22, 2026
d9c6ea6
Merge branch 'mem_789_temporal_aggregation' of github-bf06:CDCgov/PyR…
cdc-mitzimorris Apr 22, 2026
b23be72
changes per co-pilot code review
cdc-mitzimorris Apr 22, 2026
c715e46
more unit tests
cdc-mitzimorris Apr 22, 2026
21498c5
builder allows weekly or daily temporal processes
cdc-mitzimorris Apr 22, 2026
46f1ea0
code cleanup
cdc-mitzimorris Apr 23, 2026
e70c86d
refactor to WeekCycle
cdc-mitzimorris Apr 23, 2026
bfc23b4
simplify model sample/run method - specify obs data start date
cdc-mitzimorris Apr 23, 2026
529c494
fix tutorial
cdc-mitzimorris Apr 23, 2026
7a67e8f
changes per copilot review
cdc-mitzimorris Apr 23, 2026
5527261
fix logic on obs_start_date for calendar alignment
cdc-mitzimorris Apr 24, 2026
72819b0
changes per docs review
cdc-mitzimorris Apr 24, 2026
469394a
changes per code review
cdc-mitzimorris Apr 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions _typos.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
# words that should not be corrected
arange = "arange"
lod = "lod"
dows = "dows"
96 changes: 84 additions & 12 deletions docs/tutorials/building_multisignal_models.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import plotnine as p9
import pandas as pd
import time
import warnings
from datetime import date

warnings.filterwarnings("ignore")

Expand Down Expand Up @@ -67,6 +68,7 @@ from pyrenew.latent import (
SubpopulationInfections,
AR1,
RandomWalk,
StepwiseTemporalProcess,
GammaGroupSdPrior,
HierarchicalNormalPrior,
)
Expand All @@ -79,6 +81,7 @@ from pyrenew.observation import (
MeasurementNoise,
NegativeBinomialNoise,
)
from pyrenew.time import MMWR_WEEK
```

## Overview
Expand Down Expand Up @@ -237,6 +240,42 @@ baseline_rt_process = AR1(autoreg=0.9, innovation_sd=0.05)
subpop_rt_deviation_process = RandomWalk(innovation_sd=0.025)
```

### Choosing the Rt Parameter Cadence

The renewal equation is evaluated on the model's daily time axis, but the temporal process for $\mathcal{R}(t)$ does not have to sample a new parameter every day.
This separates three model choices:

- **Parameter cadence**: how often the $\mathcal{R}(t)$ temporal process samples a new latent value
- **Model time axis**: the daily axis used by the renewal equation and delay convolutions
- **Observation cadence**: the temporal granularity for each signal, such as daily ED visits or weekly hospital admissions

The AR(1) process above samples one value per model day:

```python
baseline_rt_process = AR1(autoreg=0.9, innovation_sd=0.05)
```

To use a weekly $\mathcal{R}(t)$ while still running the renewal equation daily, wrap the temporal process in `StepwiseTemporalProcess`.
The wrapper samples a coarse trajectory and broadcasts it to the daily model axis before the latent infection process uses it.

```python
weekly_baseline_rt_process = StepwiseTemporalProcess(
inner=AR1(autoreg=0.9, innovation_sd=0.05),
step_size=7,
alignment="calendar_week",
week=MMWR_WEEK,
)
```

Use `alignment="calendar_week"` when the weekly Rt blocks should align to a calendar week.
PyRenew provides class `WeekCycle` which identifies the start day of a calendar week by ISO convention, e.g. `0 == Monday`, `6 == Sunday`
and two pre-defined instances: `MMWR_WEEK: WeekCycle = WeekCycle(start_dow=6)` and `ISO_WEEK: WeekCycle = WeekCycle(start_dow=0)`.
`pyrenew.time` exports `MMWR_WEEK` (Sunday-Saturday epiweeks) and `ISO_WEEK` (Monday-Sunday); use `WeekCycle(start_dow=k)` for any other convention.
At sample or run time, pass the date of the first observation day as `obs_start_date`.
Argument `obs_start_date` is required whenever any component performs calendar-aligned work: a latent temporal process with `alignment="calendar_week"`, a count observation with `aggregation="weekly"`, or any observation with a day-of-week effect.
For `alignment="model_index"` and daily observations with no day-of-week effect, `obs_start_date` can be omitted.
The model handles the calendar bookkeeping and forwards the day-of-week information to every component that needs it.

## Observation Processes

Observation processes transform latent infections into observable signals and define the statistical model linking predictions to data. Each observation process:
Expand Down Expand Up @@ -268,7 +307,7 @@ for the first 10 months of 2023 (as reported to the CDC).
# | label: load-hospital-data
# Load daily hospital admissions for California
ca_hosp_data = datasets.load_hospital_data_for_state("CA", "2023-11-06.csv")

obs_start_date = ca_hosp_data["dates"][0]
hosp_admits = ca_hosp_data["daily_admits"]
population_size = ca_hosp_data["population"]
n_hosp_days = ca_hosp_data["n_days"]
Expand Down Expand Up @@ -658,18 +697,31 @@ model.run(
n_days_post_init=n_days,
population_size=population_size,
subpop_fractions=subpop_fractions,
hospital={"obs": model.pad_observations(hosp_counts)},
wastewater={
"obs": ww_conc,
"times": model.shift_times(ww_times),
"subpop_indices": ww_subpop_indices,
"sensor_indices": ww_sensor_indices,
"n_sensors": n_ww_sensors,
},
**obs_data,
)
samples = model.mcmc.get_samples()
```

where `**obs_data` is a data dictionary which supplies the observation start date as `obs_start_date` and a data dictionary for each set of signal data, where the name of the data dictionary corresponds to the signal name registered on the builder.
For this example, such a data dictionary would have the following structure:

```python
{
"obs_start_date": ...
"hospital": {
"obs": ...
},
"wastewater": {
"obs": ...
"times": ...
"subpop_indices": ...
"sensor_indices": ...
"n_sensors": ...
},
}
```

Comment thread
cdc-mitzimorris marked this conversation as resolved.

## Running the Model

First we declare the population structure. We have 6 subpopulations, where 5 have wastewater monitoring and 1 does not. The subpopulations with wastewater monitoring need not be contiguous indices—they could be any subset of {0, 1, ..., n_subpops-1}.
Expand Down Expand Up @@ -699,11 +751,18 @@ print(f"Total population: {float(jnp.sum(subpop_fractions)):.0%}")
```

We define a function to prepare observation data using the model's helper methods to align with the shared time axis.
The returned dictionary is structured to match the keyword arguments of `model.run()`: `obs_start_date` at the top level, plus one sub-dictionary per observation process, keyed by the name registered on the builder (`hospital`, `wastewater`).
At call time the returned dict is unpacked with `**` (for example, `**obs_data_90` in the 90-day fit below), forwarding each entry as a keyword argument to `model.run()`.

```{python}
# | label: prepare-observation-data
def prepare_observation_data(
model, n_days_fit, hosp_admits, ww_data, ww_monitored_subpops
model,
n_days_fit,
obs_start_date,
hosp_admits,
ww_data,
ww_monitored_subpops,
):
"""
Prepare observation data for fitting.
Expand All @@ -717,6 +776,8 @@ def prepare_observation_data(
The model (provides padding/shifting helpers)
n_days_fit : int
Number of days to include in fit
obs_start_date : date
Date of first observation
hosp_admits : array
Hospital admissions time series
ww_data : dict
Expand Down Expand Up @@ -749,6 +810,7 @@ def prepare_observation_data(
)

return {
"obs_start_date": obs_start_date,
"hospital": {
"obs": hosp_obs,
},
Expand All @@ -774,7 +836,12 @@ jax.clear_caches()

n_days_90 = 90
obs_data_90 = prepare_observation_data(
model, n_days_90, hosp_admits, ca_ww_data, ww_monitored_subpops
model,
n_days_90,
obs_start_date,
hosp_admits,
ca_ww_data,
ww_monitored_subpops,
)

print(f"Fitting model with {n_days_90} days of data...")
Expand Down Expand Up @@ -939,7 +1006,12 @@ jax.clear_caches()

n_days_180 = 180
obs_data_180 = prepare_observation_data(
model, n_days_180, hosp_admits, ca_ww_data, ww_monitored_subpops
model,
n_days_180,
obs_start_date,
hosp_admits,
ca_ww_data,
ww_monitored_subpops,
)

print(f"Fitting model with {n_days_180} days of data...")
Expand Down
4 changes: 2 additions & 2 deletions docs/tutorials/day_of_week_effects.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -257,8 +257,8 @@ offset_df["offset"] = pd.Categorical(
The two curves have the same shape but are phase-shifted: their weekend dips fall on different days.
Getting `first_day_dow` right matters — a misaligned offset would attribute Monday's high to Sunday or vice versa.

When using `MultiSignalModel`, the shared time axis starts `n_init` days before the first observation.
The convenience method `model.compute_first_day_dow(obs_start_dow)` converts the known day of the week of the first observation to the correct offset for element 0 of the time axis.
When using `MultiSignalModel`, pass `obs_start_date` (the date of the first observation day) to `model.sample()` or `model.run()`.
The model handles the calendar bookkeeping and forwards the day-of-week information to every component that needs it.

## Sampled observations

Expand Down
5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ known_first_party = ["pyrenew", "test"]
[tool.deptry.per_rule_ignores]
DEP004 = ["arviz", "pytest", "scipy", "bs4"]

[tool.pytest.ini_options]
markers = [
"integration: integration tests that fit models via MCMC (deselect with '-m \"not integration\"')",
]

[tool.ruff]
fix = true

Expand Down
22 changes: 22 additions & 0 deletions pyrenew/arrayutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,28 @@ def __repr__(self) -> str:
return f"PeriodicProcessSample(value={self})"


def require_shape(arr: ArrayLike, expected: tuple[int, ...], label: str) -> None:
Comment thread
cdc-mitzimorris marked this conversation as resolved.
"""
Validate that ``arr.shape`` equals ``expected``.

Parameters
----------
arr
Array whose shape is being checked.
expected
Required shape.
label
Name of the producing component, used in the error message.

Raises
------
ValueError
If ``arr.shape`` does not equal ``expected``.
"""
if arr.shape != expected:
raise ValueError(f"{label} must return shape {expected}; got {arr.shape}")


def tile_until_n(
data: ArrayLike,
n_timepoints: int,
Expand Down
Loading
Loading