Skip to content

Simulation

SEM-based data simulator with a known confound structure. See the Simulating Data guide for a conceptual overview.

compute_r2s(sim)

Convenience function to empirically estimate the R² components from the simulated data:

Text Only
- r2_naive:      R²(y ~ true_X)
- r2_conf_only:  R²(y ~ all confounds)
- r2_full:       R²(y ~ true_X + all confounds)
- r2_unique_X:   r2_full - r2_conf_only

generate_confound_grid(r2_x_y_values=GRID_R2_X_Y, kappa_values=GRID_KAPPA, n_features=105, n_features_informative=10, n_confound_only_features=10, n_pure_signal_features=10, n_confounds=2, n_samples=5000, rho_informative=0.5, rho_confound_only=0.5, rho_pure_signal=0.5, randomize_signs=True, random_state=123)

Generate the full matrix of confound scenarios for the demonstration.

Rows sweep the brain–outcome strength R2_X_y (default {0.09, 0.36, 0.81}); columns sweep the spurious fraction kappa (default {0, 0.2, ..., 1.0}) — the fraction of the naive R² that is confound-driven (see :func:simulate_data_given_kappa). The true, deconfounded value in each cell is R2_X_y_given_Z = (1 - kappa) * R2_X_y.

Each cell contains three interpretable edge classes (default 10 each): mixed (signal + confound), pure-signal (⟂ confound) and confound-only.

Returns:

Type Description
dict keyed by ``(R2_X_y, kappa)`` → simulated-data dict (``X``, ``Z``, ``y``,
``true_X``, ``info``). Each cell gets an independent seed derived from
``random_state`` so cells are reproducible and mutually independent.

simulate_data_given_R2(R2_X_y, R2_X_y_given_Z, R2_Z_y, n_features=100, n_features_informative=10, n_confound_only_features=0, n_pure_signal_features=0, n_confounds=2, n_samples=10000, rho_informative=0.5, rho_confound_only=0.5, rho_pure_signal=0.5, randomize_signs=True, random_state=None)

Simulate data that matches specified R² relationships between a latent predictor X, confounds Z, and outcome y.

Inputs (targets)

R2_X_y : float Naive R² from regressing y ~ X. R2_X_y_given_Z : float Unique / incremental R² from X after adjusting for all confounds Z: R2_X_y_given_Z = R2(y ~ X + Z) - R2(y ~ Z). R2_Z_y : float Naive R² from regressing y ~ Z (all confounds jointly).

Data structure
  • latent variable: true_X (standardized)
  • confounds: conf1..confK (K = n_confounds); only conf1 carries the confound
  • features: up to four edge classes — mixed (copies of true_X; signal + confound), optional pure_signal (copies of the confound-orthogonalised signal), optional confound_only (copies of the confound), and pure-noise edges — see _build_dataset_from_corr.
  • outcome: y (standardized)
R² logic
  • Naive R²(y ~ X) ≈ R2_X_y
  • Naive R²(y ~ Z) ≈ R2_Z_y
  • Unique R² of X given Z ≈ R2_X_y_given_Z, so R2_full ≈ R2_Z_y + R2_X_y_given_Z

Returns:

Type Description
dict with keys ``X``, ``Z``, ``y``, ``true_X`` (numpy arrays) and ``info``.

simulate_data_given_kappa(R2_X_y, kappa, n_features=100, n_features_informative=10, n_confound_only_features=10, n_pure_signal_features=10, n_confounds=2, n_samples=10000, rho_informative=0.5, rho_confound_only=0.5, rho_pure_signal=0.5, randomize_signs=True, random_state=None)

Simulate data by sweeping the confound strength as the spurious fraction kappa — the fraction of the naive R²(y~X) that is confound-driven.

Convention (the confound "steals" a fraction kappa of the brain's explained variance, adding no net variance):

Text Only
R2_X_y_given_Z = (1 - kappa) * R2_X_y     (true, deconfounded value)
R2_Z_y         =      kappa  * R2_X_y

From these the required brain–confound coupling rho = corr(confound, X) is solved internally; it depends only on kappa (not on R2_X_y): rho ≈ {0, 0.45, 0.63, 0.77, 0.89, 0.99} for kappa = {0, .2, .4, .6, .8, 1}.

Unlike pinning corr(Z,y) = corr(X,y), this parameterisation is feasible for all R2_X_y (including 0.81) across the whole kappa sweep.

kappa = 0 → no confounding (true == naive); kappa = 1 → full confounding (true → 0; rho approaches its clamp at 0.99).

Parameters:

Name Type Description Default
R2_X_y float

Naive R²(y ~ X).

required
kappa float

Spurious fraction in [0, 1].

required

Returns:

Type Description
dict with keys ``X``, ``Z``, ``y``, ``true_X`` and ``info`` (the info dict also
records ``R2_X_y``, ``R2_X_y_given_Z``, ``R2_Z_y`` and ``spurious_fraction``).