Skip to content

Commit d65a0e7

Browse files
Add files via upload
1 parent 7e56220 commit d65a0e7

13 files changed

Lines changed: 1315 additions & 0 deletions

pyproject.toml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[build-system]
2+
requires = ["setuptools>=68", "wheel"]
3+
build-backend = "setuptools.build_meta"
4+
5+
[project]
6+
name = "rlearner"
7+
version = "0.1.0"
8+
description = "Python tools for R-learner nuisance estimation and CATE workflows."
9+
requires-python = ">=3.10"
10+
dependencies = [
11+
"numpy>=1.24",
12+
"scikit-learn>=1.3",
13+
"scipy>=1.11",
14+
]
15+
16+
[tool.setuptools]
17+
package-dir = {"" = "src"}
18+
19+
[tool.setuptools.packages.find]
20+
where = ["src"]

src/rlearner/__init__.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""Public package exports for the initial R-learner package."""
2+
3+
from .evaluation import (
4+
BLPTestResult,
5+
CalibrationBinResult,
6+
CalibrationTestResult,
7+
CoefficientInference,
8+
UpliftCurvePoint,
9+
UpliftTestResult,
10+
blp_test,
11+
calibration_test,
12+
uplift_test,
13+
)
14+
from .learner import RLearner
15+
from .nuisance import (
16+
CrossFittedNuisanceEstimator,
17+
ManualNuisanceEstimator,
18+
NuisanceResult,
19+
SuperLearnerClassifier,
20+
SuperLearnerRegressor,
21+
)
22+
from .r_loss import RLossStacking, RLossWrapper
23+
24+
__all__ = [
25+
"BLPTestResult",
26+
"CalibrationBinResult",
27+
"CalibrationTestResult",
28+
"CoefficientInference",
29+
"CrossFittedNuisanceEstimator",
30+
"ManualNuisanceEstimator",
31+
"NuisanceResult",
32+
"RLearner",
33+
"RLossStacking",
34+
"RLossWrapper",
35+
"SuperLearnerClassifier",
36+
"SuperLearnerRegressor",
37+
"UpliftCurvePoint",
38+
"UpliftTestResult",
39+
"blp_test",
40+
"calibration_test",
41+
"uplift_test",
42+
]
929 Bytes
Binary file not shown.
3.5 KB
Binary file not shown.
12.9 KB
Binary file not shown.
9.25 KB
Binary file not shown.
29.8 KB
Binary file not shown.
6.99 KB
Binary file not shown.

src/rlearner/_validation.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
"""Validation helpers shared by nuisance-estimation components."""
2+
3+
from __future__ import annotations
4+
5+
from typing import Any
6+
7+
import numpy as np
8+
from sklearn.utils.validation import check_array
9+
10+
11+
def validate_features(X: Any) -> np.ndarray:
12+
"""Validate and coerce a feature matrix to a dense 2D numpy array."""
13+
return check_array(X, ensure_2d=True, dtype=None, ensure_all_finite="allow-nan")
14+
15+
16+
def validate_vector(values: Any, *, name: str, n_samples: int | None = None) -> np.ndarray:
17+
"""Validate and coerce a one-dimensional target or prediction vector."""
18+
vector = check_array(values, ensure_2d=False, dtype=None, ensure_all_finite="allow-nan")
19+
vector = np.asarray(vector).reshape(-1)
20+
21+
if n_samples is not None and vector.shape[0] != n_samples:
22+
raise ValueError(f"{name} must have length {n_samples}, got {vector.shape[0]}.")
23+
24+
return vector
25+
26+
27+
def validate_binary_treatment(d: Any, *, n_samples: int | None = None) -> np.ndarray:
28+
"""Validate that treatment assignments are binary-coded."""
29+
treatment = validate_vector(d, name="d", n_samples=n_samples)
30+
unique_values = np.unique(treatment)
31+
32+
if unique_values.size > 2 or not np.all(np.isin(unique_values, [0, 1])):
33+
raise ValueError(
34+
"Binary treatment is required for the built-in nuisance estimators. "
35+
"Expected values in {0, 1}."
36+
)
37+
38+
return treatment.astype(int, copy=False)
39+
40+
41+
def validate_manual_predictions(
42+
*,
43+
y: Any,
44+
d: Any,
45+
y_hat: Any,
46+
d_hat: Any,
47+
propensity_clip: float,
48+
) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
49+
"""Validate manual nuisance predictions against observed outcomes and treatment."""
50+
y_array = validate_vector(y, name="y")
51+
d_array = validate_binary_treatment(d, n_samples=y_array.shape[0])
52+
y_hat_array = validate_vector(y_hat, name="y_hat", n_samples=y_array.shape[0])
53+
d_hat_array = validate_vector(d_hat, name="d_hat", n_samples=y_array.shape[0])
54+
55+
if np.any(~np.isfinite(y_hat_array)):
56+
raise ValueError("y_hat must contain only finite values.")
57+
if np.any(~np.isfinite(d_hat_array)):
58+
raise ValueError("d_hat must contain only finite values.")
59+
60+
d_hat_array = np.clip(d_hat_array.astype(float, copy=False), propensity_clip, 1.0 - propensity_clip)
61+
62+
return y_array, d_array, y_hat_array, d_hat_array

0 commit comments

Comments
 (0)