logit-stream 0.1.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.
- logit_stream/__init__.py +77 -0
- logit_stream/_choice.py +64 -0
- logit_stream/_frontend.py +78 -0
- logit_stream/api.py +542 -0
- logit_stream/device/__init__.py +57 -0
- logit_stream/device/_common.py +92 -0
- logit_stream/device/fe.py +108 -0
- logit_stream/device/meat.py +210 -0
- logit_stream/device/mixed.py +168 -0
- logit_stream/device/models.py +258 -0
- logit_stream/device/nested.py +144 -0
- logit_stream/device/random_effects.py +196 -0
- logit_stream/device/store.py +96 -0
- logit_stream/estimator.py +635 -0
- logit_stream/fe.py +539 -0
- logit_stream/mixed.py +518 -0
- logit_stream/models.py +689 -0
- logit_stream/multi_fe.py +242 -0
- logit_stream/nested.py +600 -0
- logit_stream/newton.py +519 -0
- logit_stream/optimize.py +180 -0
- logit_stream/py.typed +0 -0
- logit_stream/random_effects/__init__.py +84 -0
- logit_stream/random_effects/fit.py +257 -0
- logit_stream/random_effects/linalg.py +224 -0
- logit_stream/random_effects/model.py +763 -0
- logit_stream/random_effects/sigma.py +399 -0
- logit_stream/results.py +1000 -0
- logit_stream/simulate.py +257 -0
- logit_stream/store.py +377 -0
- logit_stream-0.1.0.dist-info/METADATA +127 -0
- logit_stream-0.1.0.dist-info/RECORD +35 -0
- logit_stream-0.1.0.dist-info/WHEEL +5 -0
- logit_stream-0.1.0.dist-info/licenses/LICENSE.md +21 -0
- logit_stream-0.1.0.dist-info/top_level.txt +1 -0
logit_stream/api.py
ADDED
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
"""The public estimators: `logit_stream`, `mlogit_stream`, `ologit_stream`,
|
|
2
|
+
`clogit_stream`."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from collections.abc import Sequence
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from hdfe_stream.core.typing import PathLike, Source, Vcov
|
|
10
|
+
|
|
11
|
+
from .estimator import _fit
|
|
12
|
+
from .mixed import _fit_mixed
|
|
13
|
+
|
|
14
|
+
_COMMON = """
|
|
15
|
+
data : str, Path, polars.LazyFrame or polars.DataFrame
|
|
16
|
+
A Parquet path or glob, or a Polars LazyFrame (never collected as a
|
|
17
|
+
whole). A DataFrame works too.
|
|
18
|
+
workdir : str or Path, optional
|
|
19
|
+
Where the run directory for the fit's blocks goes. Default: the
|
|
20
|
+
``LOGIT_STREAM_WORKDIR`` environment variable, else
|
|
21
|
+
``HDFE_STREAM_WORKDIR``, else the system temporary directory. The
|
|
22
|
+
run directory is deleted when the fit ends.
|
|
23
|
+
vcov : str or dict, default "iid"
|
|
24
|
+
``"iid"`` (the inverse of the information matrix), ``"hetero"`` (the
|
|
25
|
+
sandwich, with N / (N - 1)), ``{"CRV1": var}`` / ``"CRV1:var"``
|
|
26
|
+
(clustered by `var`, or by an interaction ``"a^b"``, with
|
|
27
|
+
G / (G - 1)) -- Stata's conventions for maximum likelihood --
|
|
28
|
+
multi-way ``{"CRV1": "a+b"}`` (inclusion-exclusion over a, b and
|
|
29
|
+
a^b, each scaled with the smaller G, df G - 1, as hdfe-stream and
|
|
30
|
+
pyfixest), or ``{"CRV3": var}``, the cluster jackknife (one-way;
|
|
31
|
+
the multinomial and ordered logit without fixed effects or random
|
|
32
|
+
intercepts; one more read and G x P^2 memory).
|
|
33
|
+
cluster : str or sequence of str, optional
|
|
34
|
+
Further clusterings to compute, for `with_vcov`.
|
|
35
|
+
weights : str, optional
|
|
36
|
+
A column of positive weights, each multiplying its row's
|
|
37
|
+
log-likelihood.
|
|
38
|
+
weights_type : {"aweights", "fweights"}, default "aweights"
|
|
39
|
+
As in hdfe-stream: frequency weights count as that many
|
|
40
|
+
observations (`n_obs` is their sum, and the robust variances treat
|
|
41
|
+
each as that many independent rows); analytic weights do not, and
|
|
42
|
+
are rescaled to average 1 over the sample (over the choice sets for
|
|
43
|
+
the conditional logit), as Stata does for maximum likelihood, so
|
|
44
|
+
that nothing depends on their scale. For survey (probability)
|
|
45
|
+
weights use "aweights" with a robust or clustered vcov: that is
|
|
46
|
+
Stata's ``[pweight]`` fit.
|
|
47
|
+
tol : float, default 1e-8
|
|
48
|
+
Convergence tolerance on the Newton decrement g' H^-1 g: stop when
|
|
49
|
+
the next step would move the estimates by less than about
|
|
50
|
+
sqrt(tol) standard errors.
|
|
51
|
+
maxiter : int, default 50
|
|
52
|
+
The most Newton steps.
|
|
53
|
+
l2 : float, default 0
|
|
54
|
+
An L2 (ridge) penalty, ``l2 / 2`` times the sum of squared slopes
|
|
55
|
+
(not intercepts or cutpoints), subtracted from the log-likelihood:
|
|
56
|
+
scikit-learn's ``C`` is ``1 / l2`` for the sum of the losses. Use it
|
|
57
|
+
when the outcome is separated by a covariate. The standard errors
|
|
58
|
+
are then those of the penalized fit.
|
|
59
|
+
offset : str or polars.Expr, optional
|
|
60
|
+
A column or expression added to the linear predictor with a
|
|
61
|
+
coefficient of one, for the ordered logit (a known location shift)
|
|
62
|
+
and the conditional logit (e.g. McFadden's correction for sampled
|
|
63
|
+
alternatives, the log of each alternative's sampling probability);
|
|
64
|
+
`predict` and `resid` include it, so new data need it too. Not for
|
|
65
|
+
the multinomial logit, which would need one per outcome level.
|
|
66
|
+
start : dict, sequence or result, optional
|
|
67
|
+
Starting values for the coefficients (warm start): {name: value} for
|
|
68
|
+
some of them, all of them in the order of `coefnames`, or a previous
|
|
69
|
+
result's. The rest, and any fixed or random effects, start at the
|
|
70
|
+
null model.
|
|
71
|
+
device : {"cpu", "cuda"}, default "cpu"
|
|
72
|
+
``"cuda"`` (experimental) forms each Newton step's log-likelihood,
|
|
73
|
+
gradient and information on an NVIDIA GPU through CuPy, keeping the
|
|
74
|
+
blocks there when the sample fits in half its free memory; the
|
|
75
|
+
robust and clustered variances' scores pass runs there too (the
|
|
76
|
+
cluster jackknife's stays on the CPU), and the step itself on the
|
|
77
|
+
CPU. The estimates agree with the CPU's to the tolerance. With an
|
|
78
|
+
absorbed fixed effect and with random intercepts too (their
|
|
79
|
+
per-level and per-unit sums, and the exact Laplace terms, by
|
|
80
|
+
scatter-adds on the device), Firth's adjustment, and
|
|
81
|
+
`cfelogit_stream`. Needs ``pip install cupy-cuda12x`` (or
|
|
82
|
+
cupy-cuda13x).
|
|
83
|
+
firth : bool, default False
|
|
84
|
+
Maximize Firth's penalized likelihood, the log-likelihood plus half
|
|
85
|
+
the log-determinant of the information (a Jeffreys prior), instead
|
|
86
|
+
of the likelihood: estimates with the O(1/n) bias removed, and
|
|
87
|
+
finite when a covariate separates the outcome. Standard errors are
|
|
88
|
+
from the information at the estimates; `loglik` is the unpenalized
|
|
89
|
+
log-likelihood there; they agree with brglm2's ``brmultinom`` (type
|
|
90
|
+
``"AS_mean"``). Multinomial logit only (a two-level outcome gives the
|
|
91
|
+
binary logit's Firth fit, as R's logistf and brglm2), without fixed
|
|
92
|
+
effects, random intercepts or `l2`. A step is one read, two when the
|
|
93
|
+
iteration slows (near separation).
|
|
94
|
+
storage : {"disk", "memory"}, default "disk"
|
|
95
|
+
Where the blocks live between Newton steps: files in the run
|
|
96
|
+
directory, read one block at a time, or memory.
|
|
97
|
+
dtype : {"float64", "float32"}, default "float64"
|
|
98
|
+
The blocks' storage precision. Computation is always in float64;
|
|
99
|
+
float32 halves the disk the design takes, rounding it to 7 digits
|
|
100
|
+
(exact for indicators and small integers).
|
|
101
|
+
block_mb : float, default 64
|
|
102
|
+
The size of a block of the design, in MB.
|
|
103
|
+
chunk_mb : float, default 64
|
|
104
|
+
The most memory a chunk's working arrays take: blocks are worked on
|
|
105
|
+
in chunks of rows sized by this.
|
|
106
|
+
polars_engine : {"cpu", "gpu"}, default "cpu"
|
|
107
|
+
The engine for the fit's Polars queries (pass 0); "gpu" uses
|
|
108
|
+
cudf-polars and falls back to the CPU, query by query.
|
|
109
|
+
keep_intermediates : bool, default False
|
|
110
|
+
Keep the run directory.
|
|
111
|
+
verbose : bool, default True
|
|
112
|
+
Progress messages (to `logger` if given).
|
|
113
|
+
logger : logging.Logger, optional
|
|
114
|
+
log_level : int, optional"""
|
|
115
|
+
|
|
116
|
+
_RANDOM = """
|
|
117
|
+
random : str or list of str, optional
|
|
118
|
+
Random intercepts by this grouping column (or interaction ``a^b``):
|
|
119
|
+
each group's intercepts are drawn from a normal distribution with
|
|
120
|
+
mean 0 and a covariance Sigma that is estimated -- one intercept
|
|
121
|
+
per non-base outcome level (multinomial), one location shift
|
|
122
|
+
(ordered). A list gives several groupings, each with its own Sigma:
|
|
123
|
+
nested in one another at any depth (``["state", "puma"]``, PUMAs
|
|
124
|
+
within states), or two that cross (``["puma", "occupation"]``; the
|
|
125
|
+
smaller one's at most 6,000 effects are solved densely). The fixed
|
|
126
|
+
intercepts or cutpoints stay in the model, unless the formula
|
|
127
|
+
absorbs a fixed effect (``"y ~ x | region"``), within which nested
|
|
128
|
+
groupings must nest in turn (crossed ones allow none). Not with
|
|
129
|
+
`l2`. `vcov` may be ``"iid"`` or clustered by a variable that
|
|
130
|
+
contains whole groups of the coarsest random grouping (crossed
|
|
131
|
+
groupings allow none; not ``"hetero"``). The coefficients' standard
|
|
132
|
+
errors include Sigma's uncertainty. The predicted effects are in
|
|
133
|
+
`ranef`, and `predict` uses them (0 for a group the fit did not
|
|
134
|
+
see); Sigma, its standard errors and the sds' and correlations' (with
|
|
135
|
+
intervals on the log and Fisher-z scales) are in `random_effects`
|
|
136
|
+
and `varcorr`. A variance estimated at zero is held at a floor (an
|
|
137
|
+
sd of 0.001), with a warning. See `logit_stream.random_effects` for
|
|
138
|
+
the method.
|
|
139
|
+
random_method : {"laplace", "joint-mode"}, default "laplace"
|
|
140
|
+
The approximation to the likelihood: Laplace's (lme4's ``nAGQ = 1``,
|
|
141
|
+
ordinal's ``clmm`` default), or Laplace's with the coefficients and
|
|
142
|
+
the effects at their joint mode (lme4's ``nAGQ = 0``), which is
|
|
143
|
+
faster and close to it when groups have many rows each (with ~100
|
|
144
|
+
rows per group, the coefficients can be a few tenths of a standard
|
|
145
|
+
error away).
|
|
146
|
+
random_cov : {"unstructured", "diagonal"}, default "unstructured"
|
|
147
|
+
The form of Sigma (multinomial; the ordered logit's is 1 x 1).
|
|
148
|
+
random_tol : float, default 1e-6
|
|
149
|
+
Convergence when an update changes Sigma by less than this,
|
|
150
|
+
relative to its largest element."""
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def logit_stream(fml: str, data: Source, workdir: PathLike | None = None,
|
|
154
|
+
vcov: Vcov = "iid", cluster: Sequence[str] | str = (),
|
|
155
|
+
**options: Any):
|
|
156
|
+
"""Out-of-core binary logit, with any number of high-dimensional fixed
|
|
157
|
+
effects: hdfe-stream's `hdfe_stream.feglm_stream` with
|
|
158
|
+
``family="logit"``.
|
|
159
|
+
|
|
160
|
+
The fit is iteratively reweighted least squares with the fixed effects
|
|
161
|
+
absorbed (hdfe-stream's `StreamingGLM`), which for the logit is Newton's
|
|
162
|
+
method; its options and its result (`hdfe_stream.HDFEResult`, with
|
|
163
|
+
`fixef`, `resid`, `predict` and `sample`, and `loglik`, `loglik_null`,
|
|
164
|
+
`pseudo_r2`, `aic` and `bic`, and with fixed effects
|
|
165
|
+
`pseudo_r2_within`) are hdfe-stream's.
|
|
166
|
+
|
|
167
|
+
Parameters
|
|
168
|
+
----------
|
|
169
|
+
fml : str
|
|
170
|
+
A pyfixest-style formula with a 0/1 outcome, e.g.
|
|
171
|
+
``"hispanic ~ age + C(sex) | puma"``.
|
|
172
|
+
data, workdir, vcov, cluster
|
|
173
|
+
As in `mlogit_stream` (and `hdfe_stream.feglm_stream`).
|
|
174
|
+
**options
|
|
175
|
+
`hdfe_stream.feglm_stream`'s: ``weights``, ``weights_type``,
|
|
176
|
+
``iwls_tol``, ``iwls_maxiter``, ``separation_check``,
|
|
177
|
+
``within_pseudo_r2``, ``bias_correction`` (the incidental-parameter
|
|
178
|
+
bias of the fixed effects, as R's alpaca), ``fe_dof``, the memory
|
|
179
|
+
options, ``verbose``, ...
|
|
180
|
+
|
|
181
|
+
Returns
|
|
182
|
+
-------
|
|
183
|
+
hdfe_stream.HDFEResult or hdfe_stream.HDFEMulti
|
|
184
|
+
|
|
185
|
+
Notes
|
|
186
|
+
-----
|
|
187
|
+
Two conventions are hdfe-stream's (and pyfixest's) rather than this
|
|
188
|
+
package's: convergence is judged on the relative change in deviance
|
|
189
|
+
(``iwls_tol``), and the iid vcov has the factor (N - 1) / (N - K).
|
|
190
|
+
A two-level `mlogit_stream` fits the same model, with this package's
|
|
191
|
+
conventions, but absorbs at most one fixed effect; it is also the way
|
|
192
|
+
to Firth's penalized likelihood for a binary outcome (``firth=True``).
|
|
193
|
+
"""
|
|
194
|
+
from hdfe_stream import feglm_stream
|
|
195
|
+
return feglm_stream(fml, data, "logit", workdir=workdir, vcov=vcov, cluster=cluster,
|
|
196
|
+
**options)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def mlogit_stream(fml: str, data: Source, workdir: PathLike | None = None,
|
|
200
|
+
vcov: Vcov = "iid", cluster: Sequence[str] | str = (),
|
|
201
|
+
levels: Sequence | None = None, **options: Any):
|
|
202
|
+
return _fit("mlogit", fml, data, workdir=workdir, vcov=vcov, cluster=cluster,
|
|
203
|
+
levels=levels, **options)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def ologit_stream(fml: str, data: Source, workdir: PathLike | None = None,
|
|
207
|
+
vcov: Vcov = "iid", cluster: Sequence[str] | str = (),
|
|
208
|
+
levels: Sequence | None = None, **options: Any):
|
|
209
|
+
return _fit("ologit", fml, data, workdir=workdir, vcov=vcov, cluster=cluster,
|
|
210
|
+
levels=levels, **options)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def oprobit_stream(fml: str, data: Source, workdir: PathLike | None = None,
|
|
214
|
+
vcov: Vcov = "iid", cluster: Sequence[str] | str = (),
|
|
215
|
+
levels: Sequence | None = None, **options: Any):
|
|
216
|
+
return _fit("ologit", fml, data, workdir=workdir, vcov=vcov, cluster=cluster,
|
|
217
|
+
levels=levels, link="probit", **options)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def probit_stream(fml: str, data: Source, workdir: PathLike | None = None,
|
|
221
|
+
vcov: Vcov = "iid", cluster: Sequence[str] | str = (),
|
|
222
|
+
**options: Any):
|
|
223
|
+
"""Out-of-core binary probit, with any number of high-dimensional fixed
|
|
224
|
+
effects: hdfe-stream's `hdfe_stream.feglm_stream` with
|
|
225
|
+
``family="probit"``, as `logit_stream` is with ``family="logit"`` (see
|
|
226
|
+
there: the options, the result, the bias correction)."""
|
|
227
|
+
from hdfe_stream import feglm_stream
|
|
228
|
+
return feglm_stream(fml, data, "probit", workdir=workdir, vcov=vcov, cluster=cluster,
|
|
229
|
+
**options)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def cfelogit_stream(fml: str, data: Source, workdir: PathLike | None = None,
|
|
233
|
+
vcov: Vcov = "iid", cluster: Sequence[str] | str = (), **options: Any):
|
|
234
|
+
return _fit("cfelogit", fml, data, workdir=workdir, vcov=vcov, cluster=cluster,
|
|
235
|
+
**options)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def clogit_stream(fml: str, data: Source, case: str | Sequence[str],
|
|
239
|
+
workdir: PathLike | None = None, vcov: Vcov = "iid",
|
|
240
|
+
cluster: Sequence[str] | str = (), altwise: bool = False, **options: Any):
|
|
241
|
+
return _fit("clogit", fml, data, case=case, workdir=workdir, vcov=vcov, cluster=cluster,
|
|
242
|
+
altwise=altwise, **options)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def nlogit_stream(fml: str, data: Source, case: str | Sequence[str], alt: str,
|
|
247
|
+
nests: dict, workdir: PathLike | None = None, vcov: Vcov = "iid",
|
|
248
|
+
cluster: Sequence[str] | str = (), weights: str | None = None,
|
|
249
|
+
weights_type: str = "aweights", offset: str | Any | None = None,
|
|
250
|
+
**options: Any):
|
|
251
|
+
"""Out-of-core nested logit (RU2, any depth of nests), from long-format
|
|
252
|
+
choice data as `clogit_stream` takes it (prototype).
|
|
253
|
+
|
|
254
|
+
Parameters
|
|
255
|
+
----------
|
|
256
|
+
fml : str
|
|
257
|
+
``"chosen ~ x1 + x2 + C(alt)"``: the chosen alternative (0/1, one
|
|
258
|
+
per choice set) on the utilities' covariates, as for `clogit_stream`.
|
|
259
|
+
data : str, path, LazyFrame or DataFrame
|
|
260
|
+
One row per alternative of each choice set.
|
|
261
|
+
case : str or list of str
|
|
262
|
+
The column(s) identifying the choice sets.
|
|
263
|
+
alt : str
|
|
264
|
+
The column naming each row's alternative.
|
|
265
|
+
nests : dict
|
|
266
|
+
The tree: ``{nest name: [alternatives]}`` for two levels, and a nest
|
|
267
|
+
of nests as a dict, ``{"public": {"rail": ["train", "metro"],
|
|
268
|
+
"road": ["bus"]}, "private": ["car", "bike"]}``. Every alternative
|
|
269
|
+
of `alt` in one nest, at least two nests at the top; a nest holds
|
|
270
|
+
alternatives or nests, not both. A nest with one child has no
|
|
271
|
+
dissimilarity parameter (it is 1).
|
|
272
|
+
vcov : {"iid", "hetero"}, or a clustered one
|
|
273
|
+
The inverse information; the sandwich with the choice sets' scores
|
|
274
|
+
(``"hetero"``); or clustered, ``{"CRV1": var}`` (multi-way
|
|
275
|
+
``"a+b"``), the clusters constant within choice sets.
|
|
276
|
+
cluster : str or list of str, optional
|
|
277
|
+
Further clusterings to compute (`with_vcov` switches to them).
|
|
278
|
+
weights : str, optional
|
|
279
|
+
A column of weights, constant within each choice set.
|
|
280
|
+
weights_type : {"aweights", "fweights"}
|
|
281
|
+
Analytic weights are rescaled to average 1 over the choice sets.
|
|
282
|
+
offset : str or Polars expression, optional
|
|
283
|
+
A known term of each alternative's utility (coefficient 1).
|
|
284
|
+
**options
|
|
285
|
+
``tol`` (the Newton decrement, 1e-8), ``maxiter`` (200),
|
|
286
|
+
``device`` ("cpu" or "cuda": the passes on an NVIDIA GPU through
|
|
287
|
+
CuPy, experimental), and the storage options of `clogit_stream`
|
|
288
|
+
(``storage``, ``dtype``, ``block_mb``, ``workdir``,
|
|
289
|
+
``keep_intermediates``, ``verbose``, ``logger``, ``log_level``,
|
|
290
|
+
``polars_engine``).
|
|
291
|
+
|
|
292
|
+
Returns
|
|
293
|
+
-------
|
|
294
|
+
NestedLogitResult
|
|
295
|
+
The coefficients, then ``lambda.<nest>`` for each nest of two or
|
|
296
|
+
more children. A child c of nest n has the value U_c = V_c (an
|
|
297
|
+
alternative, V its utility) or lambda_c I_c (a nest), with I_n =
|
|
298
|
+
log sum_{c in n} exp(U_c / lambda_n), and the conditional
|
|
299
|
+
probability exp(U_c / lambda_n - I_n); an alternative's probability
|
|
300
|
+
is their product along its path (Stata's ``nlogit`` default
|
|
301
|
+
normalization). All lambdas 1 is the conditional logit, the starting
|
|
302
|
+
point; a warning if an estimate is outside (0, 1] or above its
|
|
303
|
+
parent nest's, where the model is not consistent with utility
|
|
304
|
+
maximization everywhere. `predict` and `resid` as for the
|
|
305
|
+
conditional logit.
|
|
306
|
+
"""
|
|
307
|
+
from .nested import _fit_nested
|
|
308
|
+
return _fit_nested(fml, data, case=case, alt=alt, nests=nests, workdir=workdir, vcov=vcov,
|
|
309
|
+
cluster=cluster, weights=weights, weights_type=weights_type,
|
|
310
|
+
offset=offset, **options)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def mixlogit_stream(fml: str, data: Source, case: str | Sequence[str],
|
|
314
|
+
random: dict, panel: str | Sequence[str] | None = None,
|
|
315
|
+
workdir: PathLike | None = None, vcov: Vcov = "iid", **options: Any):
|
|
316
|
+
return _fit_mixed(fml, data, case=case, panel=panel, random=random, workdir=workdir,
|
|
317
|
+
vcov=vcov, **options)
|
|
318
|
+
|
|
319
|
+
mlogit_stream.__doc__ = """Out-of-core multinomial logit by streamed Newton.
|
|
320
|
+
|
|
321
|
+
P(y = j | x) = exp(x'b_j) / sum_l exp(x'b_l), with b = 0 for the base
|
|
322
|
+
level (the first of `levels`). Each Newton step is one read of the
|
|
323
|
+
sample's blocks (see `logit_stream.newton`).
|
|
324
|
+
|
|
325
|
+
Parameters
|
|
326
|
+
----------
|
|
327
|
+
fml : str
|
|
328
|
+
A pyfixest-style formula, e.g. ``"race ~ age + C(sex) + C(state)"``.
|
|
329
|
+
The outcome is any column (strings, categoricals, integers); its
|
|
330
|
+
levels are its distinct values. An intercept is included unless the
|
|
331
|
+
formula removes it (``- 1``) or there is a fixed effect. A fixed
|
|
332
|
+
effect can be absorbed, ``"race ~ age | puma"``; then each of its
|
|
333
|
+
levels has its own intercept for each outcome level. With several
|
|
334
|
+
(``"race ~ age | puma + year"``) the model is fitted as the
|
|
335
|
+
equivalent Poisson regression on the data expanded by outcome level,
|
|
336
|
+
by hdfe-stream's `fepois_stream` (see the fixed-effects guide); then
|
|
337
|
+
``device="cuda"`` is fepois_stream's (its fixed effects' solve on the
|
|
338
|
+
GPU), and `poisson_options` passes it any of its other options.
|
|
339
|
+
Several outcomes and ``sw()``/``csw()`` give several models.""" + _COMMON + """
|
|
340
|
+
levels : sequence, optional
|
|
341
|
+
The outcome's levels in the order wanted, base first. Default: the
|
|
342
|
+
distinct values, sorted.
|
|
343
|
+
poisson_options : dict, optional
|
|
344
|
+
With several fixed effects: further options of hdfe-stream's
|
|
345
|
+
`fepois_stream` (``solver``, ``precond``, ``max_s_gb``,
|
|
346
|
+
``fixef_rm``, ...), except those the multinomial logit sets itself
|
|
347
|
+
(vcov, cluster, weights, keep, workdir).""" + _RANDOM + """
|
|
348
|
+
|
|
349
|
+
Returns
|
|
350
|
+
-------
|
|
351
|
+
MultinomialLogitResult or LogitMulti
|
|
352
|
+
|
|
353
|
+
Examples
|
|
354
|
+
--------
|
|
355
|
+
>>> fit = mlogit_stream("race ~ age + C(sex) + log_income | puma",
|
|
356
|
+
... "acs/*.parquet", vcov={"CRV1": "puma"})
|
|
357
|
+
>>> fit.summary()
|
|
358
|
+
>>> probs = fit.predict("people/*.parquet") # lazy: prob_<level> columns
|
|
359
|
+
"""
|
|
360
|
+
|
|
361
|
+
ologit_stream.__doc__ = """Out-of-core ordered logit by streamed Newton.
|
|
362
|
+
|
|
363
|
+
P(y <= j | x) = F(cut_j - x'b), F the logistic distribution, with
|
|
364
|
+
cutpoints cut_1 < ... < cut_{J-1} estimated with the slopes (there is no
|
|
365
|
+
intercept). Each Newton step is one read of the sample's blocks.
|
|
366
|
+
|
|
367
|
+
Parameters
|
|
368
|
+
----------
|
|
369
|
+
fml : str
|
|
370
|
+
A pyfixest-style formula, e.g. ``"educ ~ age + C(sex) | puma"``. The
|
|
371
|
+
outcome's levels are ordered as `levels`, or else sorted (numbers by
|
|
372
|
+
value, strings alphabetically, an Enum by its categories). One fixed
|
|
373
|
+
effect can be absorbed; ``cut1`` is then normalized to 0.""" + _COMMON + """
|
|
374
|
+
levels : sequence, optional
|
|
375
|
+
The outcome's levels from lowest to highest.""" + _RANDOM + """
|
|
376
|
+
|
|
377
|
+
Returns
|
|
378
|
+
-------
|
|
379
|
+
OrderedLogitResult or LogitMulti
|
|
380
|
+
|
|
381
|
+
Notes
|
|
382
|
+
-----
|
|
383
|
+
The cutpoints are estimated directly (not as increments), so the
|
|
384
|
+
reported standard errors are theirs; a step that would put them out of
|
|
385
|
+
order is halved, which the concavity of the log-likelihood makes safe.
|
|
386
|
+
"""
|
|
387
|
+
|
|
388
|
+
oprobit_stream.__doc__ = ologit_stream.__doc__.replace(
|
|
389
|
+
"Out-of-core ordered logit by streamed Newton.",
|
|
390
|
+
"Out-of-core ordered probit by streamed Newton.").replace(
|
|
391
|
+
"F the logistic distribution", "F the standard normal distribution").replace(
|
|
392
|
+
"OrderedLogitResult or LogitMulti", "OrderedProbitResult or LogitMulti") + """
|
|
393
|
+
Random intercepts (``random=``) and ``firth=`` are not available for the
|
|
394
|
+
ordered probit.
|
|
395
|
+
"""
|
|
396
|
+
|
|
397
|
+
cfelogit_stream.__doc__ = """Out-of-core conditional fixed-effects logit
|
|
398
|
+
(Chamberlain 1980; Stata's ``xtlogit, fe``, R's ``survival::clogit``),
|
|
399
|
+
by streamed Newton.
|
|
400
|
+
|
|
401
|
+
For a binary outcome observed several times per unit (a panel), the
|
|
402
|
+
likelihood of each unit's outcomes given their number of 1s does not
|
|
403
|
+
depend on the unit's effect, which is thus conditioned out rather than
|
|
404
|
+
estimated: the estimates are consistent however few the periods, where
|
|
405
|
+
the logit with the effects estimated (`logit_stream`) is biased with
|
|
406
|
+
short panels (see its ``bias_correction=``, for longer ones). Units whose
|
|
407
|
+
outcome never varies carry no information and are dropped. There are no
|
|
408
|
+
unit effects, so no predicted probabilities or marginal effects.
|
|
409
|
+
|
|
410
|
+
Parameters
|
|
411
|
+
----------
|
|
412
|
+
fml : str
|
|
413
|
+
A pyfixest-style formula whose one fixed effect is the unit, e.g.
|
|
414
|
+
``"employed ~ age + C(year) | person_id"``. Covariates constant
|
|
415
|
+
within every unit are dropped (their effect is the unit's).""" + _COMMON + """
|
|
416
|
+
|
|
417
|
+
Returns
|
|
418
|
+
-------
|
|
419
|
+
ConditionalFELogitResult or LogitMulti
|
|
420
|
+
|
|
421
|
+
Notes
|
|
422
|
+
-----
|
|
423
|
+
Each unit's contribution is log P(y | sum y = k) = sum y eta -
|
|
424
|
+
log e_k(exp eta), e_k the elementary symmetric polynomial of degree k,
|
|
425
|
+
computed with its gradient and information in one recursion over the
|
|
426
|
+
unit's rows: O(T k P^2) work for a unit of T rows (k <= T / 2), so long
|
|
427
|
+
panels with many 1s are costly. Each unit's rows are written together in
|
|
428
|
+
pass 0 (sorted by unit if they are not already). The robust variance
|
|
429
|
+
treats units as the observations (clustered by unit); weights and
|
|
430
|
+
clusters must be constant within units.
|
|
431
|
+
"""
|
|
432
|
+
|
|
433
|
+
clogit_stream.__doc__ = """Out-of-core conditional (McFadden) logit by streamed Newton.
|
|
434
|
+
|
|
435
|
+
Long-format data: one row per alternative of each choice set, the
|
|
436
|
+
outcome 1 on the chosen alternative and 0 on the others. P(alternative
|
|
437
|
+
a chosen in set i) = exp(x_ia'b) / sum over the set's alternatives.
|
|
438
|
+
Alternatives that were not available are simply absent. Each Newton
|
|
439
|
+
step is one read of the sample's blocks, each of which holds whole
|
|
440
|
+
choice sets.
|
|
441
|
+
|
|
442
|
+
Parameters
|
|
443
|
+
----------
|
|
444
|
+
fml : str
|
|
445
|
+
A pyfixest-style formula, e.g.
|
|
446
|
+
``"choice ~ cost + time + C(alt)"`` (``C(alt)``: alternative-specific
|
|
447
|
+
constants). There is no intercept, and a variable constant within a
|
|
448
|
+
choice set cancels: interact it with the alternatives
|
|
449
|
+
(``income:C(alt)``).
|
|
450
|
+
case : str or sequence of str
|
|
451
|
+
The column(s) identifying a choice set. Rows of a set need not be
|
|
452
|
+
consecutive (if they are not, pass 0 sorts them, out of core).""" + _COMMON + """
|
|
453
|
+
altwise : bool, default False
|
|
454
|
+
A choice set with an alternative whose values are missing is dropped
|
|
455
|
+
whole, with a warning, as Stata's ``cmclogit`` does: fitted on its
|
|
456
|
+
other alternatives it would be a different choice set. With
|
|
457
|
+
``altwise=True`` only the incomplete alternatives are dropped (a set
|
|
458
|
+
that loses its chosen alternative is then an error).
|
|
459
|
+
|
|
460
|
+
Returns
|
|
461
|
+
-------
|
|
462
|
+
ConditionalLogitResult or LogitMulti
|
|
463
|
+
|
|
464
|
+
Notes
|
|
465
|
+
-----
|
|
466
|
+
Every choice set must have exactly one chosen alternative after rows with
|
|
467
|
+
missing values are dropped; weights and clusters must be constant within
|
|
468
|
+
a set. The robust and clustered variances treat a choice set as the
|
|
469
|
+
unit.
|
|
470
|
+
"""
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
mixlogit_stream.__doc__ = """Out-of-core mixed logit by streamed maximum simulated likelihood
|
|
474
|
+
(prototype: normal and lognormal coefficients, uncorrelated).
|
|
475
|
+
|
|
476
|
+
The conditional logit of `clogit_stream`, with the coefficients named in
|
|
477
|
+
`random` varying across choosers: normal, mu + s z, or lognormal,
|
|
478
|
+
exp(mu + s z), z standard normal. A chooser's probability of their
|
|
479
|
+
choices is simulated with `draws` draws, which are regenerated in each
|
|
480
|
+
read of the data rather than stored. The optimizer takes BHHH steps,
|
|
481
|
+
then Newton steps with a finite-difference Hessian of the analytic
|
|
482
|
+
gradient; see `logit_stream.mixed`.
|
|
483
|
+
|
|
484
|
+
Parameters
|
|
485
|
+
----------
|
|
486
|
+
fml : str
|
|
487
|
+
``"chosen ~ x1 + x2 + ..."``, as for `clogit_stream`; the outcome is
|
|
488
|
+
1 for the chosen alternative, 0 otherwise.
|
|
489
|
+
data : str, Path, LazyFrame or DataFrame
|
|
490
|
+
Long-format data: one row per available alternative.
|
|
491
|
+
case : str or list of str
|
|
492
|
+
The column(s) identifying each choice set.
|
|
493
|
+
random : dict
|
|
494
|
+
Covariate name -> ``"normal"`` or ``"lognormal"``. A lognormal
|
|
495
|
+
coefficient is positive: for a negative effect (a cost), use the
|
|
496
|
+
negated covariate.
|
|
497
|
+
panel : str or list of str, optional
|
|
498
|
+
The column(s) identifying each chooser, whose choice sets share one
|
|
499
|
+
draw of the random coefficients (a panel of repeated choices). By
|
|
500
|
+
default each choice set is its own chooser.
|
|
501
|
+
workdir : str or Path, optional
|
|
502
|
+
Where the blocks are written (as for `clogit_stream`).
|
|
503
|
+
vcov : {"iid", "hetero"} or {"CRV1": panel}
|
|
504
|
+
The variance reported: the inverse Hessian, or the sandwich with the
|
|
505
|
+
choosers' scores -- clustered by chooser, so ``{"CRV1": panel}``
|
|
506
|
+
(G / (G - 1), df G - 1) with `panel`, and ``"hetero"`` without one
|
|
507
|
+
(each choice set its own chooser). Both are computed. The random
|
|
508
|
+
coefficients' standard deviations are reported as absolute values (their sign
|
|
509
|
+
is not identified; the standard errors are the same), the signed
|
|
510
|
+
estimates in ``diagnostics["sd_signed"]``.
|
|
511
|
+
draws : int
|
|
512
|
+
Draws per chooser (default 500).
|
|
513
|
+
halton : bool
|
|
514
|
+
Halton draws (default) or pseudo-random ones.
|
|
515
|
+
seed : int
|
|
516
|
+
The pseudo-random draws' seed.
|
|
517
|
+
drop : int
|
|
518
|
+
Halton points discarded at the start of each sequence (default 100).
|
|
519
|
+
tol, maxiter, switch : float, int, float
|
|
520
|
+
Convergence when the Newton decrement is below `tol` (1e-8); at
|
|
521
|
+
most `maxiter` steps (200); BHHH steps until their decrement is
|
|
522
|
+
below `switch` (default: none, Newton throughout, with BHHH steps
|
|
523
|
+
where the Hessian is not negative definite).
|
|
524
|
+
draws_cache_mb : float
|
|
525
|
+
Keep all choosers' draws in memory if they fit (default 512 MB);
|
|
526
|
+
otherwise they are regenerated in every pass. With ``device="cuda"``
|
|
527
|
+
the budget is the device's memory, where the draws are generated.
|
|
528
|
+
device : {"cpu", "cuda"}
|
|
529
|
+
``"cuda"`` (experimental): the passes on an NVIDIA GPU through CuPy,
|
|
530
|
+
the likelihood vectorized over rows and draws (on the CPU a numba
|
|
531
|
+
kernel, which is about ten times faster there than the vectorized
|
|
532
|
+
form).
|
|
533
|
+
**options
|
|
534
|
+
`storage`, `dtype`, `block_mb`, `polars_engine`, `keep_intermediates`,
|
|
535
|
+
`verbose`, `logger`, `log_level`, as for `clogit_stream`.
|
|
536
|
+
|
|
537
|
+
Returns
|
|
538
|
+
-------
|
|
539
|
+
MixedLogitResult
|
|
540
|
+
`summary`, `tidy`, `confint`, `wald_test` and the other reporting
|
|
541
|
+
methods; `predict` and `resid` are not in the prototype.
|
|
542
|
+
"""
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Newton passes on a GPU (``device="cuda"``, experimental).
|
|
2
|
+
|
|
3
|
+
Each pass's log-likelihood, gradient and information are formed on the
|
|
4
|
+
device, block by block, and only those sums come back; the Newton step (a
|
|
5
|
+
P x P solve, with a fixed effect's Schur complement or the random
|
|
6
|
+
intercepts' block elimination) runs on the CPU. That covers the
|
|
7
|
+
multinomial, ordered (logit and probit), conditional and conditional
|
|
8
|
+
fixed-effects logit, the multinomial and ordered with an absorbed fixed
|
|
9
|
+
effect (each level's sums by scatter-adds over the rows' level codes) or
|
|
10
|
+
random intercepts (each unit's sums and cross blocks, and the exact Laplace
|
|
11
|
+
terms), Firth's adjustment, and the mixed and nested logit (their
|
|
12
|
+
likelihoods vectorized: over rows and draws, the draws generated on the
|
|
13
|
+
device; over choice sets and nests). The robust and clustered variances'
|
|
14
|
+
scores pass runs there too. The blocks are copied to the device once and
|
|
15
|
+
kept there when the sample fits in half of its free memory (a design's 0/1
|
|
16
|
+
columns as bytes, expanded each pass); otherwise each pass copies them over
|
|
17
|
+
again, a block at a time.
|
|
18
|
+
|
|
19
|
+
The array namespace is hdfe-stream's (`hdfe_stream.core.gpu.cuda_backend`:
|
|
20
|
+
CuPy). The functions here are written against it, so the tests run them on a
|
|
21
|
+
numpy stand-in; on a GPU the sums are formed in a different order, so the
|
|
22
|
+
estimates agree with the CPU's to the convergence tolerance, not bitwise.
|
|
23
|
+
|
|
24
|
+
What stays on the CPU: pass 0 (unless ``polars_engine="gpu"``), the solves,
|
|
25
|
+
and the cluster jackknife (CRV3).
|
|
26
|
+
|
|
27
|
+
Layout
|
|
28
|
+
store.py the backend and `DeviceStore`
|
|
29
|
+
_common.py elementwise pieces, segment sums and maxima, scatters
|
|
30
|
+
models.py the models' sums (`accumulate`), Firth's adjustment
|
|
31
|
+
fe.py the absorbed fixed effect's pass
|
|
32
|
+
random_effects.py the random intercepts' pass
|
|
33
|
+
mixed.py the mixed logit's pass and draws
|
|
34
|
+
nested.py the nested logit's pass
|
|
35
|
+
meat.py the robust variances' scores pass
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
from .fe import fe_pass
|
|
39
|
+
from .meat import fe_meat, meat_sums, plain_meat, re_meat, scores, to_host
|
|
40
|
+
from .mixed import mxl_draws, mxl_pass
|
|
41
|
+
from .models import accumulate, firth_adjustment
|
|
42
|
+
from .nested import nl_pass
|
|
43
|
+
from .random_effects import re_pass
|
|
44
|
+
from .store import DeviceStore, backend
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def resolve(device):
|
|
48
|
+
"""device= as the drivers take it: None for the CPU, else the GPU's
|
|
49
|
+
backend (failing now, without CuPy or a GPU)."""
|
|
50
|
+
if device not in ("cpu", "cuda"):
|
|
51
|
+
raise ValueError("device must be 'cpu' or 'cuda'")
|
|
52
|
+
return None if device == "cpu" else backend()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
__all__ = ["DeviceStore", "accumulate", "backend", "fe_meat", "fe_pass", "firth_adjustment",
|
|
56
|
+
"meat_sums", "mxl_draws", "mxl_pass", "nl_pass", "plain_meat", "re_meat",
|
|
57
|
+
"re_pass", "resolve", "scores", "to_host"]
|