This vignette fits a time-stratified case-crossover model to daily mortality and PM10 in London (2002–2006), using the example dataset from Tobias et al. (2024).
Strata are defined by day-of-week within month and year via
dirichlet_multinom(..., by = "year_month_dow"). PM10 enters
through an integrated Wiener process smooth
(iwp(pm10, ...)), with relative humidity as a linear fixed
effect.
london <- fread(system.file("extdata", "london.csv", package = "hpolcc"))
london$date <- as.Date(ISOdate(london$year, london$month, london$day))
london$year_month_dow <- format(london$date, "%Y-%m-%a")
head(london)## city date year month day dow all tmean rh pm10
## <char> <Date> <int> <int> <int> <int> <int> <num> <num> <num>
## 1: London 2002-01-01 2002 1 1 2 199 -0.22500001 75.6783 71.70000
## 2: London 2002-01-02 2002 1 2 3 231 0.08749995 77.5250 40.20000
## 3: London 2002-01-03 2002 1 3 4 210 0.85000002 81.3250 41.80000
## 4: London 2002-01-04 2002 1 4 5 203 0.53750002 85.4500 50.40000
## 5: London 2002-01-05 2002 1 5 6 224 4.25000000 93.5250 49.36429
## 6: London 2002-01-06 2002 1 6 0 198 7.06874990 96.4000 31.10000
## year_month_dow
## <char>
## 1: 2002-01-Tue
## 2: 2002-01-Wed
## 3: 2002-01-Thu
## 4: 2002-01-Fri
## 5: 2002-01-Sat
## 6: 2002-01-Sun
## 0% 25% 50% 75% 100%
## 8.10000 30.70000 39.72857 50.50000 99.60000
## 0% 25% 50% 75% 100%
## -1.400507 7.506250 11.472748 16.198438 28.174999
london$temp_div10 <- london$tmean / 10
cc_formula <- dirichlet_multinom(
all,
by = "year_month_dow",
init = 0.2,
parscale = 5
) ~ rh + iwp(
pm10,
p = 2,
knots = seq(0, 100, by = 10),
ref_value = 40,
init = 0.001, parscale = 100
) + iwp(
tmean,
p = 3, knots = seq(-2, 30, by = 2),
init = 0.01, parscale = 100,
ref_value = 20
)fit <- hnlm(
formula = cc_formula,
data = london,
config = list(
transform_theta = TRUE,
num_threads = 2L,
num_shards = 50L,
verbose = FALSE
),
control = list(
maxit = 200, trace = 0, REPORT = 1,
factr = 1e3,
pgtol = 1e-8
)
)## hnlm fit
## formula: cc_formula
## n = 420
## log-lik: -5334.4
## converged: yes
## beta:
## label mle
## rh_linear 0.0006173671
## theta:
## label mle
## all_dirichlet_multinom_sd 0.0169254661
## pm10_iwp 0.0001281147
## tmean_iwp 0.0018065549
##
## Call:
## hnlm(formula = cc_formula, data = london, config = list(transform_theta = TRUE,
## num_threads = 2L, num_shards = 50L, verbose = FALSE), control = list(maxit = 200,
## trace = 0, REPORT = 1, factr = 1000, pgtol = 1e-08))
##
## Log-likelihood: -5334
## Observations: 420
## Converged: yes
##
## Parameters:
## label term model mle se
## rh_linear rh linear 0.0006174 0.0002286
## all_dirichlet_multinom_sd all dirichlet_multinom 0.0169300 0.0019800
## pm10_iwp pm10 iwp 0.0001281 0.0002505
## tmean_iwp tmean iwp 0.0018070 0.0007461
par(mfrow = c(1, 2), mar = c(2.25, 2.25, 0, 0), bty = "l", mgp = c(1, 0.5, 0))
for (D in names(fit$sample$sim)) {
the_xlim <- range(london[[D]], na.rm = TRUE)
sample_in_xlim <- fit$sample$sim[[D]][
fit$sample$x[[D]] > min(the_xlim) &
fit$sample$x[[D]] < max(the_xlim),
]
matplot(fit$sample$x[[D]], fit$sample$sim[[D]],
type = "l", lty = 1, col = "#00000010",
xlab = D, ylab = "RR", xaxs = "i", yaxs = "i",
xlim = the_xlim,
ylim = quantile(sample_in_xlim, prob = c(0.005, 0.99))
)
matlines(fit$sample$x[[D]], fit$sample$envelope$common[[D]], lty = c(2, 1, 2), col = "red")
}for_dev = TRUE builds model_data, the
combined ad_fun, and config without running
outer optimization.
config_dev <- list(
transform_theta = TRUE,
num_threads = 2L,
num_shards = 50L,
verbose = FALSE
)
forres <- hnlm(
cc_formula,
data = london,
config = config_dev,
for_dev = TRUE
)
slotNames(forres$ad_fun)## [1] "ptr" "group_sparsity" "outer" "inner"
## [5] "map_outer" "map_inner" "parallel_map" "chol_inner"
## [9] "chol_inner_list" "sizes" "info"
## [1] 420 50
## term model label init lower upper
## 1 rh linear rh_linear 0.000000 -Inf Inf
## 2 all dirichlet_multinom all_dirichlet_multinom_sd -1.609438 -Inf Inf
## 3 pm10 iwp pm10_iwp -6.907755 -20.72327 Inf
## 4 tmean iwp tmean_iwp -4.605170 -20.72327 Inf
## parscale transform
## 1 1 FALSE
## 2 5 TRUE
## 3 100 TRUE
## 4 100 TRUE
bob <- stats::optim(
par = forres$config$opt$init,
fn = adlaplace::outer_fn,
gr = adlaplace::outer_gr,
method = "L-BFGS-B",
control = forres$control,
lower = forres$config$opt$lower,
upper = forres$config$opt$upper,
config = forres$config,
ad_fun = forres$ad_fun,
cache = forres$cache,
control_inner = forres$control_inner,
hessian = TRUE
)## N = 4, M = 5 machine precision = 2.22045e-16
## At X0, 0 variables are exactly at the bounds
## At iterate 0 f= 6888.5 |proj g|= 6687.7
## At iterate 1 f = 6569.7 |proj g|= 6200.3
## At iterate 2 f = 5443.1 |proj g|= 45112
## At iterate 3 f = 5360.5 |proj g|= 7030.4
## At iterate 4 f = 5347.5 |proj g|= 5890
## At iterate 5 f = 5343.7 |proj g|= 2944.3
## At iterate 6 f = 5341.6 |proj g|= 394.61
## At iterate 7 f = 5341.2 |proj g|= 263.86
## At iterate 8 f = 5336.8 |proj g|= 53.006
## At iterate 9 f = 5334.8 |proj g|= 1704.6
## At iterate 10 f = 5334.7 |proj g|= 179.67
## At iterate 11 f = 5334.7 |proj g|= 57.641
## At iterate 12 f = 5334.7 |proj g|= 47.978
## At iterate 13 f = 5334.7 |proj g|= 65.761
## At iterate 14 f = 5334.6 |proj g|= 139.58
## At iterate 15 f = 5334.5 |proj g|= 164.76
## At iterate 16 f = 5334.5 |proj g|= 280.28
## At iterate 17 f = 5334.4 |proj g|= 218.44
## At iterate 18 f = 5334.4 |proj g|= 33.008
## At iterate 19 f = 5334.4 |proj g|= 2.6841
## At iterate 20 f = 5334.4 |proj g|= 9.7068
## At iterate 21 f = 5334.4 |proj g|= 3.0484
## At iterate 22 f = 5334.4 |proj g|= 10.847
##
## iterations 22
## function evaluations 32
## segments explored during Cauchy searches 24
## BFGS updates skipped 0
## active bounds at final generalized Cauchy point 0
## norm of the final projected gradient 10.8475
## final function value 5334.39
##
## F = 5334.39
## final value 5334.389277
## converged
At starting values, evaluate the full log-density and its gradient
over all parameters (beta, inner gamma, and
theta).
sz <- forres$ad_fun@sizes
x_full <- c(
forres$config$opt$init[seq_len(sz["beta"])],
forres$cache$gamma,
forres$config$opt$init[seq(sz["beta"] + 1L, length.out = sz["theta"])]
)
shards <- seq.int(from = 0L, length.out = adlaplace:::n_groups(forres$ad_fun@ptr))
by_shard <- vapply(
shards,
function(s) joint_log_dens(forres$ad_fun, x_full, shards = s),
numeric(1)
)
by_shard[1:5]## [1] -2785.857 -2459.259 -4613.676 -10358.298 -6973.934
## [1] -4845.69226 -4576.26136 -71.39340 -64.47563 266320.89838
## [1] 6729.06
## [1] 6729.06
## [1] -5.892584e+02 5.264055e+01 7.987757e+01 6.527927e-01 1.307021e-01
## [6] 1.049319e+03 5.248790e+02 5.808055e+01 -2.476329e+01 -3.767879e+01
## [11] -1.888044e-01 1.055043e+02 1.216595e+02 1.647132e+02 1.587220e+02
## [16] 1.348805e+02 1.001427e+02 6.204113e+01 3.397821e+01 1.524148e+01
## [21] 3.630802e+00 3.203617e-01 -1.713720e-03 7.487946e+01 2.425810e+01
## [26] 5.833047e+00 6.533312e-01 2.126975e-04 5.135125e+01 1.958144e+02
## [31] 1.344413e+03 9.984609e+00 1.590149e+01
With outer parameters fixed at their starting values,
log_lik_laplace() optimizes random effects and returns the
log-likelihood.
x_outer <- forres$model_data$data$info$parameters$init
lik <- log_lik_laplace(
x = x_outer,
ad_fun = forres$ad_fun,
config = modifyList(forres$config, list(verbose = TRUE)),
gamma = forres$cache$gamma,
control = list(report.level = 2, report.freq = 1),
deriv = TRUE
)## inner_opt: threads = 2, shards = 53, params = 33 (beta = 1, gamma = 29, theta = 3)
## Beginning optimization
##
## iter f nrm_gr status
## 1 6728.943312 13.858325 Continuing
## 2 6728.943305 0.001874 Continuing
## 3 6728.943305 0.000000 Continuing
##
## Iteration has terminated
## 3 6728.943305 0.000000 Success
##
## trace_hinv_t: threads = 2, shards = 53, params = 33
## trace_hinv_t: finished
## inner_opt: finished
## [1] 6888.477
## [1] -480.018802 1337.537165 1.464126 3.223225
## [1] -1.697941e-06 -6.855554e-06 6.603516e-07 -1.536926e-08 -1.994934e-05
## [6] 1.777748e-05
neg_log_lik is the negative profile log-likelihood after
inner optimization over random effects.
Conditional simulations of IWP smooths use
adlaplaceHgp::cond_sim_iwp() with the Laplace output and
model-data bundle:
## [1] "quantiles" "envelope" "x" "gamma" "sim"