Releases: jturner-uofl/pymmeans
Releases · jturner-uofl/pymmeans
Release list
v0.1.8
What's fixed
Memory (P0)
pairs(emm, max_contrasts=None)no longer allocates an O(m²) contrast-covariance matrix when the adjustment method doesn't need the off-diagonal correlation. Previously every adjust path built the fullL_c @ V @ L_c.T; for k=250 group levels via pairwise that's ~7.2 GB on float64 and OOM'd typical hardware. The contrast covariance is now built lazily: only"dunnett"/"mvt"paths build the full matrix; every other adjustment computes the diagonal SEs viaeinsum. Empirical: k=250 peak RSS dropped from ~7.7 GB to ~270 MB with identical numerical output. Tukey HSD at k=250 is now feasible on a laptop.
Defaults (P1)
dunnett_max_kdefault tightened from 100 to 50. Empirical exact-Dunnett wall time on the reference machine: k=30 → ~5 s, k=50 → ~4 min, k=100 → effectively unbounded for an interactive call. The previous 100 cap suggested feasibility the QMC integrator can't deliver. Override viaset_emm_options(dunnett_max_k=...)for batch jobs.
Refusal completeness (P2)
joint_tests(EmmList(...))now scans every member'sinference_kindinstead of only the first. A mixedEmmList(freq_em, post_em)with the posterior member at any position now raises the steeringValueErrorand names the offending position(s).
Errors (P2)
bootstrap_ci(em, kind='case', refit_fn=...)now accepts an explicitdata=kwarg so the post-pickle path is actually usable. Resolves the resampling source in order: liveinfo.data→ caller-supplieddata=→ clear error. Passing both populates aUserWarningand prefersinfo.data(avoids a wrong-numerics footgun).
Documentation (P2)
src/pymmeans/options.pymodule docstring +set_emm_optionsadmonition now document the ContextVar process-boundary gap explicitly (joblib loky, ProcessPoolExecutor, multiprocessing spawn). Users hitting the newdunnett_max_koption in a parallel sensitivity-analysis pipeline see the limitation at the call site rather than at the worker-side ValueError.
v0.1.7
What's fixed
joint_tests()now refuses posterior-derivedEmmListinputs. The earlier posterior refusal only covered bareEMMResult/ContrastResult; anEmmListwrapper hitTypeError: No pymmeans adapter recognises EmmList.joint_testsnow unwraps the firstmodel_info-carrying member and routes it through the sameinference_kindcheck.- Cox PH advisory warning now fires on older statsmodels releases. The 0.1.6 detection imported
PHReg,PHRegResults, andPHRegResultsWrapperin a singlefrom ... import. On statsmodels releases where the wrapper symbol is absent, the whole tuple raisedImportErrorand the warning was silently disabled. Each name is now imported in isolation. - Cox class-name collisions in the adapter metadata.
src/pymmeans/utils.pystill had two short-name string checks routing any unrelated proxy class namedPHReg/PHRegResultsinto the Cox-PH code paths (syntheticnp.log(<endog>)response_name,df_resid=inf). Both call sites now use a module-qualifiedisinstanceagainststatsmodels.duration.hazard_regression. apply_kenward_rogeron non-MixedLM fits now raises a clearValueErrorinstead ofAttributeError: 'OLSResults' object has no attribute 'cov_re'. Mirrorsapply_satterthwaite's "MixedLM-only" refusal.joint_tests()on a pickled EMM/contrast now raises a clearValueErrorinstead ofAttributeError: 'NoneType' object has no attribute 'terms'. The new error names pickle as the cause and lists fixes.bootstrap_ci(em, kind='case', ...)on a pickled EMM now raises a clearValueErrorinstead of the misleadingCannot bootstrap a zero-row dataset. Names the pickle round-trip as the cause and steers torefit_fn=.- Exact Dunnett at large k is now bounded by a configurable safety cap.
adjust='dunnett'at k=200 (199-dim MVT integral) could run effectively unbounded;_dunnettnow raises at k > 100 (overridable viaset_emm_options(dunnett_max_k=...)) and steers toadjust='dunnettx'. - README honesty pass on Satterthwaite / KR coverage. Dropped the "any RE structure" overclaim; explicitly names
cov_re/re_formulaas supported, notes thatvc_formula=crossed RE is a 0.2.0 candidate.
v0.1.6
What's fixed
Statistical correctness
pairs(apply_kenward_roger(em))now matchesapply_kenward_roger(pairs(em))to floating-point precision. The 0.1.4 KR-idempotency guard was short-circuiting on freshly-built contrast results that inheritdf_method="kenward_roger"from the source EMM but whoseSEwas still computed from the uncorrectedV_beta— so the KR-corrected SE was never written. Same fix applied to Satterthwaite. Bug surfaced as ~1e-4 SE drift onpairs(em_kr)vsapply_kenward_roger(pairs(em)).apply_satterthwaite/apply_kenward_rogernow refuse cross-correction inputs. Previouslyapply_satterthwaite(apply_kenward_roger(em))silently rebuiltSEfrom the uncorrectedV_beta, discarding the KR step while keeping the Satt df. Both functions now raiseValueErrorwith a steer-the-user message; same-method idempotency short-circuits are unchanged.joint_testsrefuses posterior-derived inputs. The 0.1.4 EMMResult dispatch path accepted any input that exposedmodel_info, including posteriorEMMResults, and silently returned NaN.- Cox PH detection no longer false-positives on class-name collision. 0.1.5 matched on bare class names (
{"PHReg", "PHRegResults", "PHRegResultsWrapper"}); the check is now a module-qualifiedisinstanceagainststatsmodels.duration.hazard_regression.
Tooling
benchmarks/bench_performance.pynow passesmax_contrasts=Nonetopairs(emmeans(model, "group"))sopairwise_k20…pairwise_k200rows indocs/PERFORMANCE_REPORT.mdare reachable; added a failure-collection gate so future regressions can't produce a partial report.ruff check src testsclean.- Narrow
filterwarningsregex on the pbmodcomp convergence test.
Documentation
- README banner:
v0.1.5→v0.1.6;docs/index.md,docs/PERFORMANCE_REPORT.md,CITATION.cffsynced. - Performance highlights:
~14×→~11.5×,~8×→~7.7×so README matchesdocs/PERFORMANCE_REPORT.mdexactly. pytest --doctest-modules src/pymmeansis now clean (ml.py::from_predictexample was crashing collection on an unindented function body;emmeans.py's doctest gained+NORMALIZE_WHITESPACE;cld.pyseeded its RNG).- Empty-parenthetical scrub artifacts cleaned from ~40 docstrings/comments across 13 source files.
Install
pip install pymmeans[tutorial] now pulls linearmodels>=5.0, so the documented install reaches the full 313-test public surface without a separate install step.
v0.1.5 — robustness + audit-5 cleanup
[0.1.5] — 2026-05-26
Fixed
regrid_response(em)on a Gaussian-identity GLM is now a
no-op (was: raisedValueError: No transform recognised). The
identity-link path now resolves tomake_tran("identity")
instead of routing throughdetect_transform("y"), which only
recognises function-call shapes. Matches Remmeans::regrid
onglm(y ~ x, family=gaussian()).- Cox PH baseline-unidentified warning now also detects the
Cox model when it appears asinfo.raw_result.model.__class__,
not only asinfo.raw_result.__class__. Wrapped result objects
(e.g. MI-pooled, custom proxies) now trigger the warning as
intended. PlainPHRegResultsbehaviour is unchanged. - Three tests now pass under
pytest -W error(strict CI):
test_response_for_ols_is_noop,test_response_scale_binomial_in_unit_interval,
andtest_pbmodcomp_bootstrap_more_conservative_in_small_sample
each carry a@pytest.mark.filterwarnings(...)decorator for
the warning the test legitimately expects. qdrg("~ x", ...)(R-style RHS-only formula with leading
tilde) is now accepted alongside the existing"x"and
"y ~ x"forms.
Fixed (documentation)
- README banner version:
(v0.1.3)→(v0.1.5)(banner was
unchanged across 0.1.3 → 0.1.4 → 0.1.5). docs/index.mdanddocs/PERFORMANCE_REPORT.mdversion stamps
synced to 0.1.5.- README and
docs/r_parity_matrix.mdtest count:312→313
(measured with the[parallel,plot,tutorial]extras installed,
which include the optionallinearmodels/joblibtests). docs/PERFORMANCE_REPORT.mdspeedup arithmetic corrected on
four rows:13.7x → 11.5x(n=1000),8.1x → 7.5x(n=10000 and
n=100000),7.6x → 7.7x(n=500000). Underlying timings
unchanged; the displayed ratios now matchR_time / pymmeans_time
exactly.
v0.1.4 — statistical-correctness fixes
[0.1.4] — 2026-05-26
Fixed (statistical correctness)
regrid_response(em, bias_adjust=True)on a GLM with a
non-identity link now raisesValueErrorunless an explicit
sigma=is supplied. The previous default (info.scale) used
the residual dispersion on the response scale as the Jensen
correction's σ² — which silently inflated the response-scale
EMM by a family-dependent factor (1.5× on canonical Poisson(log),
~3.25× on Gaussian(log)). Remmeans::summary(..., bias.adjust=TRUE)enforces the same constraint. OLS with an
LHS transform (lm(log(y) ~ ...)) is unchanged —info.scale
there is the correct sigma².- Cox PH
emmeans(fit, ..., type='link')now emits a
UserWarningadvising that theemmeancolumn is on the
relative log-hazard scale (the partial likelihood does not
identify the baseline hazard, so the reference-level row shows
emmean=0by construction).pairs(emm)andregrid_response(emm)
remain identifiable and unchanged. Remmeans.coxphomits the
link-scale column for the same reason. apply_kenward_rogeris now idempotent: a second call on an
already-KR-corrected EMM/contrast returns the input unchanged.
Previously a second application recomputed K-R from the
already-inflated vcov and drifted the df by ~0.05 due to finite-
difference noise on the doubly-corrected Hessian.
Fixed (API)
joint_tests(emm_result)now acceptsEMMResult/
ContrastResult/RefGridinput by dispatching to the underlying
model_info, matching Remmeans::joint_testsbehaviour.
Previously raisedTypeError: No adapter recognises EMMResult.pairs(emm)now emits aUserWarningwhen the input EMM
contains any non-estimable rows (emmeanis NaN, typically from
a rank-deficient design). Contrasts touching those rows still
propagate NaN to the result — the warning surfaces the issue so
users are not surprised by silent NaN downstream. Remmeans
marks these asnonEst;pymmeansflags them at the contrast
step.
Fixed (documentation)
apply_satterthwaitedocstring now documents the per-row
property of Satterthwaite df: df at an EMM cell can differ from
df on a pair contrast by an order of magnitude on the same model
(different linear-combination matrixL), and both are correct.
Includes the canonical sleepstudy numbers (EMM atDays=9→
df ≈ 23.4,Days=9 − Days=0contrast → df ≈ 161).survey.pymodule docstring now matches the implementation
for the simple-random-sample variance (the code applies the
n/(n−1)finite-sample correction with score centring; the
docstring previously cited the EHW uncentred form, which agrees
at the MLE FOC but diverges if the GLM has not fully converged)._regularize_corr_for_mvtdocstring no longer claims R's
mvtnorm::pmvtdoes "the same thing internally" — R uses
Cholesky pivoting and dimension reduction;pymmeansuses a
ridge that introduces an O(1e-9) bias invisible at the 1e-4
validation tolerance.
Deferred to 0.2.0
- Kenward-Roger algorithmic refactor (analytic derivatives or
chunked finite-difference) for memory-boundedn_theta ≥ 20
fits. Current implementation works for typical mixed-model
sizes (n_theta ≤ ~10). - Hessian-step Richardson extrapolation for poorly-scaled fits
(heritability-like parameters ≈ 1e-3). Current step formula is
adequate for the canonical reference suite but accumulates
10-50 % roundoff at extreme parameter scales.