View source: R/calibrate_dag.r
| calibrate_dag | R Documentation |
DAG to match target values
This function may be used to calibrate certain parameters in a partially specified DAG, given some global targets. For example, users may be interested in fixing marginal effects (such as the average causal treatment effect), rather than fixing the conditional treatment effects as represented by beta coefficients. See details.
calibrate_dag(lazy_dag, targets, params, n_sim_run=100000,
n_sim_eval=1000000, n_reps_eval=1,
seed=stats::runif(1, 0, 100),
optimizer="auto", optim_method=NULL,
optim_control=list(), objective_fun=NULL,
...)
lazy_dag |
An unevaluated, partially specified |
targets |
A |
params |
A named
Parameter specifications may be supplied as named lists or named numeric vectors when all supplied elements are scalar. For example, |
n_sim_run |
A single positive number, specifying the sample size that should be used when evaluating the |
n_sim_eval |
A single positive number, specifying the sample size used when evaluating the |
n_reps_eval |
A single positive number, specifying how many simulation repetitions should be performed when evaluating the final parameter values. See |
seed |
A random number generator seed. If not |
optimizer |
A single character string, specifying which optimizer should be used. Allowed values are: |
optim_method |
A single character string, specifying which |
optim_control |
This argument is directly passed to the |
objective_fun |
A custom function that should be used in the underlying |
... |
Further arguments passed to the |
We recommend consulting the associated vignette for a more detailed explanation, including multiple worked examples, available at vignette(topic="v_calibrate_dag", package="simDAG").
What it does:
This function tries to help find suitable conditional parameters, when the goal is to fix marginal ones. Instead of defining all conditional parameters directly, users may specify arbitrary (potentially marginal) global parameters instead and to leave the conditional parameters partially unspecified. It then tries to calibrate the unspecified parameters, so that the global targets are met. The resulting DAG can then be used to simulate the data with the desired global target properties using sim_from_dag, sim_discrete_time or sim_discrete_event.
How it works:
Instead of defining a fully specified DAG, users should define only a partially specified DAG, leaving one or more parameters unspecified. This should be done using the lazy_dag function (see ?lazy_dag for more information). Afterwards, users can define an arbitrary amount of targets through custom targets functions. These functions should simply calculate the current value of the desired property. For example, if the data should have a 50% prevalence in the binary variable Y, a suitable target function would simply be function(data) {mean(data$Y)} with an associated target = 0.5. Additionally, users should give rough ranges in which the unspecified parameters may lie.
Internally, root finding (if 1 target and 1 unspecified parameter, with defined ranges) or numerical optimization (all other cases) is then used to find suitable values for the unspecified parameters that satisfy the target properties defined by the user. In a nutshell, the function simply simulates a large dataset from a given parameter combination, checks the resulting target properties and tries again. This idea, although applied in a less general setting, is also described by Austin and Stafford (2008) and Austin (2023).
To inspect the found parameter values and how well those produce the desired targets, users may use the associated summary() method (see examples). If n_reps_eval > 1, the associated plot.calibrated_DAG method may also be used. To simulate data directly using the resulting parameters, users may use the dag object returned in the output.
Currently supported features:
Users may use custom functions to define the target properties, so there are very few limits to what can be specified. Additionally, any parameter in a valid expression that defines a DAG may be left unspecified. Furthermore, there is no limit put on either the number of target properties (denoted m) or the number of unspecified parameters (denoted p), although some care needs to be exercised. If p > m, there may be multiple different solutions, which is usually not problematic, but should be noted. If m > p, there likely aren't any solutions, unless some targets are highly correlated. DAGs of any kind (using discrete-time varying covariates, continuous-time varying covariates, network dependencies, ...) may be used here.
Limitations:
There is unfortunately no guarantee that a solution for any given specification exist. Even worse, there is no guarantee that if a solution exists, that this function will find it. It is, however, still much better than manually searching for a solution. Furthermore, users should keep in mind that even if a solution exists and the numeric optimization is able to converge to it, that the results are only approximate. In most cases, this should be completely fine for Monte-Carlo simulation studies. However, in some edge cases, if exact simulation specifications of marginal parameters are required, this may not be sufficient.
Convergence:
Convergence may be tricky with complicated DAGs, many params or many targets (or both). Ultimately, the same techniques that work for any numerical optimization may be used to obtain convergence, e.g. tuning the optim_control arguments, re-running the function with starting values from a previous run etc. The only thing specific to this function that has a large effect on convergence is the n_sim_run argument. Better convergence may be obtained with larger values, at the price of a longer computation time.
Returns a calibrated_DAG object. This object is basically just a list, containing the following objects:
inputs: A list containing all user inputs.
dag: A fully specified DAG, using the best sets of parameter values found to satisfy the user targets. This DAG may be directly used for simulation purposes.
est_params: The final parameter values after optimization.
target_eval: A final estimate of the targets, evaluated on the final DAG. If the function call was successful, these should be very close to the desired target parameters. If n_reps_eval > 1, these correspond to the average over all simulation repetitions.
d_target_eval: If n_reps_eval > 1 was used, this object includes three data.tables, including the estimated targets on each simulation run, the differences to the desired values and the scaled differences.
optim_obj: If optim, optim2 or optimh was used, the resulting object from that function call.
uniroot: If uniroot was used, the resulting object from that function call.
Robin Denz
Denz, Robin and Nina Timmesfeld (2026). "Simulating Complex Cross-Sectional and Longitudinal Data using the simDAG R Package". Journal of Statistical Software 116 (2), doi: 10.18637/jss.v116.i02.
Austin, Peter C. 2023. "The Iterative Bisection Procedure: A Useful Tool for Determining Parameter Values in Data-Generating Processes in Monte Carlo Simulations."" BMC Medical Research Methodology 23 (45): 1–10.
Austin, Peter C., and James Stafford. 2008. "The Performance of Two Data-Generation Processes for Data with Specified Marginal Treatment Odds Ratios."" Communications in Statistics: Simulation and Computation 37 (6): 1039–51.
library(simDAG)
library(data.table)
# Example 1: Calibrate a single parameter to match a target prevalence.
# A finite parameter range allows automatic root finding.
ldag <- lazy_dag(
empty_dag() +
node("X", type="rnorm") +
node("A", type="binomial", formula= ~ param(intercept_A) + X*0.5)
)
targets <- list(
prevalence_A = list(
target = 0.4,
eval_fun = function(data) mean(data$A)
)
)
# For a single parameter, its specification can be supplied directly.
params <- list(
start = 0,
range = c(-5, 5)
)
fit <- calibrate_dag(
lazy_dag = ldag,
targets = targets,
params = params,
n_sim_run = 2000,
n_sim_eval = 5000,
seed = 123
)
summary(fit)
# use final DAG to simulate data
data <- sim_from_dag(fit$dag, n_sim=10)
head(data)
# Example 2: Jointly calibrate treatment and outcome prevalence.
# The DAG contains two free parameters and two targets.
# Parameter ranges are optional.
ldag <- lazy_dag(
empty_dag() +
node("X", type="rnorm") +
node("A", type="binomial",
formula= ~ param(intercept_A) + X * 0.5) +
node("Y", type="binomial",
formula= ~ param(intercept_Y) + A * 0.7 + X * 0.4)
)
targets <- list(
prevalence_A = list(
target = 0.4,
eval_fun = function(data) mean(data$A)
),
prevalence_Y = list(
target = 0.3,
eval_fun = function(data) mean(data$Y)
)
)
params <- list(
intercept_A = list(start = 0),
intercept_Y = list(start = 0)
)
fit <- calibrate_dag(
lazy_dag = ldag,
targets = targets,
params = params,
n_sim_run = 2000,
n_sim_eval = 5000,
seed = 123,
optim_control = list(
maxit = 200,
trace = 0,
ndeps = c(0.05, 0.05),
factr = 100
)
)
summary(fit)
# use final DAG to simulate data
data <- sim_from_dag(fit$dag, n_sim=10)
head(data)
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.