Package 'exnexSurv'

Title: Bayesian EXNEX Models for Survival Analysis in Basket Trials
Description: Implements the Bayesian Exchangeable Non-Exchangeable (EXNEX) framework for right-censored log-normal survival data in basket trials. Based on 'Rcpp' and 'RcppArmadillo', the package provides a fast Gibbs sampler supporting EXNEX, complete pooling, and no pooling models to facilitate methodological comparisons and simulation studies.
Authors: Victor Ney [aut, cre]
Maintainer: Victor Ney <[email protected]>
License: MIT + file LICENSE
Version: 1.4.0
Built: 2026-10-13 08:14:38 UTC
Source: https://github.com/victorney/exnexsurv

Help Index


Compare multiple pooling_surv fits by WAIC

Description

Computes [compute_waic()] for each supplied fit and reports them in a single 'data.frame', sorted by ascending WAIC.

Usage

compare_waic(..., digit = 2)

Arguments

...

Two or more fitted 'pooling_surv' objects.

digit

Number of decimal places for the reported statistics.

Details

Fits are labelled with the variable names used in the call when supplied unnamed (e.g. 'fit_exnex', 'fit_complete'), or by the names given to '...'; when even those are unavailable, the fitted 'pooling' variant is used, with a final 'fit1'/'fit2' fallback for duplicate or anonymous labels.

Value

A 'data.frame' with one row per model and columns 'model', 'waic', 'se_elpd_waic', 'lpd', 'p_waic', 'elpd_waic'.


Pointwise log-likelihood and WAIC from an pooling_surv fit

Description

Computes the Watanabe-Akaike Information Criterion (WAIC) for a fitted 'pooling_surv' model. WAIC is a fully Bayesian information criterion that uses the posterior draws of the log-likelihood and adds a penalty for effective number of parameters pwaicp_{waic}.

Usage

compute_waic(fit, ...)

Arguments

fit

A fitted 'pooling_surv' object.

...

Unused.

Details

For each observation ii, with posterior draws indexed by s=1,…,Ss=1,\ldots,S, we compute the pointwise log-likelihood log⁡p(yi∣θ(s))\log p(y_i \mid \theta^{(s)}), where the log-normal AFT model gives for an observed event (δi=1\delta_i=1)

log⁡p(yi∣θ(s))=log⁡ϕ ⁣(log⁡ti−ηi(s)σ(s))−log⁡(σ(s)ti),\log p(y_i \mid \theta^{(s)}) = \log\phi\!\left(\frac{\log t_i-\eta_i^{(s)}}{\sigma^{(s)}}\right) - \log(\sigma^{(s)} t_i),

and for a censored observation (δi=0\delta_i=0)

log⁡p(yi∣θ(s))=log⁡ ⁣[1−Φ ⁣(log⁡ti−ηi(s)σ(s))],\log p(y_i \mid \theta^{(s)}) = \log\!\left[1-\Phi\!\left(\frac{\log t_i-\eta_i^{(s)}}{\sigma^{(s)}}\right)\right],

where ηi(s)\eta_i^{(s)} is the linear predictor built from the draws.

WAIC is then

lpd=∑ilog⁡ ⁣(1S∑sexp⁡log⁡p(yi∣θ(s))),lpd = \sum_i \log\!\left(\frac{1}{S}\sum_s \exp\log p(y_i\mid\theta^{(s)})\right),

pwaic=∑ivars(log⁡p(yi∣θ(s))),p_{waic} = \sum_i \mathrm{var}_s\big(\log p(y_i\mid\theta^{(s)})\big),

WAIC=−2(lpd−pwaic).\mathrm{WAIC} = -2(lpd - p_{waic}).

Rows with extreme leverage ('p_waic' above a large threshold) may flag issues; the function returns a 'pointwise' matrix so users can investigate.

Value

A named list with elements 'waic', 'se_elpd_waic', 'lpd', 'p_waic', 'elpd_waic', and 'pointwise' (an 'n' by 3 matrix with columns 'lpd', 'p_waic', 'waic').


Convergence diagnostics (R-hat and effective sample size)

Description

