Component-dose network meta-analysis with dose-dependent interactions

This vignette shows the basic workflow for cdtmbnma. The example is the sacubitril and valsartan sitting systolic blood-pressure dose plane. Each row is one arm. d_sac and d_val are component dose columns; zero means absent.

library(cdtmbnma)
sv <- sacval_example()
head(sv)
#>   study                    arm d_sac d_val   n      y    sd       se
#> 1  Izzo           Izzo_placebo     0     0  57  -7.00 22.40 2.966952
#> 2  Izzo      Izzo_valsartan320     0   320 143 -16.10 22.20 1.856457
#> 3  Izzo  Izzo_freeSac50_Val320    50   320 132 -19.35 14.62 1.272508
#> 4  Izzo Izzo_freeSac100_Val320   100   320 141 -21.26 14.52 1.222805
#> 5  Izzo Izzo_freeSac200_Val320   200   320 144 -23.64 14.81 1.234167
#> 6  Izzo Izzo_freeSac400_Val320   400   320 143 -20.95 14.73 1.231784
#>                               sd_source
#> 1            imputed (per-study median)
#> 2                    reported/NMA-table
#> 3 derived from LS-mean SE (sensitivity)
#> 4 derived from LS-mean SE (sensitivity)
#> 5 derived from LS-mean SE (sensitivity)
#> 6 derived from LS-mean SE (sensitivity)

Building the single-timepoint design

cdt_data() turns the arm-level table into the model design. Studies with an all-zero arm use that arm as the within-study reference. Studies without an all-zero arm use their lowest-total-dose arm as the baseline and issue a warning.

d <- cdt_data(
  sv,
  study = "study",
  components = c("d_sac", "d_val"),
  outcome = "continuous",
  y = "y",
  se = "se",
  dstar = c(d_sac = 200, d_val = 320)
)
#> Warning: Study 'Cheung' has no all-zero arm; using its lowest-total-dose arm as
#> baseline.
#> Warning: Study 'Huo' has no all-zero arm; using its lowest-total-dose arm as
#> baseline.
#> Warning: Study 'Rakugi' has no all-zero arm; using its lowest-total-dose arm as
#> baseline.
#> Warning: Study 'Ruilope' has no all-zero arm; using its lowest-total-dose arm
#> as baseline.
d
#> <cdt_data>
#>   arms:       18 across 5 studies
#>   components: d_sac, d_val
#>   outcome:    continuous
#>   interaction pairs: d_sac x d_val

Fitting the bilinear interaction model

cdt_fit() compiles the Stan model on first use and samples. The bilinear interaction parameter is interpreted at the dstar corner: here, sacubitril 200 mg and valsartan 320 mg.

fit <- cdt_fit(
  d,
  interaction = "bilinear",
  chains = 4,
  iter_warmup = 1000,
  iter_sampling = 1000,
  seed = 1
)
summary(fit)
#>                     variable       mean         sd       2.5%      97.5%
#> 1                emax: d_sac -10.873789  4.4902883 -21.138062  -3.613413
#> 2                emax: d_val -16.799202  4.4574411 -25.498066  -7.646193
#> 3 interaction: d_sac x d_val   1.668438  2.0804147  -2.356954   5.769118
#> 4                      omega   2.679341  0.7409213   1.477284   4.325007
#> 5                ED50: d_sac  72.694478 55.3874434  10.102404 206.487855
#> 6                ED50: d_val 132.608041 72.6750655  36.047003 312.716574
#>       rhat ess_bulk
#> 1 1.001257 2441.782
#> 2 1.001892 2034.533
#> 3 1.000637 2396.622
#> 4 1.001502 1815.534
#> 5 1.000063 3286.171
#> 6 1.001904 2972.049

The marginal component curves vary one component at a time while holding the other at zero.

plot(fit)
#> Warning: Dropping 'draws_df' class as required metadata was removed.
#> Warning: Dropping 'draws_df' class as required metadata was removed.

Prediction at an unobserved dose pair

predict() returns the relative effect against the all-zero reference at any component-dose combination, including dose pairs not directly observed in a trial.

predict(fit, newdata = data.frame(d_sac = 100, d_val = 160))
#> Warning: Dropping 'draws_df' class as required metadata was removed.
#> Warning: Dropping 'draws_df' class as required metadata was removed.
#> Warning: Dropping 'draws_df' class as required metadata was removed.
#>   d_sac d_val      mean       sd      2.5%     97.5%
#> 1   100   160 -15.36185 2.303096 -19.93087 -10.82761

Additive benchmark

The additive model is fitted by setting interaction = "none".

fit_add <- cdt_fit(d, interaction = "none")

If the loo package is installed, the pointwise log likelihood stored in Stan’s generated quantities can be used for information-criterion comparisons.

loo::loo_compare(
  loo::loo(fit$fit$draws("log_lik")),
  loo::loo(fit_add$fit$draws("log_lik"))
)

Stage-one longitudinal model

The package also contains a narrower two-component longitudinal interface. It is useful when repeated arm means over time are available and an exponential approach-to-asymptote is reasonable. The example below builds a small design but does not fit it during vignette building.

long_dat <- data.frame(
  study = rep("trial1", 6),
  arm = rep(c("placebo", "A", "AB"), each = 2),
  week = rep(c(4, 8), 3),
  dose_A = rep(c(0, 10, 10), each = 2),
  dose_B = rep(c(0, 0, 20), each = 2),
  y = c(0, 0, -1, -2, -2, -3),
  se = rep(1, 6)
)

time_design <- cdt_time_data(
  long_dat,
  study = "study",
  arm = "arm",
  time = "week",
  components = c("dose_A", "dose_B"),
  y = "y",
  se = "se",
  dstar = c(dose_A = 10, dose_B = 20)
)
time_design
#> <cdt_time_data>
#>   observations: 6
#>   arms:         3 across 1 studies
#>   components:   dose_A, dose_B
#>   model:        two-component exponential time-course with bilinear interaction
fit_time <- cdt_time_fit(time_design)
predict(fit_time, data.frame(dose_A = 10, dose_B = 20))