trop 0.1.2__py3-none-any.whl → 0.1.4__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.
trop/cv.py CHANGED
@@ -13,25 +13,7 @@ ArrayLike = Union[np.ndarray, Sequence[Sequence[float]]]
13
13
 
14
14
 
15
15
  def _validate_panel(Y: np.ndarray, treated_periods: int, n_treated_units: int) -> None:
16
- """
17
- Validate panel dimensions and basic placebo-treatment parameters.
18
-
19
- Parameters
20
- ----------
21
- Y:
22
- Outcome panel of shape (N, T).
23
- treated_periods:
24
- Number of treated (post) periods assumed to be the final columns of the panel.
25
- Must satisfy 1 <= treated_periods < T.
26
- n_treated_units:
27
- Number of treated units to sample without replacement from {0, ..., N-1}.
28
- Must satisfy 1 <= n_treated_units < N.
29
-
30
- Raises
31
- ------
32
- ValueError
33
- If Y is not 2D, or if treated_periods / n_treated_units are out of range.
34
- """
16
+ """Validate panel dimensions and placebo CV inputs."""
35
17
  if Y.ndim != 2:
36
18
  raise ValueError("Y must be a 2D array of shape (N, T).")
37
19
  N, T = Y.shape
@@ -42,24 +24,7 @@ def _validate_panel(Y: np.ndarray, treated_periods: int, n_treated_units: int) -
42
24
 
43
25
 
44
26
  def _as_list(grid: Iterable[float]) -> List[float]:
45
- """
46
- Convert a lambda grid iterable into a non-empty list of floats.
47
-
48
- Parameters
49
- ----------
50
- grid:
51
- Iterable of candidate lambda values.
52
-
53
- Returns
54
- -------
55
- List[float]
56
- The grid converted to a list of floats.
57
-
58
- Raises
59
- ------
60
- ValueError
61
- If the grid is empty.
62
- """
27
+ """Convert an iterable of grid values to a non-empty list of floats."""
63
28
  grid_list = list(grid)
64
29
  if len(grid_list) == 0:
65
30
  raise ValueError("lambda_grid must be non-empty.")
@@ -118,41 +83,48 @@ def TROP_cv_single(
118
83
  verbose: bool = False,
119
84
  ) -> float:
120
85
  """
121
- Cross-validate one lambda parameter while keeping the other two fixed.
86
+ Tune one TROP tuning parameter via placebo cross-validation on a control-only panel.
87
+
88
+ For each candidate value in `lambda_grid`, this routine repeatedly assigns a placebo
89
+ treatment to random units in the last `treated_periods` columns, computes the
90
+ corresponding TROP estimate, and selects the lambda that minimizes the RMSE of
91
+ placebo effects.
122
92
 
123
93
  Parameters
124
94
  ----------
125
- Y_control:
126
- Control-only panel (N x T) used for placebo CV.
127
- n_treated_units:
128
- Number of placebo treated units to sample each trial.
129
- treated_periods:
130
- Number of placebo treated (post) periods (assumed final columns).
131
- fixed_lambdas:
132
- Tuple of two lambdas to hold fixed; interpretation depends on `lambda_cv`:
133
- - lambda_cv='unit': fixed_lambdas=(lambda_time, lambda_nn)
134
- - lambda_cv='time': fixed_lambdas=(lambda_unit, lambda_nn)
135
- - lambda_cv='nn' : fixed_lambdas=(lambda_unit, lambda_time)
136
- lambda_grid:
137
- Grid of candidate values for the lambda being tuned.
138
- If None, uses np.arange(0, 2, 0.2).
139
- lambda_cv:
140
- Which lambda to tune: {'unit','time','nn'}.
141
- n_trials:
142
- Number of placebo trials per lambda.
143
- n_jobs:
144
- joblib parallelism. -1 uses all available cores.
145
- prefer:
146
- joblib backend preference. Use 'threads' by default for solver stability.
147
- random_seed:
148
- Seed for generating trial seeds (deterministic CV).
149
- solver, verbose:
150
- Passed through to TROP_TWFE_average.
95
+ Y_control : array_like of shape (N, T)
96
+ Control-only outcome panel used for placebo cross-validation.
97
+ n_treated_units : int
98
+ Number of placebo treated units sampled (without replacement) per trial.
99
+ treated_periods : int
100
+ Number of placebo treated (post) periods, taken as the final columns.
101
+ fixed_lambdas : tuple of float, default=(0.0, 0.0)
102
+ Values held fixed for the two lambdas not being tuned. Interpretation depends on
103
+ `lambda_cv`:
104
+ - 'unit': (lambda_time, lambda_nn)
105
+ - 'time': (lambda_unit, lambda_nn)
106
+ - 'nn' : (lambda_unit, lambda_time)
107
+ lambda_grid : iterable of float or None, default=None
108
+ Candidate values for the lambda being tuned. If None, uses ``np.arange(0, 2, 0.2)``.
109
+ lambda_cv : {'unit', 'time', 'nn'}, default='unit'
110
+ Which lambda to tune.
111
+ n_trials : int, default=200
112
+ Number of placebo trials per candidate lambda.
113
+ n_jobs : int, default=-1
114
+ Number of parallel jobs for placebo trials. ``-1`` uses all available cores.
115
+ prefer : {'threads', 'processes'}, default='threads'
116
+ joblib backend preference.
117
+ random_seed : int, default=0
118
+ Seed for generating trial seeds (deterministic tuning).
119
+ solver : str or None, default=None
120
+ CVXPY solver passed to ``TROP_TWFE_average``.
121
+ verbose : bool, default=False
122
+ Verbosity flag passed to ``TROP_TWFE_average``.
151
123
 
152
124
  Returns
153
125
  -------
154
126
  float
155
- Lambda value that minimizes RMSE of placebo ATEs.
127
+ Selected lambda value minimizing the RMSE of placebo estimates.
156
128
  """