Computes R-hat, bulk effective sample size (ESS), and tail ESS for every parameter of a fitted 'pooling_surv' object, using the 'posterior' package. All three follow the definitions in Vehtari, Gelman, Simpson, Carpenter & Buerkner (2021, "Rank-normalization, folding, and localization"): the R-hat is split and rank-normalized, bulk ESS is computed on rank-normalized draws, and tail ESS quantifies sampling adequacy in the 5 diagnostic is computed (so 'chains = 1' still gets a meaningful R-hat).

Usage

convergence_diagnostics(fit, ...)

Arguments

fit

A fitted 'pooling_surv' object (result of 'pooling_surv()').

...

Unused.

Details

Rules of thumb: R-hat close to 1.0 (say < 1.01) and ESS in the hundreds give reasonably stable summaries. Low ESS with a good R-hat means the chain is slow to move but honest; both R-hat above ~1.01 and very low ESS (e.g. below 100) are worth investigating before trusting the posterior.

Value

A data frame with one row per parameter and columns 'parameter', 'rhat', 'ess_bulk', and 'ess_tail'. All values are 'NA' if the 'posterior' package is not installed.

Examples

set.seed(2719)
trial_data <- simulate_data(n = 90, K = 3, censoring_rate = 0.3)
fit <- pooling_surv(survival::Surv(time, event) ~ group,
                    data = trial_data,
                    iter = 300, warmup = 150, chains = 2)
convergence_diagnostics(fit)

Deprecated alias for EXNEX fitting

Description

'exnex_surv()' is deprecated in favour of [pooling_surv()]. It emits a one-line deprecation message and forces 'pooling = "exnex"', so existing calls continue to fit the EXNEX hierarchy unchanged.

Usage

exnex_surv(x, ...)

Arguments

x

An object containing the predictors (subgroup assignment). Can be a data frame or a formula.

...

Additional arguments passed to methods.

Value

An object of class 'pooling_surv', identical to 'pooling_surv(..., pooling = "exnex")'.

Examples

## Not run: 
fit <- exnex_surv(survival::Surv(time, event) ~ group, data = d)

## End(Not run)

Posterior median survival time

Description

For the log-normal AFT model the median survival time for a linear predictor η\eta is tmed=exp⁡(η)t_{med} = \exp(\eta), since S(t)=0.5S(t)=0.5 when log⁡t=η\log t = \eta. Posterior draws of θ\theta and β\beta therefore induce a posterior distribution of tmedt_{med} whose quantiles are reported.

Usage

median_survival(fit, newdata = NULL, level = 0.95, ...)

Arguments

fit

A fitted 'pooling_surv' object.

newdata

Optional data frame (one row gives one median).

level

Credible-interval level (default '0.95').

...

Unused.

Value

A 'data.frame' with columns 'group', 'median', 'lower', 'upper'.


Plot parameter traces from an pooling_surv fit

Description

Generates one bayesplot traceplot per parameter.

Usage

## S3 method for class 'pooling_surv'
plot(x, parameters = NULL, ask = interactive(), ...)

Arguments

x

A fitted 'pooling_surv' object.

parameters

Optional character vector of parameter names to plot.

ask

Should R pause between plots? Defaults to 'interactive()'.

...

Unused.

Value

Invisibly returns 'x'.


Plot survival curves from an pooling_surv fit

Description

Draws the posterior median and credible band of the survival function for each group. Requires 'ggplot2'.

Usage

## S3 method for class 'survival_exnex'
plot(x, ...)

Arguments

x

An object of class 'survival_exnex' returned by [survival_curves()].

...

Unused.

Value

Invisibly returns 'x'.


Fit Bayesian Pooling Survival Models

Description

Fits Bayesian models for right-censored log-normal survival data in basket trials using a data-augmented Gibbs sampler. The 'pooling' argument selects the structural assumption on the basket effects:

Usage

pooling_surv(x, ...)

## Default S3 method:
pooling_surv(x, ...)

## S3 method for class 'formula'
pooling_surv(
  formula,
  data,
  pooling = "exnex",
  priors = list(),
  iter = 2000,
  warmup = 1000,
  thin = 1,
  chains = 1,
  parallel_chains = FALSE,
  group_col = NULL,
  seed = NULL,
  verbose = TRUE,
  ...
)

## S3 method for class 'data.frame'
pooling_surv(
  x,
  y,
  pooling = "exnex",
  priors = list(),
  iter = 2000,
  warmup = 1000,
  thin = 1,
  chains = 1,
  parallel_chains = FALSE,
  group_col = NULL,
  seed = NULL,
  verbose = TRUE,
  ...
)

