logit-stream 0.1.0__tar.gz

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.
Files changed (61) hide show
  1. logit_stream-0.1.0/CHANGELOG.md +86 -0
  2. logit_stream-0.1.0/CITATION.cff +21 -0
  3. logit_stream-0.1.0/LICENSE.md +21 -0
  4. logit_stream-0.1.0/MANIFEST.in +2 -0
  5. logit_stream-0.1.0/PKG-INFO +127 -0
  6. logit_stream-0.1.0/README.md +72 -0
  7. logit_stream-0.1.0/logit_stream/__init__.py +77 -0
  8. logit_stream-0.1.0/logit_stream/_choice.py +64 -0
  9. logit_stream-0.1.0/logit_stream/_frontend.py +78 -0
  10. logit_stream-0.1.0/logit_stream/api.py +542 -0
  11. logit_stream-0.1.0/logit_stream/device/__init__.py +57 -0
  12. logit_stream-0.1.0/logit_stream/device/_common.py +92 -0
  13. logit_stream-0.1.0/logit_stream/device/fe.py +108 -0
  14. logit_stream-0.1.0/logit_stream/device/meat.py +210 -0
  15. logit_stream-0.1.0/logit_stream/device/mixed.py +168 -0
  16. logit_stream-0.1.0/logit_stream/device/models.py +258 -0
  17. logit_stream-0.1.0/logit_stream/device/nested.py +144 -0
  18. logit_stream-0.1.0/logit_stream/device/random_effects.py +196 -0
  19. logit_stream-0.1.0/logit_stream/device/store.py +96 -0
  20. logit_stream-0.1.0/logit_stream/estimator.py +635 -0
  21. logit_stream-0.1.0/logit_stream/fe.py +539 -0
  22. logit_stream-0.1.0/logit_stream/mixed.py +518 -0
  23. logit_stream-0.1.0/logit_stream/models.py +689 -0
  24. logit_stream-0.1.0/logit_stream/multi_fe.py +242 -0
  25. logit_stream-0.1.0/logit_stream/nested.py +600 -0
  26. logit_stream-0.1.0/logit_stream/newton.py +519 -0
  27. logit_stream-0.1.0/logit_stream/optimize.py +180 -0
  28. logit_stream-0.1.0/logit_stream/py.typed +0 -0
  29. logit_stream-0.1.0/logit_stream/random_effects/__init__.py +84 -0
  30. logit_stream-0.1.0/logit_stream/random_effects/fit.py +257 -0
  31. logit_stream-0.1.0/logit_stream/random_effects/linalg.py +224 -0
  32. logit_stream-0.1.0/logit_stream/random_effects/model.py +763 -0
  33. logit_stream-0.1.0/logit_stream/random_effects/sigma.py +399 -0
  34. logit_stream-0.1.0/logit_stream/results.py +1000 -0
  35. logit_stream-0.1.0/logit_stream/simulate.py +257 -0
  36. logit_stream-0.1.0/logit_stream/store.py +377 -0
  37. logit_stream-0.1.0/logit_stream.egg-info/PKG-INFO +127 -0
  38. logit_stream-0.1.0/logit_stream.egg-info/SOURCES.txt +59 -0
  39. logit_stream-0.1.0/logit_stream.egg-info/dependency_links.txt +1 -0
  40. logit_stream-0.1.0/logit_stream.egg-info/requires.txt +29 -0
  41. logit_stream-0.1.0/logit_stream.egg-info/top_level.txt +1 -0
  42. logit_stream-0.1.0/pyproject.toml +103 -0
  43. logit_stream-0.1.0/setup.cfg +4 -0
  44. logit_stream-0.1.0/tests/test_api.py +146 -0
  45. logit_stream-0.1.0/tests/test_cfelogit.py +89 -0
  46. logit_stream-0.1.0/tests/test_clustering.py +75 -0
  47. logit_stream-0.1.0/tests/test_conditional.py +170 -0
  48. logit_stream-0.1.0/tests/test_device.py +341 -0
  49. logit_stream-0.1.0/tests/test_firth_and_fit.py +116 -0
  50. logit_stream-0.1.0/tests/test_fixed_effects.py +118 -0
  51. logit_stream-0.1.0/tests/test_kernels.py +105 -0
  52. logit_stream-0.1.0/tests/test_mixed.py +213 -0
  53. logit_stream-0.1.0/tests/test_multi_fe.py +121 -0
  54. logit_stream-0.1.0/tests/test_multinomial.py +226 -0
  55. logit_stream-0.1.0/tests/test_nested.py +318 -0
  56. logit_stream-0.1.0/tests/test_offset_start.py +74 -0
  57. logit_stream-0.1.0/tests/test_ordered.py +98 -0
  58. logit_stream-0.1.0/tests/test_probit.py +92 -0
  59. logit_stream-0.1.0/tests/test_random.py +593 -0
  60. logit_stream-0.1.0/tests/test_simulate.py +76 -0
  61. logit_stream-0.1.0/tests/test_weights_scale.py +53 -0
