def load_ihdp(replications: int = 2, random_state: int = 1):
"""Load IHDP semi-synthetic benchmark (CausalML format)."""
base_url = "https://raw.githubusercontent.com/uber/causalml/master/docs/examples/data"
cols = ["treatment", "y_factual", "y_cfactual", "mu0", "mu1"] + [f"X{i}" for i in range(25)]
parts = []
for i in range(1, 10):
url = f"{base_url}/ihdp_npci_{i}.csv"
tmp = pd.read_csv(url, header=None)
tmp.columns = cols[: tmp.shape[1]]
parts.append(tmp)
df = pd.concat(parts, ignore_index=True)
if replications > 1:
df = pd.concat([df] * replications, ignore_index=True)
perm = list(range(6, 25)) + list(range(6))
xcols = [f"X{i}" for i in range(25)]
X = df[xcols].to_numpy(dtype=float)[:, perm]
treatment = df["treatment"].to_numpy(dtype=int)
y = df["y_factual"].to_numpy(dtype=float)
mu0 = df["mu0"].to_numpy(dtype=float)
mu1 = df["mu1"].to_numpy(dtype=float)
tau = np.where(
treatment == 1,
df["y_factual"] - df["y_cfactual"],
df["y_cfactual"] - df["y_factual"],
)
n = len(X)
rng = np.random.default_rng(random_state)
val_idx = rng.choice(n, size=int(0.2 * n), replace=False)
train_idx = np.setdiff1d(np.arange(n), val_idx)
return df, X, treatment, y, tau, mu0, mu1, train_idx, val_idx
def preprocess_ihdp_features(train_x, test_x, cont_cols=None):
"""Scale continuous covariates (cols 19–24 after binary-first permutation)."""
cont_cols = cont_cols or list(range(19, 25))
train = train_x.copy()
test = test_x.copy()
means = train[:, cont_cols].mean(axis=0)
sds = train[:, cont_cols].std(axis=0)
sds[sds == 0] = 1.0
train[:, cont_cols] = (train[:, cont_cols] - means) / sds
test[:, cont_cols] = (test[:, cont_cols] - means) / sds
return train, test
def synthetic_ihdp_fallback(n=5000, p=25, random_state=42):
rng = np.random.default_rng(random_state)
X = rng.normal(size=(n, p))
lin = 0.3 * X[:, 0] - 0.2 * X[:, 1] + 0.15 * X[:, 2]
tau = 0.5 + 0.2 * np.tanh(X[:, 4])
mu0 = lin + 0.1 * X[:, 6] ** 2
mu1 = mu0 + tau
ps = 1 / (1 + np.exp(-(0.2 * X[:, 0] - 0.1 * X[:, 1])))
t = rng.binomial(1, ps)
y = np.where(t == 1, mu1, mu0) + rng.normal(scale=1.0, size=n)
perm = list(range(6, 25)) + list(range(6))
return X[:, perm], t, y, mu0, mu1, tau
try:
_, X, treatment, y, tau, mu0, mu1, _, _ = load_ihdp(replications=2 if run_fast else 10)
except Exception:
X, treatment, y, mu0, mu1, tau = synthetic_ihdp_fallback()
n = len(X)
rng = np.random.default_rng(42)
idx = rng.permutation(n)
train_end, val_end = int(0.7 * n), int(0.85 * n)
tr_i, va_i, te_i = idx[:train_end], idx[train_end:val_end], idx[val_end:]
def grab(i):
return X[i], treatment[i], y[i], mu0[i], mu1[i]
X_train, t_train, y_train, mu0_tr, mu1_tr = grab(tr_i)
X_val, t_val, y_val, _, _ = grab(va_i)
X_test, t_test, y_test, mu0_te, mu1_te = grab(te_i)
X_train_s, _ = preprocess_ihdp_features(X_train, X_val)
_, X_val_s = preprocess_ihdp_features(X_train, X_val)
_, X_test_s = preprocess_ihdp_features(X_train, X_test)
print(f"Train: {len(tr_i)} | Val: {len(va_i)} | Test: {len(te_i)}")