Arguments

x

An object containing the predictors (subgroup assignment). Can be a data frame or a formula.

...

Additional arguments passed to methods.

formula

A model formula with structure: 'Surv(time, event) ~ group + covariates'. The first RHS variable is the group/basket assignment. Additional variables are covariates.

data

A data frame containing the variables in the formula.

pooling

Pooling mode for the basket effects: one of "exnex" (default), "complete", or "none" (see Description).

priors

Optional named list of prior hyperparameters. Supported fields: a_sigma, b_sigma (inverse-Gamma shape and scale for the residual variance σ2\sigma^2); a_tau, b_tau (inverse-Gamma for the between-basket variance τ2\tau^2. Only used with pooling = "exnex"); p_mix (EXNEX mixture weight, strictly between 0 and 1. Only used with pooling = "exnex"); m_mu, v_mu (prior mean and variance of the exchangeable center μ\mu; with pooling = "complete" they act as the prior of the shared basket effect); m_nex, v_nex (prior mean and variance of the nonexchangeable component; with pooling = "none" they are the priors of the independent basket effects); v_beta (variance of the regression-coefficient prior). p_mix, m_nex, and v_nex each accept either a scalar, replicated across baskets, or a numeric vector of length K with one value per basket, matching the basket-specific notation of the model. All other fields are scalars. Absent fields keep the defaults (inverse-Gamma(2,2) for the variances, p_mix = 0.5, m_mu = 0, v_mu = 1e4, m_nex = 0, v_nex = 1e4, v_beta = 1e4); unknown fields are ignored. Priors that are irrelevant for the selected pooling mode are ignored with a warning.

iter

Total number of MCMC iterations. Default is 2000.

warmup

Number of warmup iterations to discard. Default is 1000. Posterior samples will have 'ceiling((iter - warmup) / thin)' rows per chain.

thin

Thinning interval applied after warmup: keep every 'thin'-th post-warmup draw. Default is 1 (keep all draws). Use 'thin > 1' to reduce memory and autocorrelation.

chains

Number of independent MCMC chains. Default is 1.

parallel_chains

Logical. If 'TRUE', all 'chains' run concurrently on background R sessions (via the 'future' framework); if 'FALSE' (default), chains run sequentially, one after the other.

group_col

Name of the column in 'x' that represents the basket/group assignment. If NULL (default), assumes the first column in x is the group.

seed

Random seed for reproducibility (optional).

verbose

If TRUE (default), show a live progress bar ('progressr') that advances as the chains run, in both sequential and parallel execution. Silence it with 'verbose = FALSE'.

y

A Surv object or matrix containing outcome (time and event status).

Details

For patient ii, the model is

log⁡Ti=θg[i]+XiTβ+εi,εi∼N(0,σ2),\log T_i = \theta_{g[i]} + X_i^{\mathsf T}\beta + \varepsilon_i,\quad \varepsilon_i\sim\mathcal N(0,\sigma^2),

and the argument pooling selects the prior structure of the basket effects θj\theta_j:

"exnex" (default)

latent EXNEX hierarchy,

θj∣Zj∼Zj N(μ,τ2)+(1−Zj) N(m0j,v0j),Zj∼Bern(pexch,j),\theta_j\mid Z_j\sim Z_j\,\mathcal N(\mu,\tau^2) +(1-Z_j)\,\mathcal N(m_{0j},v_{0j}),\quad Z_j\sim\mathrm{Bern}(p_{\mathrm{exch},j}),

so that each basket either borrows strength from the exchangeable component N(μ,τ2)\mathcal N(\mu,\tau^2) or follows its own non-exchangeable prior N(m0j,v0j)\mathcal N(m_{0j},v_{0j}).

"complete"

complete pooling: a single shared basket effect θj=θ\theta_j=\theta for all baskets with prior θ∼N(mμ,vμ)\theta\sim\mathcal N(m_{\mu},v_{\mu}).

"none"

no pooling: independent basket effects, θj∼N(m0j,v0j)\theta_j\sim\mathcal N(m_{0j},v_{0j}) for j=1,…,Kj=1,\dots,K.

