Skip to content

Ascertainment Models for Related Observation Processes

Overview

An ascertainment rate is the rate at which an infection results in an observed event. For hospital admissions, this is often called the infection-hospitalization rate (IHR). For emergency department visits, it can be called the infection-to-ED-visit rate (IEDR). For a count observation process, the ascertainment rate scales delayed latent infections to obtain the expected number of observed events:

\[ \mu_s(t) = \alpha_s \sum_{d=0}^{D_s} I(t-d)\,\pi_{s,d} \]

where \(s\) identifies the signal, \(\alpha_s\) is its ascertainment rate and \(\pi_{s,d}\) is its delay distribution.

From a modeling perspective, an ascertainment rate may be considered constant over the observation period or allowed to vary over time. A time-varying ascertainment component combines a scalar baseline rate with a temporal process that describes deviations from that baseline over time as follows:

\(b_s\) is the baseline rate for signal \(s\), \(z_s(t)\) is its temporal deviation, and

\[ \alpha_s(t) = \operatorname{logit}^{-1} \left(\operatorname{logit}(b_s) + z_s(t)\right) \]

Thus, \(b_s\) is the ascertainment rate when the temporal deviation is zero while \(z_s(t)\) determines how the rate changes during the observation period.

We use AscertainmentModel classes to assign ascertainment rates to each signal in a PyRenew model. These components reflect fundamental modeling assumptions about how information is shared across signals and over time:

When a signal’s ascertainment rate has little to do with ascertainment rates for other signals, it can be modeled as statistically independent of other ascertainment rates. PyRenew provides an IndependentAscertainment class. Alternatively, PyRenew allows the user to use a raw RandomVariable to specify any independent ascertainment rate.

Sometimes, however, ascertainment rates for two or more signals in a multi-signal model should be modeled as statistically dependent. For example, hospital admissions and emergency department visits both reflect the degree to which infected individuals need to and are able to obtain care. The two rates might be expected to scale up or down together. In PyRenew, use the JointAscertainment and RatioLinkedAscertainment classes to model dependent ascertainment rates. The JointAscertainment class assigns the baseline rates a joint prior. The RatioLinkedAscertainment expresses one baseline rate relative to another.

Ascertainment components and the builder

An ascertainment component is a model-level component shared by multiple observation processes. Define it and register it with PyrenewBuilder’s add_ascertainment() method before constructing those observation processes. Each observation process then sets its ascertainment_rate_rv using the component’s for_signal() method.

builder = PyrenewBuilder()
builder.configure_latent(...)

ascertainment = JointAscertainment(...)
builder.add_ascertainment(ascertainment)

hospital_obs = PopulationCounts(
    name="hospital",
    ascertainment_rate_rv=ascertainment.for_signal("hospital"),
    ...
)
builder.add_observation(hospital_obs)

ed_obs = PopulationCounts(
    name="ed_visits",
    ascertainment_rate_rv=ascertainment.for_signal("ed_visits"),
    ...
)
builder.add_observation(ed_obs)

During model execution, the registered component supplies the signal-specific rates to the observation processes that reference it. The signal name passed to for_signal() must match a name declared by the ascertainment component. It does not have to match the observation-process name, but matching names usually makes the model specification and output easier to interpret.

Choosing an ascertainment model

All three ascertainment components can produce constant rates or add signal-specific temporal processes. They differ primarily in how the scalar baseline rates are specified and whether relationships are imposed among signals.

Component Baseline Relationship Probability bounds
IndependentAscertainment One RandomVariable per signal No relationship imposed Rates must be valid probabilities
JointAscertainment Logit-scale multivariate normal Prior covariance Inverse logit keeps every rate in (0, 1)
RatioLinkedAscertainment A base rate RandomVariable
and a ratio RandomVariable
Linked rate = base rate x multiplier The derived linked rate is not bounded by construction

Use IndependentAscertainment when a signal or signals have time-varying ascertainment rates that are not related through a joint prior or deterministic relationship.

Use JointAscertainment when prior dependence among baseline rates is naturally represented by a covariance structure on the logit scale.

Use RatioLinkedAscertainment when a relative quantity, such as IHR / IEDR, is scientifically meaningful and can be assigned an interpretable prior.

For constant, unrelated rates that do not otherwise need an ascertainment component, each observation process can instead receive a RandomVariable directly, as described in the next section.

Independent ascertainment rates

The IndependentAscertainment component models a signal baseline in the case where the ascertainment rate is constant, or a signal baseline plus temporal process if the ascertainment rate varies over time. Use IndependentAscertainment when a signal needs a temporal process or when representing the rates through a named model-level component makes the model easier to organize.

