hdfe-stream 0.2.2__tar.gz → 0.4.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 (149) hide show
  1. hdfe_stream-0.4.0/CHANGELOG.md +290 -0
  2. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/CITATION.cff +2 -2
  3. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/MANIFEST.in +1 -0
  4. hdfe_stream-0.4.0/PKG-INFO +242 -0
  5. hdfe_stream-0.4.0/README.md +185 -0
  6. hdfe_stream-0.4.0/docs/_static/custom.css +10 -0
  7. hdfe_stream-0.4.0/docs/benchmarks/akm.md +126 -0
  8. hdfe_stream-0.4.0/docs/benchmarks/covariates.md +101 -0
  9. hdfe_stream-0.4.0/docs/benchmarks/glm.md +79 -0
  10. hdfe_stream-0.4.0/docs/benchmarks/glm_covariates.md +71 -0
  11. hdfe_stream-0.4.0/docs/benchmarks/index.md +123 -0
  12. hdfe_stream-0.4.0/docs/benchmarks/kss.md +107 -0
  13. hdfe_stream-0.4.0/docs/changelog.md +2 -0
  14. hdfe_stream-0.4.0/docs/conf.py +70 -0
  15. hdfe_stream-0.4.0/docs/examples/akm_variance.md +102 -0
  16. hdfe_stream-0.4.0/docs/examples/connected_sets.md +198 -0
  17. hdfe_stream-0.4.0/docs/examples/fewer_fixed_effects.md +47 -0
  18. hdfe_stream-0.4.0/docs/examples/formulas.md +338 -0
  19. {hdfe_stream-0.2.2/docs → hdfe_stream-0.4.0/docs/examples}/glm.md +78 -154
  20. hdfe_stream-0.4.0/docs/examples/index.md +87 -0
  21. hdfe_stream-0.4.0/docs/examples/leave_out_kss.md +123 -0
  22. hdfe_stream-0.4.0/docs/examples/out_of_core.md +195 -0
  23. hdfe_stream-0.4.0/docs/examples/quickstart.md +101 -0
  24. hdfe_stream-0.4.0/docs/examples/reporting.md +73 -0
  25. hdfe_stream-0.4.0/docs/examples/varying_slopes.md +93 -0
  26. hdfe_stream-0.4.0/docs/examples/weights_and_iv.md +64 -0
  27. hdfe_stream-0.4.0/docs/examples/wide_designs.md +70 -0
  28. hdfe_stream-0.4.0/docs/index.md +201 -0
  29. hdfe_stream-0.2.2/docs/kss_methodological_differences.md → hdfe_stream-0.4.0/docs/kss/differences.md +11 -4
  30. hdfe_stream-0.4.0/docs/kss/draws.md +96 -0
  31. hdfe_stream-0.4.0/docs/kss/index.md +253 -0
  32. hdfe_stream-0.4.0/docs/kss/inference.md +265 -0
  33. hdfe_stream-0.4.0/docs/kss/streaming.md +86 -0
  34. hdfe_stream-0.4.0/docs/reference/connected.md +12 -0
  35. hdfe_stream-0.4.0/docs/reference/estimation.md +11 -0
  36. hdfe_stream-0.4.0/docs/reference/index.md +40 -0
  37. hdfe_stream-0.4.0/docs/reference/kss.md +19 -0
  38. hdfe_stream-0.4.0/docs/reference/lowlevel.md +16 -0
  39. hdfe_stream-0.4.0/docs/reference/results.md +30 -0
  40. hdfe_stream-0.4.0/docs/reference/utilities.md +42 -0
  41. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/hdfe_stream/__init__.py +33 -16
  42. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/hdfe_stream/_types.py +1 -2
  43. hdfe_stream-0.4.0/hdfe_stream/api.py +256 -0
  44. hdfe_stream-0.4.0/hdfe_stream/connected.py +686 -0
  45. hdfe_stream-0.4.0/hdfe_stream/estimator.py +680 -0
  46. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/hdfe_stream/families.py +5 -0
  47. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/hdfe_stream/glm.py +128 -74
  48. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/hdfe_stream/inference.py +93 -6
  49. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/hdfe_stream/inverse.py +6 -3
  50. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/hdfe_stream/kernels_base.py +103 -0
  51. hdfe_stream-0.4.0/hdfe_stream/kernels_glm.py +200 -0
  52. hdfe_stream-0.4.0/hdfe_stream/kernels_graph.py +352 -0
  53. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/hdfe_stream/kernels_inverse.py +0 -17
  54. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/hdfe_stream/leaveout.py +163 -219
  55. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/hdfe_stream/leaveout_match.py +3 -4
  56. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/hdfe_stream/leaveout_se.py +43 -16
  57. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/hdfe_stream/leaveout_weakid.py +42 -26
  58. hdfe_stream-0.4.0/hdfe_stream/multcomp.py +217 -0
  59. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/hdfe_stream/passes.py +258 -135
  60. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/hdfe_stream/reporting.py +26 -2
  61. hdfe_stream-0.4.0/hdfe_stream/results.py +1032 -0
  62. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/hdfe_stream/simulate.py +116 -67
  63. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/hdfe_stream/solve.py +65 -22
  64. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/hdfe_stream/utils.py +6 -0
  65. hdfe_stream-0.4.0/hdfe_stream/wildboot.py +141 -0
  66. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/hdfe_stream/workspace.py +28 -13
  67. hdfe_stream-0.4.0/hdfe_stream.egg-info/PKG-INFO +242 -0
  68. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/hdfe_stream.egg-info/SOURCES.txt +46 -4
  69. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/hdfe_stream.egg-info/requires.txt +8 -0
  70. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/pyproject.toml +14 -3
  71. hdfe_stream-0.4.0/tests/test_cells.py +152 -0
  72. hdfe_stream-0.4.0/tests/test_connected.py +449 -0
  73. hdfe_stream-0.4.0/tests/test_glm_kernel.py +37 -0
  74. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/tests/test_inverse.py +27 -0
  75. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/tests/test_leaveout.py +164 -66
  76. hdfe_stream-0.4.0/tests/test_memory_budget.py +58 -0
  77. hdfe_stream-0.4.0/tests/test_postestimation.py +226 -0
  78. hdfe_stream-0.4.0/tests/test_predict.py +174 -0
  79. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/tests/test_sample.py +4 -3
  80. hdfe_stream-0.4.0/tests/test_wildboot.py +282 -0
  81. hdfe_stream-0.2.2/CHANGELOG.md +0 -136
  82. hdfe_stream-0.2.2/PKG-INFO +0 -632
  83. hdfe_stream-0.2.2/README.md +0 -583
  84. hdfe_stream-0.2.2/docs/kss.md +0 -604
  85. hdfe_stream-0.2.2/hdfe_stream/api.py +0 -183
  86. hdfe_stream-0.2.2/hdfe_stream/estimator.py +0 -492
  87. hdfe_stream-0.2.2/hdfe_stream/kernels_glm.py +0 -54
  88. hdfe_stream-0.2.2/hdfe_stream/kernels_graph.py +0 -135
  89. hdfe_stream-0.2.2/hdfe_stream/results.py +0 -563
  90. hdfe_stream-0.2.2/hdfe_stream.egg-info/PKG-INFO +0 -632
  91. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/LICENSE.md +0 -0
  92. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/docs/figures/akm_disk.dark.svg +0 -0
  93. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/docs/figures/akm_disk.light.svg +0 -0
  94. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/docs/figures/akm_memory.dark.svg +0 -0
  95. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/docs/figures/akm_memory.light.svg +0 -0
  96. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/docs/figures/akm_time.dark.svg +0 -0
  97. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/docs/figures/akm_time.light.svg +0 -0
  98. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/docs/figures/covariates_disk.dark.svg +0 -0
  99. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/docs/figures/covariates_disk.light.svg +0 -0
  100. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/docs/figures/covariates_memory.dark.svg +0 -0
  101. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/docs/figures/covariates_memory.light.svg +0 -0
  102. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/docs/figures/covariates_time.dark.svg +0 -0
  103. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/docs/figures/covariates_time.light.svg +0 -0
  104. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/docs/figures/glm_covariates_disk.dark.svg +0 -0
  105. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/docs/figures/glm_covariates_disk.light.svg +0 -0
  106. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/docs/figures/glm_covariates_memory.dark.svg +0 -0
  107. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/docs/figures/glm_covariates_memory.light.svg +0 -0
  108. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/docs/figures/glm_covariates_time.dark.svg +0 -0
  109. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/docs/figures/glm_covariates_time.light.svg +0 -0
  110. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/docs/figures/glm_disk.dark.svg +0 -0
  111. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/docs/figures/glm_disk.light.svg +0 -0
  112. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/docs/figures/glm_memory.dark.svg +0 -0
  113. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/docs/figures/glm_memory.light.svg +0 -0
  114. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/docs/figures/glm_time.dark.svg +0 -0
  115. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/docs/figures/glm_time.light.svg +0 -0
  116. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/docs/figures/kss_disk.dark.svg +0 -0
  117. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/docs/figures/kss_disk.light.svg +0 -0
  118. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/docs/figures/kss_memory.dark.svg +0 -0
  119. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/docs/figures/kss_memory.light.svg +0 -0
  120. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/docs/figures/kss_time.dark.svg +0 -0
  121. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/docs/figures/kss_time.light.svg +0 -0
  122. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/hdfe_stream/_columns.py +0 -0
  123. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/hdfe_stream/am_interval.py +0 -0
  124. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/hdfe_stream/feterms.py +0 -0
  125. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/hdfe_stream/formula.py +0 -0
  126. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/hdfe_stream/kernels_se.py +0 -0
  127. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/hdfe_stream/kernels_slopes.py +0 -0
  128. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/hdfe_stream/py.typed +0 -0
  129. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/hdfe_stream/report.py +0 -0
  130. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/hdfe_stream.egg-info/dependency_links.txt +0 -0
  131. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/hdfe_stream.egg-info/top_level.txt +0 -0
  132. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/setup.cfg +0 -0
  133. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/tests/conftest.py +0 -0
  134. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/tests/test_am_interval.py +0 -0
  135. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/tests/test_cleanup.py +0 -0
  136. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/tests/test_column_names.py +0 -0
  137. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/tests/test_crv3.py +0 -0
  138. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/tests/test_feis.py +0 -0
  139. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/tests/test_fewer_fe.py +0 -0
  140. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/tests/test_formula.py +0 -0
  141. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/tests/test_glm.py +0 -0
  142. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/tests/test_iv_weights.py +0 -0
  143. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/tests/test_logging.py +0 -0
  144. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/tests/test_lowlevel.py +0 -0
  145. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/tests/test_ols.py +0 -0
  146. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/tests/test_reporting.py +0 -0
  147. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/tests/test_simulate.py +0 -0
  148. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/tests/test_singletons.py +0 -0
  149. {hdfe_stream-0.2.2 → hdfe_stream-0.4.0}/tests/test_vcov.py +0 -0