The first predictor variable in the model formula is interpreted as the basket/group assignment; additional variables are covariates in the linear predictor on the log-survival scale.

Censored event times are handled by data augmentation: a censored log-time is imputed from its truncated-Normal conditional distribution (inverse-CDF in log space) before the remaining parameters are updated with their conjugate full conditionals. The sampler keeps only the posterior draws of θj\theta_j, β\beta, and σ2\sigma^2.

Value

An object of class 'pooling_surv' containing posterior samples and metadata. Components include pooling (the fitted variant), draws (a data frame with columns theta_1, ..., theta_K, beta_1, ..., beta_P, and sigma2; under pooling = "complete" the theta_* columns hold identical draws from the shared effect), data (the processed data: time, event, group, X, n, n_groups, n_covariates, cov_names, chain_seeds), priors (the supplied priors), resolved_priors (defaults merged with overrides), iter, warmup, chains, and blueprint. Use summary(), print(), and plot() to inspect it.

Examples

# Small simulated dataset: three baskets, one covariate, ~25% censoring
set.seed(1)
n <- 90
group <- factor(rep(1:3, each = 30))
x1 <- rnorm(n)
eta <- rep(c(1.1, 1.6, 2.0), each = 30) + 0.5 * x1
true_time <- exp(eta + rnorm(n, 0, 0.6))
cens_time <- runif(n, 2, 9)
d <- data.frame(
  time = pmin(true_time, cens_time),
  event = as.integer(true_time <= cens_time),
  group = group,
  x1 = x1
)

# Fit the EXNEX hierarchy with group effects and one covariate
fit <- pooling_surv(
  survival::Surv(time, event) ~ group + x1,
  data = d,
  priors = list(p_mix = 0.7),
  iter = 300, warmup = 150, chains = 1
)
print(fit, show_trace = FALSE)
summary(fit)

# Complete and no pooling use the same data augmentation machinery
fit_complete <- pooling_surv(
  survival::Surv(time, event) ~ group + x1,
  data = d, pooling = "complete",
  iter = 300, warmup = 150, chains = 1
)
fit_none <- pooling_surv(
  survival::Surv(time, event) ~ group + x1,
  data = d, pooling = "none",
  iter = 300, warmup = 150, chains = 1
)

Posterior predictive survival curves

Description

Posterior predictive check of the fitted log-normal AFT model: the posterior predictive mean survival curve per group (averaged over all posterior draws) is overlaid with the observed Kaplan-Meier curve and a band of replicated Kaplan-Meier curves. Systematic separation of the observed KM from the posterior mean band indicates model misfit for that group.

Usage

posterior_predictive_check(
  fit,
  times = NULL,
  n_replicates = 200,
  newdata = NULL,
  plot = TRUE,
  ...
)

Arguments

fit

A fitted 'pooling_surv' object.

times

Optional vector of evaluation times; defaults to a grid of 100 points between 0 and the largest observed time.

n_replicates

Number of posterior draws used for the replicated Kaplan-Meier band. Default 200.

newdata

Optional data frame with one row per group giving the covariates for the predictive curves. If 'NULL', covariates are zero.

plot

Should a 'ggplot2' figure be drawn? Default 'TRUE'.

...

Unused.

Value

Invisibly returns a list with 'posterior' (data frame: 'group', 'time', 'mean', 'lwr', 'upr') and 'observed' (data frame: 'group', 'time', 'surv'). Requires 'ggplot2' when 'plot = TRUE'.

Examples

set.seed(2719)
trial_data <- simulate_data(n = 90, K = 3, censoring_rate = 0.3)
fit <- pooling_surv(survival::Surv(time, event) ~ group,
                    data = trial_data,
                    iter = 300, warmup = 150, chains = 2)
## Not run: posterior_predictive_check(fit)

Print an pooling_surv fit

Description

Prints a compact summary and optionally traceplots of posterior draws.

Usage

## S3 method for class 'pooling_surv'
print(x, show_trace = TRUE, parameters = NULL, max_parameters = Inf, ...)

Arguments

x

A fitted 'pooling_surv' object.

show_trace

Should traceplots be shown? Default is 'TRUE'.

parameters

Optional character vector of parameter names for traceplots.

max_parameters

Maximum number of parameters to plot.

...

Unused.

Value

Invisibly returns 'x'.


