Skip to main content

hdfe-stream

Regression with several high-dimensional fixed effects, on data that does not fit in memory: linear (feols_stream), and Poisson, logit and probit (fepois_stream, feglm_stream), with the Kline–Saggio–Sølvsten bias correction for AKM variance decompositions (leave_out_kss).

from hdfe_stream import feols_stream

fit = feols_stream(
    "log_earn ~ age_squared + age_cubed | worker_id + firm_id + year",
    "data/*.parquet",              # a Parquet path or glob, never fully loaded
    workdir="scratch",             # where intermediates go: disk, not memory
    vcov={"CRV1": "worker_id"},
)
fit.summary()

Formulas follow pyfixest/fixest syntax and are parsed by pyfixest's own parser, so the syntax, the coefficient names and the results all match. The difference is where the work happens: rows are streamed off disk in batches and never held as a design matrix.

The data can be a Parquet path or glob, a Polars LazyFrame, or a Polars DataFrame. Using file paths or LazyFrames keeps memory usage to a minimum, but using DataFrames works well for smaller data and for trying things out.

Documentation: leetucker.github.io/hdfe-stream, with examples, the KSS bias correction, benchmarks and the function reference.

Features

When to use this, and when not to

When your data do not fit in memory, use this. It was built for that case: a matched employer–employee panel — tens of millions of rows, millions of worker effects, an AKM variance decomposition — on a machine or a secure enclave where the data are larger than the memory you are allowed. Every other library compared here needs the whole panel in memory before it starts; this one holds a bounded slice of it at a time, and uses scratch disk for the rest.

When they do fit, it is still a sensible default for medium and large regressions. From about 400,000 workers (3.4 million rows) up, it was the fastest of the libraries benchmarked: against pyfixest, xhdfe, R's fixest and Julia's FixedEffectModels.jl on the AKM regression and on wide designs, against pyfixest on Poisson and logit, and against xhdfe on the KSS decomposition, and at the largest sizes it used a fraction of their memory. At 5 million workers it fitted the AKM regression in 23 seconds on a 16-vCPU machine, about as fast as xhdfe on an NVIDIA H100 (24 seconds), without a GPU (see should I use a GPU?). The benchmarks are simulated employer–employee panels, a shape that suits its design (how it works); one whose fixed-effect cells are no smaller than the data may do less well.

It is also a reasonable general-purpose regression engine. OLS and weighted least squares, 2SLS, and binary logit and probit fit with no fixed effects or one, from a formula, with robust and clustered standard errors. On simulated data with 10 covariates, from a few million rows up it was within a small factor of the fastest libraries, ahead of some, and far lighter. At 20 million rows an OLS took 6.5 s and 2.5 GB, against 5.4 s and 7.5 GB for scikit-learn, 8.5 s and 5.7 GB for polars-ols, and 9.1 s and 15.7 GB for pyfixest; a logit took 9.4 s and 2.4 GB, against 12.8 s and 7.7 GB for statsmodels and 15.9 s and 20.6 GB for pyfixest. It is not the fastest: fixest was about 2.2 times faster at OLS and glum about 2.2 times faster at logit, scikit-learn, polars-ols and statsmodels were faster from 1 to 5 million rows, and a hand-written solve in NumPy is faster still. Those packages return coefficients or, at most, classical standard errors; here you also get clustering, weights, IV and formulas, and a memory use that stays near 2.5 GB where theirs reaches 5 to 20 GB. See the OLS, IV, logit and probit benchmarks.

On small data it gains you nothing. Below about 400,000 workers the in-memory libraries were faster — by a second or less on the AKM regression, and by six seconds on the smallest KSS decomposition — and lighter: an hdfe_stream process starts at about 0.8 GB. The same holds for plain regressions below about a million rows: a fit takes about a second, against tenths of a second for the fast libraries.

