Skip to main content
mixedlm-rs logo

mixedlm-rs

Fast linear mixed-effects models for Python, powered by Rust.

PyPI CI Python License: MIT

mixedlm-rs fits Gaussian linear mixed-effects models using lme4's profiled ML/REML formulation and a compiled Rust core. It provides a statsmodels-style formula and array API, with no R installation required.

Use it for repeated measurements within subjects, patients within clinics, or other data with one grouping factor, including correlated random intercepts and slopes. The implementation is designed to make models with many groups fast while checking convergence explicitly.

This is beta software. It supports a subset of statsmodels.MixedLM; crossed effects, multiple levels of nesting and GLMMs are outside its scope. See compatibility before migrating an existing analysis.

Install

python -m pip install mixedlm-rs

Python 3.10 or later is required. NumPy, SciPy, pandas and Patsy are installed as dependencies. Neither statsmodels nor R is needed for normal use.

Binary wheels are available on PyPI:

Platform Architectures
Windows x86-64
macOS Intel x86-64, Apple Silicon ARM64
Linux, glibc 2.17+ x86-64, ARM64

The wheels use Python's stable ABI (cp310-abi3). On a matching platform, installation needs no Rust compiler. Building from source requires Rust 1.83+ and a compatible native build toolchain; pip installs the Maturin build backend. There are no published wheels for 32-bit systems or musl-based Linux.

Currently verified: the project's CI matrix covers Python 3.10–3.14 on Windows, macOS and Linux, with separate checks for the declared dependency floors and minimum Rust version. Release checks install each platform's wheel outside the source tree and test a wheel rebuilt from the source archive. See the CI runs for the status of a particular commit. Python versions beyond 3.14 are not yet part of that test matrix.

Quick start

This example simulates 18 subjects measured on 10 days. Each subject has their own baseline and rate of change. It runs after installation, with no data file to fetch.

import numpy as np
import pandas as pd
import mixedlm_rs as mlm

rng = np.random.default_rng(0)
n_subjects, n_days = 18, 10
subject = np.repeat(np.arange(n_subjects), n_days)
day = np.tile(np.arange(n_days), n_subjects)

intercept = rng.normal(250, 25, n_subjects)[subject]
slope = rng.normal(10, 6, n_subjects)[subject]
reaction = intercept + slope * day + rng.normal(0, 25, subject.size)
data = pd.DataFrame({"reaction": reaction, "day": day, "subject": subject})

model = mlm.mixedlm(
    "reaction ~ day",
    data,
    groups="subject",
    re_formula="~day",
)
result = model.fit()  # REML by default

print(result.summary())
print(f"fixed effects: {result.fe_params}")
print(f"converged: {result.converged}")
print(f"singular: {result.singular}")

reaction ~ day specifies the fixed effects. groups="subject" identifies independent clusters; re_formula="~day" gives each subject a random intercept and slope, with their covariance estimated. Omit re_formula for a random intercept only. Group sizes do not have to be equal.

To use maximum likelihood instead, call .fit(reml=False) on a new model. Use ML when comparing likelihoods of models with different fixed-effect specifications fitted to the same observations; their REML criteria are not directly comparable.

Inspect estimates and predict

Continuing the example:

print(result.fe_params_labelled)  # Fixed effects as a named pandas Series
print(result.bse_fe)             # Fixed-effect standard errors
print(result.cov_re)             # Random-effect covariance matrix
print(result.scale)              # Residual variance
print(result.diagnostics)        # Optimizer and numerical diagnostics

new_data = pd.DataFrame({"day": [0, 5, 10]})
print(result.predict(new_data))

predict() returns the population-level, fixed-effects prediction. It does not add fitted subject effects, so this example needs no subject column. result.fittedvalues includes the fitted random effects for the training observations. result.random_effects contains the estimated effects by group.

The main numerical attributes, including fe_params and cov_re, are NumPy arrays even for formula fits. Use fe_params_labelled or params_labelled when you need names.

Use arrays directly

The equivalent model can be constructed without a formula:

X = np.column_stack([np.ones(len(data)), data["day"].to_numpy()])
array_result = mlm.MixedLM(
    endog=data["reaction"].to_numpy(),
    exog=X,
    groups=data["subject"].to_numpy(),
    exog_re=X,
).fit()

Include an intercept column explicitly in array inputs when the model needs one. exog defines fixed effects; exog_re defines random effects within each group.

Save and load a fit

result.save("fit.pkl")
restored = mlm.MixedLMResults.load("fit.pkl")
print(restored.predict(new_data))

The default save retains the training data needed to rebuild supported formula designs. Only load files you trust: this uses Python pickle. Custom formula functions and reduced-data saves have additional persistence limitations.

