| Title: | Domain-Agnostic Causal Simulation Engine with Mixed Variable Types |
|---|---|
| Description: | A domain-agnostic simulation engine for structural causal models with heterogeneous variable types. Users define systems of continuous, categorical, and ordinal features, declare a directed acyclic graph (DAG) over them, attach typed generative mechanisms to each node, and simulate synthetic populations. A pre-built mechanism library covers all nine parent-type/child-type combinations. Vine copula mechanisms estimate full joint dependence from observed data via pair-copula constructions (Bedford and Cooke, 2002) <doi:10.1214/aos/1031689014>. Individual-level counterfactual queries are supported through partial propagation, which freezes unaffected random draws and resamples only the downstream subgraph, enabling individual treatment effect (ITE) estimation alongside population-level average treatment effects (ATE). Any pre-fitted predictive model can serve as a generative mechanism through three wrapper functions. Systems serialise to JSON for cross-language portability with the companion Python package. |
| Authors: | Danilo Sarti [aut, cre] |
| Maintainer: | Danilo Sarti <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.1.2 |
| Built: | 2026-10-28 23:30:13 UTC |
| Source: | https://github.com/cran/lepidoptera |
Add a dependency (edge set + mechanism) to a system
add_dependency(system, parents, child, mechanism)add_dependency(system, parents, child, mechanism)
system |
A |
parents |
Character vector of parent feature names (use
|
child |
Character. Name of the dependent feature. Must already
exist (see |
mechanism |
A |
The updated system.
Add a feature to a system
add_feature(system, spec)add_feature(system, spec)
system |
A |
spec |
A |
The updated system.
For each feature in affected_features: mean/sd shift for continuous
features, level-frequency shift for categorical/ordinal features.
compare(result)compare(result)
result |
A |
A named list, one entry per affected feature.
Fits a copula on fit_data and returns a mechanism that draws the child
feature conditional on realised parent values. Supports both the built-in
Gaussian copula (base R, no extra packages) and the full family catalogue of
rvinecopulib (package must be installed for non-Gaussian families).
copula_mechanism( variables, family = "gaussian", marginals = "auto", nominal_handling = c("regression", "arbitrary_order"), fit_data = NULL )copula_mechanism( variables, family = "gaussian", marginals = "auto", nominal_handling = c("regression", "arbitrary_order"), fit_data = NULL )
variables |
Character vector. |
family |
Copula family.
|
marginals |
|
nominal_handling |
|
fit_data |
A |
A mechanism object (not JSON-serializable; spec = NULL).
v1 scope: distribution must be a marginal mechanism (no parents) –
the feature is resampled from distribution independently of its
original parents. Use mechanism_override() for a conditional
replacement.
do_distribution(target, distribution)do_distribution(target, distribution)
target |
Character. Feature name. |
distribution |
A marginal |
An intervention object.
Classic do-operator: disconnects target from its parents and sets it
to value for every unit.
do_fix(target, value)do_fix(target, value)
target |
Character. Feature name (or vector of names). |
value |
The fixed value (or vector of values, one per target). |
An intervention object.
Export a system to JSON or YAML
export_system(system, path, format = c("json", "yaml"))export_system(system, path, format = c("json", "yaml"))
system |
A |
path |
Output file path. |
format |
Character. |
Invisibly, path.
Creates a feature_spec: name, type, domain, and optional metadata.
See SPEC.md section 3.2.
feature_spec(name, type, domain, metadata = NULL)feature_spec(name, type, domain, metadata = NULL)
name |
Character. Unique identifier within the system. |
type |
Character. One of |
domain |
List. For |
metadata |
Optional named list, never interpreted by the core. |
A feature_spec object (S3 list with class "feature_spec").
Valid feature types
feature_types()feature_types()
A character vector of the allowed FeatureType values:
"continuous", "categorical", "ordinal".
Runs the invariants in SPEC.md section 3.5:
every feature has exactly one mechanism;
the dependency graph is acyclic;
every edge references existing features;
each mechanism's declared parent types match the actual types of the parents it was attached to;
the mechanism's output type matches the feature's declared type.
finalize_system(system)finalize_system(system)
system |
A |
The same system, marked finalized, ready for simulate().
Import a system from JSON or YAML
import_system(path, format = c("json", "yaml"))import_system(path, format = c("json", "yaml"))
path |
Input file path. |
format |
Character. |
A system object. Not yet finalized – call
finalize_system() before simulating.
v1 scope: parents must all be continuous columns of fit_data. For
a continuous child, fits OLS + Gaussian residual noise. For a
categorical/ordinal child, uses a k-nearest-neighbors empirical
sampler over standardized parent space.
learned_mechanism(parents, child, fit_data, uncertainty = "auto", k = 10L)learned_mechanism(parents, child, fit_data, uncertainty = "auto", k = 10L)
parents |
Character vector of continuous parent feature names. |
child |
Character. Name of the column in |
fit_data |
A |
uncertainty |
Reserved for future use; currently only |
k |
Number of neighbours for the categorical/ordinal branch. |
A mechanism object (not JSON-serializable – spec is
NULL, since it carries fitted parameters as a closure).
Returns "gaussian" always (built-in base-R path). When rvinecopulib
is installed the full parametric family list is prepended, together with
the meta-set names "auto", "all", "parametric", "onepar",
"twopar".
list_copula_families()list_copula_families()
Character vector of family identifiers accepted by
copula_mechanism().
A mechanism whose value is generated by an LLM call
llm_mechanism( prompt_template, model, parents, parent_types, output_type, levels = NULL, parser = "auto" )llm_mechanism( prompt_template, model, parents, parent_types, output_type, levels = NULL, parser = "auto" )
prompt_template |
A |
model |
Function |
parents |
Character vector of parent feature names. |
parent_types |
Character vector, one |
output_type |
One of |
levels |
Required when |
parser |
|
A mechanism object (not JSON-serializable – spec is
NULL, since it carries a live model function).
Pre-built mechanisms cover the type-combination matrix in SPEC.md
section 4. mechanism_library_get(key) returns a factory (a
function), not a ready mechanism – pre-built mechanisms always need
parameters. Call the factory with those parameters to obtain the
actual mechanism object, e.g.
mechanism_library_get("linear_gaussian")(coefs = list(age = 0.1), sd = 1).
mechanism_library_get(key)mechanism_library_get(key)
key |
Character. One of |
A factory function.
The replacement mechanism must declare the same number and types of
parents that target was originally given in add_dependency() (the
parent set itself is not changed by this intervention) – mismatches
surface as a validation error when the derived system is finalized.
mechanism_override(target, mechanism)mechanism_override(target, mechanism)
target |
Character. Feature name. |
mechanism |
A |
An intervention object.
Turns any model object the user has already trained (a ranger random
forest, a glm, an xgboost booster, a keras network, ...) into a
standard lepidoptera mechanism. The user provides a single
predict_fn that accepts a named list of parent values and returns one
predicted value.
model_mechanism( predict_fn, parents, parent_types, output_type, levels = NULL, name = "model_mechanism" )model_mechanism( predict_fn, parents, parent_types, output_type, levels = NULL, name = "model_mechanism" )
predict_fn |
A function |
parents |
Character vector of parent feature names (must match the
names present in |
parent_types |
Character vector of |
output_type |
One of |
levels |
Required when |
name |
Optional character label stored in the mechanism object.
Defaults to |
This differs from learned_mechanism() in a fundamental way:
learned_mechanism() fits a model internally on fit_data;
model_mechanism() uses a model that was already fitted outside
lepidoptera. The user is responsible for the predict function, including
reshaping inputs, choosing the right output slot, and optionally adding
stochastic noise.
Quick patterns:
# ranger random forest
rf <- ranger::ranger(outcome ~ age + bp, data = df, probability = TRUE)
mech <- model_mechanism(
predict_fn = function(pv) {
nd <- data.frame(age = as.numeric(pv$age), bp = as.numeric(pv$bp))
pred <- predict(rf, nd)$predictions
sample(colnames(pred), 1, prob = pred[1,])
},
parents = c("age", "bp"),
parent_types = c("continuous", "continuous"),
output_type = "categorical",
levels = c("no_response", "response")
)
# glm (logistic regression)
fit <- glm(response ~ age + dose, data = trial, family = binomial)
mech <- model_mechanism(
predict_fn = function(pv) {
nd <- data.frame(age = as.numeric(pv$age), dose = as.numeric(pv$dose))
p <- predict(fit, nd, type = "response")[[1]]
sample(c("yes","no"), 1, prob = c(p, 1-p))
},
parents = c("age", "dose"),
parent_types = c("continuous", "continuous"),
output_type = "categorical",
levels = c("yes", "no")
)
# lm (linear regression) with residual noise
fit_lm <- lm(sbp ~ age + bmi, data = cohort)
sigma <- summary(fit_lm)$sigma
mech <- model_mechanism(
predict_fn = function(pv) {
nd <- data.frame(age = as.numeric(pv$age), bmi = as.numeric(pv$bmi))
pred <- predict(fit_lm, nd)[[1]]
pred + rnorm(1, 0, sigma) # add residual noise manually
},
parents = c("age", "bmi"),
parent_types = c("continuous", "continuous"),
output_type = "continuous"
)
A mechanism object (not JSON-serializable – spec = NULL).
Create an empty system
new_system()new_system()
A system object with no features, edges, or mechanisms yet.
Requires the optional igraph package
(install.packages("igraph")).
plot_graph(system)plot_graph(system)
system |
A |
Invisibly, the igraph graph object plotted.
Apply interventions and propagate their effects
propagate( system, interventions, n = NULL, seed = NULL, mode = c("full", "partial") )propagate( system, interventions, n = NULL, seed = NULL, mode = c("full", "partial") )
system |
A finalized |
interventions |
A list of |
n |
Integer. Number of units to simulate for the baseline (required). |
seed |
Optional integer seed, shared between baseline and intervened population for paired comparison. |
mode |
Character. |
A simulation_result: a list with baseline, intervened,
interventions, affected_features, propagation_order.
Register a custom mechanism
register_mechanism(name, fn, parent_types, output_type)register_mechanism(name, fn, parent_types, output_type)
name |
Character. Key under which the mechanism is registered. |
fn |
Function with signature |
parent_types |
Character vector, one |
output_type |
One of |
The registered mechanism object.
Preserves the dependency on parents; applies fn to the value that the
original mechanism would have produced.
shift(target, fn)shift(target, fn)
target |
Character. Feature name. |
fn |
Function applied to the originally generated value. |
An intervention object.
Simulate a population from a system
simulate(system, n, seed = NULL)simulate(system, n, seed = NULL)
system |
A finalized |
n |
Integer. Number of units to generate. |
seed |
Optional integer seed for reproducibility. |
A data.frame with one row per unit and one column per feature,
typed according to each feature's FeatureType (factor for
categorical/ordinal columns, numeric for continuous ones).