Posterior probability that one group beats another

Description

Estimates the posterior probability that a certain scalar summary of the survival distribution for group 'a' is greater than the same summary for group 'b'. Supported summaries are the median survival time, the survival probability at a fixed time 'S(t_0)', and the restricted mean survival time (RMST). The function works draw-by-draw on the posterior, so the probability is computed on the joint posterior of the two groups.

Usage

probability_superiority(
  fit,
  a,
  b,
  function_of = "median",
  newdata = NULL,
  times = NULL,
  tmax = NULL,
  ...
)

Arguments

fit

A fitted 'pooling_surv' object.

a

An integer index (or group label) of the first group.

b

An integer index (or group label) of the second group.

function_of

Character; one of '"median"', '"survival"' (alias '"S_t"'), or '"rmst"'. Selects which summary is compared.

newdata

Optional data frame with one row per group, used to fix covariates. If 'NULL', covariates are set to zero.

times

A scalar time at which to evaluate the survival function when 'function_of = "survival"'.

tmax

A positive horizon when 'function_of = "rmst"'.

...

Unused.

Value

A named list with elements 'prob' (posterior probability that the summary of group 'a' exceeds that of group 'b'), 'summary' (which summary was used), and 'groups' (the two groups compared).


Posterior rank probabilities for each group

Description

For every posterior draw, the groups are ranked by a chosen survival summary (median survival, survival at a fixed time, or RMST) and the frequency of each rank is returned. Useful as a decision aid: the row of the output for rank 1 gives the posterior probability that each group is the best.

Usage

rank_probabilities(
  fit,
  newdata = NULL,
  function_of = "median",
  times = NULL,
  tmax = NULL,
  ...
)

Arguments

fit

A fitted 'pooling_surv' object.

newdata

Optional data frame with one row per group used to fix covariates. If 'NULL', covariates are set to zero.

function_of

Character; one of '"median"', '"survival"' (alias '"S_t"'), or '"rmst"'.

times

Scalar time when 'function_of = "survival"'.

tmax

Positive horizon when 'function_of = "rmst"'.

...

Unused.

Value

A data frame with columns 'group', 'rank', and 'prob'. Rank 1 is the largest summary value (best group).

Examples

set.seed(2719)
trial_data <- simulate_data(n = 90, K = 3, censoring_rate = 0.3)
fit <- pooling_surv(survival::Surv(time, event) ~ group,
                    data = trial_data,
                    iter = 300, warmup = 150, chains = 2)
rank_probabilities(fit)

Restricted mean survival time (RMST) from an pooling_surv fit

Description

Computes the restricted mean survival time up to a horizon 'tmax':

RMST(tmax)=∫0tmaxS(t) dt.RMST(tmax) = \int_0^{tmax} S(t)\,dt.

The integral is evaluated numerically (trapezoidal rule over a fine grid) for every posterior draw, then summarised with posterior quantiles.

Usage

rmst(fit, tmax = NULL, newdata = NULL, level = 0.95, grid_points = 400, ...)

Arguments

fit

A fitted 'pooling_surv' object.

tmax

A positive scalar horizon up to which the RMST is computed. Defaults to 'max(fit$data$time)'.

newdata

Optional data frame (one row gives one RMST).

level

Credible-interval level (default '0.95').

grid_points

Resolution of the numerical quadrature (default '400').

...

Unused.

Value

A 'data.frame' with columns 'group', 'rmst', 'lower', 'upper'.


Visualize shrinkage of group effects

Description

Compares the hierarchical (EXNEX posterior) estimates of the group-specific log-median survival 'theta_j' (at zero covariates) against the unpooled, basket-only Kaplan-Meier median estimate computed from the original data. A short segment between the two points per group shows the amount of shrinkage (partial pooling) applied by the model.

Usage

shrinkage_plot(fit, ...)

Arguments

fit

A fitted 'pooling_surv' object.

...

Unused.

Value

Invisibly returns a data frame with columns 'group', 'posterior_mean', 'posterior_lwr', 'posterior_upr', and 'unpooled' – the log-scale values used in the plot. Requires 'ggplot2'.

Examples

set.seed(2719)
trial_data <- simulate_data(n = 90, K = 3, censoring_rate = 0.3)
fit <- pooling_surv(survival::Surv(time, event) ~ group,
                    data = trial_data,
                    iter = 300, warmup = 150, chains = 2)