157
129
  Y = np.asarray(Y_control, dtype=float)
158
130
  _validate_panel(Y, treated_periods, n_treated_units)
@@ -234,53 +206,54 @@ def TROP_cv_cycle(
234
206
  verbose: bool = False,
235
207
  ) -> Tuple[float, float, float]:
236
208
  """
237
- Coordinate-descent style cross-validation for (lambda_unit, lambda_time, lambda_nn).
209
+ Tune (lambda_unit, lambda_time, lambda_nn) by coordinate-descent placebo cross-validation.
238
210
 
239
- This routine alternates between optimizing lambda_unit, lambda_time, and lambda_nn
240
- (via `TROP_cv_single`) while holding the other two fixed, until it reaches a fixed
241
- point (no change in the selected lambdas) or until `max_iter` iterations are reached.
211
+ Iteratively updates one tuning parameter at a time using `TROP_cv_single` (holding the
212
+ other two fixed) until the selected triplet stops changing or `max_iter` is reached.
213
+ Each update minimizes the RMSE of placebo effects on a control-only panel.
242
214
 
243
215
  Parameters
244
216
  ----------
245
- Y_control:
246
- Control-only panel (N x T) used for placebo CV.
247
- n_treated_units:
248
- Number of placebo treated units to sample each trial.
249
- treated_periods:
250
- Number of placebo treated (post) periods (assumed final columns).
251
- unit_grid:
252
- Grid of candidate values for lambda_unit (unit-distance decay).
253
- time_grid:
254
- Grid of candidate values for lambda_time (time-distance decay).
255
- nn_grid:
256
- Grid of candidate values for lambda_nn (nuclear-norm penalty).
257
- lambdas_init:
258
- Optional initial values (lambda_unit, lambda_time, lambda_nn). If None, initializes
259
- each lambda to the mean of its corresponding grid.
260
- max_iter:
217
+ Y_control : array_like of shape (N, T)
218
+ Control-only outcome panel used for placebo cross-validation.
219
+ n_treated_units : int
220
+ Number of placebo treated units sampled (without replacement) per trial.
221
+ treated_periods : int
222
+ Number of placebo treated (post) periods, taken as the final columns.
223
+ unit_grid : sequence of float
224
+ Candidate values for `lambda_unit` (unit-distance decay).
225
+ time_grid : sequence of float
226
+ Candidate values for `lambda_time` (time-distance decay).
227
+ nn_grid : sequence of float
228
+ Candidate values for `lambda_nn` (nuclear-norm penalty).
229
+ lambdas_init : tuple of float or None, default=None
230
+ Initial values (lambda_unit, lambda_time, lambda_nn). If None, initializes each
231
+ parameter to the mean of its grid.
232
+ max_iter : int, default=50
261
233
  Maximum number of coordinate-descent iterations.
262
- n_trials:
234
+ n_trials : int, default=200
263
235
  Number of placebo trials per grid point in each coordinate update.
264
- n_jobs:
265
- joblib parallelism. -1 uses all available cores.
266
- prefer:
267
- joblib backend preference. Use 'threads' by default for solver stability.
268
- random_seed:
269
- Seed for generating trial seeds (deterministic CV).
270
- solver, verbose:
271
- Passed through to TROP_TWFE_average.
236
+ n_jobs : int, default=-1
237
+ Number of parallel jobs for placebo trials. ``-1`` uses all available cores.
238
+ prefer : {'threads', 'processes'}, default='threads'
239
+ joblib backend preference.
240
+ random_seed : int, default=0
241
+ Seed for generating trial seeds (deterministic tuning).
242
+ solver : str or None, default=None
243
+ CVXPY solver passed to ``TROP_TWFE_average``.
244
+ verbose : bool, default=False
245
+ Verbosity flag passed to ``TROP_TWFE_average``.
272
246
 
273
247
  Returns
274
248
  -------
275
- Tuple[float, float, float]
276
- (lambda_unit, lambda_time, lambda_nn) at the converged fixed point.
249
+ tuple of float
250
+ (lambda_unit, lambda_time, lambda_nn) at the fixed point of the coordinate updates.
277
251
 
278
252
  Raises
279
253
  ------
280
254
  RuntimeError
281
- If the procedure does not converge to a fixed point within `max_iter`.
255
+ If the procedure does not converge within `max_iter`.
282
256
  """