For constant rate that is not related to another signal’s rate, an ascertainment component is not required. In this situation, the ascertainment component would only be used once, when specifying the ascertainment_rate_rv on the observation process corresponding to that signal via ascertainment.for_signal() method. Since the ascertainment.for_signal() method returns a RandomVariable accessor, specifying the RandomVariable directly simplifies the model specification:

hospital_obs = PopulationCounts(
    name="hospital",
    ascertainment_rate_rv=DistributionalVariable("ihr", dist.Beta(2, 198)),
    delay_distribution_rv=hosp_delay_rv,
    noise=hosp_noise,
)

For multiple independent signals with constant rates, each observation process can similarly receive its own RandomVariable.

Joint ascertainment

JointAscertainment places a multivariate normal prior on the logit-transformed ascertainment rates:

\[ \boldsymbol{\eta} \sim \operatorname{MultivariateNormal} \left( \operatorname{logit}(\boldsymbol{\alpha}_0), \boldsymbol{\Sigma} \right), \qquad \boldsymbol{\alpha} = \operatorname{logit}^{-1}(\boldsymbol{\eta}). \]

The baseline rates \(\boldsymbol{\alpha}_0\) set the prior location. The covariance matrix \(\boldsymbol{\Sigma}\) controls the scale of variation in each logit-transformed rate and how strongly the rates vary together under the prior. It describes prior dependence, not a causal relationship between the signals.

joint_ascertainment = JointAscertainment(
    name="he_ascertainment",
    signals=("hospital", "ed_visits"),
    baseline_rates=jnp.array([0.01, 0.02]),
    scale_tril=jnp.array(
        [
            [0.7, 0.0],
            [0.35, 0.606],
        ]
    ),
)

The order of signals determines how entries in baseline_rates and the covariance parameter are interpreted. The inverse-logit transformation guarantees that every sampled rate lies between zero and one.

During model execution, this component creates one vector sample site, he_ascertainment_eta, and signal-specific deterministic sites such as he_ascertainment_hospital and he_ascertainment_ed_visits.

Ratio-linked ascertainment

RatioLinkedAscertainment samples a base rate and a ratio of the linked rate to the base rate:

\[ \alpha_{\mathrm{linked}} = \alpha_{\mathrm{base}}\rho, \qquad \rho = \frac{\alpha_{\mathrm{linked}}}{\alpha_{\mathrm{base}}}. \]

For a hospital and ED model, the IEDR can be the base rate and the ratio can represent IHR / IEDR. A ratio below one means that hospitalization is less likely than an ED visit for a given infection; a ratio above one means that it is more likely.

ratio_linked_ascertainment = RatioLinkedAscertainment(
    name="he_ascertainment",
    base_signal="ed_visits",
    linked_signal="hospital",
    base_rate_rv=DistributionalVariable("iedr", dist.Beta(1, 100)),
    ratio_rv=DistributionalVariable(
        "ihr_rel_iedr",
        dist.LogNormal(jnp.log(0.5), 0.35),
    ),
)

Note: These prior parameters are illustrative; they should be chosen using domain knowledge appropriate to the pathogen, population, observation definitions, and study period.

This model does not specify a covariance between the two signal rates directly. However, the signal rates are not independent: the linked rate contains the sampled base rate. The priors are assigned to the base rate and the ratio, and together they induce a distribution for the linked rate.

The ratio may be any positive value. Unlike JointAscertainment, this component does not automatically constrain the derived linked rate to the unit interval. If both rates should be \(<1\) (for instance because both are probabilities), make sure to choose priors that make \(\alpha_{\mathrm{base}}\rho > 1\) negligibly likely.

Time-varying ascertainment

Any ascertainment component can add a temporal process to one or more signals. The temporal process generates the deviation \(z_s(t)\), and the ascertainment component combines it with the signal’s baseline rate. Because this calculation takes the logit of \(b_s\), a time-varying signal requires \(0 < b_s < 1\). A constant ascertainment rate may equal \(0\) or \(1\), but those endpoints cannot be combined with a temporal process because their logits are infinite and thus a finite temporal deviation would leave the rate fixed at the endpoint.

\[ \alpha_s(t) = \operatorname{logit}^{-1} \left( \operatorname{logit}(b_s) + z_s(t) \right). \]

For example, the following component gives the ED ascertainment rate a logit-normal baseline and a weekly AR(1) deviation.

First, define the scalar baseline rate:

ed_baseline_rv = LogitNormalVariable(
    name="p_ed_visit",
    median=0.005,
    scale=0.3,
)

The baseline has median 0.005 on the probability scale. Its scale parameter is the standard deviation on the logit scale.

