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/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"]