| Title: | Federated Average Treatment Effect Estimation Across Studies |
|---|---|
| Description: | Client-side 'DataSHIELD' package that orchestrates the federated estimation of the average treatment effect (ATE) across multiple independent randomised controlled trials (RCTs), each held on a separate data server. Implements in a federated way the g-formula estimator from Khellaf, Bellet and Josse (2025) "Federated Causal Inference: Multi-Study ATE Estimation beyond Meta-Analysis" <doi:10.48550/arXiv.2410.16870>, including variance estimation and optional adjustment for study-specific intercept shifts. |
| Authors: | Francisco Andrade [aut, cre], Rémi Khellaf [aut], Julie Josse [aut] |
| Maintainer: | Francisco Andrade <[email protected]> |
| License: | AGPL (>= 3) |
| Version: | 0.1.0 |
| Built: | 2026-11-14 07:04:50 UTC |
| Source: | https://github.com/cran/dsFedAteClient |
Client-side function that orchestrates the federated estimation of an average treatment effect (ATE) – and, optionally, its variance and confidence interval – from multiple independent RCTs, each held on a separate data server. Randomisation holds within each study; the combined dataset is not necessarily itself a single RCT.
ds.EstimateAte( datasources = NULL, base_name, outcome_name, treatment_name, covariate_names, compute_variance = TRUE, study_effects = FALSE, heterogeneous = TRUE, heteroscedastic_arms = FALSE, quiet = FALSE )ds.EstimateAte( datasources = NULL, base_name, outcome_name, treatment_name, covariate_names, compute_variance = TRUE, study_effects = FALSE, heterogeneous = TRUE, heteroscedastic_arms = FALSE, quiet = FALSE )
datasources |
A list of |
base_name |
A character string naming the server-side data frame (must be identical across all servers). |
outcome_name |
A character string naming the outcome variable. |
treatment_name |
A character string naming the binary treatment indicator. |
covariate_names |
A character vector of covariate names. |
compute_variance |
Logical. If |
study_effects |
Logical. If |
heterogeneous |
Logical. Controls which variance formula is used.
|
heteroscedastic_arms |
Logical. Only has effect when
|
quiet |
Logical. If |
The function fits separate arm-specific linear models via
ds.glm, broadcasts the pooled coefficients back
to the servers, and collects local statistics via a single aggregate call
to localAteDS. When compute_variance = FALSE, three scalars are returned per server
(, , ): and
suffice for the point estimate; is additionally
returned so that arm sizes can be checked for asymptotic reliability.
The residual sum of squares and projected scatter are omitted, as these
constitute additional disclosure.
Server-side objects created.
Two symbols are always created per server: base_name_treated
(treated-arm subset) and base_name_control (control-arm subset),
both used by ds.glm. When study_effects = TRUE, a third
symbol base_name_study_adj is also created, holding the data
frame augmented with the K-1 study dummy columns. All three persist
in the server workspace after the function returns, as is typical for
DataSHIELD assign operations. The caller's original base_name
object is never modified.
When compute_variance = TRUE (the default), a delta-method variance
estimate and 95% confidence interval are also returned. The formula used
depends on the heterogeneous argument; see below.
A list. When compute_variance = TRUE (the default),
seven elements:
Federated ATE estimate .
Variance estimate .
Lower 95-percent confidence interval bound.
Upper 95-percent confidence interval bound.
Total sample size across all servers.
Integer vector of per-server sample sizes.
Numeric vector of per-server local ATE estimates.
When compute_variance = FALSE, four elements:
ate, n_total, n_k, ate_k.
The structure is the same regardless of study_effects.
Khellaf, R., Bellet, A., and Josse, J. (2025). Federated Causal Inference: Multi-Study ATE Estimation beyond Meta-Analysis. Proceedings of the 28th International Conference on Artificial Intelligence and Statistics (AISTATS 2025).
if (requireNamespace("DSLite", quietly = TRUE) && requireNamespace("dsFedAte", quietly = TRUE)) { library(dsFedAte) library(DSLite) set.seed(1) n <- 200 study_data <- data.frame(W = rbinom(n, 1, 0.5), X1 = rnorm(n)) study_data$Y <- 0.5 * study_data$W + study_data$X1 + rnorm(n) datashield_env <- new.env(parent = emptyenv()) options(datashield.env = datashield_env, datashield.privacyControlLevel = "banana", nfilter.glm = 0.33) srv <- DSLite::newDSLiteServer(tables = list(study_data = study_data)) srv$config(DSLite::defaultDSConfiguration(include = c("dsBase", "dsFedAte"))) srv$aggregateMethod("localAteDS", "localAteDS") srv$aggregateMethod("listDisclosureSettingsDS", "listDisclosureSettingsDS") srv$aggregateMethod("checkPermissivePrivacyControlLevel", "checkPermissivePrivacyControlLevel") assign("dslite.server", srv, envir = datashield_env) builder <- DSI::newDSLoginBuilder() builder$append(server = "s1", url = "dslite.server", table = "study_data", driver = "DSLiteDriver") conns <- DSI::datashield.login(logins = builder$build(), assign = FALSE) DSI::datashield.assign.table(conns, symbol = "study_data", table = "study_data") result <- ds.EstimateAte( datasources = conns, base_name = "study_data", outcome_name = "Y", treatment_name = "W", covariate_names = "X1", quiet = TRUE ) print(result$ate) DSI::datashield.logout(conns) }if (requireNamespace("DSLite", quietly = TRUE) && requireNamespace("dsFedAte", quietly = TRUE)) { library(dsFedAte) library(DSLite) set.seed(1) n <- 200 study_data <- data.frame(W = rbinom(n, 1, 0.5), X1 = rnorm(n)) study_data$Y <- 0.5 * study_data$W + study_data$X1 + rnorm(n) datashield_env <- new.env(parent = emptyenv()) options(datashield.env = datashield_env, datashield.privacyControlLevel = "banana", nfilter.glm = 0.33) srv <- DSLite::newDSLiteServer(tables = list(study_data = study_data)) srv$config(DSLite::defaultDSConfiguration(include = c("dsBase", "dsFedAte"))) srv$aggregateMethod("localAteDS", "localAteDS") srv$aggregateMethod("listDisclosureSettingsDS", "listDisclosureSettingsDS") srv$aggregateMethod("checkPermissivePrivacyControlLevel", "checkPermissivePrivacyControlLevel") assign("dslite.server", srv, envir = datashield_env) builder <- DSI::newDSLoginBuilder() builder$append(server = "s1", url = "dslite.server", table = "study_data", driver = "DSLiteDriver") conns <- DSI::datashield.login(logins = builder$build(), assign = FALSE) DSI::datashield.assign.table(conns, symbol = "study_data", table = "study_data") result <- ds.EstimateAte( datasources = conns, base_name = "study_data", outcome_name = "Y", treatment_name = "W", covariate_names = "X1", quiet = TRUE ) print(result$ate) DSI::datashield.logout(conns) }