@@ -0,0 +1,290 @@
1
+ # Changelog
2
+
3
+ ## 0.4.0
4
+
5
+ New features.
6
+
7
+ - **`HDFEResult.predict()`** predicts on new data (a DataFrame, a LazyFrame, or
8
+ a Parquet path or glob) or on the fit's source, lazily, so the data can be
9
+ larger than memory. It returns the data with a prediction column: the linear
10
+ predictor, or with `type="response"` a GLM's mean. Rows with a fixed-effect
11
+ level the fit did not estimate get null. It supports `i()` covariates,
12
+ interacted fixed effects, varying slopes, and IV models.
13
+ - **`HDFEResult.confint()`**: confidence intervals, pointwise or, with
14
+ `joint=True`, simultaneous over the selected coefficients, as pyfixest's.
15
+ - **`HDFEResult.wald_test()`**: Wald tests of R beta = q, or that the
16
+ coefficients selected by `keep`/`drop` are zero, under the current vcov, with
17
+ F or chi-squared p-values. F is reported by default for any OLS hypothesis
18
+ (chi-squared for GLMs), with `df_t` denominator degrees of freedom, so a
19
+ one-coefficient test has `tidy()`'s p-value.
20
+ - **`bonferroni()` and `holm()`**: multiple-testing corrections of one or
21
+ more coefficients' p-values across models (results, `HDFEMulti`, or lists
22
+ of them). Holm's step-down adjustment controls the familywise error rate as
23
+ Bonferroni's does, and never rejects less often.
24
+ - **The wild cluster bootstrap: `bootcluster=`, `HDFEResult.wildboottest()`,
25
+ `rwolf()` and `wyoung()`.** With `bootcluster=`, the fit keeps, for each
26
+ cluster, the sums the bootstrap needs (about G x k^2 numbers, collected in the
27
+ residual pass it makes anyway; nothing without the option). After the fit,
28
+ `wildboottest()` runs the restricted or unrestricted wild cluster bootstrap of
29
+ any coefficient from those sums, without reading the data, and `rwolf()` and
30
+ `wyoung()` give Romano-Wolf and Westfall-Young adjusted p-values across models
31
+ on the same draws. One-way, OLS only, and every fixed effect must be nested
32
+ within the clusters (a fixed effect with few levels, such as year, can go in
33
+ as covariates instead).
34
+
35
+ Fixes.
36
+
37
+ - `cluster="firm_id"`, a single string, was read as a list of characters; it
38
+ now means one clustering, as documented.
39
+
40
+ Performance.
41
+
42
+ - **Cells are collapsed from sorted rows, without a hash aggregation.** Pass 0
43
+ now sorts each worker's rows by the other fixed effects' levels too, so that
44
+ every cell (one combination of levels) is a run of consecutive rows, and pass
45
+ 1 collapses the runs as it reads the sorted rows, in a multithreaded kernel.
46
+ This replaces Polars' group_by over each slice of rows, and the cell table it
47
+ wrote to disk and read back. Where nearly every row is a cell of its own
48
+ (e.g., worker, firm and year effects on a worker-year panel), pass 0 sorts by
49
+ the streamed dimension alone. The new `collapse_cells` option (`"auto"`, the
50
+ default; True; False) overrides that choice, and
51
+ `diagnostics["cells_collapsed"]` reports it. Together with the faster
52
+ iterations below, this reduced compute time by 5-31% on large AKM
53
+ configurations, and peak disk by up to 40%, with the same or lower memory.
54
+ - **Faster conjugate-gradient iterations.** The vector work of each iteration
55
+ (dot products, norms, and the updates of the solution, residual and search
56
+ direction) runs as fused, multithreaded kernels instead of single-threaded
57
+ numpy operations.
58
+ - **Faster IRLS steps for Poisson, logit and probit.** Each step's read of the
59
+ rows (every row's linear predictor, the family's mean, weight and working
60
+ response, the deviance and log-likelihood, and the sums per cell) runs as one
61
+ fused, multithreaded kernel, instead of a dozen single-threaded numpy
62
+ operations over each chunk. On large regression benchmarks, compute time
63
+ declined by 11-32%.
64
+ - **Pass 1 builds the Jacobi diagonal and step 2's right-hand side** chunk by
65
+ chunk while it has the cells in memory, instead of reading the identifying
66
+ cells back for each, when the variables are solved in one block (up to
67
+ `rhs_block` of them). Each of those reads is a pass over data as large as
68
+ the cell table, which matters most where the scratch disk is slow.
69
+ - **A memory budget for per-thread accumulators.** `stream_cg` and the
70
+ leave-out passes give each thread its own level-sized accumulator, which
71
+ with many threads and many levels could take more memory than the rest of
72
+ the fit (64 threads, 30 million firm-years and 8 columns: about 120 GB,
73
+ allocated afresh every iteration). They now get one copy per thread only
74
+ while all of them fit in half of `max_s_gb`, with fewer threads on those
75
+ passes when that binds, and `stream_cg` allocates its copies once per
76
+ solve. Lowering `max_s_gb` tightens it.
77
+
78
+ ## 0.3.0
79
+
80
+ New features.
81
+
82
+ - **`connected_components`** finds the connected components of the graph of
83
+ two fixed-effect dimensions (either may be an interaction, `firm_id^year`)
84
+ by union-find over the rows as they stream, holding level-sized arrays
85
+ only. It returns a table per dimension giving each level its component,
86
+ numbered by size so that component 0 is the largest (ties go to the
87
+ component with the smallest level of the first dimension), and a table of
88
+ component sizes. `largest()` filters the data to the largest connected
89
+ set and `label()` adds each row's component, both lazily (on the data the
90
+ components were found in, or on other data passed to them). `min_degree`
91
+ removes levels with fewer distinct partners in the other dimension,
92
+ repeating until none is left, before the components are found: `(2, 1)`
93
+ keeps only movers, `2` the 2-core. `leave_one_out=True` prunes to the
94
+ leave-one-out connected set of KSS estimation instead. `rank_by` says
95
+ whether "largest" counts rows (the default) or the levels of one dimension.
96
+ - **`connected=`, `min_degree=` and `connected_fe=`** in `feols_stream`,
97
+ `fepois_stream`, `feglm_stream` and `StreamingHDFE` restrict the fit to a
98
+ pair of fixed effects (`connected_fe`, by default the first two listed),
99
+ among the rows with no missing values and before singletons are dropped:
100
+ `min_degree` prunes levels with too few distinct partners, then
101
+ `connected="largest"` keeps the largest connected set of what is left, or
102
+ `connected="leave_one_out"` the leave-one-out connected set, as
103
+ `connected_components` does. `sample()` reports the rows they drop as
104
+ `"min_degree"`, `"disconnected"` and `"leave_one_out"`, and
105
+ `diagnostics["connected"]` and the summary count them.
106
+ - **Python 3.15** is supported and tested in CI. pyfixest, which the
107
+ `formula` extra needs, has no 3.15 wheels yet (0.60.0), and building it
108
+ from source needs Rust and `PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1`; the
109
+ rest of the package installs from wheels.
110
+
111
+ Changes that can affect existing code.
112
+
113
+ - **`leave_one_out_connected` and `LeaveOutSet` are removed.** Use
114
+ `connected_components(data, "worker_id + firm_id", leave_one_out=True)`,
115
+ whose `.largest()` is the pruned panel, or `connected="leave_one_out"`
116
+ in a fit. The set is the same: on simulated panels with many components,
117
+ workers observed once and several rounds of cut workers, the workers and
118
+ firms kept are identical. It is found 5-6 times faster (38s against 218s on
119
+ 150 million rows), and a null worker or firm id no longer makes it fail.
120
+ - **`leave_out_kss` prunes in the fit**, with `connected="leave_one_out"`, so
121
+ the leave-one-out connected set is that of the rows the model can use:
122
+ before, rows with a missing outcome or covariate were part of the network
123
+ being pruned. `lo.pruning` is now a dict of the pruning's counts (the
124
+ fit's `diagnostics["connected"]["leave_one_out"]`), and the rows it dropped
125
+ are those `lo.fit.sample()` labels `"leave_one_out"`.
126
+ - **The `component` column of `fixef()` is numbered by size**, so component
127
+ 0 is the largest (by rows in the estimation sample), as in
128
+ `connected_components`. Ties go to the component with the smallest level of
129
+ that dimension. It was numbered by each component's smallest level.
130
+ - **Removed `hdfe_stream.iter_group_chunks` and `StreamingHDFE.from_formula`.**
131
+ The first read only the fit's internal row files, sorted by its internal
132
+ group code, and stays internal; the second duplicated `feols_stream`.
133
+
134
+ Fixes.
135
+
136
+ - **The leave-out operator's null space is right with several connected
137
+ components.** `apply_inverse` built one null vector for every number up to
138
+ the largest component label, which with several components left gaps, and
139
+ each gap added a spurious vector. Leave-out estimation runs on a connected
140
+ set and was not affected.
141
+
142
+ Documentation.
143
+
144
+ - **A documentation site**, at
145
+ [leetucker.github.io/hdfe-stream](https://leetucker.github.io/hdfe-stream/),
146
+ built with Sphinx from `docs/` and published by a new GitHub Actions
147
+ workflow. It has a page for each example, a section on leave-out variance
148
+ components (bringing together `docs/kss.md`, the methodological differences
149
+ and a derivation of the correction), a page for each benchmark, a function
150
+ reference built from the docstrings, and this changelog. `docs/kss.md`,
151
+ `docs/glm.md` and `docs/kss_methodological_differences.md` moved into it,
152
+ and the README is shorter, pointing to it. The public docstrings are now
153
+ numpydoc, documenting every option, including `tol`, `maxiter`,
154
+ `row_group_size` and `log_level` of `StreamingHDFE`, and the attributes of
155
+ `HDFEResult` and the other result classes. `pip install
156
+ "hdfe-stream[docs]"` installs what building it needs.
157
+ - **New example, `connected_sets.py`**, and additions to the others:
158
+ `sample()` and joining the residuals back on `row_id`, `fe_dof`, and CRV3
159
+ (`formulas.py`); `summary_json` (`quickstart.py`); `keep=` and a
160
+ decomposition by group (`akm_variance.py`); and `StreamingGLM` (`glm.py`).
161
+
162
+ ## 0.2.2
163
+
164
+ - **Polars 2.0 support.** `fit.sample()` returns the source's rows in the source's
165
+ order again. Polars 2.0 collects with the streaming engine by default, whose
166
+ joins do not keep row order unless asked, so the rows came back shuffled.
167
+ Under Polars 2.0, repeated fits of the same model can differ in the last
168
+ digit or so (around 1e-16 relative): its streaming engine adds floating-point
169
+ sums in an order that depends on thread scheduling. Under Polars 1.x they
170
+ are identical bit for bit.
171
+ - **Dropping singletons no longer inflates memory with a wide design.** The
172
+ dropped levels were anti-joined after the design was evaluated, so the
173
+ streaming join buffered every design column. The rows are now dropped from
174
+ the source columns before the design is evaluated.
175
+ - **Finding singletons takes fewer scans.** The search used to stop only
176
+ after a full round over the fixed effects found nothing. It now stops once
177
+ every fixed effect has been checked since the last drop, since a dimension's
178
+ own drop cannot leave a singleton in it: four scans instead of six for a
179
+ typical three-way model, about 40% less time spent on singletons, and the
180
+ same memory. The rows dropped are the same.
181
+ - **Sorting the buckets and building the cell table takes less memory under
182
+ Polars 2.0.** Each bucket is sorted with Polars' in-memory engine and then
183
+ written, rather than sunk through the streaming engine, whose sort peaked
184
+ about 1.5x higher. Similarly, Pass 1 grouped each bucket with a streaming
185
+ `group_by`, which under Polars 2.0 gives every thread its own hash table: with
186
+ as many cells as rows, as in an AKM panel, the fit's peak more than doubled.
187
+ The rows are sorted by fe[0] group, so each bucket is now grouped a slice of
188
+ whole groups at a time and the cells appended in order. This reduces memory
189
+ use in AKM specifications and leaves wide designs unchanged or a little
190
+ better.
191
+ - **The explicit solver builds its matrix faster and in less memory.** The
192
+ reduced matrix S is now written directly in compressed form, one row at a
193
+ time, instead of being assembled from chunks of coordinate triples that
194
+ were merged with scipy. Each row's size is counted first, so S is allocated
195
+ once and nothing larger than S is held while it is built; `max_s_gb` is
196
+ checked before S is allocated rather than partway through. Estimates are
197
+ unchanged up to rounding.
198
+ - **Removed `triple_budget` and `dense_max_levels`** from `StreamingHDFE`. They
199
+ tuned the old way of building S and have no counterpart in the new one.
200
+ - **Importing hdfe_stream costs a little less memory.** p-values, critical
201
+ values and the weak-identification interval use `scipy.special`'s
202
+ distribution functions instead of `scipy.stats`, which is no longer
203
+ imported. The values are the same.
204
+ - **Empty run directories no longer pile up on NFS.** A file deleted while
205
+ still open or memory-mapped becomes a hidden .nfs file on NFS, so its run
206
+ directory could not be removed and was left empty once the process exited.
207
+ A failed removal is now retried after collecting garbage on every platform
208
+ (it was Windows only); the leave-out functions release their memory maps as
209
+ soon as they are done (`reload_intermediates()` maps them back in); and
210
+ empty run directories more than a minute old are removed by every new fit
211
+ in the same working directory and by `hdfe_stream.cleanup()`.
212
+ - **Slightly less verbose default logging.** CG iteration messages are now
213
+ logged at DEBUG level and therefore not printed by default. Lower your log
214
+ level to obtain this information.
215
+
216
+
217
+ ## 0.2.1
218
+
219
+ - **`fe_dof="exact"` no longer depends on the order of the fixed effects.**
220
+ With three or more fixed effects it counted the connected components of the
221
+ streamed dimension and whichever came next in the formula, so reordering the
222
+ formula could change the standard errors. It now counts the components of
223
+ every pair of dimensions and uses the pair with the most, which is never
224
+ more than the true number of redundant levels. With two fixed effects
225
+ nothing changes, except in the clustered case below.
226
+ - **Clustered standard errors with fixed effects nested in the clusters.**
227
+ Under `fe_dof="exact"`, the components of a pair of fixed effects that are
228
+ both nested within the clusters were subtracted from K, although those
229
+ fixed effects' levels are already dropped from it. K could then fall below
230
+ the number of covariates and the standard errors were too small. Such a pair
231
+ is no longer used, and with no other pair K is pyfixest's.
232
+ - **`fit.sample()` labels every singleton.** With three or more fixed effects
233
+ that each had singleton levels, rows dropped only for the third or a later
234
+ dimension came back with `in_sample=True`, so the counts disagreed with
235
+ `n_obs` and the summary's singleton count. The fit itself was right. GLM
236
+ `"separation"` labels had the same fault.
237
+ - **A NaN in a fixed-effect or cluster column is missing**, as a null is,
238
+ rather than a level of its own. For fixed effects this matches pyfixest;
239
+ for clusters, where pyfixest raises an error, the row is dropped as it is
240
+ for a null cluster. `sample()` reports these rows as `"missing"`.
241
+ - **Polars 1.41 is excluded** from the supported versions. Its streaming join
242
+ panics (`called Option::unwrap() on a None value`) once a frame has about
243
+ 130 columns, which a model with more than about 120 covariates reaches in
244
+ pass 0. Polars 1.40 and earlier, and 1.42 and later, are unaffected.
245
+ - **A panic inside Polars is reported as a `RuntimeError`** that names the
246
+ Polars version, says it is a Polars bug, and suggests a fix, instead of a
247
+ bare `PanicException`.
248
+
249
+ ## 0.2.0
250
+
251
+ Changes to defaults, so results can differ from 0.1.0 without any change to
252
+ your code.
253
+
254
+ - **Default `vcov` is now `"iid"`**, with or without fixed effects, matching
255
+ pyfixest 0.60. Before, a model with fixed effects defaulted to CRV1 clustered
256
+ on the first fixed effect. Pass `vcov={"CRV1": "worker_id"}` for the old
257
+ behavior.
258
+ - **Singleton observations are dropped by default** (`fixef_rm="singleton"`),
259
+ as in pyfixest, repeating until none is left. `fixef_rm="none"` keeps them.
260
+ `leave_out_kss` fits with `fixef_rm="none"`, since its pruning defines the
261
+ sample.
262
+
263
+ New features.
264
+
265
+ - **`row_id` in `resid()`**: each row carries its position in the source before
266
+ any row was dropped, so residuals join back onto the source (`row_id=` renames
267
+ the column).
268
+ - **`fit.sample()`** returns the source, lazily, with `row_id`, `in_sample` and
269
+ `dropped_because` (`"missing"`, `"singleton"` or `"separation"`).
270
+ `multi.sample()` returns `{formula: frame}`.
271
+ - **Polars `DataFrame` inputs** are accepted wherever a Parquet path or
272
+ `LazyFrame` was.
273
+ - **Reserved column prefix `__hdfe_`.** All of the estimator's internal columns
274
+ now use it, and a source with a column starting with it is refused. This fixes
275
+ fits failing when a user column was named `w` (with weights), `v0`, `c1`,
276
+ `gcode` and similar.
277
+
278
+ ## 0.1.0
279
+
280
+ First release.
281
+
282
+ - `feols_stream`: out-of-core OLS with several high-dimensional fixed effects,
283
+ varying slopes, weights, IV, and CRV1/CRV3 and other variance estimators.
284
+ - `fepois_stream` and `feglm_stream`: Poisson, logit and probit by iteratively
285
+ reweighted least squares over the same passes.
286
+ - AKM variance decomposition and leave-out (KSS) bias correction with
287
+ standard errors and weak-identification diagnostics.
288
+ - Reporting through pyfixest (`to_pyfixest()`, `etable`).
289
+ - `summary_json()` and `summary_dict()`: the model-level information of
290
+ `summary()` as JSON or a dict, for saving to disk.
@@ -7,8 +7,8 @@ authors:
7
7
  - family-names: Tucker
8
8
  given-names: Lee
9
9
  email: lee.c.tucker@census.gov
10
- version: 0.2.2
11
- date-released: 2026-10-08
10
+ version: 0.4.0
11
+ date-released: 2026-10-16
12
12
  license: MIT
13
13
  repository-code: "https://github.com/leetucker/hdfe-stream"
14
14
  url: "https://github.com/leetucker/hdfe-stream"
@@ -1,6 +1,7 @@
1
1
  include README.md CHANGELOG.md CITATION.cff LICENSE.md
2
2
  graft tests
3
3
  graft docs
4
+ prune docs/_build
4
5
  prune benchmarks
5
6
  prune prototypes
6
7
  prune examples
@@ -0,0 +1,242 @@
1
+ Metadata-Version: 2.4
2
+ Name: hdfe-stream
3
+ Version: 0.4.0
4
+ Summary: Out-of-core regression with several high-dimensional fixed effects: OLS, Poisson, logit and probit, with AKM/KSS variance decompositions
5
+ Author: Lee Tucker
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/leetucker/hdfe-stream
8
+ Project-URL: Source, https://github.com/leetucker/hdfe-stream
9
+ Project-URL: Issues, https://github.com/leetucker/hdfe-stream/issues
10
+ Project-URL: Documentation, https://leetucker.github.io/hdfe-stream/
11
+ Project-URL: Changelog, https://github.com/leetucker/hdfe-stream/blob/main/CHANGELOG.md
12
+ Keywords: econometrics,fixed effects,high-dimensional,out-of-core,AKM,KSS,pyfixest
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: polars!=1.41.*,>=1.37
29
+ Requires-Dist: numpy>=1.24
30
+ Requires-Dist: numba>=0.59
31
+ Requires-Dist: pyarrow>=14.0
32
+ Requires-Dist: scipy>=1.11
33
+ Provides-Extra: within
34
+ Requires-Dist: within-py>=0.3.0; extra == "within"
35
+ Provides-Extra: amg
36
+ Requires-Dist: pyamg>=5.1; extra == "amg"
37
+ Provides-Extra: formula
38
+ Requires-Dist: pyfixest<0.61.0,>=0.60.0; extra == "formula"
39
+ Requires-Dist: formulaic>=1.0; extra == "formula"
40
+ Requires-Dist: pandas>=2.0; extra == "formula"
41
+ Provides-Extra: all
42
+ Requires-Dist: hdfe-stream[amg,formula,within]; extra == "all"
43
+ Provides-Extra: benchmark
44
+ Requires-Dist: hdfe-stream[formula]; extra == "benchmark"
45
+ Requires-Dist: matplotlib>=3.7; extra == "benchmark"
46
+ Provides-Extra: docs
47
+ Requires-Dist: sphinx>=8.0; extra == "docs"
48
+ Requires-Dist: myst-parser>=4.0; extra == "docs"
49
+ Requires-Dist: furo>=2024.8.6; extra == "docs"
50
+ Requires-Dist: sphinx-copybutton>=0.5; extra == "docs"
51
+ Requires-Dist: sphinx-design>=0.6; extra == "docs"
52
+ Provides-Extra: test
53
+ Requires-Dist: pytest>=7.0; extra == "test"
54
+ Requires-Dist: hdfe-stream[all]; extra == "test"
55
+ Requires-Dist: wildboottest>=0.3.2; extra == "test"
56
+ Dynamic: license-file
57
+
58
+ # hdfe-stream
59
+
60
+ Regression with several high-dimensional fixed effects, on data that does not
61
+ fit in memory: linear (`feols_stream`), and Poisson, logit and probit
62
+ (`fepois_stream`, `feglm_stream`), with the Kline–Saggio–Sølvsten bias
63
+ correction for AKM variance decompositions (`leave_out_kss`).
64
+
65
+ ```python
66
+ from hdfe_stream import feols_stream
67
+
68
+ fit = feols_stream(
69
+ "log_earn ~ age_squared + age_cubed | worker_id + firm_id + year",
70
+ "data/*.parquet", # a Parquet path or glob, never fully loaded
71
+ workdir="scratch", # where intermediates go: disk, not memory
72
+ vcov={"CRV1": "worker_id"},
73
+ )
74
+ fit.summary()
75
+ ```
76
+
77
+ Formulas follow [pyfixest](https://github.com/py-econometrics/pyfixest)/fixest
78
+ syntax and are parsed by pyfixest's own parser, so the syntax, the coefficient
79
+ names and the results all match. The difference is where the work happens: rows
80
+ are streamed off disk in batches and never held as a design matrix.
81
+
82
+ The data can be a Parquet path or glob, a Polars LazyFrame, or a Polars
83
+ DataFrame. Using file paths or LazyFrames keeps memory usage to a minimum, but
84
+ using DataFrames works well for smaller data and for trying things out.
85
+
86
+ **Documentation:
87
+ [leetucker.github.io/hdfe-stream](https://leetucker.github.io/hdfe-stream/)**,
88
+ with examples, the KSS bias correction, benchmarks and the function reference.
89
+
90
+ ## Features
91
+
92
+ - [Any number of fixed effects](https://leetucker.github.io/hdfe-stream/examples/formulas.html#fixed-effects),
93
+ including [one or none](https://leetucker.github.io/hdfe-stream/examples/fewer_fixed_effects.html),
94
+ and interactions (`firm_id^year`)
95
+ - [pyfixest formula syntax](https://leetucker.github.io/hdfe-stream/examples/formulas.html#covariates):
96
+ transformations, categoricals, interactions, event studies
97
+ - [Several models in one call](https://leetucker.github.io/hdfe-stream/examples/formulas.html#several-models-at-once),
98
+ sharing passes over the data
99
+ - [Standard errors](https://leetucker.github.io/hdfe-stream/examples/formulas.html#standard-errors):
100
+ iid, robust, multi-way clustered, and one-way CRV3
101
+ - [Weighted least squares and 2SLS](https://leetucker.github.io/hdfe-stream/examples/weights_and_iv.html)
102
+ - [Poisson, logit and probit](https://leetucker.github.io/hdfe-stream/examples/glm.html)
103
+ - [Varying slopes in fixed effects](https://leetucker.github.io/hdfe-stream/examples/varying_slopes.html)
104
+ (`worker_id[t]`), on the streamed dimension only
105
+ - [Wide designs](https://leetucker.github.io/hdfe-stream/examples/wide_designs.html),
106
+ with hundreds of covariates
107
+ - [Connected components](https://leetucker.github.io/hdfe-stream/examples/connected_sets.html),
108
+ the largest connected set, and `min_degree` pruning
109
+ - [AKM variance decompositions](https://leetucker.github.io/hdfe-stream/examples/akm_variance.html),
110
+ in a streaming pass
111
+ - [KSS leave-out variance components](https://leetucker.github.io/hdfe-stream/kss/index.html),
112
+ with standard errors and weak-identification diagnostics
113
+ - [The sample, row by row](https://leetucker.github.io/hdfe-stream/examples/formulas.html#missing-values-and-the-sample):
114
+ which rows were dropped and why, and residuals joined back to the source
115
+ - [Regression tables and plots](https://leetucker.github.io/hdfe-stream/examples/reporting.html)
116
+ through pyfixest
117
+ - [Memory and disk controls](https://leetucker.github.io/hdfe-stream/examples/out_of_core.html),
118
+ a choice of solvers, and logging
119
+ - [Simulated panels](https://leetucker.github.io/hdfe-stream/examples/index.html#the-simulated-data)
120
+ with known effects, for testing
121
+
122
+ ## When to use this, and when not to
123
+
124
+ **Use pyfixest.** It is the better tool whenever your data fits in memory:
125
+ mature, broader in scope, more inference options, and no scratch directory to
126
+ think about. If a `pandas` DataFrame of your data fits comfortably in RAM, stop
127
+ reading.
128
+
129
+ **Use this** when it doesn't. The case it was built for is a matched
130
+ employer–employee panel — tens of millions of rows, millions of worker effects,
131
+ an AKM variance decomposition — on a machine or a secure enclave where the data
132
+ is larger than the memory you are allowed. Concretely, reach for it when:
133
+
134
+ - the data does not fit in memory, or fits so tightly that nothing else does;
135
+ - you have one very high-cardinality dimension (workers, patients, students)
136
+ whose groups each touch only a few levels of the others;
137
+ - you want **worker-specific slopes** as well as intercepts (`worker_id[t]`),
138
+ which multiplies the parameter count by the number of slopes;
139
+ - the design is **wide**: hundreds of covariates, such as a categorical
140
+ expanded into fine indicators or many interactions. An in-memory library
141
+ needs rows × covariates in memory before it starts; this needs one batch of
142
+ rows × covariates at a time, with any number of fixed effects, including one
143
+ or none (see [many covariates](https://leetucker.github.io/hdfe-stream/benchmarks/covariates.html));
144
+ - you need a hard, predictable memory ceiling — a shared cluster, or a job that
145
+ must not be the one that gets killed.
146
+
147
+ You are trading memory for disk, and you need scratch space: about five times
148
+ the size of your Parquet input while the fit runs, and only the result files
149
+ once it finishes. See [performance](#performance) for what that buys. Point
150
+ `workdir=` at it, or set the `HDFE_STREAM_WORKDIR` environment variable once
151
+ (for example to a cluster's scratch filesystem) and leave `workdir` out; a
152
+ `workdir` passed to a call takes precedence, and with neither, the system
153
+ temporary directory is used.
154
+
155
+ ## How it works, in one paragraph
156
+
157
+ One fixed-effect dimension — normally the largest — is **streamed**: it
158
+ is never represented as a vector in memory. Every other dimension is held as a
159
+ vector sized by its number of *levels*, not rows. Rows are read in batches,
160
+ hash-partitioned into buckets by the streamed dimension, sorted one bucket at a
161
+ time, and reduced to a table of cells (one per combination of fixed-effect
162
+ levels). The reduced normal equations for the non-streamed dimensions are then
163
+ solved by conjugate gradient, and the streamed effects are recovered group by
164
+ group in a final pass that writes residuals and fixed effects straight to
165
+ Parquet. So memory scales with firms × years, not with workers × rows, and that
166
+ asymmetry is the whole design. The covariates travel with the rows: what is
167
+ held at once is one bucket or batch of rows × covariates, never all of them,
168
+ which is why a wide design fits where an in-memory one does not. Full detail is in
169
+ [`hdfe_stream/__init__.py`](https://github.com/leetucker/hdfe-stream/blob/main/hdfe_stream/__init__.py).
170
+
171
+ ## Install
172
+
173
+ ```bash
174
+ pip install hdfe-stream[formula] # formula syntax needs pyfixest
175
+ ```
176
+
177
+ `polars`, `numpy`, `numba`, `pyarrow` and `scipy` are required. The extras:
178
+
179
+ | extra | what it adds |
180
+ |---|---|
181
+ | `formula` | `feols_stream` formula syntax and pyfixest reporting (pyfixest, formulaic, pandas) |
182
+ | `within` | the `solver="within"` backend |
183
+ | `amg` | algebraic multigrid preconditioning (`precond="amg"`) for hard geometries |
184
+ | `all` | all of the above |
185
+ | `test` | the test suite |
186
+ | `docs` | building the documentation (Sphinx) |
187
+
188
+ On Python 3.15, pyfixest 0.60 (for the `formula` extra) has no wheels yet. It
189
+ builds from source with a Rust toolchain and
190
+ `PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1` set; everything else installs from
191
+ wheels.
192
+
193
+ Without `formula` you still get `StreamingHDFE`, the lower-level interface that
194
+ takes column names or Polars expressions instead of a formula string.
195
+
196
+ ## Performance
197
+
198
+ The standard three-way AKM regression,
199
+ `log_earn ~ age_squared + age_cubed | worker_id + firm_id + year`, clustered by
200
+ worker, on a simulated panel of 5 million workers and 42.5 million rows,
201
+ against [pyfixest](https://github.com/py-econometrics/pyfixest) and
202
+ [xhdfe](https://github.com/reisportela/xhdfe-xfe). Every configuration agrees on
203
+ the coefficients to within 3e-10.
204
+
205
+ | configuration | wall time | peak memory | peak disk |
206
+ |---|---:|---:|---:|
207
+ | pyfixest (MAP, its default) | 1,960 s | 20.7 GB | — |
208
+ | pyfixest (LSMR) | 138 s | 25.3 GB | — |
209
+ | xhdfe | 238 s | 19.0 GB | — |
210
+ | hdfe_stream (`explicit`, its default) | 52 s | 4.1 GB | 4.1 GB |
211
+ | hdfe_stream (`stream_cg`) | 54 s | 4.0 GB | 4.1 GB |
212
+ | **hdfe_stream (`stream_cg`, low memory)** | **60 s** | **3.2 GB** | 4.3 GB |
213
+
214
+ <picture>
215
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/leetucker/hdfe-stream/main/docs/figures/akm_memory.dark.svg">
216
+ <img alt="AKM regression: peak memory against the number of workers, one line per configuration, log-log" src="https://raw.githubusercontent.com/leetucker/hdfe-stream/main/docs/figures/akm_memory.light.svg">
217
+ </picture>
218
+
219
+ On small data the in-memory libraries use less memory: an hdfe_stream process
220
+ costs about 0.9 GB before it has done anything. The
221
+ [benchmarks](https://leetucker.github.io/hdfe-stream/benchmarks/index.html) cover every size from 212,000 rows up,
222
+ designs with up to 503 covariates, Poisson and logit, and the KSS
223
+ decomposition, with how each was measured.
224
+
225
+ ## Documentation
226
+
227
+ - [**Examples**](https://leetucker.github.io/hdfe-stream/examples/index.html): one page per feature, on simulated
228
+ data that ships with the package: formulas and standard errors, weights and
229
+ IV, Poisson and logit, wide designs, varying slopes, connected sets, running
230
+ out of core, and reporting.
231
+ - [**Leave-out variance components**](https://leetucker.github.io/hdfe-stream/kss/index.html): the
232
+ Kline–Saggio–Sølvsten bias correction for the AKM decomposition, its standard
233
+ errors and weak-identification diagnostic, and every way it differs from the
234
+ reference implementations.
235
+ - [**Benchmarks**](https://leetucker.github.io/hdfe-stream/benchmarks/index.html): time, memory and disk against
236
+ pyfixest and xhdfe.
237
+ - [**Function reference**](https://leetucker.github.io/hdfe-stream/reference/index.html): every option.
238
+ - [**Limitations**](https://leetucker.github.io/hdfe-stream/index.html#limitations): what it does not do.
239
+ - [**Changelog**](https://github.com/leetucker/hdfe-stream/blob/main/CHANGELOG.md).
240
+
241
+ The scripts behind the examples are in [`examples/`](https://github.com/leetucker/hdfe-stream/tree/main/examples), and the
242
+ benchmarks in [`benchmarks/`](https://github.com/leetucker/hdfe-stream/tree/main/benchmarks).