Next, define a temporal process for deviations from that baseline:

ed_deviation_process = WeeklyTemporalProcess(
    inner=AR1(
        autoreg_rv=DistributionalVariable(
            "autoreg_p_ed_visit",
            dist.Beta(1, 100),
        ),
        innovation_sd_rv=DistributionalVariable(
            "p_ed_visit_w_sd",
            dist.TruncatedNormal(0.0, 0.01, low=0),
        ),
        parameterization="innovation",
    ),
    start_dow=MMWR_WEEK,
)

The AR1 process describes how the deviation evolves from one week to the next. The autoregressive coefficient controls its persistence, and the innovation standard deviation controls the size of weekly changes. WeeklyTemporalProcess aligns those changes with MMWR weeks and expands the weekly values over the model’s daily time axis.

Finally, associate both objects with the ED signal:

ascertainment = IndependentAscertainment(
    name="ed_ascertainment",
    rate_rvs={"ed": ed_baseline_rv},
    temporal_processes={"ed": ed_deviation_process},
)

The keys in rate_rvs and temporal_processes identify the signal to which each object applies. For a constant ascertainment rate, omit the temporal_processes argument. For a component with multiple signals, include entries only for the signals whose rates should vary over time. For example:

# Constant rate
IndependentAscertainment(
    name="ed_ascertainment",
    rate_rvs={"ed": ed_baseline_rv},
)

# Time-varying ED rate
IndependentAscertainment(
    name="ed_ascertainment",
    rate_rvs={"ed": ed_baseline_rv},
    temporal_processes={"ed": ed_deviation_process},
)

Register and connect the component using the same builder pattern as for a constant ascertainment component:

builder.add_ascertainment(ascertainment)

ed_obs = PopulationCounts(
    name="ed",
    ascertainment_rate_rv=ascertainment.for_signal("ed"),
    ...
)

The same pattern applies to temporal processes supplied to JointAscertainment and RatioLinkedAscertainment.

Building a model with an ascertainment component

The complete builder pattern is the same for any ascertainment component - here we use a RatioLinkedAscertainment but the sequence of definitions is the same for JointAscertainment or IndependentAscertainment.

builder = PyrenewBuilder()
builder.configure_latent(
    PopulationInfections,
    gen_int_rv=gen_int_rv,
    I0_rv=I0_rv,
    log_rt_time_0_rv=log_rt_time_0_rv,
    single_rt_process=rt_process,
)

ascertainment = RatioLinkedAscertainment(
    name="he_ascertainment",
    base_signal="ed_visits",
    linked_signal="hospital",
    base_rate_rv=DistributionalVariable("iedr", dist.Beta(1, 100)),
    ratio_rv=DistributionalVariable(
        "ihr_rel_iedr",
        dist.LogNormal(jnp.log(0.5), 0.35),
    ),
)
builder.add_ascertainment(ascertainment)

hospital_obs = PopulationCounts(
    name="hospital",
    ascertainment_rate_rv=ascertainment.for_signal("hospital"),
    delay_distribution_rv=hosp_delay_rv,
    noise=NegativeBinomialNoise(hosp_concentration_rv),
    aggregation="weekly",
    reporting_schedule="regular",
    start_dow=MMWR_WEEK,
)
builder.add_observation(hospital_obs)

ed_obs = PopulationCounts(
    name="ed_visits",
    ascertainment_rate_rv=ascertainment.for_signal("ed_visits"),
    delay_distribution_rv=ed_delay_rv,
    noise=NegativeBinomialNoise(ed_concentration_rv),
    day_of_week_rv=ed_day_of_week_rv,
)
builder.add_observation(ed_obs)

model = builder.build()

Shared assumptions and limitations

  • Ascertainment models produce one scalar baseline rate per signal.
  • Supplying temporal_processes adds signal-specific deviations to selected baselines to model time-varying ascertainment rates.
  • For JointAscertainment and RatioLinkedAscertainment models, the temporal deviations do not preserve the baseline covariance or ratio pointwise over time.

The choice of ascertainment model should consider:

  • whether the priors keep all rates, including derived linked rates, within scientifically plausible ranges
  • whether treating ascertainment rates as constant over the model period is scientifically reasonable
  • whether daily, stepwise, or calendar-week deviations match the scientific question
  • whether the rates should be related at all
  • whether a covariance structure or a relative multiplier is easier to justify

Ascertainment rates are generally difficult to distinguish from the scale of latent infections using surveillance counts alone. Flexible infection and ascertainment trajectories can be weakly identified without informative priors, external information, or strong structural assumptions. Use time variation only when the available information can support it, and assess sensitivity to both baseline and temporal-process priors.