283
-
284
257
  Y = np.asarray(Y_control, dtype=float)
285
258
  _validate_panel(Y, treated_periods, n_treated_units)
286
259
 
@@ -348,39 +321,50 @@ def TROP_cv_joint(
348
321
  verbose: bool = False,
349
322
  ) -> Tuple[float, float, float]:
350
323
  """
351
- Joint grid search over (lambda_unit, lambda_time, lambda_nn).
324
+ Select (lambda_unit, lambda_time, lambda_nn) by joint placebo cross-validation.
325
+
326
+ Performs a full grid search over `unit_grid` × `time_grid` × `nn_grid`. For each
327
+ candidate triple, repeatedly assigns a placebo treatment to random units in the
328
+ last `treated_periods` columns and selects the triple that minimizes the RMSE of
329
+ placebo effects on the control-only panel.
352
330
 
353
331
  Parameters
354
332
  ----------
355
- Y_control:
356
- Control-only panel (N x T) used for placebo CV.
357
- n_treated_units:
358
- Number of placebo treated units to sample each trial.
359
- treated_periods:
360
- Number of placebo treated (post) periods (assumed final columns).
361
- unit_grid:
362
- Grid of candidate values for lambda_unit (unit-distance decay).
363
- time_grid:
364
- Grid of candidate values for lambda_time (time-distance decay).
365
- nn_grid:
366
- Grid of candidate values for lambda_nn (nuclear-norm penalty).
367
- n_trials:
368
- Number of placebo trials per (lambda_unit, lambda_time, lambda_nn) triple.
369
- n_jobs:
370
- joblib parallelism. -1 uses all available cores.
371
- prefer:
372
- joblib backend preference. Use 'threads' by default for solver stability.
373
- random_seed:
374
- Seed for generating trial seeds (deterministic CV).
375
- solver, verbose:
376
- Passed through to TROP_TWFE_average.
333
+ Y_control : array_like of shape (N, T)
334
+ Control-only outcome panel used for placebo cross-validation.
335
+ n_treated_units : int
336
+ Number of placebo treated units sampled (without replacement) per trial.
337
+ treated_periods : int
338
+ Number of placebo treated (post) periods, taken as the final columns.
339
+ unit_grid : sequence of float
340
+ Candidate values for `lambda_unit` (unit-distance decay).
341
+ time_grid : sequence of float
342
+ Candidate values for `lambda_time` (time-distance decay).
343
+ nn_grid : sequence of float
344
+ Candidate values for `lambda_nn` (nuclear-norm penalty).
345
+ n_trials : int, default=200
346
+ Number of placebo trials per candidate triple.
347
+ n_jobs : int, default=-1
348
+ Number of parallel jobs for placebo trials. ``-1`` uses all available cores.
349
+ prefer : {'threads', 'processes'}, default='threads'
350
+ joblib backend preference.
351
+ random_seed : int, default=0
352
+ Seed for generating trial seeds (deterministic tuning).
353
+ solver : str or None, default=None
354
+ CVXPY solver passed to ``TROP_TWFE_average``.
355
+ verbose : bool, default=False
356
+ Verbosity flag passed to ``TROP_TWFE_average``.
377
357
 
378
358
  Returns
379
359
  -------
380
- Tuple[float, float, float]
381
- (lambda_unit, lambda_time, lambda_nn) triple that minimizes the RMSE of placebo ATEs.
382
- """
360
+ tuple of float
361
+ (lambda_unit, lambda_time, lambda_nn) minimizing the RMSE of placebo estimates.
383
362
 
