Dose-escalation cap natural-history treatment policy

Description

Creates a natural-history modified treatment policy (G-LMTP) for an ordered / ordinal / count treatment (a dose) that caps the natural per-period increase at delta (Diaz, Williams, Morzywolek & Rudolph 2026, the dose-escalation example). Comparing the natural dose at \(t\) with the natural dose at \(t-1\) (both under the regime),

A^d_t = \[\begin{cases} A_{t-1} + \delta & A_t - A_{t-1} > \delta \\ A_t & \text{otherwise,}\end{cases}\]

so a patient may follow their natural dose unless it would jump by more than delta in one period, in which case the increase is capped. Because the cap reads the natural dose at the previous period (the natural-value history), it is a genuine G-LMTP and is estimated by the augmented engine, not dynamic().

For an ordered dose, enter the treatment flexibly via treatment_form = ~ factor(dose) (or ~ splines::ns(dose, df)) in causat() so a kinked capped-dose response is not misspecified; with the default bare-numeric treatment term the per-step models fit the kink through a single slope and the plug-in carries a small asymptotic bias when the cap binds.

Intended for a discrete ordered exposure where A_{t-1} + delta stays within the observed support (e.g. integer dose levels with an integer delta); the per-label outcome models predict at the capped value, so a capped dose far outside the support would extrapolate.

Usage

cap_escalation(delta = 1, budget = 1024L)

Arguments

delta Positive numeric. Maximum allowed per-period increase in the natural dose. Default 1.
budget Positive integer. Worst-step enumeration budget, as in grace_period(). Default 1024.

Value

A causatr_glmtp object (also inheriting causatr_intervention).

References

Diaz I, Williams NT, Morzywolek P, Rudolph KE (2026). Modified treatment policies that depend on the natural history of treatment. arXiv:2605.24167.

See Also

grace_period(), carry_forward(), contrast()

Other glmtp: carry_forward(), grace_period()

Examples

library("causatr")

# The policy object is a fit-free constructor (runnable on its own):
cap_escalation(delta = 1)

# A small longitudinal ordinal-dose dataset (dose in {0, 1, 2}; outcome at the
# final period). Enter the dose flexibly (`~ factor(dose)`) so the kinked
# capped response is not misspecified, then cap natural increases at 1/period.
set.seed(1)
n <- 400L
tau <- 3L
dose_data <- data.frame(
  id = rep(seq_len(n), each = tau),
  t = rep(seq_len(tau), times = n),
  L0 = rep(rnorm(n), each = tau),
  L = rnorm(n * tau)
)
dose_data$dose <- pmin(2L, rpois(n * tau, lambda = 0.8))
dose_data$Y <- ifelse(
  dose_data$t == tau,
  1 + 0.7 * dose_data$dose + 0.4 * dose_data$L + rnorm(n * tau),
  NA_real_
)
fit <- causat(dose_data, outcome = "Y", treatment = "dose",
              confounders = ~ L0, confounders_tv = ~ L,
              id = "id", time = "t", estimator = "gcomp",
              treatment_form = ~ factor(dose))
contrast(fit, interventions = list(
  cap = cap_escalation(1),
  natural = NULL
), ci_method = "bootstrap")