Skip to content

Releases: jturner-uofl/pymmeans

v0.1.8

Choose a tag to compare

@jturner-uofl jturner-uofl released this 27 May 13:18

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 full L_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 via einsum. 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_k default 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 via set_emm_options(dunnett_max_k=...) for batch jobs.

Refusal completeness (P2)

  • joint_tests(EmmList(...)) now scans every member's inference_kind instead of only the first. A mixed EmmList(freq_em, post_em) with the posterior member at any position now raises the steering ValueError and names the offending position(s).

Errors (P2)

  • bootstrap_ci(em, kind='case', refit_fn=...) now accepts an explicit data= kwarg so the post-pickle path is actually usable. Resolves the resampling source in order: live info.data → caller-supplied data= → clear error. Passing both populates a UserWarning and prefers info.data (avoids a wrong-numerics footgun).

Documentation (P2)

  • src/pymmeans/options.py module docstring + set_emm_options admonition now document the ContextVar process-boundary gap explicitly (joblib loky, ProcessPoolExecutor, multiprocessing spawn). Users hitting the new dunnett_max_k option in a parallel sensitivity-analysis pipeline see the limitation at the call site rather than at the worker-side ValueError.

v0.1.7

Choose a tag to compare

@jturner-uofl jturner-uofl released this 27 May 12:15

What's fixed

  • joint_tests() now refuses posterior-derived EmmList inputs. The earlier posterior refusal only covered bare EMMResult / ContrastResult; an EmmList wrapper hit TypeError: No pymmeans adapter recognises EmmList. joint_tests now unwraps the first model_info-carrying member and routes it through the same inference_kind check.
  • Cox PH advisory warning now fires on older statsmodels releases. The 0.1.6 detection imported PHReg, PHRegResults, and PHRegResultsWrapper in a single from ... import. On statsmodels releases where the wrapper symbol is absent, the whole tuple raised ImportError and the warning was silently disabled. Each name is now imported in isolation.
  • Cox class-name collisions in the adapter metadata. src/pymmeans/utils.py still had two short-name string checks routing any unrelated proxy class named PHReg / PHRegResults into the Cox-PH code paths (synthetic np.log(<endog>) response_name, df_resid=inf). Both call sites now use a module-qualified isinstance against statsmodels.duration.hazard_regression.
  • apply_kenward_roger on non-MixedLM fits now raises a clear ValueError instead of AttributeError: 'OLSResults' object has no attribute 'cov_re'. Mirrors apply_satterthwaite's "MixedLM-only" refusal.
  • joint_tests() on a pickled EMM/contrast now raises a clear ValueError instead of AttributeError: '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 clear ValueError instead of the misleading Cannot bootstrap a zero-row dataset. Names the pickle round-trip as the cause and steers to refit_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; _dunnett now raises at k > 100 (overridable via set_emm_options(dunnett_max_k=...)) and steers to adjust='dunnettx'.
  • README honesty pass on Satterthwaite / KR coverage. Dropped the "any RE structure" overclaim; explicitly names cov_re / re_formula as supported, notes that vc_formula= crossed RE is a 0.2.0 candidate.

v0.1.6

Choose a tag to compare

@jturner-uofl jturner-uofl released this 27 May 11:00

What's fixed

Statistical correctness

  • pairs(apply_kenward_roger(em)) now matches apply_kenward_roger(pairs(em)) to floating-point precision. The 0.1.4 KR-idempotency guard was short-circuiting on freshly-built contrast results that inherit df_method="kenward_roger" from the source EMM but whose SE was still computed from the uncorrected V_beta — so the KR-corrected SE was never written. Same fix applied to Satterthwaite. Bug surfaced as ~1e-4 SE drift on pairs(em_kr) vs apply_kenward_roger(pairs(em)).
  • apply_satterthwaite / apply_kenward_roger now refuse cross-correction inputs. Previously apply_satterthwaite(apply_kenward_roger(em)) silently rebuilt SE from the uncorrected V_beta, discarding the KR step while keeping the Satt df. Both functions now raise ValueError with a steer-the-user message; same-method idempotency short-circuits are unchanged.
  • joint_tests refuses posterior-derived inputs. The 0.1.4 EMMResult dispatch path accepted any input that exposed model_info, including posterior EMMResults, 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-qualified isinstance against statsmodels.duration.hazard_regression.