Use pyfixest when you need what a streaming design does not offer. It is more mature and more widely used, and has inference and clustering options this library lacks or limits: CRV3 with fixed effects that are not nested in the clusters, separation checks on the covariates and bias corrections for Poisson and logit, and more (see limitations). Use xhdfe when GPU speed is the priority: its CUDA backend made the most of the GPU in the GPU benchmark.

This library is at its strongest when:

  • the data do not fit in memory, or fit so tightly that nothing else does;
  • you have one very high-cardinality dimension (workers, patients, students) whose groups each touch only a few levels of the others;
  • you want worker-specific slopes as well as intercepts (worker_id[t]), which multiplies the parameter count by the number of slopes;
  • the design is wide: hundreds of covariates, such as a categorical expanded into fine indicators or many interactions. An in-memory library needs rows × covariates in memory before it starts; this needs one batch of rows × covariates at a time, with any number of fixed effects, including one or none (see many covariates);
  • you need a hard, predictable memory ceiling — a shared cluster, or a job that must not be the one that gets killed.

You are trading memory for disk, and you need scratch space: about five times the size of your Parquet input while the fit runs, and only the result files once it finishes. See performance for what that buys. Point workdir= at it, or set the HDFE_STREAM_WORKDIR environment variable once (for example to a cluster's scratch filesystem) and leave workdir out; a workdir passed to a call takes precedence, and with neither, the system temporary directory is used.

How it works, in one paragraph

One fixed-effect dimension — normally the largest — is streamed: it is never represented as a vector in memory. Every other dimension is held as a vector sized by its number of levels, not rows. Rows are read in batches, hash-partitioned into buckets by the streamed dimension, sorted one bucket at a time, and reduced to a table of cells (one per combination of fixed-effect levels). The reduced normal equations for the non-streamed dimensions are then solved by conjugate gradient, and the streamed effects are recovered group by group in a final pass that writes residuals and fixed effects straight to Parquet. So memory scales with firms × years, not with workers × rows, and that asymmetry is the whole design. The covariates travel with the rows: what is held at once is one bucket or batch of rows × covariates, never all of them, which is why a wide design fits where an in-memory one does not. Full detail is in hdfe_stream/__init__.py.

Install

pip install hdfe-stream[formula]     # formula syntax needs pyfixest

polars, numpy, numba, pyarrow and scipy are required. The extras:

extra what it adds
formula feols_stream formula syntax and pyfixest reporting (pyfixest, formulaic, pandas)
within the solver="within" backend
amg algebraic multigrid preconditioning (precond="amg") for hard geometries
all all of the above
test the test suite
docs building the documentation (Sphinx)

Without formula you still get StreamingHDFE, the lower-level interface that takes column names or Polars expressions instead of a formula string.

The experimental GPU options need CuPy and cudf-polars for your CUDA version (pip install cupy-cuda13x cudf-polars-cu13); there is no extra for them, since the packages differ by CUDA version. See CPU and GPU.

Performance

The standard three-way AKM regression, log_earn ~ age_squared + age_cubed | worker_id + firm_id + year, clustered by worker, on a simulated panel of 5 million workers and 42.5 million rows, against pyfixest, xhdfe, R's fixest and Julia's FixedEffectModels.jl. Every configuration agrees on the coefficients to within 2e-9.

configuration wall time peak memory peak disk
pyfixest (LSMR, its default) 59 s 22.0 GB —
xhdfe 79 s 19.3 GB —
fixest (R) 152 s 9.1 GB —
FixedEffectModels.jl (Julia) 79 s 9.5 GB —
hdfe_stream (explicit, its default) 23 s 3.9 GB 2.6 GB
hdfe_stream (stream_cg) 23 s 3.8 GB 2.6 GB
hdfe_stream (stream_cg, low memory) 26 s 3.9 GB 2.7 GB
hdfe_stream (max_memory_gb=2) 25 s 3.2 GB 2.6 GB
AKM regression: peak memory against the number of workers, one line per configuration, log-log

On small data the in-memory libraries use less memory: an hdfe_stream process costs about 0.8 GB before it has done anything. The benchmarks cover every size from 212,000 rows up, designs with up to 503 covariates, Poisson and logit, and the KSS decomposition, with how each was measured. They also cover plain OLS, 2SLS, logit and probit without fixed effects or with one, against scikit-learn, polars-ols, statsmodels and others: at 20 million rows hdfe_stream is not the fastest, but is within a small factor of most and uses a fraction of their memory.

Should I use a GPU?

Probably not for this. hdfe_stream's GPU options are experimental, and on an NVIDIA H100 their gains were modest throughout and appeared only at large sizes: about 4% at 5 million workers with both options on, and no consistent gain below that. A GPU-oriented library such as xhdfe is the better choice if GPU performance is the priority: its CUDA backend took 24 s at 5 million workers, against 107 s on its CPU backend. But hdfe_stream on a 16-vCPU CPU machine took 23 s, and that machine costs about an eighth as much per hour as AWS's smallest H100 machine.

For plain OLS, 2SLS and logit with no fixed effects the answer is less clear-cut. At 20 million rows GPU libraries fitted them in 3 to 5 s (cuML, and a hand-written CuPy solve), 1.3 to 3.4 times faster than hdfe_stream on the CPU of the same machine, and not at all below a few million rows. But hdfe_stream's own GPU options did little for them: polars_engine="gpu" was slower at every size, and device="cuda", which runs a logit's IRLS steps on the GPU, saved 26% at 20 million rows, 10% at 5 million and nothing below that. And fixest, on the 16-vCPU CPU machine, fitted the same OLS in 2.9 s. See plain regressions on the GPU. CPU and GPU has the numbers.

Documentation

  • Examples: one page per feature, on simulated data that ships with the package: formulas and standard errors, weights and IV, Poisson and logit, wide designs, varying slopes, connected sets, running out of core, and reporting.
  • Leave-out variance components: the Kline–Saggio–Sølvsten bias correction for the AKM decomposition, its standard errors and weak-identification diagnostic, and every way it differs from the reference implementations.
  • Resource limits and profiling: profile=True estimates a fit's memory and disk before you run it; the packages cap their threads and Polars' memory at a PBS/Slurm/cgroup allocation (the standalone stream-limits package), and their memory guards follow what is left of it.
  • Benchmarks: time, memory and disk against pyfixest, xhdfe, fixest and FixedEffectModels.jl.
  • Function reference: every option.
  • Limitations: what it does not do.
  • Changelog.

The scripts behind the examples are in examples/, and the benchmarks in benchmarks/.

Metadata

Release files for hdfe-stream 0.11.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for hdfe-stream 0.11.0
File Size Uploaded Provenance Actions
hdfe_stream-0.11.0.tar.gz 716.1 kB With provenance Details for hdfe_stream-0.11.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for hdfe-stream 0.11.0
File Interpreter ABI Platform Actions
hdfe_stream-0.11.0-py3-none-any.whl Python 3 none any Details for hdfe_stream-0.11.0-py3-none-any.whl

Total release size: 1.0 MB

Release files / hdfe_stream-0.11.0.tar.gz

Download URL hdfe_stream-0.11.0.tar.gz
Size 716.1 kB
Tags Source
SHA-256 checksum
How to use checksums
73cb713726a922881b9feff9a21f89bfe198eb80a382c23a047084d2efda3048
BLAKE2b-256 checksum
How to use checksums
a6a8f5d0d66e526c45f844de7d0b3a1181a6451001ab37083c0d9c5a2ab86068
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 13, 2026.

Transparency log

Release files / hdfe_stream-0.11.0-py3-none-any.whl

Download URL hdfe_stream-0.11.0-py3-none-any.whl
Size 301.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a968f7b89c1011675b6ed030ff50a75c01afd0c830fa04b9de016fa9caf73914
BLAKE2b-256 checksum
How to use checksums
065159befbdcf7b1035070c858bf510f72008db221b146f74f1d6d10bf02aedf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 13, 2026.

Transparency log

Security for hdfe-stream 0.11.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for hdfe_stream-0.11.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 13, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page