Spaces:
Running
Running
| from __future__ import annotations | |
| import json | |
| from pathlib import Path | |
| import joblib | |
| import numpy as np | |
| import pandas as pd | |
| import trackio | |
| from causal import estimate_effects, fit_nuisance, generate_scm | |
| PROJECT_DIR = Path(__file__).resolve().parent | |
| ARTIFACT_DIR = PROJECT_DIR / "artifacts" / "causal-forge" | |
| DATA_DIR = PROJECT_DIR / "data" | |
| REGIMES = { | |
| "both_correct": (True, True), | |
| "propensity_misspecified": (False, True), | |
| "outcome_misspecified": (True, False), | |
| "both_misspecified": (False, False), | |
| } | |
| ESTIMATORS = ["naive", "ipw", "outcome_regression", "aipw"] | |
| def summarize(records: list[dict]) -> dict: | |
| summary = {} | |
| for regime in REGIMES: | |
| subset = [record for record in records if record["regime"] == regime] | |
| truth = np.asarray([record["truth"] for record in subset]) | |
| regime_summary = {} | |
| for estimator in ESTIMATORS: | |
| estimates = np.asarray([record[estimator] for record in subset]) | |
| errors = estimates - truth | |
| regime_summary[estimator] = { | |
| "mean_estimate": float(estimates.mean()), | |
| "bias": float(errors.mean()), | |
| "mean_absolute_error": float(np.abs(errors).mean()), | |
| "rmse": float(np.sqrt(np.mean(errors**2))), | |
| } | |
| coverage = np.mean( | |
| [ | |
| record["aipw_ci_low"] <= record["truth"] <= record["aipw_ci_high"] | |
| for record in subset | |
| ] | |
| ) | |
| regime_summary["aipw"]["confidence_interval_coverage"] = float(coverage) | |
| summary[regime] = regime_summary | |
| return summary | |
| def save_dataset(sample) -> None: | |
| frame = pd.DataFrame(sample.x, columns=["x1", "x2", "x3"]) | |
| frame["treatment"] = sample.treatment | |
| frame["outcome"] = sample.outcome | |
| frame["true_propensity"] = sample.propensity | |
| frame["potential_outcome_control"] = sample.y0 | |
| frame["potential_outcome_treated"] = sample.y1 | |
| frame["individual_treatment_effect"] = sample.ite | |
| DATA_DIR.mkdir(parents=True, exist_ok=True) | |
| frame.to_parquet(DATA_DIR / "causal_benchmark.parquet", index=False) | |
| def main() -> None: | |
| replications = 100 | |
| samples_per_replication = 3_000 | |
| trackio.init( | |
| project="causal-forge", | |
| name="double-robustness-benchmark-v1", | |
| config={ | |
| "replications": replications, | |
| "samples_per_replication": samples_per_replication, | |
| "cross_fitting_folds": 5, | |
| "regimes": list(REGIMES), | |
| }, | |
| ) | |
| records = [] | |
| for replication in range(replications): | |
| sample = generate_scm(samples_per_replication, seed=9100 + replication) | |
| for regime, (propensity_correct, outcome_correct) in REGIMES.items(): | |
| estimates = estimate_effects( | |
| sample, | |
| propensity_correct=propensity_correct, | |
| outcome_correct=outcome_correct, | |
| folds=5, | |
| seed=replication, | |
| ) | |
| records.append( | |
| {"replication": replication, "regime": regime, **estimates} | |
| ) | |
| if (replication + 1) % 10 == 0: | |
| recent = records[-40:] | |
| trackio.log( | |
| { | |
| "replication": replication + 1, | |
| "aipw_recent_mae": float( | |
| np.mean( | |
| [ | |
| abs(record["aipw"] - record["truth"]) | |
| for record in recent | |
| ] | |
| ) | |
| ), | |
| } | |
| ) | |
| summary = summarize(records) | |
| benchmark = generate_scm(20_000, seed=12043) | |
| final_models = fit_nuisance( | |
| benchmark.x, | |
| benchmark.treatment, | |
| benchmark.outcome, | |
| propensity_correct=True, | |
| outcome_correct=True, | |
| ) | |
| save_dataset(benchmark) | |
| ARTIFACT_DIR.mkdir(parents=True, exist_ok=True) | |
| joblib.dump(final_models, ARTIFACT_DIR / "nuisance_models.joblib") | |
| results = { | |
| "benchmark": "Causal Forge", | |
| "replications": replications, | |
| "samples_per_replication": samples_per_replication, | |
| "cross_fitting_folds": 5, | |
| "structural_truth": "known heterogeneous individual treatment effects", | |
| "summary": summary, | |
| } | |
| (ARTIFACT_DIR / "evaluation.json").write_text( | |
| json.dumps(results, indent=2), encoding="utf-8" | |
| ) | |
| pd.DataFrame(records).to_parquet( | |
| ARTIFACT_DIR / "replication_estimates.parquet", index=False | |
| ) | |
| trackio.log( | |
| { | |
| "both_correct_aipw_mae": summary["both_correct"]["aipw"][ | |
| "mean_absolute_error" | |
| ], | |
| "both_correct_naive_mae": summary["both_correct"]["naive"][ | |
| "mean_absolute_error" | |
| ], | |
| "propensity_misspecified_aipw_mae": summary[ | |
| "propensity_misspecified" | |
| ]["aipw"]["mean_absolute_error"], | |
| "outcome_misspecified_aipw_mae": summary["outcome_misspecified"][ | |
| "aipw" | |
| ]["mean_absolute_error"], | |
| } | |
| ) | |
| trackio.finish() | |
| print(json.dumps(results, indent=2)) | |
| if __name__ == "__main__": | |
| main() | |