| Title: | Bayesian Tree Ensembles for Data Fusion and Causal Inference |
|---|---|
| Description: | Bayesian tree ensemble models for data fusion and causal inference. The main model FusionForest() combines data from a randomised controlled trial and an observational study using separate tree forests, allowing for unmeasured confounding in the observational data. Continuous and (interval-)censored survival outcomes are supported. Posterior summaries of treatment effect estimates and interpretable linear projections are provided. |
| Authors: | Tijn Jacobs [aut, cre] (ORCID: <https://orcid.org/0009-0003-6188-9296>) |
| Maintainer: | Tijn Jacobs <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 1.1.0 |
| Built: | 2026-10-15 17:17:24 UTC |
| Source: | https://github.com/tijn-jacobs/fusionforests |
A combined data set of a randomised controlled trial (RCT) and a
real-world data (RWD) source with a right-censored time-to-event outcome,
built to illustrate FusionForest. The covariates are real;
the outcomes are simulated. The data say nothing about any real
treatment.
breast_fusionbreast_fusion
A data frame with 3668 rows (686 RCT, 2982 RWD) and 13 columns:
Row identifier, matching breast_fusion_truth$id.
Factor, "RCT" or "RWD".
Source indicator (1 = RCT, 0 = RWD), as used by
FusionForest.
Treatment indicator (1 = treated, 0 = control).
Observed relapse-free time in years.
Event indicator (1 = event, 0 = censored).
Age in years.
Menopausal status (0 = pre, 1 = post).
Tumour size (0 = at most 20 mm, 1 = 20–50 mm, 2 = more than 50 mm).
Tumour grade (0 = grade 1 or 2, 1 = grade 3).
Number of positive lymph nodes.
Progesterone receptor (fmol/l).
Oestrogen receptor (fmol/l).
The covariates come from the German Breast Cancer Study Group 2 trial
(gbsg; the RCT rows) and the Rotterdam tumour bank
(rotterdam; the RWD rows), harmonised to a common
set. The treatment A is a generic binary treatment. In the RCT it
is randomised with probability 1/2. In the RWD it is the real, non-random
treatment assignment of the Rotterdam cohort, so it is confounded. Part
of that confounding acts through variables that are not included here
(year of surgery and chemotherapy; see breast_fusion_truth).
Event times follow a Weibull accelerated failure time model whose
baseline coefficients and scale were estimated from the real GBSG2
outcomes. The heterogeneous treatment effect and the effects of the
unobserved variables are made up. Censoring times are drawn from each
source's real censoring distribution, so follow-up in the RCT is shorter
than in the RWD. The script that builds the data and an example analysis
are installed with the package, in
system.file("scripts", package = "FusionForests").
Covariates from the survival package data sets
gbsg and rotterdam; outcomes simulated.
Royston, P. and Altman, D. G. (2013). External validation of a Cox prognostic model: principles and methods. BMC Medical Research Methodology, 13, 33.
Schumacher, M., Bastert, G., Bojar, H., et al. (1994). Randomized 2 x 2 trial evaluating hormonal treatment and the duration of chemotherapy in node-positive breast cancer patients. Journal of Clinical Oncology, 12, 2086–2093.
breast_fusion_truth for the simulation truth.
table(breast_fusion$source, breast_fusion$A)table(breast_fusion$source, breast_fusion$A)
The true treatment effects, uncensored event times and unobserved
confounders behind breast_fusion. Rows are aligned with
breast_fusion and share its id. Use these only to check
estimates; a real analysis would not have them.
breast_fusion_truthbreast_fusion_truth
A data frame with 3668 rows and 6 columns:
Row identifier, matching breast_fusion$id.
Factor, "RCT" or "RWD".
True conditional average treatment effect on the log-time
scale; exp(tau) is the acceleration factor.
Uncensored log event time (log years).
Chemotherapy (1 = yes, 0 = no); unobserved confounder. All RCT rows are 1.
Year of surgery; unobserved confounder. Real for the RWD rows, drawn uniformly from 1984–1989 for the RCT rows.
Simulated; see breast_fusion.
summary(breast_fusion_truth$tau)summary(breast_fusion_truth$tau)
Computes posterior draws of the survival difference or the acceleration
factor at the test rows of a FusionForest fit.
fusion_estimand( fit, estimand = c("SD", "AF"), time = NULL, target_source = c("rwd", "rct"), population_average = FALSE, bayesian_bootstrap = TRUE, seed = NULL )fusion_estimand( fit, estimand = c("SD", "AF"), time = NULL, target_source = c("rwd", "rct"), population_average = FALSE, bayesian_bootstrap = TRUE, seed = NULL )
fit |
A |
estimand |
|
time |
Time point for |
target_source |
Target population: |
population_average |
If |
bayesian_bootstrap |
If |
seed |
Optional seed for the Bayesian bootstrap weights. |
The survival function under treatment in target source
is
with the error distribution in source and
For fits with the "centered" or "adaptive" treatment
coding, the function uses the coded treatment instead of .
Two estimands are available:
"SD"Survival difference
.
"AF"Acceleration factor .
It does not depend on the error distribution or the target source.
The population average weights the test rows. By default, the weights are drawn from a Dirichlet(1, ..., 1) distribution at each iteration: the Bayesian bootstrap (Rubin, 1981).
A matrix of posterior draws with one row per iteration and one
column per test row. With population_average = TRUE, a vector
with one draw per iteration. The result carries the attributes
estimand, time and target_source.
Rubin, D.~B. (1981). The Bayesian bootstrap. The Annals of Statistics, 9, 130–134.
# Right-censored fusion fit that stores the posterior draws set.seed(1) n <- 100 X <- matrix(rnorm(n * 3), n, 3) s <- rbinom(n, 1, 0.5) a <- rbinom(n, 1, 0.5) true_time <- exp(1 + X[, 1] + 0.5 * a + 0.3 * rnorm(n)) cens_time <- rexp(n, rate = 1 / (2 * mean(true_time))) time <- pmin(true_time, cens_time) status <- as.integer(true_time <= cens_time) fit <- FusionForest( y = time, status = status, X_train_control = X, X_train_treat = X, treatment_indicator_train = a, source_indicator_train = s, X_test_control = X, X_test_treat = X, treatment_indicator_test = a, source_indicator_test = s, outcome_type = "right-censored", N_post = 50, N_burn = 25, store_posterior_sample = TRUE, verbose = FALSE ) # Posterior draws of the acceleration factor per test row af <- fusion_estimand(fit, estimand = "AF") quantile(colMeans(af), c(0.25, 0.5, 0.75))# Right-censored fusion fit that stores the posterior draws set.seed(1) n <- 100 X <- matrix(rnorm(n * 3), n, 3) s <- rbinom(n, 1, 0.5) a <- rbinom(n, 1, 0.5) true_time <- exp(1 + X[, 1] + 0.5 * a + 0.3 * rnorm(n)) cens_time <- rexp(n, rate = 1 / (2 * mean(true_time))) time <- pmin(true_time, cens_time) status <- as.integer(true_time <= cens_time) fit <- FusionForest( y = time, status = status, X_train_control = X, X_train_treat = X, treatment_indicator_train = a, source_indicator_train = s, X_test_control = X, X_test_treat = X, treatment_indicator_test = a, source_indicator_test = s, outcome_type = "right-censored", N_post = 50, N_burn = 25, store_posterior_sample = TRUE, verbose = FALSE ) # Posterior draws of the acceleration factor per test row af <- fusion_estimand(fit, estimand = "AF") quantile(colMeans(af), c(0.25, 0.5, 0.75))
Summarises the heterogeneous treatment effect by a linear model in a few covariates. Each posterior draw of the effect is projected onto a linear basis by least squares (Woody, Carvalho and Murray, 2020). The result is a posterior sample of the projection coefficients. No refitting is needed.
fusion_projection( fit, basis, X_eval, target = c("cate", "af"), af_scale = c("multiplicative", "log"), which = c("test", "train"), weights = c("uniform", "bayesian_bootstrap"), center = TRUE, scale = FALSE, seed = NULL )fusion_projection( fit, basis, X_eval, target = c("cate", "af"), af_scale = c("multiplicative", "log"), which = c("test", "train"), weights = c("uniform", "bayesian_bootstrap"), center = TRUE, scale = FALSE, seed = NULL )
fit |
A |
basis |
One-sided formula for the basis, such as
|
X_eval |
Data frame with the covariates of the evaluation rows: the
test or training rows of the fit (see |
target |
|
af_scale |
For |
which |
Evaluation rows: |
weights |
|
center, scale
|
Centre or scale the basis columns other than the
intercept. Defaults: |
seed |
Optional seed for the Bayesian bootstrap weights. |
At iteration , the coefficients solve
Here is or
, depending on target and
af_scale, and is the basis given by basis.
The weights are equal by default. With
weights = "bayesian_bootstrap", they are drawn from a
Dirichlet(1, ..., 1) distribution at each iteration (Rubin, 1981). This
adds the uncertainty about the covariate distribution.
The basis columns other than the intercept are centred on X_eval,
and optionally scaled. The centring and scaling constants are returned
as attributes.
A matrix of posterior draws of the coefficients, with one row
per iteration and one column per basis term. The attributes record
target, af_scale, which, weights, the
centring and scaling constants, and the formula.
Woody, S., Carvalho, C.~M., and Murray, J.~S. (2020). Bayesian inference with posterior projection. Journal of Computational and Graphical Statistics, 29(4), 798–808.
Rubin, D.~B. (1981). The Bayesian bootstrap. The Annals of Statistics, 9, 130–134.
# Continuous fusion fit that stores the posterior draws set.seed(1) n <- 100 X <- matrix(rnorm(n * 3), n, 3) colnames(X) <- paste0("x", 1:3) s <- rbinom(n, 1, 0.5) a <- rbinom(n, 1, 0.5) y <- X[, 1] + (0.5 + 0.3 * X[, 2]) * a + rnorm(n) fit <- FusionForest( y = y, X_train_control = X, X_train_treat = X, treatment_indicator_train = a, source_indicator_train = s, X_test_control = X, X_test_treat = X, treatment_indicator_test = a, source_indicator_test = s, N_post = 50, N_burn = 25, store_posterior_sample = TRUE, verbose = FALSE ) # Summarise the treatment effect by a linear model in x1, x2 and x3 proj <- fusion_projection(fit, basis = ~ x1 + x2 + x3, X_eval = as.data.frame(X)) colMeans(proj)# Continuous fusion fit that stores the posterior draws set.seed(1) n <- 100 X <- matrix(rnorm(n * 3), n, 3) colnames(X) <- paste0("x", 1:3) s <- rbinom(n, 1, 0.5) a <- rbinom(n, 1, 0.5) y <- X[, 1] + (0.5 + 0.3 * X[, 2]) * a + rnorm(n) fit <- FusionForest( y = y, X_train_control = X, X_train_treat = X, treatment_indicator_train = a, source_indicator_train = s, X_test_control = X, X_test_treat = X, treatment_indicator_test = a, source_indicator_test = s, N_post = 50, N_burn = 25, store_posterior_sample = TRUE, verbose = FALSE ) # Summarise the treatment effect by a linear model in x1, x2 and x3 proj <- fusion_projection(fit, basis = ~ x1 + x2 + x3, X_eval = as.data.frame(X)) colMeans(proj)
Fits the Bayesian fusion forest. The model combines a randomised controlled trial (RCT) with real-world data (RWD) to estimate heterogeneous treatment effects. The RWD may be confounded.
FusionForest( y, status = NULL, observed_left_time = NULL, observed_right_time = NULL, interval_censoring_indicator = NULL, X_train_control, X_train_treat, treatment_indicator_train, source_indicator_train, X_test_control = NULL, X_test_treat = NULL, X_test_deconf = NULL, treatment_indicator_test = NULL, source_indicator_test = NULL, outcome_type = "continuous", timescale = "time", number_of_trees_control = 200, number_of_trees_treat = 100, number_of_trees_deconf = 50, number_of_trees_deviation = 50, k_control = 1, k_treat = 0.5, k_deconf = 1, k_deviation = 1, power_control = 2, base_control = 0.95, power_deviation = 2, base_deviation = 0.95, power_treat = 3, base_treat = 0.95, power_deconf = 3, base_deconf = 0.25, p_grow = 0.4, p_prune = 0.4, nu = 3, q = 0.9, sigma = NULL, N_post = 5000, N_burn = 5000, treatment_coding = c("centered", "binary", "adaptive"), propensity_train = NULL, propensity_test = NULL, error_dist = c("source_hdp_scale", "gaussian", "source_gaussian", "shared_dp", "source_dp", "source_dp_scale", "source_hdp"), error_truncation_K = 50L, error_atom_scale = 0.5, error_mass_init = 1, store_posterior_sample = FALSE, verbose = TRUE )FusionForest( y, status = NULL, observed_left_time = NULL, observed_right_time = NULL, interval_censoring_indicator = NULL, X_train_control, X_train_treat, treatment_indicator_train, source_indicator_train, X_test_control = NULL, X_test_treat = NULL, X_test_deconf = NULL, treatment_indicator_test = NULL, source_indicator_test = NULL, outcome_type = "continuous", timescale = "time", number_of_trees_control = 200, number_of_trees_treat = 100, number_of_trees_deconf = 50, number_of_trees_deviation = 50, k_control = 1, k_treat = 0.5, k_deconf = 1, k_deviation = 1, power_control = 2, base_control = 0.95, power_deviation = 2, base_deviation = 0.95, power_treat = 3, base_treat = 0.95, power_deconf = 3, base_deconf = 0.25, p_grow = 0.4, p_prune = 0.4, nu = 3, q = 0.9, sigma = NULL, N_post = 5000, N_burn = 5000, treatment_coding = c("centered", "binary", "adaptive"), propensity_train = NULL, propensity_test = NULL, error_dist = c("source_hdp_scale", "gaussian", "source_gaussian", "shared_dp", "source_dp", "source_dp_scale", "source_hdp"), error_truncation_K = 50L, error_atom_scale = 0.5, error_mass_init = 1, store_posterior_sample = FALSE, verbose = TRUE )
y |
Numeric vector of outcomes: survival times or a continuous response. |
status |
Event indicator (1 = event, 0 = censored). Required for right-censored outcomes. Use 0 for interval-censored rows. |
observed_left_time, observed_right_time, interval_censoring_indicator
|
Optional vectors for interval censoring, on the same scale as
Leave all three |
X_train_control |
Covariate matrix for the
|
X_train_treat |
Covariate matrix for the |
treatment_indicator_train |
Treatment (1 = treated, 0 = control). |
source_indicator_train |
Source (1 = RCT, 0 = RWD). |
X_test_control, X_test_treat, X_test_deconf
|
Optional covariate
matrices for prediction. |
treatment_indicator_test, source_indicator_test
|
Treatment and source of the test rows. Both default to 1. |
outcome_type |
|
timescale |
|
number_of_trees_control, number_of_trees_treat, number_of_trees_deconf
|
Number of trees |
number_of_trees_deviation |
Number of trees in the |
k_control, k_treat, k_deconf, k_deviation
|
Step-height scales
|
power_control, base_control, power_deviation, base_deviation, power_treat, base_treat, power_deconf, base_deconf
|
Tree-structure prior of each forest. A node at depth |
p_grow, p_prune
|
Probabilities of a grow and a prune proposal. |
nu |
Degrees of freedom of the scaled inverse chi-squared prior on
|
q |
Quantile that calibrates the scale |
sigma |
Fixed error standard deviation. If |
N_post, N_burn
|
Number of posterior and burn-in iterations. |
treatment_coding |
How the treatment enters the model. The coded
treatment
|
propensity_train, propensity_test
|
Propensity scores in (0, 1) for
the training and test rows. Only used with
|
error_dist |
Prior for the error distribution. All mixtures are centred, so the error has mean zero in each source. One of:
|
error_truncation_K |
Truncation level of the mixture. Default 50. Increase it if the number of occupied components gets close to this level. |
error_atom_scale |
Prior standard deviation of the mixture locations, on the standardised scale. Default 0.5. |
error_mass_init |
Starting value of the concentration parameters.
They have a |
store_posterior_sample |
If |
verbose |
If |
The model decomposes the log survival time as
with treatment (1 = treated, 0 = control) and source
(1 = RCT, 0 = RWD). The error has mean zero;
see error_dist. For a continuous outcome, the outcome replaces
.
Each component has its own BART forest. The arguments of a forest carry the suffix in brackets.
(control)Baseline log survival time under control, shared by both sources.
(deviation)Difference between the RWD and
RCT baselines. The baseline is
.
(treat)Treatment effect on the log-time scale.
(deconf)Confounding function: the bias in the RWD treatment contrast.
The step heights of forest
have prior
with
where is the number of trees and the scale. A smaller
shrinks the forest more strongly toward zero. All defaults
follow the paper.
The model above uses the binary treatment indicator , as in the
paper (treatment_coding = "binary"). The default "centered"
coding replaces by . The functions and
keep their meaning. The baseline forests then describe the
average of the two arms instead of the control arm.
A FusionForest object: a list with
Posterior mean of the fitted values.
Posterior
mean of .
Posterior mean
of .
Posterior mean
of . The training values cover the RWD rows only.
Posterior
mean of . The training values cover the RWD rows only.
Posterior draws of on the standardised scale.
For the error models with a scale per source, this is a pooled
scale. The per-source scales are in dp_sigma_g.
Acceptance rates of the tree proposals.
All posterior draws. Only
with store_posterior_sample = TRUE.
Posterior draws of
the error distribution. Only for error models other than
"gaussian".
Settings used by the summary functions.
# Small simulated fusion example combining an RCT and RWD set.seed(1) n <- 100 X <- matrix(rnorm(n * 3), n, 3) s <- rbinom(n, 1, 0.5) # 1 = RCT, 0 = RWD a <- rbinom(n, 1, 0.5) # treatment y <- X[, 1] + 0.5 * a + rnorm(n) fit <- FusionForest( y = y, X_train_control = X, X_train_treat = X, treatment_indicator_train = a, source_indicator_train = s, N_post = 50, N_burn = 25, verbose = FALSE ) print(fit) summary(fit)# Small simulated fusion example combining an RCT and RWD set.seed(1) n <- 100 X <- matrix(rnorm(n * 3), n, 3) s <- rbinom(n, 1, 0.5) # 1 = RCT, 0 = RWD a <- rbinom(n, 1, 0.5) # treatment y <- X[, 1] + 0.5 * a + rnorm(n) fit <- FusionForest( y = y, X_train_control = X, X_train_treat = X, treatment_indicator_train = a, source_indicator_train = s, N_post = 50, N_burn = 25, verbose = FALSE ) print(fit) summary(fit)
Prints the main settings of a FusionForest() fit: outcome type,
treatment coding, error model, sample sizes and number of draws.
## S3 method for class 'FusionForest' print(x, ...)## S3 method for class 'FusionForest' print(x, ...)
x |
A |
... |
Ignored. |
x, invisibly.
Print a FusionForest summary
## S3 method for class 'summary.FusionForest' print(x, digits = 3, ...)## S3 method for class 'summary.FusionForest' print(x, digits = 3, ...)
x |
A |
digits |
Number of significant digits to print. |
... |
Ignored. |
x, invisibly.
Summarises a FusionForest() fit: the error standard deviation, the
estimated treatment effects of the training rows and the acceptance
rates.
## S3 method for class 'FusionForest' summary(object, ...)## S3 method for class 'FusionForest' summary(object, ...)
object |
A |
... |
Ignored. |
A summary.FusionForest object: a list with
meta: the settings of the fit;
sigma: posterior draws of the error standard deviation on the
standardised scale;
treatment_effects: posterior mean treatment effect of each
training row;
acceptance_ratios: mean acceptance rate per forest.