## Not run: shrinkage_plot(fit)

Simulate basket-trial log-normal survival data

Description

Generates a synthetic dataset from the log-normal AFT model used by 'exnexSurv':

log⁡Ti=θg[i]+Xi⊤β+ε,ε∼N(0,σ2).\log T_i = \theta_{g[i]} + X_i^\top\beta + \varepsilon,\qquad \varepsilon\sim\mathcal N(0,\sigma^2).

Healthy baskets draw their location from a common baseline plus random noise; "outlier" baskets (specified via 'outlier_baskets') have their location shifted by 'resist_delta'. The function is useful for simulation studies and teaching examples.

Usage

simulate_data(
  n = 30,
  K = 9,
  beta = c(0.5, -0.2),
  sigma = 1.2,
  outlier_baskets = NULL,
  resist_delta = -0.8,
  censoring_rate = NULL,
  censor_upper = NULL,
  theta = 0,
  seed = NULL
)

Arguments

n

Number of patients per basket. A scalar is replicated across all 'K' baskets; a numeric vector of length 'K' assigns a size to each basket individually.

K

Number of baskets (default '9').

beta

Numeric vector of regression coefficients for the covariates. Length determines the number of covariates.

sigma

Residual standard deviation (default '1.2').

outlier_baskets

Optional integer vector of basket indices (1 to K) whose true location is shifted away from the healthy population. These are the baskets the EXNEX model is designed to detect.

resist_delta

Additive shift applied to the 'theta' of outlier baskets (default '-0.8').

censoring_rate

Approximate proportion of censoring after 'censor_upper'; if 'NULL', no censoring is applied.

censor_upper

Upper bound of the censoring-time uniform distribution.

theta

Baseline location for the healthy (non-outlier) baskets; a scalar used as the centre around which the healthy basket locations vary.

seed

Optional seed for reproducibility.

Value

A 'data.frame' with columns 'time', 'event', 'group' (a factor), and one covariate column ('x1', ...) per entry in 'beta'. True parameter values are stored as attributes 'true_theta', 'true_beta', and 'true_sigma'.


Summarize posterior draws from an pooling_surv fit

Description

Summarize posterior draws from an pooling_surv fit

Usage

## S3 method for class 'pooling_surv'
summary(object, probs = c(0.05, 0.5, 0.95), ...)

Arguments

object

A fitted 'pooling_surv' object.

probs

Quantiles to report.

...

Unused.

Value

A data frame with posterior summaries.


Survival curves from an pooling_surv fit

Description

Computes posterior survival curves S(t)=Pr⁡(T>t)S(t) = \Pr(T > t) from a fitted 'pooling_surv' model for a log-normal AFT specification:

log⁡Ti=θg[i]+Xi⊤β+ε,ε∼N(0,σ2).\log T_i = \theta_{g[i]} + X_i^\top\beta + \varepsilon,\qquad \varepsilon\sim\mathcal N(0,\sigma^2).

For a fixed time tt and linear predictor η\eta, the survival probability of the log-normal distribution is

S(t)=1−Φ ⁣(log⁡t−ησ),S(t) = 1 - \Phi\!\left(\frac{\log t - \eta}{\sigma}\right),

where σ2\sigma^2 is the residual variance. Posterior draws of (θ,β,σ2)(\theta, \beta, \sigma^2) are propagated through this expression to obtain a full posterior distribution of S(t)S(t) at each time point.

Usage

survival_curves(fit, newdata = NULL, times = NULL, level = 0.95, ...)

Arguments

fit

A fitted 'pooling_surv' object.

newdata

Optional data frame with columns matching the covariates of the model. If it contains a 'group' column, that is used for the group index; otherwise the first group is used for all rows.

times

Optional numeric vector of times at which to evaluate the curves. If 'NULL', a sensible grid is built from the observed follow-up times.

level

Credible-interval level (default '0.95').

...

Unused.

Details

If 'newdata' is supplied, each row is evaluated at its own group and covariate values; otherwise the covariates are fixed at zero and the first group is used (with a warning if more than one group exists).

Value

An object of class 'survival_exnex' (also a 'data.frame') with long format columns: 'time', 'median', 'lower', 'upper', and 'group'.