Moving from statsmodels

For a supported model, start by changing the formula API import:

- import statsmodels.formula.api as smf
+ import mixedlm_rs as smf

The mixedlm(...), MixedLM(...) and MixedLM.from_formula(...) entry points follow familiar statsmodels conventions. Existing code still needs review: some return types differ, optimizer arguments do not select the same algorithms, and several methods are unimplemented. The compatibility guide lists the supported contract, warnings and exceptions.

Convergence and inference

Inspect both result.converged and result.singular:

Attribute Meaning
converged The fit passed the implementation's numerical convergence checks.
singular The estimated random-effect covariance is at or near a singular boundary.

A singular fit can be a valid converged optimum, for example when a random intercept variance is zero. Conversely, returning estimates does not establish convergence. This package can also return converged=False with a warning.

The optimizer checks local stationarity and probes away from boundary solutions. These checks do not prove that it found a global optimum or that the model is identifiable. Examine warnings and result.diagnostics before using an uncertain fit.

Reported fixed-effect tests use normal/Wald inference. There are no Satterthwaite or Kenward–Roger small-sample corrections. Fixed-effect covariance is conditional on the fitted variance parameters; joint covariance across fixed and variance parameters is not available. Wald intervals for variance parameters are especially unreliable near zero.

Numerical validation

The primary reference is lme4, with checks against published fits and recorded R results for sleepstudy, Dyestuff and Dyestuff2, under ML and REML. These cover correlated random slopes and a zero-variance boundary fit. The reference datasets are GPL-2 test fixtures kept in the Git repository; they are not shipped in the wheels or source archive. See dataset provenance.

Agreement is assessed at the precision and tolerances of the recorded references. Additional tests compare against statsmodels, check derivatives with finite differences, and probe the objective independently of the analytic gradient. A convergence warning from another package alone does not establish that its estimates are wrong.

The recorded comparison on 120 randomised fixtures reports:

The same 120 fixtures as one bar: 42 better optimum, 52 same optimum, 26 statsmodels did not converge, 0 worse.
Outcome Count
statsmodels did not converge 26
mixedlm-rs found a strictly better optimum 42
same optimum 52
mixedlm-rs found a worse optimum 0

“Better” and “worse” refer to the fitted likelihood criterion on the same model and data, using the experiment's absolute deviance tolerance. They do not mean better prediction or establish which model is scientifically appropriate.

A separate 400-case adversarial sweep recorded 99 statsmodels nonconvergences, 130 better optima for mixedlm-rs, 168 ties and 3 worse optima. Those losses have absolute deviance gaps of approximately 2.74e-6 to 6.71e-5. Every case passed the sweep's independent stationarity check. These are finite test suites, not estimates of failure rates in general use.

Both experiments are recorded in bench/baseline.json at commit 83c01d5. The correctness report provides the reference fits, thresholds, generators and known difficult cases.

Performance

The recorded benchmark below uses synthetic data with a random intercept and slope, fitted through each package's formula API on the same machine. Timings include model construction and fitting; they exclude data generation and printing a summary.

Observations Groups statsmodels mixedlm-rs Speedup
2,000 100 0.353 s 0.0070 s 50x
10,000 500 1.813 s 0.0085 s 215x
20,000 1,000 2.666 s 0.0107 s 250x
40,000 5,000 11.472 s 0.0141 s 814x
100,000 20,000 43.358 s 0.0244 s 1777x
200,000 50,000 108.354 s 0.0456 s 2375x
500,264 125,066 Not measured 0.124 s —
Fit time against grouping factor levels, log-log. statsmodels rises from 0.35 to 108 seconds; mixedlm-rs stays between 0.007 and 0.046 seconds.

Measurement conditions: Windows 11, AMD Ryzen 5 5600G, 12 logical CPUs, Python 3.14.3 and statsmodels 0.15.0. mixedlm-rs reports the minimum of three runs. statsmodels also uses three runs through 40,000 observations, but only one run for the 100,000- and 200,000-observation cases. Thread counts were not pinned: Rayon used the available logical CPUs, and OMP/OpenBLAS thread settings were unset. These are wall-clock comparisons with those defaults, not comparisons at a fixed CPU budget. Speedups use unrounded timings.

Both fits reported convergence in every timed comparison, and their estimates and likelihoods passed the benchmark's agreement checks. The workload favors many small groups; speedups depend on model shape, data, dependencies and hardware, and should not be assumed for every analysis.

The measurements were recorded on 2026-09-19 at commit d01ab61 (0.2.0). bench/performance.json contains all repetitions, dependency versions, thread settings and the extension hash. Reproduce the comparison with bench/performance.py.

