-
Notifications
You must be signed in to change notification settings - Fork 9
Description
Day-of-week effects in the count observation process
Day-of-week effects are a multiplicative adjustment to predicted counts, applied after the delay convolution and before the noise model. Adding this to the observation process extends the pipeline:
infections -> [delay convolution] -> delayed_incidence
delayed_incidence * ascertainment * day_of_week_effect -> expected_counts
expected_counts -> [noise] -> observations
Convolution must come first. Ascertainment and day-of-week effects are both element-wise multipliers on the convolved signal, so their relative order is interchangeable. The net effect is to impose a periodic weekly pattern on the expected value of the likelihood.
Interface Change to Counts
Add one optional parameter to the Counts constructor:
class Counts(_CountBase):
def __init__(
self,
name: str,
ascertainment_rate_rv: RandomVariable,
delay_distribution_rv: RandomVariable,
noise: CountNoise,
day_of_week_rv: RandomVariable | None = None, # NEW
) -> None: ...day_of_week_rv is any RandomVariable that samples to a 1D array of shape (7,) summing to 7.0 — one multiplicative effect per day of the week. The sum-to-7 constraint preserves weekly totals. When None, no day-of-week adjustment is applied (the current default behavior).
Data Interface
When day-of-week effects are configured, sample() accepts a required first_day_dow:
# At model.run() time:
ed_visits={
"obs": model.pad_observations(ed_counts),
"first_day_dow": 2, # timeseries starts on Wednesday (0=Mon, 6=Sun)
}The offset is data, not a model parameter — it depends on when the timeseries begins. Setting it to None (or omitting it when day_of_week_rv is None) disables the adjustment.
Implementation
The core logic uses the existing tile_until_n utility from pyrenew.arrayutils:
def compute_day_of_week_effect(
dow_effect: ArrayLike,
n_timepoints: int,
first_day_dow: int,
) -> ArrayLike:
"""
Tile a 7-element day-of-week effect vector across a timeseries.
Parameters
----------
dow_effect : ArrayLike
Day-of-week multiplicative effects, shape (7,).
Entry j is the multiplier for day-of-week j (0=Monday, 6=Sunday).
Should sum to 7.0 to preserve weekly totals.
n_timepoints : int
Length of the time series.
first_day_dow : int
Day of the week for the first timepoint (0=Monday, 6=Sunday).
Returns
-------
ArrayLike
Shape (n_timepoints,). Periodic multiplicative adjustments.
"""
return tile_until_n(dow_effect, n_timepoints, offset=first_day_dow)Inside Counts._predicted_obs(), after the delay convolution:
predicted = ascertainment * convolve(infections, delay_pmf)
if self.day_of_week_rv is not None and first_day_dow is not None:
dow_effect = self.day_of_week_rv()
daily_effect = compute_day_of_week_effect(
dow_effect, predicted.shape[0], first_day_dow
)
predicted = predicted * daily_effectWhy This Works for Epidemiologists
The 7-element effect vector is the single knob that controls the day-of-week adjustment. An epidemiologist can:
-
Use a fixed effect from external data:
day_of_week_rv = DeterministicVariable("dow", empirical_dow_effects)
-
Infer the effect by placing a Dirichlet prior scaled to sum to 7:
day_of_week_rv = TransformedVariable( "dow_effect", DistributionalVariable( "dow_raw", dist.Dirichlet(jnp.array([5, 5, 5, 5, 5, 2, 2])) ), transform=lambda x: x * 7, )
-
Disable it by omitting the parameter entirely (the default).
-
Compare models by fitting with and without day-of-week effects and comparing ELPD/WAIC.
Reference Implementation
PyRenew already has the building blocks for this:
pyrenew.arrayutils.tile_until_n()— tiles a short array to lengthn_timepointswith an offset, used bypyrenew.process.periodiceffect.DayOfWeekEffectdocs/tutorials/random_variables.qmd— demonstratesAscertainmentWithDayOfWeek, a customRandomVariablethat bundles baseline ascertainment with a Dirichlet day-of-week effect, applies it after delay convolution, and shows the weekly reporting patternpyrenew-multisignalHEW model (EDVisitObservationProcessinhew/model.py:418-424) — usestile_until_n(ed_wday_effect_raw, n)to multiply the convolved signal by a periodic day-of-week effect
The goal is to bring this capability into the Counts class as a clean, optional, composable parameter — matching the pattern established by right_truncation_rv in #702.
Builder Usage Example
With this feature, specifying day-of-week effects in the pyrenew-multisignal ED visit model via the builder:
from pyrenew.deterministic import DeterministicPMF, DeterministicVariable
from pyrenew.randomvariable import TransformedVariable, DistributionalVariable
from pyrenew.observation import Counts, NegativeBinomialNoise
from pyrenew.model import PyrenewBuilder
# --- ED Visit observation process with day-of-week ---
ed_obs = Counts(
name="ed_visits",
ascertainment_rate_rv=DeterministicVariable("ier", 0.005),
delay_distribution_rv=DeterministicPMF("inf_to_ed", inf_to_ed_pmf),
noise=NegativeBinomialNoise(
DeterministicVariable("ed_concentration", 20.0)
),
day_of_week_rv=TransformedVariable(
"dow_effect",
DistributionalVariable(
"dow_raw", dist.Dirichlet(jnp.array([5, 5, 5, 5, 5, 2, 2]))
),
transform=lambda x: x * 7,
),
)
# --- Build model ---
builder = PyrenewBuilder()
builder.configure_latent(...)
builder.add_observation(ed_obs)
model = builder.build()
# --- Fit (with day-of-week) ---
model.run(
...,
ed_visits={
"obs": model.pad_observations(ed_counts),
"first_day_dow": 2, # data starts on Wednesday
},
)
# --- Forecast (day-of-week still applies) ---
model.predict(
...,
ed_visits={
"first_day_dow": 2, # same offset for forecast continuation
},
)The observation process definition is ~10 lines. The day-of-week behavior is explicit, visible at construction time, and controlled by a single data parameter at run time.
Scope
This design covers:
- Adding
day_of_week_rvas an optional parameter toCountsandCountsBySubpop - The
compute_day_of_week_effectutility function (wrappingtile_until_n) - Passing
first_day_dowas runtime data
This design does NOT cover (separate work):
- Right truncation (Handling right-truncated observation process count data #702)
- Time-varying ascertainment (AR process on ascertainment rate)
- Weekly aggregation
- These are orthogonal features that compose independently