@@ -0,0 +1,86 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ The first release: logit models by streamed Newton's method, for data that
6
+ do not fit in memory. Formulas are pyfixest-style (hdfe-stream's front end);
7
+ data are Parquet files, Polars LazyFrames or DataFrames. Pass 0 writes the
8
+ evaluated design as blocks of arrays, and each Newton step is one read of
9
+ them.
10
+
11
+ ### Models
12
+
13
+ - **Binary:** `logit_stream` and `probit_stream` (hdfe-stream's `feglm_stream`),
14
+ with any number of absorbed fixed effects, fit statistics (`loglik`,
15
+ `aic`, `bic`, `pseudo_r2_within`) and the incidental-parameter bias
16
+ correction (`bias_correction=`).
17
+ - **Multinomial and ordered:** `mlogit_stream`, `ologit_stream` and
18
+ `oprobit_stream`.
19
+ - **Conditional (McFadden):** `clogit_stream`; the conditional logit drops a
20
+ choice set whole when an alternative has missing values (Stata's
21
+ `cmclogit`), or keeps the complete alternatives with `altwise=True`.
22
+ - **Panels:** `cfelogit_stream`, Chamberlain's conditional fixed-effects logit
23
+ (Stata's `xtlogit, fe`); matches statsmodels' `ConditionalLogit`.
24
+ - **Nested logit** (experimental): `nlogit_stream`, a tree of nests of any
25
+ depth, in the utility-consistent normalization (Stata's `nlogit`
26
+ default). Matches R's mlogit.
27
+ - **Mixed logit** (experimental prototype): `mixlogit_stream`, by streamed
28
+ maximum simulated likelihood, with normal and lognormal coefficients,
29
+ panels, and Halton or pseudo-random draws regenerated in every read.
30
+ - **Firth's penalized likelihood** (`firth=True`) for the multinomial and
31
+ binary logit, matching brglm2 and logistf; an optional ridge penalty
32
+ (`l2=`).
33
+
34
+ ### Fixed and random effects
35
+
36
+ - One absorbed fixed effect for the multinomial and ordered logit, solved
37
+ exactly in each Newton step (`"y ~ x | puma"`); several for the
38
+ multinomial logit (`"y ~ x | a + b"`), fitted as the equivalent Poisson
39
+ regression on the data expanded by outcome level (`fepois_stream`).
40
+ - Random intercepts for the multinomial and ordered logit (`random=`;
41
+ experimental): Laplace's approximation (matching `glmer` and ordinal's
42
+ `clmm`) or the joint mode, with an unstructured or diagonal covariance
43
+ over the outcome levels. Nested groupings at any depth, optionally within
44
+ a fixed effect, or two crossed. `varcorr()` and `ranef()` report the
45
+ covariance and the predicted effects.
46
+ - With a fixed effect, `loglik_fe_null` and `pseudo_r2_within`.
47
+
48
+ ### Inference and results
49
+
50
+ - Variances: iid, heteroskedasticity-robust and clustered (one-way,
51
+ multi-way, or by an interaction), with Stata's small-sample factors for
52
+ maximum likelihood; the cluster jackknife (`{"CRV3": var}`). Frequency
53
+ weights, and analytic weights rescaled to average 1 as Stata does.
54
+ - Results subclass hdfe-stream's `RegressionResult`: `summary`, `tidy`,
55
+ `confint` (pointwise or simultaneous), `wald_test`, `with_vcov`,
56
+ `to_pyfixest` and `etable`, and lazy `predict`, `resid` (including the
57
+ ordered models' probability-scale and generalized residuals), `sample` and
58
+ `fixef`.
59
+ - `offset=` and warm starts (`start=`).
60
+ - Convergence by the Newton decrement, with step-halving and a
61
+ Levenberg–Marquardt fallback, collinearity detection and warnings for
62
+ separated coefficients.
63
+
64
+ ### Scale and speed
65
+
66
+ - **GPU** (`device="cuda"`, experimental): the Newton passes and the
67
+ variances' scores pass of every model, on an NVIDIA GPU through CuPy, with
68
+ the blocks kept on the device when they fit; `polars_engine="gpu"` runs
69
+ pass 0 on Polars' GPU engine. Tested on a numpy stand-in; the GPU
70
+ benchmark covers the hardware.
71
+ - 0/1 design columns are stored as one byte each, and the multinomial
72
+ information is computed from each row's nonzeros when the design is mostly
73
+ indicators.
74
+ - Numba kernels that accumulate per thread (the nested, mixed and
75
+ conditional fixed-effects logit).
76
+ - `storage=`, `dtype=`, `block_mb=` and `chunk_mb=` control the block store.
77
+
78
+ ### Tools
79
+
80
+ - `logit_stream.simulate`: `simulate_people`, `simulate_choices` (with
81
+ nests, or tasks and a random coefficient) and `simulate_panel`.
82
+ - `examples/`: a standalone script for each page of the guide.
83
+ - `benchmarks/`: ACS microdata, the Swissmetro survey, simulated panels and a
84
+ GPU suite, against scikit-learn, statsmodels, mord, pyfixest, xlogit,
85
+ jaxlogit, Biogeme, and R's glm, fixest, nnet, MASS, ordinal, survival,
86
+ mlogit, logitr and alpaca. Resumable.
@@ -0,0 +1,21 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use this software in your research, please cite it as below."
3
+ type: software
4
+ title: "logit-stream"
5
+ abstract: "Out-of-core logit models: binary, multinomial, ordered, conditional, nested, mixed and panel logit, with fixed and random effects, for data that do not fit in memory."
6
+ authors:
7
+ - family-names: Tucker
8
+ given-names: Lee
9
+ email: lee.c.tucker@census.gov
10
+ version: 0.1.0
11
+ date-released: 2026-10-29
12
+ license: MIT
13
+ repository-code: "https://github.com/leetucker/logit-stream"
14
+ url: "https://github.com/leetucker/logit-stream"
15
+ keywords:
16
+ - econometrics
17
+ - logit
18
+ - multinomial logit
19
+ - discrete choice
20
+ - out-of-core
21
+ - fixed effects
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Lee Tucker
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,2 @@
1
+ include LICENSE.md README.md CHANGELOG.md CITATION.cff
2
+ include logit_stream/py.typed
@@ -0,0 +1,127 @@
1
+ Metadata-Version: 2.4
2
+ Name: logit-stream
3
+ Version: 0.1.0
4
+ Summary: Out-of-core logit models by streamed Newton: binary, multinomial, ordered and conditional logit, with absorbed fixed effects
5
+ Author: Lee Tucker
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/leetucker/logit-stream
8
+ Project-URL: Source, https://github.com/leetucker/logit-stream
9
+ Project-URL: Issues, https://github.com/leetucker/logit-stream/issues
10
+ Project-URL: Documentation, https://leetucker.github.io/logit-stream/
11
+ Project-URL: Changelog, https://github.com/leetucker/logit-stream/blob/main/CHANGELOG.md
12
+ Keywords: econometrics,logit,multinomial logit,ordered logit,conditional logit,discrete choice,out-of-core,fixed effects,imputation
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Programming Language :: Python :: 3.15
23
+ Classifier: Topic :: Scientific/Engineering
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.10
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE.md
28
+ Requires-Dist: hdfe-stream[formula]<0.7,>=0.6
29
+ Requires-Dist: polars!=1.41.*,>=1.37
30
+ Requires-Dist: numpy>=1.24
31
+ Requires-Dist: numba>=0.59
32
+ Requires-Dist: pyarrow>=14.0
33
+ Requires-Dist: scipy>=1.11
34
+ Requires-Dist: threadpoolctl>=3.1
35
+ Provides-Extra: benchmark
36
+ Requires-Dist: scikit-learn>=1.5; extra == "benchmark"
37
+ Requires-Dist: statsmodels>=0.14; extra == "benchmark"
38
+ Requires-Dist: xlogit>=0.2.7; extra == "benchmark"
39
+ Requires-Dist: pylogit>=1.0.1; extra == "benchmark"
40
+ Requires-Dist: mord>=0.7; extra == "benchmark"
41
+ Requires-Dist: matplotlib>=3.7; extra == "benchmark"
42
+ Requires-Dist: pandas>=2.0; extra == "benchmark"
43
+ Provides-Extra: docs
44
+ Requires-Dist: sphinx>=8.0; extra == "docs"
45
+ Requires-Dist: myst-parser>=4.0; extra == "docs"
46
+ Requires-Dist: furo>=2024.8.6; extra == "docs"
47
+ Requires-Dist: sphinx-copybutton>=0.5; extra == "docs"
48
+ Requires-Dist: sphinx-design>=0.6; extra == "docs"
49
+ Provides-Extra: test
50
+ Requires-Dist: pytest>=7.0; extra == "test"
51
+ Requires-Dist: scikit-learn>=1.5; extra == "test"
52
+ Requires-Dist: statsmodels>=0.14; extra == "test"
53
+ Requires-Dist: pandas>=2.0; extra == "test"
54
+ Dynamic: license-file
55
+
56
+ # logit-stream
57
+
58
+ Logit models for data that do not fit in memory: binary, multinomial,
59
+ ordered, conditional, nested and mixed logit, fitted by Newton's method with
60
+ the data streamed off disk, from
61
+ [pyfixest](https://github.com/py-econometrics/pyfixest)-style formulas, with
62
+ absorbed fixed effects or random intercepts.
63
+
64
+ ```python
65
+ from logit_stream import mlogit_stream
66
+
67
+ fit = mlogit_stream(
68
+ "race ~ C(agecat) + female + C(inccat) + C(cit) | puma",
69
+ "acs/*.parquet", # a Parquet path or glob, never fully loaded
70
+ vcov={"CRV1": "puma"},
71
+ )
72
+ fit.summary()
73
+ fit.predict("people/*.parquet").sink_parquet("imputed.parquet") # lazily
74
+ ```
75
+
76
+ | | |
77
+ |---|---|
78
+ | `logit_stream`, `probit_stream` | binary logit and probit with any number of fixed effects ([hdfe-stream](https://github.com/leetucker/hdfe-stream)'s `feglm_stream`) |
79
+ | `mlogit_stream` | multinomial logit (any number of fixed effects; several through a Poisson regression) |
80
+ | `ologit_stream`, `oprobit_stream` | ordered logit and probit |
81
+ | `clogit_stream` | conditional (McFadden) logit on long-format choice data |
82
+ | `cfelogit_stream` | Chamberlain's conditional fixed-effects logit for panels |
83
+ | `mixlogit_stream`, `nlogit_stream` | mixed and nested logit (prototypes) |
84
+
85
+ Newton's method for a logit needs the data only through sums over rows -- the
86
+ log-likelihood, its gradient and the information matrix -- so each step is one
87
+ read of the data, block by block, and one dense solve; the likelihoods are
88
+ concave, so a handful of steps suffice. Memory grows with the number of
89
+ coefficients, not of rows. The multinomial and ordered logit absorb one fixed
90
+ effect exactly, by eliminating its per-level blocks of the information matrix
91
+ in every step.
92
+
93
+ Results share [hdfe-stream](https://github.com/leetucker/hdfe-stream)'s
94
+ reporting: `summary()`, `tidy()`, `summary_dict()`, `summary_json()`,
95
+ `confint()`, `wald_test()`, `with_vcov()`, `etable()`, and lazy `predict()`,
96
+ `resid()`, `sample()` and `fixef()` frames. Estimates and standard errors
97
+ match statsmodels and scikit-learn (see `tests/`).
98
+
99
+ Documentation: <https://leetucker.github.io/logit-stream/> (build locally with
100
+ `pip install -e ".[docs]"` and `sphinx-build -b html docs docs/_build/html`).
101
+ `examples/` has a runnable script for each page of its guide, on simulated
102
+ data.
103
+
104
+ ## Install
105
+
106
+ ```bash
107
+ pip install logit-stream
108
+ ```
109
+
110
+ Python 3.10 or later. On Python 3.15, pyfixest 0.60 (which hdfe-stream's
111
+ formula extra brings in) has no wheels yet: it builds from source with a Rust
112
+ toolchain and `PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1` set; everything else
113
+ installs from wheels.
114
+
115
+ ## Benchmarks
116
+
117
+ `benchmarks/` compares logit-stream with scikit-learn, statsmodels, mord and
118
+ R's glm, fixest, nnet, MASS and ordinal on American Community Survey
119
+ microdata (10,000 to 12.9 million adults); with xlogit, pylogit,
120
+ statsmodels, jaxlogit, Biogeme and R's mlogit and logitr on the Swissmetro
121
+ choice survey, resampled to 10 million choice sets; on short panels with
122
+ statsmodels and R's survival, fixest and alpaca; and on a GPU. See
123
+ `benchmarks/README.md` and the documentation's benchmark pages.
124
+
125
+ ## License
126
+
127
+ MIT.
@@ -0,0 +1,72 @@
1
+ # logit-stream
2
+
3
+ Logit models for data that do not fit in memory: binary, multinomial,
4
+ ordered, conditional, nested and mixed logit, fitted by Newton's method with
5
+ the data streamed off disk, from
6
+ [pyfixest](https://github.com/py-econometrics/pyfixest)-style formulas, with
7
+ absorbed fixed effects or random intercepts.
8
+
9
+ ```python
10
+ from logit_stream import mlogit_stream
11
+
12
+ fit = mlogit_stream(
13
+ "race ~ C(agecat) + female + C(inccat) + C(cit) | puma",
14
+ "acs/*.parquet", # a Parquet path or glob, never fully loaded
15
+ vcov={"CRV1": "puma"},
16
+ )
17
+ fit.summary()
18
+ fit.predict("people/*.parquet").sink_parquet("imputed.parquet") # lazily
19
+ ```
20
+
21
+ | | |
22
+ |---|---|
23
+ | `logit_stream`, `probit_stream` | binary logit and probit with any number of fixed effects ([hdfe-stream](https://github.com/leetucker/hdfe-stream)'s `feglm_stream`) |
24
+ | `mlogit_stream` | multinomial logit (any number of fixed effects; several through a Poisson regression) |
25
+ | `ologit_stream`, `oprobit_stream` | ordered logit and probit |
26
+ | `clogit_stream` | conditional (McFadden) logit on long-format choice data |
27
+ | `cfelogit_stream` | Chamberlain's conditional fixed-effects logit for panels |
28
+ | `mixlogit_stream`, `nlogit_stream` | mixed and nested logit (prototypes) |
29
+
30
+ Newton's method for a logit needs the data only through sums over rows -- the
31
+ log-likelihood, its gradient and the information matrix -- so each step is one
32
+ read of the data, block by block, and one dense solve; the likelihoods are
33
+ concave, so a handful of steps suffice. Memory grows with the number of
34
+ coefficients, not of rows. The multinomial and ordered logit absorb one fixed
35
+ effect exactly, by eliminating its per-level blocks of the information matrix
36
+ in every step.
37
+
38
+ Results share [hdfe-stream](https://github.com/leetucker/hdfe-stream)'s
39
+ reporting: `summary()`, `tidy()`, `summary_dict()`, `summary_json()`,
40
+ `confint()`, `wald_test()`, `with_vcov()`, `etable()`, and lazy `predict()`,
41
+ `resid()`, `sample()` and `fixef()` frames. Estimates and standard errors
42
+ match statsmodels and scikit-learn (see `tests/`).
43
+
44
+ Documentation: <https://leetucker.github.io/logit-stream/> (build locally with
45
+ `pip install -e ".[docs]"` and `sphinx-build -b html docs docs/_build/html`).
46
+ `examples/` has a runnable script for each page of its guide, on simulated
47
+ data.
48
+
49
+ ## Install
50
+
51
+ ```bash
52
+ pip install logit-stream
53
+ ```
54
+
55
+ Python 3.10 or later. On Python 3.15, pyfixest 0.60 (which hdfe-stream's
56
+ formula extra brings in) has no wheels yet: it builds from source with a Rust
57
+ toolchain and `PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1` set; everything else
58
+ installs from wheels.
59
+
60
+ ## Benchmarks
61
+
62
+ `benchmarks/` compares logit-stream with scikit-learn, statsmodels, mord and
63
+ R's glm, fixest, nnet, MASS and ordinal on American Community Survey
64
+ microdata (10,000 to 12.9 million adults); with xlogit, pylogit,
65
+ statsmodels, jaxlogit, Biogeme and R's mlogit and logitr on the Swissmetro
66
+ choice survey, resampled to 10 million choice sets; on short panels with
67
+ statsmodels and R's survival, fixest and alpaca; and on a GPU. See
68
+ `benchmarks/README.md` and the documentation's benchmark pages.
69
+
70
+ ## License
71
+
72
+ MIT.
@@ -0,0 +1,77 @@
1
+ """logit-stream: logit models fitted out of core by streamed Newton.
2
+
3
+ Binary (`logit_stream`, `probit_stream`: hdfe-stream's `feglm_stream`, with
4
+ any number of absorbed fixed effects), multinomial (`mlogit_stream`),
5
+ ordered (`ologit_stream`, `oprobit_stream`), conditional (`clogit_stream`),
6
+ conditional fixed-effects (`cfelogit_stream`), mixed (`mixlogit_stream`) and
7
+ nested (`nlogit_stream`) logit, from pyfixest-style formulas, on data that
8
+ need not fit in memory: Parquet files, Polars LazyFrames, or DataFrames.
9
+
10
+ Layout
11
+ api.py the estimators
12
+ _frontend.py formulas -> models (hdfe-stream's formula compiler)
13
+ store.py pass 0, into hdfe-stream's block store (hdfe_stream.core.store)
14
+ models.py the likelihoods: per chunk, log-likelihood, gradient,
15
+ information, scores
16
+ newton.py Newton's method, collinearity, the variance's middle matrix
17
+ estimator.py formula + source -> result, and the inference
18
+ fe.py one absorbed fixed effect (multinomial, ordered)
19
+ multi_fe.py several, for the multinomial logit (a Poisson regression)
20
+ random_effects/ random intercepts (Laplace's approximation)
21
+ optimize.py Newton and quasi-Newton steps from an analytic gradient,
22
+ for the likelihoods that are not concave
23
+ _choice.py what the mixed and nested logit's fits share
24
+ mixed.py the mixed logit (simulated likelihood)
25
+ nested.py the nested logit
26
+ device/ the passes on a GPU (CuPy)
27
+ results.py the result classes
28
+ simulate.py simulated data for examples and tests
29
+ """
30
+
31
+ from __future__ import annotations
32
+
33
+ from hdfe_stream.core.multcomp import bonferroni, holm
34
+ from hdfe_stream.core.reporting import etable
35
+
36
+ from .api import (
37
+ cfelogit_stream,
38
+ clogit_stream,
39
+ logit_stream,
40
+ mixlogit_stream,
41
+ mlogit_stream,
42
+ nlogit_stream,
43
+ ologit_stream,
44
+ oprobit_stream,
45
+ probit_stream,
46
+ )
47
+ from .results import (
48
+ ConditionalFELogitResult,
49
+ ConditionalLogitResult,
50
+ LogitMulti,
51
+ LogitResult,
52
+ MixedLogitResult,
53
+ MultinomialLogitResult,
54
+ NestedLogitResult,
55
+ OrderedLogitResult,
56
+ OrderedProbitResult,
57
+ )
58
+
59
+
60
+ def _version():
61
+ try:
62
+ from importlib.metadata import version
63
+ return version("logit-stream")
64
+ except Exception: # noqa: BLE001 -- not installed
65
+ return "0.1.0"
66
+
67
+
68
+ __version__ = _version()
69
+
70
+ __all__ = [
71
+ "logit_stream", "probit_stream", "mlogit_stream", "ologit_stream", "oprobit_stream",
72
+ "clogit_stream", "cfelogit_stream", "mixlogit_stream", "nlogit_stream",
73
+ "LogitResult", "MultinomialLogitResult", "OrderedLogitResult", "OrderedProbitResult",
74
+ "ConditionalLogitResult", "ConditionalFELogitResult",
75
+ "MixedLogitResult", "NestedLogitResult",
76
+ "LogitMulti", "etable", "bonferroni", "holm", "__version__",
77
+ ]
@@ -0,0 +1,64 @@
1
+ """What the fits on choice data with `optimize._GradientDriver` (the mixed
2
+ and the nested logit) share: the one formula, the conditional logit as the
3
+ starting point, and the diagnostics of the fit."""
4
+
5
+ from __future__ import annotations
6
+
7
+ import time
8
+
9
+ import numpy as np
10
+
11
+
12
+ def plan_one(fml, lf, engine, log, model):
13
+ """The formula's one model, without fixed effects (`model` names the
14
+ estimator in the errors)."""
15
+ from ._frontend import _plan
16
+ log(f"planning {fml!r}")
17
+ specs = _plan(fml, lf, intercept=False, collect=lambda q, what: engine.collect(q, what))
18
+ if len(specs) != 1:
19
+ raise ValueError(f"multiple estimation is not available for the {model}")
20
+ spec = specs[0]
21
+ if spec.fe:
22
+ raise ValueError(f"{spec.fml}: fixed effects are not available for the {model}")
23
+ return spec
24
+
25
+
26
+ def clogit_start(p0, xnames, spec, logger, log):
27
+ """The conditional logit on the store: the starting point (it refuses
28
+ collinear covariates, which these fits do not drop)."""
29
+ from .models import _Conditional
30
+ from .newton import _Driver
31
+ log("starting values: the conditional logit")
32
+ K = len(xnames)
33
+ cdrv = _Driver(p0.store, _Conditional(xnames, K), tol=1e-8, maxiter=50, l2=0.0,
34
+ chunk_mb=64.0, log=lambda m: None, logger=logger)
35
+ cfit = cdrv.run(np.zeros(K), spec.fml)
36
+ if cfit.dropped:
37
+ raise ValueError(f"{spec.fml}: collinear covariates {cfit.dropped}; drop them (they "
38
+ "are not dropped for this model)")
39
+ return cfit
40
+
41
+
42
+ def fit_diagnostics(fit, cfit, p0, *, storage, dtype, tol, t0, device=None, drv=None,
43
+ **more):
44
+ """The diagnostics of a `_GradientFit` started from `cfit`."""
45
+ out = {
46
+ "optimizer": {"iterations": fit.iterations, "bhhh_steps": fit.bhhh_steps,
47
+ "newton_steps": fit.newton_steps, "quasi_steps": fit.quasi_steps,
48
+ "converged": fit.converged, "decrement": fit.decrement,
49
+ "loglik": fit.path, "passes": fit.passes, "seconds": fit.seconds,
50
+ "tol": tol},
51
+ "start": {"loglik_clogit": float(cfit.ll), "passes": cfit.passes},
52
+ "gradient": [float(v) for v in fit.g],
53
+ "pass0_seconds": p0.seconds, "n_rows": p0.n_obs, "n_raw": p0.n_raw,
54
+ "store": {"storage": storage, "dtype": dtype, "blocks": len(p0.store.blocks),
55
+ "bytes": int(p0.store.bytes)},
56
+ "disk_peak_gb": round((p0.store.bytes if storage == "disk" else 0) / 1e9, 4),
57
+ "seconds_total": round(time.time() - t0, 2),
58
+ **more,
59
+ }
60
+ if device is not None:
61
+ ds = drv._dstore
62
+ out["device"] = {"name": device.name, "resident": bool(ds and ds.resident),
63
+ "bytes": int(ds.bytes) if ds else 0}
64
+ return out
@@ -0,0 +1,78 @@
1
+ """Formula front end: a pyfixest-style formula -> the models to fit.
2
+
3
+ The right-hand side is compiled by hdfe-stream's formula compiler
4
+ (`hdfe_stream.core.formula`), which runs pyfixest's own parser and model
5
+ matrix on a tiny synthetic frame and turns each column back into a Polars
6
+ expression. Coefficient names and the coding of categorical variables are
7
+ therefore exactly pyfixest's. The outcome is handled here: for the
8
+ multinomial and ordered models it is a categorical variable, not a number,
9
+ so it is kept as a raw column and coded once its levels are known.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from dataclasses import dataclass, field
15
+
16
+ import polars as pl
17
+ from hdfe_stream.core.feterms import parse_fe
18
+ from hdfe_stream.core.formula import (
19
+ categorical_vars,
20
+ compile_design,
21
+ numeric_expr,
22
+ pyfixest_formula_api,
23
+ )
24
+
25
+
26
+ @dataclass
27
+ class _Spec:
28
+ """One model: its formula, outcome, covariates and fixed effects."""
29
+
30
+ fml: str
31
+ depvar: str
32
+ y: pl.Expr # the outcome, uncast
33
+ x: list = field(default_factory=list) # [(coefficient name, Float64 expr)]
34
+ fe: tuple = ()
35
+
36
+
37
+ def _outcome_expr(dep, schema):
38
+ """A column is taken as it is (strings, categoricals, integers);
39
+ anything else is a numeric expression such as ``I(educ > 12)``."""
40
+ if dep in schema:
41
+ return pl.col(dep)
42
+ return numeric_expr(dep)
43
+
44
+
45
+ def _plan(fml: str, lf: pl.LazyFrame, intercept: bool, collect) -> list[_Spec]:
46
+ """Expand `fml` into its models (pyfixest's multiple-estimation syntax:
47
+ several outcomes, ``sw()``, ``csw()``, ...).
48
+
49
+ `intercept`: whether a model without fixed effects gets the model
50
+ matrix's constant (multinomial logit does; ordered and conditional logit
51
+ do not, their cutpoints and choice sets absorbing it). Without it the
52
+ categorical coding is still reduced rank, as with it.
53
+ `collect(lf, what)` runs a query (so that the GPU engine applies).
54
+ """
55
+ Formula, _ = pyfixest_formula_api()
56
+ schema = lf.collect_schema()
57
+ specs = Formula.parse(fml)
58
+ cats = set()
59
+ for s in specs:
60
+ if s.first_stage is not None:
61
+ raise ValueError(f"{s.formula}: IV formulas are not available for logit models")
62
+ cats |= categorical_vars(s.second_stage.split("~", 1)[1], schema)
63
+ levels = {}
64
+ for v in sorted(cats):
65
+ u = collect(lf.select(pl.col(v).drop_nulls().unique().sort()), f"levels of {v}")
66
+ levels[v] = u[v].to_list()
67
+ out = []
68
+ for s in specs:
69
+ dep, rhs = (t.strip() for t in s.second_stage.split("~", 1))
70
+ fe = tuple(parse_fe(s.fixed_effects))
71
+ for d in fe:
72
+ if "[" in d:
73
+ raise ValueError(f"{s.formula}: varying slopes are not available for logit models")
74
+ _, cols = compile_design(dep, rhs, schema, levels, intercept=intercept and not fe)
75
+ if not intercept:
76
+ cols = [(nm, ex) for nm, ex in cols if nm != "Intercept"]
77
+ out.append(_Spec(fml=s.formula, depvar=dep, y=_outcome_expr(dep, schema), x=cols, fe=fe))
78
+ return out