Tooling

  • benchmarks/bench_performance.py now passes max_contrasts=None to pairs(emmeans(model, "group")) so pairwise_k20pairwise_k200 rows in docs/PERFORMANCE_REPORT.md are reachable; added a failure-collection gate so future regressions can't produce a partial report.
  • ruff check src tests clean.
  • Narrow filterwarnings regex on the pbmodcomp convergence test.

Documentation

  • README banner: v0.1.5v0.1.6; docs/index.md, docs/PERFORMANCE_REPORT.md, CITATION.cff synced.
  • Performance highlights: ~14×~11.5×, ~8×~7.7× so README matches docs/PERFORMANCE_REPORT.md exactly.
  • pytest --doctest-modules src/pymmeans is now clean (ml.py::from_predict example was crashing collection on an unindented function body; emmeans.py's doctest gained +NORMALIZE_WHITESPACE; cld.py seeded 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

Choose a tag to compare

@jturner-uofl jturner-uofl released this 26 May 17:34

[0.1.5] — 2026-05-26

Fixed

  • regrid_response(em) on a Gaussian-identity GLM is now a
    no-op (was: raised ValueError: No transform recognised). The
    identity-link path now resolves to make_tran("identity")
    instead of routing through detect_transform("y"), which only
    recognises function-call shapes. Matches R emmeans::regrid
    on glm(y ~ x, family=gaussian()).
  • Cox PH baseline-unidentified warning now also detects the
    Cox model when it appears as info.raw_result.model.__class__,
    not only as info.raw_result.__class__. Wrapped result objects
    (e.g. MI-pooled, custom proxies) now trigger the warning as
    intended. Plain PHRegResults behaviour 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,
    and test_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.md and docs/PERFORMANCE_REPORT.md version stamps
    synced to 0.1.5.
  • README and docs/r_parity_matrix.md test count: 312313
    (measured with the [parallel,plot,tutorial] extras installed,
    which include the optional linearmodels / joblib tests).
  • docs/PERFORMANCE_REPORT.md speedup 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 match R_time / pymmeans_time
    exactly.

v0.1.4 — statistical-correctness fixes

Choose a tag to compare

@jturner-uofl jturner-uofl released this 26 May 12:30

[0.1.4] — 2026-05-26

Fixed (statistical correctness)

  • regrid_response(em, bias_adjust=True) on a GLM with a
    non-identity link now raises ValueError unless 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)). R emmeans::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
    UserWarning advising that the emmean column is on the
    relative log-hazard scale (the partial likelihood does not
    identify the baseline hazard, so the reference-level row shows
    emmean=0 by construction). pairs(emm) and regrid_response(emm)
    remain identifiable and unchanged. R emmeans.coxph omits the
    link-scale column for the same reason.
  • apply_kenward_roger is 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 accepts EMMResult /
    ContrastResult / RefGrid input by dispatching to the underlying
    model_info, matching R emmeans::joint_tests behaviour.
    Previously raised TypeError: No adapter recognises EMMResult.
  • pairs(emm) now emits a UserWarning when the input EMM
    contains any non-estimable rows (emmean is 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. R emmeans
    marks these as nonEst; pymmeans flags them at the contrast
    step.

Fixed (documentation)

  • apply_satterthwaite docstring 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 matrix L), and both are correct.
    Includes the canonical sleepstudy numbers (EMM at Days=9
    df ≈ 23.4, Days=9 − Days=0 contrast → df ≈ 161).
  • survey.py module 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_mvt docstring no longer claims R's
    mvtnorm::pmvt does "the same thing internally" — R uses
    Cholesky pivoting and dimension reduction; pymmeans uses 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-bounded n_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.