The largest case uses a group count motivated by statsmodels issue #9097. Its reported 41-minute timing is not a measurement made here. Our synthetic case is not a reproduction of their dataset and should not be read as a measured speedup over that report.

Against lme4

bench/three_way.py fits byte-identical CSV files with all three packages in one session on the same machine, with lme4 2.0.6 run through R 4.6.1 and timed inside R. Each Python fit is checked against lme4's before its time is reported.

Fit time against groups, log-log, for three packages. At 125,066 groups mixedlm-rs takes 110 ms and lme4 7.03 s; statsmodels takes 108 s at 50,000 groups.
Observations Groups lme4 statsmodels mixedlm-rs vs lme4
2,000 100 0.030 s 0.351 s 0.0075 s 4x
10,000 500 0.075 s 1.739 s 0.0083 s 9x
20,000 1,000 0.163 s 2.636 s 0.0130 s 13x
40,000 5,000 0.322 s 11.041 s 0.0160 s 20x
100,000 20,000 1.135 s 43.134 s 0.0273 s 42x
200,000 50,000 2.409 s 107.936 s 0.0468 s 51x
500,264 125,066 7.033 s Not measured 0.110 s 64x

Accuracy is measured on the same 120 randomised fixtures as above, this time against lme4's optimum:

Outcomes on 120 fixtures relative to lme4. mixedlm-rs: 113 match, 7 higher likelihood. statsmodels: 74 match, 3 higher, 26 lower and flagged not converged, 17 lower but reported converged.

mixedlm-rs matched lme4's optimum on 113 fixtures and reached a higher likelihood on 7. It did not finish below lme4 on any fixture. statsmodels finished below lme4 on 43, and on 17 of those it still reported convergence. lme4 is a reference here, not ground truth: its optimiser can stop short too. Both experiments were recorded on 2026-09-19 at commit 7677222 (0.2.0) in bench/three_way.json, under the same measurement conditions and caveats as above.

The earlier lme4 and pymer4 timings, including pymer4's, are kept in the benchmark report as historical records. The pymer4 path also performs lmerTest inference and result extraction, so its elapsed time covers different work from a bare model fit.

Against the previous release

Full-fit speedup of 0.2.0 over 0.1.1 per case, at 1 and 12 threads: 1.4x to 2.2x on continuous random slopes, 4.0x to 6.4x on one thread where groups share a design.

0.2.0 fits 1.4x to 6.4x faster than 0.1.1 on the same machine. The largest gains are on designs where many groups share the same random-effects design, such as a random intercept or a slope over a shared visit schedule. There the work scales with the number of distinct group designs, not the number of groups. Recorded with bench/phases.py in bench/phases.json, which alternates the two builds and keeps every sample.

How it works

The Rust core evaluates a profiled ML/REML criterion using penalised least squares. With one grouping factor, the random-effect system separates into small per-group blocks. The implementation precomputes group statistics, factorises those blocks in Rust, and uses Rayon for parallel work.

An analytic gradient reuses the factorisation work. The default optimizer is SciPy's L-BFGS-B, calling the Rust objective and gradient. In the recorded implementation-stage comparison, replacing this project's finite differences with its analytic gradient reduced objective evaluations from 44–64 to 11–13. That is a comparison between stages of this implementation, not a claim that analytic gradients are new or that statsmodels lacks them.

See the design and gradient derivation for the equations, implementation details and validation.

Scope and limitations

Area Current boundary
Grouping One grouping factor. No crossed subject/item effects or separate effects at multiple nesting levels.
Model family Gaussian linear mixed models only; no binomial or Poisson GLMMs.
Covariance structures No variance-component formulas, free masks or covariance penalties.
Additional fitting methods No fit_regularized, profile_re, bootstrap or get_distribution.
Fixed-effect design Rank-deficient or numerically rank-deficient designs are rejected; redundant columns are not dropped automatically.
Inference Normal/Wald inference, with the limitations described above.
Optimization Local convergence checks; no guarantee of a global optimum. method="rust" is experimental.

These restrictions matter for designs such as crossed participant/item experiments or students within classrooms within schools. Combining IDs into one grouping variable does not reproduce separate random effects at each level. Read the full limitations and API compatibility guide for argument handling, result types, numerical edge cases and persistence.

Development

Building a checkout requires Rust 1.83+ and Python 3.10+. Use a virtual environment, then install the package and test tools:

git clone https://github.com/Fatin-Ishraq/mixedlm-rs.git
cd mixedlm-rs
python -m pip install ".[test,lint]"
python -m pytest tests/ -q
cargo test --lib --locked
python -m ruff check .

The Git checkout includes the numerical reference fixtures and benchmark scripts omitted from published distributions. CI also runs type checking, Rust linting, dependency-floor checks and artifact validation.

