cbps 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cbps/__init__.py +3462 -0
- cbps/constants.py +46 -0
- cbps/core/__init__.py +93 -0
- cbps/core/cbps_binary.py +1943 -0
- cbps/core/cbps_continuous.py +945 -0
- cbps/core/cbps_multitreat.py +1123 -0
- cbps/core/cbps_optimal.py +507 -0
- cbps/core/results.py +1447 -0
- cbps/data/Blackwell.csv +571 -0
- cbps/data/LaLonde.csv +3213 -0
- cbps/data/npcbps_continuous_sim.csv +501 -0
- cbps/data/nsw.csv +723 -0
- cbps/data/nsw_dw.csv +446 -0
- cbps/data/political_ads_urban_niebler.csv +16266 -0
- cbps/data/psid_controls.csv +2491 -0
- cbps/data/psid_controls2.csv +254 -0
- cbps/data/psid_controls3.csv +129 -0
- cbps/data/simulation_dgp1_seed12345.csv +201 -0
- cbps/data/simulation_dgp2_seed12345.csv +201 -0
- cbps/data/simulation_dgp3_seed12345.csv +201 -0
- cbps/data/simulation_dgp4_seed12345.csv +201 -0
- cbps/datasets/__init__.py +78 -0
- cbps/datasets/blackwell.py +112 -0
- cbps/datasets/continuous.py +223 -0
- cbps/datasets/lalonde.py +272 -0
- cbps/datasets/npcbps_sim.py +101 -0
- cbps/diagnostics/__init__.py +101 -0
- cbps/diagnostics/balance.py +760 -0
- cbps/diagnostics/balance_cbmsm_addon.py +162 -0
- cbps/diagnostics/continuous_diagnostics.py +259 -0
- cbps/diagnostics/normality.py +173 -0
- cbps/diagnostics/ocbps_conditions.py +197 -0
- cbps/diagnostics/overlap.py +198 -0
- cbps/diagnostics/plots.py +1193 -0
- cbps/diagnostics/weights_diag.py +205 -0
- cbps/highdim/__init__.py +84 -0
- cbps/highdim/gmm_loss.py +340 -0
- cbps/highdim/hdcbps.py +1078 -0
- cbps/highdim/lasso_utils.py +498 -0
- cbps/highdim/weight_funcs.py +298 -0
- cbps/inference/__init__.py +42 -0
- cbps/inference/asyvar.py +621 -0
- cbps/inference/vcov_outcome.py +217 -0
- cbps/iv/__init__.py +48 -0
- cbps/iv/cbiv.py +2603 -0
- cbps/logging_config.py +45 -0
- cbps/msm/__init__.py +45 -0
- cbps/msm/cbmsm.py +1871 -0
- cbps/msm/rank_diagnostics.py +112 -0
- cbps/nonparametric/__init__.py +58 -0
- cbps/nonparametric/cholesky_whitening.py +232 -0
- cbps/nonparametric/empirical_likelihood.py +339 -0
- cbps/nonparametric/npcbps.py +1036 -0
- cbps/nonparametric/taylor_approx.py +207 -0
- cbps/py.typed +0 -0
- cbps/sklearn/__init__.py +42 -0
- cbps/sklearn/estimator.py +378 -0
- cbps/utils/__init__.py +82 -0
- cbps/utils/formula.py +415 -0
- cbps/utils/helpers.py +378 -0
- cbps/utils/numerics.py +438 -0
- cbps/utils/r_compat.py +109 -0
- cbps/utils/validation.py +224 -0
- cbps/utils/variance_transform.py +483 -0
- cbps/utils/weights.py +586 -0
- cbps-0.2.0.dist-info/METADATA +1090 -0
- cbps-0.2.0.dist-info/RECORD +70 -0
- cbps-0.2.0.dist-info/WHEEL +5 -0
- cbps-0.2.0.dist-info/licenses/LICENSE +661 -0
- cbps-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Continuous Treatment Dataset Loaders
|
|
3
|
+
=====================================
|
|
4
|
+
|
|
5
|
+
This module provides datasets for continuous treatment CBPS methods, including
|
|
6
|
+
simulation data and real-world application data from Fong, Hazlett, and Imai (2018).
|
|
7
|
+
|
|
8
|
+
The simulation data enables systematic evaluation of CBPS estimators under
|
|
9
|
+
various model misspecification scenarios. The political ads dataset provides
|
|
10
|
+
a real-world application for continuous treatment effect estimation.
|
|
11
|
+
|
|
12
|
+
References
|
|
13
|
+
----------
|
|
14
|
+
Fong, C., Hazlett, C., and Imai, K. (2018). Covariate balancing propensity
|
|
15
|
+
score for a continuous treatment: Application to the efficacy of political
|
|
16
|
+
advertisements. *The Annals of Applied Statistics*, 12(1), 156-177.
|
|
17
|
+
DOI: 10.1214/17-AOAS1101
|
|
18
|
+
|
|
19
|
+
Urban, C. and Niebler, S. (2014). Dollars on the sidewalk: Should U.S.
|
|
20
|
+
Presidential candidates advertise in uncontested states? *American Journal
|
|
21
|
+
of Political Science*, 58(2), 322-336.
|
|
22
|
+
"""
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Tuple, Dict, Any
|
|
25
|
+
import pandas as pd
|
|
26
|
+
import numpy as np
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def load_continuous_simulation(
|
|
30
|
+
dgp: int = 1,
|
|
31
|
+
seed: int = 12345
|
|
32
|
+
) -> Tuple[pd.DataFrame, Dict[str, Any]]:
|
|
33
|
+
"""Load continuous treatment simulation data (Fong et al. 2018, Section 4).
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
dgp : {1, 2, 3, 4}, default=1
|
|
38
|
+
Data generating process type:
|
|
39
|
+
|
|
40
|
+
- **DGP1**: Both treatment and outcome correctly specified (linear)
|
|
41
|
+
- **DGP2**: Treatment model misspecified (contains (X2+0.5)² nonlinear term)
|
|
42
|
+
- **DGP3**: Outcome model misspecified (contains 2(X2+0.5)² nonlinear term)
|
|
43
|
+
- **DGP4**: Doubly misspecified (both models incorrect)
|
|
44
|
+
|
|
45
|
+
seed : int, default=12345
|
|
46
|
+
Random seed (current CSV files use seed=12345).
|
|
47
|
+
|
|
48
|
+
Returns
|
|
49
|
+
-------
|
|
50
|
+
data : DataFrame
|
|
51
|
+
Simulation data (200 observations × 12 columns: T, Y, X1-X10).
|
|
52
|
+
metadata : dict
|
|
53
|
+
Metadata dictionary containing:
|
|
54
|
+
|
|
55
|
+
- ``'dgp'``: DGP type (1-4)
|
|
56
|
+
- ``'n'``: Number of observations (200)
|
|
57
|
+
- ``'k'``: Number of covariates (10)
|
|
58
|
+
- ``'true_ate'``: True ATE (1.0)
|
|
59
|
+
- ``'cov_structure'``: Covariance structure (equicorrelated MVN, ρ=0.2)
|
|
60
|
+
|
|
61
|
+
Notes
|
|
62
|
+
-----
|
|
63
|
+
Based on Fong, Hazlett, and Imai (2018) Section 4.1 simulation design.
|
|
64
|
+
|
|
65
|
+
**Column Ordering**: T (treatment), Y (outcome), X1-X10 (covariates).
|
|
66
|
+
|
|
67
|
+
**Paper Benchmarks** (500 Monte Carlo replications):
|
|
68
|
+
|
|
69
|
+
- DGP1: CBGPS balance F-statistic < 0.01 (near-perfect balance)
|
|
70
|
+
- DGP2: CBGPS F < 0.1 (robust despite treatment misspecification)
|
|
71
|
+
- DGP3: CBGPS F < 0.1 (robust despite outcome misspecification)
|
|
72
|
+
- DGP4: All methods biased (double misspecification, expected)
|
|
73
|
+
|
|
74
|
+
References
|
|
75
|
+
----------
|
|
76
|
+
Fong, C., Hazlett, C., and Imai, K. (2018). Covariate balancing propensity
|
|
77
|
+
score for a continuous treatment: Application to the efficacy of political
|
|
78
|
+
advertisements. *The Annals of Applied Statistics*, 12(1), 156-177.
|
|
79
|
+
|
|
80
|
+
Examples
|
|
81
|
+
--------
|
|
82
|
+
>>> data, meta = load_continuous_simulation(dgp=1, seed=12345)
|
|
83
|
+
>>> data.shape
|
|
84
|
+
(200, 12)
|
|
85
|
+
>>> meta['true_ate']
|
|
86
|
+
1.0
|
|
87
|
+
>>> list(data.columns)
|
|
88
|
+
['T', 'Y', 'X1', 'X2', 'X3', 'X4', 'X5', 'X6', 'X7', 'X8', 'X9', 'X10']
|
|
89
|
+
"""
|
|
90
|
+
if dgp not in [1, 2, 3, 4]:
|
|
91
|
+
raise ValueError(f"dgp must be one of [1, 2, 3, 4], got: {dgp}")
|
|
92
|
+
|
|
93
|
+
# Get data directory (supports both installed and development modes)
|
|
94
|
+
package_data_dir = Path(__file__).parent.parent / "data"
|
|
95
|
+
if package_data_dir.exists():
|
|
96
|
+
data_dir = package_data_dir
|
|
97
|
+
else:
|
|
98
|
+
# Fall back to project root data directory (development mode)
|
|
99
|
+
current_file = Path(__file__)
|
|
100
|
+
cbps_package = current_file.parent.parent
|
|
101
|
+
cbps_python = cbps_package.parent
|
|
102
|
+
project_root = cbps_python.parent
|
|
103
|
+
data_dir = project_root / "data"
|
|
104
|
+
|
|
105
|
+
if not data_dir.exists():
|
|
106
|
+
raise FileNotFoundError(
|
|
107
|
+
f"Cannot find data directory. Tried:\n"
|
|
108
|
+
f" 1. Package data: {package_data_dir}\n"
|
|
109
|
+
f" 2. Project data: {data_dir}\n"
|
|
110
|
+
f"Please ensure the package is properly installed with data files."
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# Construct filename
|
|
114
|
+
filename = f"simulation_dgp{dgp}_seed{seed}.csv"
|
|
115
|
+
filepath = data_dir / filename
|
|
116
|
+
|
|
117
|
+
# Read CSV file
|
|
118
|
+
df = pd.read_csv(filepath, dtype=np.float64)
|
|
119
|
+
|
|
120
|
+
# Metadata
|
|
121
|
+
metadata = {
|
|
122
|
+
'dgp': dgp,
|
|
123
|
+
'n': 200,
|
|
124
|
+
'k': 10,
|
|
125
|
+
'true_ate': 1.0,
|
|
126
|
+
'cov_structure': 'Equicorrelated MVN with rho=0.2',
|
|
127
|
+
'sigma_xi_sq': 4.0 if dgp == 1 else 2.25,
|
|
128
|
+
'sigma_epsilon_sq': 25.0,
|
|
129
|
+
'misspecified_treatment': dgp in [2, 4],
|
|
130
|
+
'misspecified_outcome': dgp in [3, 4]
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return df, metadata
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def load_political_ads() -> Tuple[pd.DataFrame, Dict[str, Any]]:
|
|
137
|
+
"""Load political advertising data (Urban and Niebler 2014).
|
|
138
|
+
|
|
139
|
+
Returns
|
|
140
|
+
-------
|
|
141
|
+
data : DataFrame
|
|
142
|
+
Political advertising data (16265 observations × 155 columns).
|
|
143
|
+
metadata : dict
|
|
144
|
+
Metadata containing:
|
|
145
|
+
|
|
146
|
+
- ``'n'``: 16265 (number of observations)
|
|
147
|
+
- ``'p'``: 155 (number of columns)
|
|
148
|
+
- ``'treatment_col'``: 'TotAds' (total advertising count)
|
|
149
|
+
- ``'boxcox_lambda'``: -0.16 (transformation parameter)
|
|
150
|
+
- ``'paper_f_stat_cbgps'``: 9.33e-5 (paper benchmark)
|
|
151
|
+
|
|
152
|
+
Notes
|
|
153
|
+
-----
|
|
154
|
+
**Treatment Variable**: TotAds (advertising count, range: 0-22,380).
|
|
155
|
+
|
|
156
|
+
The treatment requires Box-Cox transformation (λ=-0.16) for approximate
|
|
157
|
+
normality before applying continuous treatment CBPS.
|
|
158
|
+
|
|
159
|
+
**Paper Benchmarks** (Fong et al. 2018, Section 5):
|
|
160
|
+
|
|
161
|
+
- Table 1: 15 covariates with Pearson correlation < 0.001 (CBGPS)
|
|
162
|
+
- Figure 3: Full model F-statistic = 9.33e-5 (CBGPS) vs 215.3 (MLE)
|
|
163
|
+
|
|
164
|
+
References
|
|
165
|
+
----------
|
|
166
|
+
Urban, C. and Niebler, S. (2014). Dollars on the sidewalk: Should U.S.
|
|
167
|
+
Presidential candidates advertise in uncontested states? *American Journal
|
|
168
|
+
of Political Science*, 58(2), 322-336.
|
|
169
|
+
|
|
170
|
+
Fong, C., Hazlett, C., and Imai, K. (2018). Covariate balancing propensity
|
|
171
|
+
score for a continuous treatment. *The Annals of Applied Statistics*,
|
|
172
|
+
12(1), 156-177.
|
|
173
|
+
|
|
174
|
+
Examples
|
|
175
|
+
--------
|
|
176
|
+
>>> data, meta = load_political_ads()
|
|
177
|
+
>>> data.shape
|
|
178
|
+
(16265, 155)
|
|
179
|
+
>>> 'TotAds' in data.columns
|
|
180
|
+
True
|
|
181
|
+
>>> meta['boxcox_lambda']
|
|
182
|
+
-0.16
|
|
183
|
+
"""
|
|
184
|
+
# Get data directory (supports both installed and development modes)
|
|
185
|
+
package_data_dir = Path(__file__).parent.parent / "data"
|
|
186
|
+
if package_data_dir.exists():
|
|
187
|
+
data_dir = package_data_dir
|
|
188
|
+
else:
|
|
189
|
+
# Fall back to project root data directory (development mode)
|
|
190
|
+
current_file = Path(__file__)
|
|
191
|
+
cbps_package = current_file.parent.parent
|
|
192
|
+
cbps_python = cbps_package.parent
|
|
193
|
+
project_root = cbps_python.parent
|
|
194
|
+
data_dir = project_root / "data"
|
|
195
|
+
|
|
196
|
+
if not data_dir.exists():
|
|
197
|
+
raise FileNotFoundError(
|
|
198
|
+
f"Cannot find data directory. Tried:\n"
|
|
199
|
+
f" 1. Package data: {package_data_dir}\n"
|
|
200
|
+
f" 2. Project data: {data_dir}\n"
|
|
201
|
+
f"Please ensure the package is properly installed with data files."
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
filepath = data_dir / "political_ads_urban_niebler.csv"
|
|
205
|
+
|
|
206
|
+
# Read CSV file
|
|
207
|
+
df = pd.read_csv(filepath, low_memory=False)
|
|
208
|
+
|
|
209
|
+
metadata = {
|
|
210
|
+
'n': 16265,
|
|
211
|
+
'p': 155,
|
|
212
|
+
'treatment_col': 'TotAds',
|
|
213
|
+
'treatment_range': (0, 22380),
|
|
214
|
+
'boxcox_lambda': -0.16,
|
|
215
|
+
'paper_f_stat_cbgps': 9.33e-5,
|
|
216
|
+
'paper_f_stat_mle': 215.3,
|
|
217
|
+
'covariates_count': 15,
|
|
218
|
+
'squared_terms_count': 8
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return df, metadata
|
|
222
|
+
|
|
223
|
+
|
cbps/datasets/lalonde.py
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LaLonde Dataset Loaders
|
|
3
|
+
========================
|
|
4
|
+
|
|
5
|
+
This module provides functions to load the National Supported Work (NSW)
|
|
6
|
+
demonstration dataset, widely used as a benchmark in propensity score and
|
|
7
|
+
causal inference research.
|
|
8
|
+
|
|
9
|
+
The NSW demonstration was a labor training program conducted in the mid-1970s.
|
|
10
|
+
LaLonde (1986) used this dataset to evaluate econometric methods for estimating
|
|
11
|
+
treatment effects from observational data by comparing results to experimental
|
|
12
|
+
benchmarks.
|
|
13
|
+
|
|
14
|
+
References
|
|
15
|
+
----------
|
|
16
|
+
LaLonde, R. J. (1986). Evaluating the econometric evaluations of training
|
|
17
|
+
programs with experimental data. *American Economic Review*, 76(4), 604-620.
|
|
18
|
+
|
|
19
|
+
Dehejia, R. H. and Wahba, S. (1999). Causal effects in nonexperimental
|
|
20
|
+
studies: Reevaluating the evaluation of training programs. *Journal of the
|
|
21
|
+
American Statistical Association*, 94(448), 1053-1062.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Tuple, Union
|
|
26
|
+
import pandas as pd
|
|
27
|
+
import numpy as np
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _get_data_dir() -> Path:
|
|
31
|
+
"""
|
|
32
|
+
Get the absolute path to the data directory.
|
|
33
|
+
|
|
34
|
+
Supports two modes:
|
|
35
|
+
1. Installed mode: Load from package data directory (site-packages/cbps/data/)
|
|
36
|
+
2. Development mode: Fall back to project root data directory
|
|
37
|
+
|
|
38
|
+
Priority is given to the package data directory; if it does not exist,
|
|
39
|
+
falls back to the project root data directory.
|
|
40
|
+
|
|
41
|
+
Returns
|
|
42
|
+
-------
|
|
43
|
+
Path
|
|
44
|
+
Absolute path to the data directory.
|
|
45
|
+
"""
|
|
46
|
+
# Option 1: Try package data directory (installed mode)
|
|
47
|
+
package_data_dir = Path(__file__).parent.parent / "data"
|
|
48
|
+
if package_data_dir.exists():
|
|
49
|
+
return package_data_dir
|
|
50
|
+
|
|
51
|
+
# Option 2: Fall back to project root data directory (development mode)
|
|
52
|
+
current_file = Path(__file__)
|
|
53
|
+
cbps_package = current_file.parent.parent
|
|
54
|
+
cbps_python = cbps_package.parent
|
|
55
|
+
project_root = cbps_python.parent
|
|
56
|
+
project_data_dir = project_root / "data"
|
|
57
|
+
|
|
58
|
+
if project_data_dir.exists():
|
|
59
|
+
return project_data_dir
|
|
60
|
+
|
|
61
|
+
# Raise clear error if neither exists
|
|
62
|
+
raise FileNotFoundError(
|
|
63
|
+
f"Cannot find data directory. Tried:\n"
|
|
64
|
+
f" 1. Package data: {package_data_dir}\n"
|
|
65
|
+
f" 2. Project data: {project_data_dir}\n"
|
|
66
|
+
f"Please ensure the package is properly installed with data files."
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def load_lalonde(
|
|
71
|
+
dehejia_wahba_only: bool = True,
|
|
72
|
+
return_X_y: bool = False
|
|
73
|
+
) -> Union[pd.DataFrame, Tuple[pd.DataFrame, pd.Series]]:
|
|
74
|
+
"""Load the LaLonde dataset (NSW job training program evaluation).
|
|
75
|
+
|
|
76
|
+
Parameters
|
|
77
|
+
----------
|
|
78
|
+
dehejia_wahba_only : bool, default=True
|
|
79
|
+
If True, load the Dehejia-Wahba subsample (445 observations).
|
|
80
|
+
If False, load the full LaLonde dataset (3212 observations).
|
|
81
|
+
return_X_y : bool, default=False
|
|
82
|
+
If True, return (X, y) tuple; otherwise return DataFrame.
|
|
83
|
+
|
|
84
|
+
Returns
|
|
85
|
+
-------
|
|
86
|
+
DataFrame or (DataFrame, Series)
|
|
87
|
+
If return_X_y=False: Complete DataFrame with standardized column names.
|
|
88
|
+
If return_X_y=True: (covariates, treatment) tuple.
|
|
89
|
+
|
|
90
|
+
Notes
|
|
91
|
+
-----
|
|
92
|
+
The dataset contains data from the National Supported Work (NSW)
|
|
93
|
+
demonstration, a labor training program conducted in the mid-1970s.
|
|
94
|
+
|
|
95
|
+
**Variables**
|
|
96
|
+
|
|
97
|
+
- ``treat``: Treatment indicator (1=received training, 0=control)
|
|
98
|
+
- ``age``: Age in years
|
|
99
|
+
- ``educ``: Years of education
|
|
100
|
+
- ``black``: African American indicator
|
|
101
|
+
- ``hisp``: Hispanic indicator
|
|
102
|
+
- ``married``: Marital status indicator
|
|
103
|
+
- ``nodegr``: No high school degree indicator
|
|
104
|
+
- ``re74``, ``re75``: Real earnings in 1974, 1975 (pre-treatment)
|
|
105
|
+
- ``re78``: Real earnings in 1978 (outcome)
|
|
106
|
+
|
|
107
|
+
**Data Dimensions**
|
|
108
|
+
|
|
109
|
+
- Dehejia-Wahba subsample: 445 observations, 11 columns
|
|
110
|
+
- Full dataset: 3212 observations, 12 columns
|
|
111
|
+
|
|
112
|
+
References
|
|
113
|
+
----------
|
|
114
|
+
LaLonde, R. J. (1986). Evaluating the econometric evaluations of training
|
|
115
|
+
programs with experimental data. *American Economic Review*, 76(4), 604-620.
|
|
116
|
+
|
|
117
|
+
Dehejia, R. H. and Wahba, S. (1999). Causal effects in nonexperimental
|
|
118
|
+
studies: Reevaluating the evaluation of training programs. *Journal of the
|
|
119
|
+
American Statistical Association*, 94(448), 1053-1062.
|
|
120
|
+
|
|
121
|
+
Examples
|
|
122
|
+
--------
|
|
123
|
+
>>> df = load_lalonde(dehejia_wahba_only=True)
|
|
124
|
+
>>> df.shape
|
|
125
|
+
(445, 11)
|
|
126
|
+
>>> 'educ' in df.columns
|
|
127
|
+
True
|
|
128
|
+
"""
|
|
129
|
+
data_dir = _get_data_dir()
|
|
130
|
+
|
|
131
|
+
if dehejia_wahba_only:
|
|
132
|
+
# Load Dehejia-Wahba subsample
|
|
133
|
+
filepath = data_dir / "nsw_dw.csv"
|
|
134
|
+
df = pd.read_csv(filepath)
|
|
135
|
+
|
|
136
|
+
# Standardize column names
|
|
137
|
+
column_mapping = {
|
|
138
|
+
'education': 'educ',
|
|
139
|
+
'hispanic': 'hisp',
|
|
140
|
+
'nodegree': 'nodegr'
|
|
141
|
+
}
|
|
142
|
+
df = df.rename(columns=column_mapping)
|
|
143
|
+
|
|
144
|
+
# Set appropriate dtypes
|
|
145
|
+
df = df.astype({
|
|
146
|
+
'treat': np.int64, 'age': np.int64, 'educ': np.int64,
|
|
147
|
+
'black': np.int64, 'hisp': np.int64, 'married': np.int64,
|
|
148
|
+
'nodegr': np.int64, 're74': np.float64, 're75': np.float64,
|
|
149
|
+
're78': np.float64
|
|
150
|
+
})
|
|
151
|
+
else:
|
|
152
|
+
# Load full LaLonde dataset
|
|
153
|
+
filepath = data_dir / "LaLonde.csv"
|
|
154
|
+
df = pd.read_csv(filepath)
|
|
155
|
+
# Column names already standardized in this file
|
|
156
|
+
|
|
157
|
+
if return_X_y:
|
|
158
|
+
y = df['treat']
|
|
159
|
+
X = df.drop('treat', axis=1)
|
|
160
|
+
return X, y
|
|
161
|
+
else:
|
|
162
|
+
return df
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def load_lalonde_psid_combined(
|
|
166
|
+
psid_version: str = 'main',
|
|
167
|
+
return_combined: bool = True
|
|
168
|
+
) -> Union[Tuple[pd.DataFrame, pd.DataFrame], pd.DataFrame]:
|
|
169
|
+
"""Load NSW experimental and PSID control group data for evaluation bias tests.
|
|
170
|
+
|
|
171
|
+
Parameters
|
|
172
|
+
----------
|
|
173
|
+
psid_version : {'main', 'controls2', 'controls3'}, default='main'
|
|
174
|
+
PSID control group version:
|
|
175
|
+
|
|
176
|
+
- ``'main'``: Full PSID controls (2490 observations)
|
|
177
|
+
- ``'controls2'``: Restricted PSID controls (253 observations)
|
|
178
|
+
- ``'controls3'``: Further restricted PSID controls (128 observations)
|
|
179
|
+
|
|
180
|
+
return_combined : bool, default=True
|
|
181
|
+
If True (default), return combined DataFrame.
|
|
182
|
+
If False, return (nsw_exp, psid_ctrl) tuple.
|
|
183
|
+
|
|
184
|
+
Returns
|
|
185
|
+
-------
|
|
186
|
+
combined : DataFrame (default)
|
|
187
|
+
Combined DataFrame with standardized column names.
|
|
188
|
+
|
|
189
|
+
- main version: (3212, 11)
|
|
190
|
+
- controls2 version: (975, 11)
|
|
191
|
+
- controls3 version: (850, 11)
|
|
192
|
+
|
|
193
|
+
(nsw_exp, psid_ctrl) : tuple of DataFrame (when return_combined=False)
|
|
194
|
+
Tuple of NSW experimental sample and PSID control sample.
|
|
195
|
+
|
|
196
|
+
- nsw_exp: (722, 10) - NSW experimental sample (missing re74)
|
|
197
|
+
- psid_ctrl: (2490, 11) or (253, 11) or (128, 11) - PSID controls
|
|
198
|
+
|
|
199
|
+
Notes
|
|
200
|
+
-----
|
|
201
|
+
Used for reproducing evaluation bias tests from Smith and Todd (2005) and
|
|
202
|
+
the original CBPS paper. The NSW sample contains experimental participants,
|
|
203
|
+
while the PSID samples serve as observational comparison groups.
|
|
204
|
+
|
|
205
|
+
**Data Dimensions**
|
|
206
|
+
|
|
207
|
+
- main: nsw=(722, 10), psid=(2490, 11), combined=(3212, 11)
|
|
208
|
+
- controls2: nsw=(722, 10), psid=(253, 11), combined=(975, 11)
|
|
209
|
+
- controls3: nsw=(722, 10), psid=(128, 11), combined=(850, 11)
|
|
210
|
+
|
|
211
|
+
References
|
|
212
|
+
----------
|
|
213
|
+
Smith, J. A. and Todd, P. E. (2005). Does matching overcome LaLonde's
|
|
214
|
+
critique of nonexperimental estimators? *Journal of Econometrics*,
|
|
215
|
+
125(1-2), 305-353.
|
|
216
|
+
|
|
217
|
+
Examples
|
|
218
|
+
--------
|
|
219
|
+
>>> df = load_lalonde_psid_combined()
|
|
220
|
+
>>> df.shape
|
|
221
|
+
(3212, 11)
|
|
222
|
+
>>> int((df['treat'] == 0).sum()), int((df['treat'] == 1).sum())
|
|
223
|
+
(2915, 297)
|
|
224
|
+
>>> 'educ' in df.columns
|
|
225
|
+
True
|
|
226
|
+
|
|
227
|
+
>>> # Return separate DataFrames
|
|
228
|
+
>>> nsw_exp, psid_ctrl = load_lalonde_psid_combined(return_combined=False)
|
|
229
|
+
>>> nsw_exp.shape, psid_ctrl.shape
|
|
230
|
+
((722, 10), (2490, 11))
|
|
231
|
+
"""
|
|
232
|
+
data_dir = _get_data_dir()
|
|
233
|
+
|
|
234
|
+
# Load NSW experimental sample (722 observations, 10 columns - missing re74)
|
|
235
|
+
nsw = pd.read_csv(data_dir / "nsw.csv")
|
|
236
|
+
|
|
237
|
+
# Standardize column names to match package convention
|
|
238
|
+
column_mapping = {
|
|
239
|
+
'education': 'educ',
|
|
240
|
+
'hispanic': 'hisp',
|
|
241
|
+
'nodegree': 'nodegr'
|
|
242
|
+
}
|
|
243
|
+
nsw = nsw.rename(columns=column_mapping)
|
|
244
|
+
|
|
245
|
+
# Load corresponding PSID control group
|
|
246
|
+
psid_files = {
|
|
247
|
+
'main': 'psid_controls.csv',
|
|
248
|
+
'controls2': 'psid_controls2.csv',
|
|
249
|
+
'controls3': 'psid_controls3.csv'
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if psid_version not in psid_files:
|
|
253
|
+
raise ValueError(
|
|
254
|
+
f"psid_version must be one of {list(psid_files.keys())}, "
|
|
255
|
+
f"got: {psid_version}"
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
psid = pd.read_csv(data_dir / psid_files[psid_version])
|
|
259
|
+
# Standardize column names for PSID control sample
|
|
260
|
+
psid = psid.rename(columns=column_mapping)
|
|
261
|
+
|
|
262
|
+
if return_combined:
|
|
263
|
+
# Return combined DataFrame with consistent column ordering
|
|
264
|
+
# NSW sample is missing re74, which will be filled with NaN
|
|
265
|
+
nsw_with_re74 = nsw.copy()
|
|
266
|
+
if 're74' not in nsw_with_re74.columns:
|
|
267
|
+
nsw_with_re74['re74'] = np.nan
|
|
268
|
+
combined = pd.concat([nsw_with_re74, psid], ignore_index=True)
|
|
269
|
+
return combined
|
|
270
|
+
else:
|
|
271
|
+
# Return separate DataFrames: (nsw_exp, psid_ctrl)
|
|
272
|
+
return nsw, psid
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""
|
|
2
|
+
npCBPS Simulation Data Loaders
|
|
3
|
+
===============================
|
|
4
|
+
|
|
5
|
+
This module provides functions to load continuous treatment simulation data
|
|
6
|
+
for validating nonparametric CBPS (npCBPS) implementations.
|
|
7
|
+
|
|
8
|
+
The npCBPS methodology estimates stabilizing inverse propensity score weights
|
|
9
|
+
using empirical likelihood without requiring a parametric model for the
|
|
10
|
+
generalized propensity score, as described in Fong, Hazlett, and Imai (2018).
|
|
11
|
+
|
|
12
|
+
References
|
|
13
|
+
----------
|
|
14
|
+
Fong, C., Hazlett, C., and Imai, K. (2018). Covariate balancing propensity
|
|
15
|
+
score for a continuous treatment: Application to the efficacy of political
|
|
16
|
+
advertisements. *The Annals of Applied Statistics*, 12(1), 156-177.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
import pandas as pd
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def load_npcbps_continuous_sim() -> pd.DataFrame:
|
|
24
|
+
"""Load npCBPS continuous treatment simulation data.
|
|
25
|
+
|
|
26
|
+
Returns
|
|
27
|
+
-------
|
|
28
|
+
pd.DataFrame
|
|
29
|
+
Simulation data (500 rows × 7 columns).
|
|
30
|
+
|
|
31
|
+
Notes
|
|
32
|
+
-----
|
|
33
|
+
**Variables**
|
|
34
|
+
|
|
35
|
+
- ``Y``: Outcome variable (continuous)
|
|
36
|
+
- ``T``: Treatment variable (continuous)
|
|
37
|
+
- ``X1``-``X5``: Covariates (multivariate normal, correlation=0.5)
|
|
38
|
+
|
|
39
|
+
**Data Characteristics**
|
|
40
|
+
|
|
41
|
+
- Observations: 500
|
|
42
|
+
- Covariate structure: Multivariate normal with pairwise correlation 0.5
|
|
43
|
+
|
|
44
|
+
Raises
|
|
45
|
+
------
|
|
46
|
+
FileNotFoundError
|
|
47
|
+
If simulation data file does not exist.
|
|
48
|
+
|
|
49
|
+
Examples
|
|
50
|
+
--------
|
|
51
|
+
>>> from cbps.datasets import load_npcbps_continuous_sim
|
|
52
|
+
>>> df = load_npcbps_continuous_sim()
|
|
53
|
+
>>> df.shape
|
|
54
|
+
(500, 7)
|
|
55
|
+
>>> list(df.columns)
|
|
56
|
+
['Y', 'T', 'X1', 'X2', 'X3', 'X4', 'X5']
|
|
57
|
+
"""
|
|
58
|
+
# Get data directory (supports both installed and development modes)
|
|
59
|
+
package_data_dir = Path(__file__).parent.parent / "data"
|
|
60
|
+
if package_data_dir.exists():
|
|
61
|
+
data_dir = package_data_dir
|
|
62
|
+
else:
|
|
63
|
+
# Fall back to project root data directory (development mode)
|
|
64
|
+
data_dir = Path(__file__).parent.parent.parent.parent / "data"
|
|
65
|
+
|
|
66
|
+
if not data_dir.exists():
|
|
67
|
+
raise FileNotFoundError(
|
|
68
|
+
f"Cannot find data directory. Tried:\n"
|
|
69
|
+
f" 1. Package data: {package_data_dir}\n"
|
|
70
|
+
f" 2. Project data: {data_dir}\n"
|
|
71
|
+
f"Please ensure the package is properly installed with data files."
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
filepath = data_dir / "npcbps_continuous_sim.csv"
|
|
75
|
+
|
|
76
|
+
if not filepath.exists():
|
|
77
|
+
raise FileNotFoundError(
|
|
78
|
+
f"Simulation data not found: {filepath}\n"
|
|
79
|
+
"Please ensure the package is properly installed with data files."
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
df = pd.read_csv(filepath)
|
|
83
|
+
|
|
84
|
+
# Validate data dimensions
|
|
85
|
+
expected_shape = (500, 7)
|
|
86
|
+
if df.shape != expected_shape:
|
|
87
|
+
raise ValueError(
|
|
88
|
+
f"Unexpected data shape: {df.shape}\n"
|
|
89
|
+
f"Expected: {expected_shape}"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# Validate column names
|
|
93
|
+
expected_columns = ['Y', 'T', 'X1', 'X2', 'X3', 'X4', 'X5']
|
|
94
|
+
if list(df.columns) != expected_columns:
|
|
95
|
+
raise ValueError(
|
|
96
|
+
f"Unexpected columns: {list(df.columns)}\n"
|
|
97
|
+
f"Expected: {expected_columns}"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
return df
|
|
101
|
+
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Diagnostics and Visualization Module
|
|
3
|
+
=====================================
|
|
4
|
+
|
|
5
|
+
This module provides tools for assessing covariate balance before and after
|
|
6
|
+
propensity score weighting, a critical step in evaluating causal inference
|
|
7
|
+
methodology.
|
|
8
|
+
|
|
9
|
+
Balance Assessment
|
|
10
|
+
------------------
|
|
11
|
+
For binary and multi-valued treatments, balance is measured via **standardized
|
|
12
|
+
mean differences (SMD)**. Following Austin (2009) and Stuart (2010), an SMD
|
|
13
|
+
below 0.1 indicates acceptable balance.
|
|
14
|
+
|
|
15
|
+
For continuous treatments, balance is assessed via **weighted Pearson
|
|
16
|
+
correlations** between covariates and the treatment variable. An absolute
|
|
17
|
+
correlation near zero indicates that the treatment is conditionally independent
|
|
18
|
+
of covariates given the weights.
|
|
19
|
+
|
|
20
|
+
Functions
|
|
21
|
+
---------
|
|
22
|
+
balance_cbps
|
|
23
|
+
Compute standardized mean differences for binary/multi-valued treatments.
|
|
24
|
+
|
|
25
|
+
balance_cbps_continuous
|
|
26
|
+
Compute weighted correlations for continuous treatments.
|
|
27
|
+
|
|
28
|
+
plot_cbps
|
|
29
|
+
Visualize SMD before and after weighting (requires matplotlib).
|
|
30
|
+
|
|
31
|
+
plot_cbps_continuous
|
|
32
|
+
Visualize correlation reduction for continuous treatments.
|
|
33
|
+
|
|
34
|
+
References
|
|
35
|
+
----------
|
|
36
|
+
Imai, K. and Ratkovic, M. (2014). Covariate balancing propensity score.
|
|
37
|
+
Journal of the Royal Statistical Society, Series B, 76(1), 243-263.
|
|
38
|
+
|
|
39
|
+
Fong, C., Hazlett, C., and Imai, K. (2018). Covariate balancing propensity
|
|
40
|
+
score for a continuous treatment. The Annals of Applied Statistics, 12(1),
|
|
41
|
+
156-177.
|
|
42
|
+
|
|
43
|
+
Austin, P.C. (2009). Balance diagnostics for comparing the distribution of
|
|
44
|
+
baseline covariates between treatment groups in propensity-score matched
|
|
45
|
+
samples. Statistics in Medicine, 28(25), 3083-3107.
|
|
46
|
+
|
|
47
|
+
Stuart, E.A. (2010). Matching methods for causal inference: A review and a
|
|
48
|
+
look forward. Statistical Science, 25(1), 1-21.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
from .balance import balance_cbps, balance_cbps_continuous, omnibus_balance_test
|
|
52
|
+
from .continuous_diagnostics import (
|
|
53
|
+
diagnose_cbgps_quality,
|
|
54
|
+
print_balance_diagnosis,
|
|
55
|
+
compute_f_statistic as compute_f_statistic_continuous,
|
|
56
|
+
compute_weighted_correlations
|
|
57
|
+
)
|
|
58
|
+
from .weights_diag import weight_diagnostics
|
|
59
|
+
from .overlap import check_overlap
|
|
60
|
+
from .normality import test_treatment_normality
|
|
61
|
+
from .ocbps_conditions import verify_ocbps_conditions
|
|
62
|
+
|
|
63
|
+
# plots module is imported as optional dependency
|
|
64
|
+
try:
|
|
65
|
+
from .plots import (
|
|
66
|
+
plot_cbps, plot_cbps_continuous,
|
|
67
|
+
love_plot, plot_weight_distribution, plot_ps_overlap,
|
|
68
|
+
)
|
|
69
|
+
__all__ = [
|
|
70
|
+
'balance_cbps',
|
|
71
|
+
'balance_cbps_continuous',
|
|
72
|
+
'omnibus_balance_test',
|
|
73
|
+
'plot_cbps',
|
|
74
|
+
'plot_cbps_continuous',
|
|
75
|
+
'love_plot',
|
|
76
|
+
'plot_weight_distribution',
|
|
77
|
+
'plot_ps_overlap',
|
|
78
|
+
'diagnose_cbgps_quality',
|
|
79
|
+
'print_balance_diagnosis',
|
|
80
|
+
'compute_f_statistic_continuous',
|
|
81
|
+
'compute_weighted_correlations',
|
|
82
|
+
'weight_diagnostics',
|
|
83
|
+
'check_overlap',
|
|
84
|
+
'test_treatment_normality',
|
|
85
|
+
'verify_ocbps_conditions',
|
|
86
|
+
]
|
|
87
|
+
except ImportError:
|
|
88
|
+
# When matplotlib is not installed, only export balance functions
|
|
89
|
+
__all__ = [
|
|
90
|
+
'balance_cbps',
|
|
91
|
+
'balance_cbps_continuous',
|
|
92
|
+
'omnibus_balance_test',
|
|
93
|
+
'diagnose_cbgps_quality',
|
|
94
|
+
'print_balance_diagnosis',
|
|
95
|
+
'compute_f_statistic_continuous',
|
|
96
|
+
'compute_weighted_correlations',
|
|
97
|
+
'weight_diagnostics',
|
|
98
|
+
'check_overlap',
|
|
99
|
+
'test_treatment_normality',
|
|
100
|
+
'verify_ocbps_conditions',
|
|
101
|
+
]
|