363
+ Raises
364
+ ------
365
+ RuntimeError
366
+ If all parameter combinations fail (e.g., solver failures for every triple).
367
+ """
384
368
  Y = np.asarray(Y_control, dtype=float)
385
369
  _validate_panel(Y, treated_periods, n_treated_units)
386
370
 
trop/estimator.py CHANGED
@@ -22,44 +22,48 @@ def TROP_TWFE_average(
22
22
  verbose: bool = False,
23
23
  ) -> float:
24
24
  """
25
- Triply Robust Panel (TROP) estimator in a TWFE framework with:
26
- - distance-based unit weights (lambda_unit)
27
- - distance-based time weights (lambda_time)
28
- - optional low-rank regression adjustment (nuclear norm penalty, lambda_nn)
25
+ Compute the TROP treatment effect with unit/time weighting and optional low-rank
26
+ outcome model.
29
27
 
30
28
  Parameters
31
29
  ----------
32
- Y:
33
- Outcome matrix of shape (N, T).
34
- W:
35
- Treatment indicator matrix of shape (N, T). Typically binary {0,1}.
36
- Convention in the provided codebase: treated units are treated in the final
37
- `treated_periods` columns, but the function will run for any W.
38
- treated_units:
39
- Indices of treated units (row indices into Y/W).
40
- lambda_unit:
41
- Nonnegative tuning parameter controlling unit-weight decay.
42
- lambda_time:
43
- Nonnegative tuning parameter controlling time-weight decay.
44
- lambda_nn:
45
- Nuclear norm penalty weight for the low-rank component L.
46
- Use np.inf to disable low-rank adjustment (i.e., no L term).
47
- treated_periods:
48
- Number of treated (post) periods at the end of the panel used to define:
49
- - the time-distance center
50
- - the pre-period mask for unit distance computation
51
- solver:
52
- Optional CVXPY solver name. If None, chooses a reasonable default:
53
- - if lambda_nn is finite: uses SCS (supports nuclear norm / SDP forms)
54
- - if lambda_nn is infinite: uses OSQP (fast for pure quadratic problems)
55
- verbose:
56
- Passed to CVXPY solve().
30
+ Y : array_like of shape (N, T)
31
+ Outcome matrix.
32
+ W : array_like of shape (N, T)
33
+ Treatment indicator (often binary). The estimator uses ``W`` as provided;
34
+ ``treated_periods`` is used only to construct weights/masks, not to infer
35
+ treatment timing.
36
+ treated_units : sequence of int
37
+ Row indices of treated units used to form the reference (average) treated
38
+ trajectory for unit-distance weighting.
39
+ lambda_unit : float
40
+ Nonnegative decay parameter for unit weights: ``exp(-lambda_unit * dist_unit)``.
41
+ lambda_time : float
42
+ Nonnegative decay parameter for time weights: ``exp(-lambda_time * dist_time)``.
43
+ lambda_nn : float
44
+ Nuclear-norm penalty weight for the low-rank component ``L``. Use
45
+ ``np.inf`` to disable the low-rank adjustment (i.e., omit ``L``).
46
+ treated_periods : int, default=10
47
+ Number of final columns treated as the "post/tail block" for constructing
48
+ (a) the pre-period mask (all but last ``treated_periods`` columns) used in
49
+ unit distances, and (b) the time-distance center.
50
+ solver : str or None, default=None
51
+ CVXPY solver name. If None, uses "SCS" when ``lambda_nn`` is finite and
52
+ "OSQP" when ``lambda_nn`` is infinite.
53
+ verbose : bool, default=False
54
+ Passed to ``cvxpy.Problem.solve``.
57
55
 
58
56
  Returns
59
57
  -------
