From d688549b192ff1aba79d82cbd897a509d4928e50 Mon Sep 17 00:00:00 2001 From: Jeong-Yoon Lee Date: Fri, 6 Mar 2026 13:54:48 -0800 Subject: [PATCH 1/3] Add .worktrees/ to .gitignore Co-Authored-By: Claude Opus 4.6 --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 2d446e8a..584da151 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,6 @@ uv.lock # Claude Code artifacts CLAUDE.md -.claude/ \ No newline at end of file +.claude/.worktrees/ +.worktrees/ +.worktrees/ From 829ff4b6bd1a826e5efe71381003e06a1b870b99 Mon Sep 17 00:00:00 2001 From: Jeong-Yoon Lee Date: Fri, 6 Mar 2026 16:03:47 -0800 Subject: [PATCH 2/3] Fix SensitivityPlaceboTreatment ignoring actual treatment groups (#491) Previously hardcoded `np.random.randint(2)` which always generated binary 0/1 treatment, ignoring multi-treatment groups and non-numeric treatment labels. Now uses `np.random.permutation(treatment)` to randomly shuffle actual treatment assignments, preserving the original group labels and distribution. Co-Authored-By: Claude Opus 4.6 --- causalml/metrics/sensitivity.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/causalml/metrics/sensitivity.py b/causalml/metrics/sensitivity.py index 3bd186d2..4240593d 100644 --- a/causalml/metrics/sensitivity.py +++ b/causalml/metrics/sensitivity.py @@ -282,7 +282,8 @@ def sensitivity_estimate(self): X = self.df[self.inference_features].values p = self.df[self.p_col].values - treatment_new = np.random.randint(2, size=num_rows) + treatment = self.df[self.treatment_col].values + treatment_new = np.random.permutation(treatment) y = self.df[self.outcome_col].values ate_new, ate_new_lower, ate_new_upper = self.get_ate_ci(X, p, treatment_new, y) From cc514fa69966f46b7ce1d087c2b2fff73af49082 Mon Sep 17 00:00:00 2001 From: Jeong-Yoon Lee Date: Fri, 6 Mar 2026 16:20:31 -0800 Subject: [PATCH 3/3] Address review: remove unused num_rows, add string label test - Remove unused `num_rows` variable (no longer needed after permutation fix) - Add test_SensitivityPlaceboTreatment_string_labels to verify fix works with non-numeric treatment labels ("control"/"treatment1") Co-Authored-By: Claude Opus 4.6 --- causalml/metrics/sensitivity.py | 2 -- tests/test_sensitivity.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/causalml/metrics/sensitivity.py b/causalml/metrics/sensitivity.py index 4240593d..c8f90092 100644 --- a/causalml/metrics/sensitivity.py +++ b/causalml/metrics/sensitivity.py @@ -278,8 +278,6 @@ def sensitivity_estimate(self): Returns: (pd.DataFrame): a summary dataframe """ - num_rows = self.df.shape[0] - X = self.df[self.inference_features].values p = self.df[self.p_col].values treatment = self.df[self.treatment_col].values diff --git a/tests/test_sensitivity.py b/tests/test_sensitivity.py index 7c0b0248..4062758e 100644 --- a/tests/test_sensitivity.py +++ b/tests/test_sensitivity.py @@ -104,6 +104,34 @@ def test_SensitivityPlaceboTreatment(): print(sens_summary) +def test_SensitivityPlaceboTreatment_string_labels(): + y, X, treatment, tau, b, e = synthetic_data( + mode=1, n=100000, p=NUM_FEATURES, sigma=1.0 + ) + + # Convert binary treatment to string labels + treatment_str = np.where(treatment == 1, "treatment1", "control") + + INFERENCE_FEATURES = ["feature_" + str(i) for i in range(NUM_FEATURES)] + df = pd.DataFrame(X, columns=INFERENCE_FEATURES) + df[TREATMENT_COL] = treatment_str + df[OUTCOME_COL] = y + df[SCORE_COL] = e + + learner = BaseXLearner(LinearRegression(), control_name="control") + sens = SensitivityPlaceboTreatment( + df=df, + inference_features=INFERENCE_FEATURES, + p_col=SCORE_COL, + treatment_col=TREATMENT_COL, + outcome_col=OUTCOME_COL, + learner=learner, + ) + + sens_summary = sens.summary(method="Placebo Treatment") + print(sens_summary) + + def test_SensitivityRandomCause(): y, X, treatment, tau, b, e = synthetic_data( mode=1, n=100000, p=NUM_FEATURES, sigma=1.0