For bug reports, include a minimal example, package and dependency versions, platform, warnings and result.diagnostics when a fit is available. Use GitHub issues. See the changelog for release history.

License and acknowledgements

mixedlm-rs is MIT licensed. Bundled dependencies retain their own licenses; see third-party notices and license texts.

The statistical formulation follows lme4 and the work of Bates, Mächler, Bolker and Walker, Fitting Linear Mixed-Effects Models Using lme4 (2015). The Python API follows statsmodels. This is an independent implementation; neither upstream project maintains or endorses it.

Metadata

Release files for mixedlm-rs 0.2.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 mixedlm-rs 0.2.0
File Size Uploaded
mixedlm_rs-0.2.0.tar.gz 344.7 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for mixedlm-rs 0.2.0
File
mixedlm_rs-0.2.0-cp310-abi3-win_amd64.whl CPython 3.10 abi3 Windows x86-64 Details
mixedlm_rs-0.2.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.10 abi3 Linux glibc 2.17+ x86-64 Details
mixedlm_rs-0.2.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.10 abi3 Linux glibc 2.17+ ARM64 Details
mixedlm_rs-0.2.0-cp310-abi3-macosx_11_0_arm64.whl CPython 3.10 abi3 macOS 11.0+ ARM64 Details
mixedlm_rs-0.2.0-cp310-abi3-macosx_10_12_x86_64.whl CPython 3.10 abi3 macOS 10.12+ x86-64 Details

Total release size: 2.8 MB

Release files / mixedlm_rs-0.2.0.tar.gz

Download URL mixedlm_rs-0.2.0.tar.gz
Size 344.7 kB
Tags Source
SHA-256 checksum
How to use checksums
f8eb5b1c63d57b19eaffd636fde9f6756817fdab11c110fcaa8be73dfa00bfc5
BLAKE2b-256 checksum
How to use checksums
6493ee430acc199161668e3f991702202b0ec35b04f9b04f49111ca7ba51b8b9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Sep 19, 2026.

Transparency log

Release files / mixedlm_rs-0.2.0-cp310-abi3-win_amd64.whl

Download URL mixedlm_rs-0.2.0-cp310-abi3-win_amd64.whl
Size 393.1 kB
Tags CPython 3.10 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
4c2b9c7e6858ef60ec63860ae62d778cbc9451914d9b5a97e36aa699f0e9eda8
BLAKE2b-256 checksum
How to use checksums
93362237f70c5cfe67365c81dcf45a1dcf6393df8ead95b962860ef9881bae16
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Sep 19, 2026.

Transparency log

Release files / mixedlm_rs-0.2.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL mixedlm_rs-0.2.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 533.3 kB
Tags CPython 3.10 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
5b9d60286ebda22422913a1f20820199f19e30d095d1bc57fc047bd639cd2b48
BLAKE2b-256 checksum
How to use checksums
e97d5adcb819ea068976017fda2ef28f8ca98d658f53ec952f8d5a3e0e02f7df
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Sep 19, 2026.

Transparency log

Release files / mixedlm_rs-0.2.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL mixedlm_rs-0.2.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 510.7 kB
Tags CPython 3.10 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
7c94f1f03b843459a346b034e72472da266ff35d043ec7fe8b6a6be1098ff026
BLAKE2b-256 checksum
How to use checksums
d1a35ca5b266c3294dcbc383e17b2e15a4c9fcf8a53261b039e72dffb4d0eac6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Sep 19, 2026.

Transparency log

Release files / mixedlm_rs-0.2.0-cp310-abi3-macosx_11_0_arm64.whl

Download URL mixedlm_rs-0.2.0-cp310-abi3-macosx_11_0_arm64.whl
Size 472.3 kB
Tags CPython 3.10 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
80b56447b2e2b05c30a191db06784202de3b090d505da76a10bb37bdb73716ee
BLAKE2b-256 checksum
How to use checksums
4320644dbcc948d76f5081c5432a04625c71406db83e30d2939cde57aae7946a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Sep 19, 2026.

Transparency log

Release files / mixedlm_rs-0.2.0-cp310-abi3-macosx_10_12_x86_64.whl

Download URL mixedlm_rs-0.2.0-cp310-abi3-macosx_10_12_x86_64.whl
Size 499.3 kB
Tags CPython 3.10 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
26fa5004a33781a5d3225ae478a651ea8590a44d99aac0209ec5247b7b90acbf
BLAKE2b-256 checksum
How to use checksums
f28149a660a046b9fa839f70ba3d807aab8b34819ab21187279f67949813be4e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Sep 19, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

6 release files

0.1.1

6 release files

0.1.0

6 release files

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