60
58
  float
61
- Estimated average treatment effect tau.
62
-
59
+ Estimated treatment-effect parameter ``tau`` from the weighted TWFE objective.
60
+
61
+ Raises
62
+ ------
63
+ ValueError
64
+ If input shapes are inconsistent or tuning parameters are invalid.
65
+ RuntimeError
66
+ If the optimization fails to produce a finite ``tau``.
63
67
  """
64
68
  Y = np.asarray(Y, dtype=float)
65
69
  W = np.asarray(W, dtype=float)
@@ -1,12 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trop
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: Triply Robust Panel (TROP) estimator: weighted TWFE with optional low-rank adjustment.
5
5
  Author: Susan Athey, Guido Imbens, Zhaonan Qu, Davide Viviano
6
6
  License-Expression: MIT
7
- Project-URL: Homepage, https://github.com/zhaonanq/TROP
8
- Project-URL: Repository, https://github.com/zhaonanq/TROP
9
- Project-URL: Issues, https://github.com/zhaonanq/TROP/issues
7
+ Project-URL: Homepage, https://github.com/ostasovskyi/TROP-Estimator
8
+ Project-URL: Repository, https://github.com/ostasovskyi/TROP-Estimator
9
+ Project-URL: Issues, https://github.com/ostasovskyi/TROP-Estimator/issues
10
+ Project-URL: Documentation, https://ostasovskyi.github.io/TROP-Estimator/
10
11
  Keywords: causal-inference,panel-data,factor-models,difference-in-differences,synthetic-control,synthetic-controls,trop,twfe
11
12
  Classifier: Development Status :: 3 - Alpha
12
13
  Classifier: Intended Audience :: Science/Research
@@ -46,6 +47,13 @@ Reference:
46
47
 
47
48
  ---
48
49
 
50
+ ## Links
51
+
52
+ - **Documentation**: https://ostasovskyi.github.io/TROP-Estimator/
53
+ - **Source code**: https://github.com/ostasovskyi/TROP-Estimator
54
+
55
+ ---
56
+
49
57
  ## Installation
50
58
 
51
59
  ```
@@ -0,0 +1,8 @@
1
+ trop/__init__.py,sha256=B94vrDZevg2l6ijN4lut7wo0MYTEtBTTZhqmMQtq7Qg,205
2
+ trop/cv.py,sha256=tbT2mvPO_gQ28GT5CIPZR1NqL11J3PjxsTHYxNWmVRk,14908
3
+ trop/estimator.py,sha256=iZFH9D664OsEZascxAEdSqOBPj26DofXHk6jWgz_42Q,6257
4
+ trop-0.1.4.dist-info/licenses/LICENSE,sha256=VqjvjioQz04uLYBj4ye0x-_Ss77-WTIuEWWCW_awEz8,1065
5
+ trop-0.1.4.dist-info/METADATA,sha256=LD3UoSdXXTq_gx_jhVy55OoKEcOvi5Ws7f1J_IQxMmw,2258
6
+ trop-0.1.4.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
7
+ trop-0.1.4.dist-info/top_level.txt,sha256=jaqQZFm3D5B4vPBAKZtXfEAYnpl9FKsNHqlM49kcwTI,5
8
+ trop-0.1.4.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.10.1)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,8 +0,0 @@
1
- trop/__init__.py,sha256=B94vrDZevg2l6ijN4lut7wo0MYTEtBTTZhqmMQtq7Qg,205
2
- trop/cv.py,sha256=7tumpAaAiiK_F8nUUAcAwnecNAfc2XghftgJsNWVsAQ,14017
3
- trop/estimator.py,sha256=FWMO39GbL6k3Vz5g1V7SpR6t5wP3N81V5gGSFIe65Xw,6001
4
- trop-0.1.2.dist-info/licenses/LICENSE,sha256=VqjvjioQz04uLYBj4ye0x-_Ss77-WTIuEWWCW_awEz8,1065
5
- trop-0.1.2.dist-info/METADATA,sha256=YSyeONhxn4JO_NZBKT1ubVa5J96BhlWGtmRi-GJ0rLk,1997
6
- trop-0.1.2.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
7
- trop-0.1.2.dist-info/top_level.txt,sha256=jaqQZFm3D5B4vPBAKZtXfEAYnpl9FKsNHqlM49kcwTI,5
8
- trop-0.1.2.dist-info/RECORD,,