Skip to content

Add random_state to AddUnobservedCommonCause for reproducible simulation refutation#1571

Open
immu4989 wants to merge 1 commit into
py-why:mainfrom
immu4989:fix/add-unobserved-common-cause-random-state
Open

Add random_state to AddUnobservedCommonCause for reproducible simulation refutation#1571
immu4989 wants to merge 1 commit into
py-why:mainfrom
immu4989:fix/add-unobserved-common-cause-random-state

Conversation

@immu4989
Copy link
Copy Markdown
Contributor

@immu4989 immu4989 commented Jun 6, 2026

Part of making DoWhy's refutation results reproducible (follow-up to #1556 and #1557; related: #556, #418).

What

The add_unobserved_common_cause refuter is non-deterministic and can't be seeded. In the direct-simulation path, _include_confounders_effect draws w_random = stdnorm.rvs(num_rows) with no random_state, so each run produces a different simulated confounder and a different refutation result. The residuals-based include_simulated_confounder path has the same issue via np.random.normal.

The class only exposes shuffle_random_seed, which applies solely to the non-parametric-partial-R2 path — there's no way to make the simulation-based refutation reproducible.

Changes

  • Add a random_state parameter (int or np.random.RandomState, default None) to AddUnobservedCommonCause and thread it through sensitivity_simulation_simulate_confounders_effect_once_include_confounders_effect (into stdnorm.rvs), and through include_simulated_confounder_generate_confounder_from_residuals (into the normal draw).
  • Convert int seeds to a np.random.RandomState once and pass the instance through, matching the convention already used by RandomCommonCause.
  • random_state=None preserves the existing (non-deterministic) behavior, so this is backward compatible.
  • Minor: the two outcomes = np.random.rand(len(...)) lines were only pre-allocating an array that's immediately overwritten in the loop; replaced with np.zeros(...) to avoid an unnecessary RNG draw.
  • Add regression tests asserting same-seed runs are identical and different-seed runs differ.

Usage

refute = model.refute_estimate(
    identified_estimand,
    estimate,
    method_name="add_unobserved_common_cause",
    confounders_effect_on_treatment="binary_flip",
    confounders_effect_on_outcome="linear",
    effect_strength_on_treatment=0.01,
    effect_strength_on_outcome=0.02,
    random_state=42,
)

Verification

  • Same seed → identical new_effect; different seeds → different; default path unchanged and still non-deterministic.
  • New tests in tests/causal_refuters/test_add_unobserved_common_cause.py pass; black/isort clean.

…ion refutation

Signed-off-by: Imran Ahamed <immu4989@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant