pystatistics 2.1.0__tar.gz → 2.2.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.
- {pystatistics-2.1.0 → pystatistics-2.2.0}/CHANGELOG.md +64 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/PKG-INFO +69 -1
- {pystatistics-2.1.0 → pystatistics-2.2.0}/README.md +68 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pyproject.toml +1 -1
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/__init__.py +1 -1
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/mvnmle/backends/_em_batched.py +114 -12
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/mvnmle/backends/em.py +54 -6
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/mvnmle/mcar_test.py +12 -2
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/mvnmle/solvers.py +9 -3
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/mvnmle/test_mcar.py +6 -1
- {pystatistics-2.1.0 → pystatistics-2.2.0}/.github/workflows/publish.yml +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/.github/workflows/trigger-docs-rebuild.yml +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/.gitignore +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/.release/CHECKLIST.md +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/.release/UNRELEASED.md +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/.release/release.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/CLAUDE.md +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/LICENSE +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/benchmarks/mvnmle_bench.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/docs/DESIGN.md +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/docs/Forge.md +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/docs/GPU_BACKEND_NOTES.md +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/docs/Makefile +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/docs/PYSTATSBIO_CONTEXT.md +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/docs/ROADMAP.md +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/docs/_static/custom.css +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/docs/anova.rst +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/docs/conf.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/docs/core.rst +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/docs/descriptive.rst +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/docs/hypothesis.rst +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/docs/index.rst +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/docs/mixed.rst +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/docs/montecarlo.rst +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/docs/mvnmle.rst +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/docs/regression.rst +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/docs/survival.rst +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/GPU_BACKEND_CONVENTION.md +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/anova/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/anova/_common.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/anova/_contrasts.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/anova/_levene.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/anova/_posthoc.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/anova/_repeated.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/anova/_ss.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/anova/design.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/anova/solution.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/anova/solvers.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/core/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/core/capabilities.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/core/compute/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/core/compute/device.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/core/compute/linalg/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/core/compute/linalg/batched.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/core/compute/linalg/cholesky.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/core/compute/linalg/determinant.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/core/compute/linalg/qr.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/core/compute/linalg/solve.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/core/compute/linalg/svd.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/core/compute/optimization/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/core/compute/optimization/convergence.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/core/compute/precision.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/core/compute/timing.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/core/compute/tolerances.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/core/datasource.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/core/exceptions.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/core/protocols.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/core/result.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/core/validation.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/descriptive/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/descriptive/_missing.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/descriptive/_quantile_types.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/descriptive/backends/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/descriptive/backends/cpu.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/descriptive/backends/gpu.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/descriptive/design.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/descriptive/solution.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/descriptive/solvers.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/gam/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/gam/_basis.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/gam/_common.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/gam/_fit.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/gam/_gam.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/gam/_gcv.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/gam/_smooth.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/gam/backends/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/gam/backends/_gpu_family.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/gam/backends/gpu_pirls.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/gam/solution.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/hypothesis/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/hypothesis/_common.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/hypothesis/_design_factories.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/hypothesis/_p_adjust.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/hypothesis/backends/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/hypothesis/backends/_chisq_test.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/hypothesis/backends/_fisher_test.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/hypothesis/backends/_ks_test.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/hypothesis/backends/_prop_test.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/hypothesis/backends/_t_test.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/hypothesis/backends/_var_test.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/hypothesis/backends/_wilcox_test.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/hypothesis/backends/cpu.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/hypothesis/backends/gpu.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/hypothesis/design.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/hypothesis/solution.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/hypothesis/solvers.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/mixed/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/mixed/_common.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/mixed/_deviance.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/mixed/_pirls.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/mixed/_pls.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/mixed/_random_effects.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/mixed/_satterthwaite.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/mixed/design.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/mixed/solution.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/mixed/solvers.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/montecarlo/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/montecarlo/_ci.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/montecarlo/_common.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/montecarlo/_influence.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/montecarlo/backends/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/montecarlo/backends/cpu.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/montecarlo/backends/gpu.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/montecarlo/design.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/montecarlo/solution.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/montecarlo/solvers.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/multinomial/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/multinomial/_common.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/multinomial/_likelihood.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/multinomial/_solver.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/multinomial/backends/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/multinomial/backends/gpu_likelihood.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/multinomial/solution.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/multivariate/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/multivariate/_common.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/multivariate/_factor.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/multivariate/_pca.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/multivariate/_rotation.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/multivariate/backends/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/multivariate/backends/gpu_pca.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/mvnmle/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/mvnmle/_monotone.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/mvnmle/_objectives/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/mvnmle/_objectives/base.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/mvnmle/_objectives/cpu.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/mvnmle/_objectives/gpu_fp32.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/mvnmle/_objectives/gpu_fp64.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/mvnmle/_objectives/parameterizations.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/mvnmle/_utils.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/mvnmle/backends/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/mvnmle/backends/_squarem.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/mvnmle/backends/cpu.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/mvnmle/backends/gpu.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/mvnmle/datasets.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/mvnmle/design.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/mvnmle/patterns.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/mvnmle/solution.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/ordinal/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/ordinal/_common.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/ordinal/_likelihood.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/ordinal/_solver.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/ordinal/backends/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/ordinal/backends/gpu_likelihood.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/ordinal/solution.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/py.typed +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/regression/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/regression/_formatting.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/regression/_glm.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/regression/_linear.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/regression/_nb_theta.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/regression/backends/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/regression/backends/cpu.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/regression/backends/cpu_glm.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/regression/backends/gpu.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/regression/backends/gpu_glm.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/regression/design.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/regression/families.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/regression/solution.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/regression/solvers.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/survival/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/survival/_common.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/survival/_cox.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/survival/_discrete.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/survival/_km.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/survival/_logrank.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/survival/backends/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/survival/backends/cpu.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/survival/backends/gpu.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/survival/design.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/survival/solution.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/survival/solvers.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/timeseries/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/timeseries/_acf.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/timeseries/_arima_batch.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/timeseries/_arima_factored.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/timeseries/_arima_fit.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/timeseries/_arima_forecast.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/timeseries/_arima_kalman.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/timeseries/_arima_likelihood.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/timeseries/_arima_order.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/timeseries/_common.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/timeseries/_decomposition.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/timeseries/_differencing.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/timeseries/_ets_fit.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/timeseries/_ets_forecast.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/timeseries/_ets_models.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/timeseries/_stationarity.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/timeseries/_whittle.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/timeseries/backends/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/timeseries/backends/whittle_batch_gpu.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/pystatistics/timeseries/backends/whittle_gpu.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/anova/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/anova/conftest.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/anova/test_contrasts.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/anova/test_design.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/anova/test_factorial.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/anova/test_levene.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/anova/test_oneway.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/anova/test_posthoc.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/anova/test_r_validation.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/anova/test_repeated_measures.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/benchmark_gpu.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/conftest.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/core/test_datasource.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/core/test_exceptions.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/core/test_result.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/core/test_validation.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/descriptive/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/descriptive/conftest.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/descriptive/test_cor.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/descriptive/test_cov.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/descriptive/test_describe.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/descriptive/test_gpu.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/descriptive/test_missing.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/descriptive/test_moments.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/descriptive/test_quantile.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/descriptive/test_r_validation.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/anova_ancova_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/anova_ancova_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/anova_bonferroni_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/anova_bonferroni_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/anova_eta_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/anova_eta_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/anova_levene_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/anova_levene_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/anova_oneway_balanced_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/anova_oneway_balanced_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/anova_oneway_unbalanced_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/anova_oneway_unbalanced_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/anova_rm_mixed_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/anova_rm_mixed_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/anova_rm_within_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/anova_rm_within_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/anova_tukey_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/anova_tukey_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/anova_twoway_balanced_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/anova_twoway_balanced_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/anova_twoway_unbalanced_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/anova_twoway_unbalanced_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/basic_100x3.csv +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/basic_100x3_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/basic_100x3_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/collinear_almost.csv +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/collinear_almost_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/collinear_almost_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/desc_basic_100x5.csv +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/desc_basic_100x5_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/desc_basic_100x5_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/desc_constant_column.csv +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/desc_constant_column_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/desc_constant_column_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/desc_extreme_values.csv +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/desc_extreme_values_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/desc_extreme_values_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/desc_large_1000x10.csv +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/desc_large_1000x10_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/desc_large_1000x10_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/desc_nan_columnwise.csv +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/desc_nan_columnwise_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/desc_nan_columnwise_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/desc_nan_scattered.csv +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/desc_nan_scattered_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/desc_nan_scattered_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/desc_negative_correlation.csv +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/desc_negative_correlation_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/desc_negative_correlation_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/desc_perfect_correlation.csv +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/desc_perfect_correlation_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/desc_perfect_correlation_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/desc_single_column.csv +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/desc_single_column_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/desc_single_column_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/desc_ties.csv +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/desc_ties_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/desc_ties_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/different_scales.csv +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/different_scales_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/different_scales_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/generate_anova_fixtures.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/generate_descriptive_fixtures.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/generate_fixtures.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/generate_glm_fixtures.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/generate_hypothesis_fixtures.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/generate_mixed_fixtures.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/generate_montecarlo_fixtures.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/generate_survival_fixtures.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/glm_binomial_balanced.csv +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/glm_binomial_balanced_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/glm_binomial_balanced_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/glm_binomial_basic.csv +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/glm_binomial_basic_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/glm_binomial_basic_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/glm_binomial_large.csv +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/glm_binomial_large_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/glm_binomial_large_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/glm_binomial_separated.csv +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/glm_binomial_separated_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/glm_binomial_separated_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/glm_gaussian_basic.csv +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/glm_gaussian_basic_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/glm_gaussian_basic_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/glm_gaussian_large.csv +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/glm_gaussian_large_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/glm_gaussian_large_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/glm_poisson_basic.csv +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/glm_poisson_basic_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/glm_poisson_basic_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/glm_poisson_large_counts.csv +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/glm_poisson_large_counts_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/glm_poisson_large_counts_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/glm_poisson_zeros.csv +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/glm_poisson_zeros_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/glm_poisson_zeros_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/high_noise.csv +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/high_noise_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/high_noise_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/htest_chisq_2x2_yates_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/htest_chisq_2x2_yates_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/htest_chisq_3x3_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/htest_chisq_3x3_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/htest_chisq_gof_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/htest_chisq_gof_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/htest_chisq_gof_unequal_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/htest_chisq_gof_unequal_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/htest_fisher_2x2_less_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/htest_fisher_2x2_less_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/htest_fisher_2x2_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/htest_fisher_2x2_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/htest_fisher_3x3_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/htest_fisher_3x3_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/htest_ks_onesample_norm_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/htest_ks_onesample_norm_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/htest_ks_twosample_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/htest_ks_twosample_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/htest_prop_onesample_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/htest_prop_onesample_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/htest_prop_twosample_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/htest_prop_twosample_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/htest_t_onesample_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/htest_t_onesample_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/htest_t_paired_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/htest_t_paired_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/htest_t_pooled_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/htest_t_pooled_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/htest_t_welch_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/htest_t_welch_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/htest_var_basic_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/htest_var_basic_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/htest_wilcox_ranksum_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/htest_wilcox_ranksum_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/htest_wilcox_signed_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/htest_wilcox_signed_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/ill_conditioned.csv +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/ill_conditioned_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/ill_conditioned_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/large_coeffs.csv +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/large_coeffs_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/large_coeffs_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/mc_boot_ci_90_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/mc_boot_ci_90_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/mc_boot_ci_normal_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/mc_boot_ci_normal_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/mc_boot_ci_skewed_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/mc_boot_ci_skewed_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/mc_boot_mean_balanced_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/mc_boot_mean_balanced_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/mc_boot_mean_ordinary_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/mc_boot_mean_ordinary_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/mc_boot_median_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/mc_boot_median_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/mc_boot_variance_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/mc_boot_variance_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/mc_perm_greater_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/mc_perm_greater_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/mc_perm_not_significant_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/mc_perm_not_significant_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/mc_perm_significant_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/mc_perm_significant_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/mixed/glmm_binomial.csv +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/mixed/glmm_poisson.csv +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/mixed/lmm_crossed.csv +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/mixed/lmm_intercept.csv +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/mixed/lmm_ml.csv +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/mixed/lmm_no_effect.csv +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/mixed/lmm_slope.csv +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/mixed/mixed_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/mixed/mixed_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/near_square.csv +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/near_square_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/near_square_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/no_intercept.csv +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/no_intercept_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/no_intercept_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/run_r_anova_validation.R +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/run_r_descriptive_validation.R +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/run_r_glm_validation.R +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/run_r_hypothesis_validation.R +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/run_r_mixed_validation.R +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/run_r_montecarlo_validation.R +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/run_r_survival_validation.R +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/run_r_validation.R +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/run_validation.sh +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/small_noise.csv +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/small_noise_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/small_noise_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/surv_cox_breslow_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/surv_cox_breslow_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/surv_cox_single_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/surv_cox_single_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/surv_cox_ties_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/surv_cox_ties_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/surv_cox_two_cov_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/surv_cox_two_cov_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/surv_km_basic_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/surv_km_basic_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/surv_km_heavy_cens_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/surv_km_heavy_cens_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/surv_km_loglog_ci_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/surv_km_loglog_ci_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/surv_km_no_cens_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/surv_km_no_cens_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/surv_km_plain_ci_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/surv_km_plain_ci_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/surv_km_ties_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/surv_km_ties_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/surv_lr_peto_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/surv_lr_peto_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/surv_lr_three_group_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/surv_lr_three_group_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/surv_lr_two_group_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/surv_lr_two_group_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/tall_skinny.csv +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/tall_skinny_meta.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/tall_skinny_r_results.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/fixtures/validate_against_r.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/gam/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/gam/test_gam.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/hypothesis/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/hypothesis/conftest.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/hypothesis/test_chisq_test.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/hypothesis/test_design_split.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/hypothesis/test_fisher_test.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/hypothesis/test_gpu.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/hypothesis/test_ks_test.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/hypothesis/test_p_adjust.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/hypothesis/test_prop_test.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/hypothesis/test_r_validation.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/hypothesis/test_t_test.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/hypothesis/test_var_test.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/hypothesis/test_wilcox_test.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/mixed/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/mixed/conftest.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/mixed/test_glmm.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/mixed/test_lmm_crossed.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/mixed/test_lmm_intercept.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/mixed/test_lmm_nested.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/mixed/test_lmm_slope.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/mixed/test_pls.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/mixed/test_r_validation.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/mixed/test_random_effects.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/mixed/test_satterthwaite.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/montecarlo/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/montecarlo/conftest.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/montecarlo/test_batched_solver.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/montecarlo/test_boot_ci.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/montecarlo/test_bootstrap.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/montecarlo/test_gpu.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/montecarlo/test_influence.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/montecarlo/test_permutation.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/montecarlo/test_r_validation.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/multinomial/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/multinomial/test_multinom.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/multivariate/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/multivariate/test_multivariate.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/mvnmle/references/apple_em_reference.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/mvnmle/references/apple_reference.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/mvnmle/references/generate_em_fixtures.R +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/mvnmle/references/little_mcar_apple.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/mvnmle/references/little_mcar_complete.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/mvnmle/references/little_mcar_extreme.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/mvnmle/references/little_mcar_missvals.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/mvnmle/references/little_mcar_simple_mcar.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/mvnmle/references/little_mcar_summary.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/mvnmle/references/missvals_em_reference.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/mvnmle/references/missvals_reference.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/mvnmle/references/small_test_reference.json +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/mvnmle/test_em.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/mvnmle/test_gpu.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/mvnmle/test_mlest.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/mvnmle/test_mom_mcar.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/mvnmle/test_monotone.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/mvnmle/test_no_silent_fallback.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/mvnmle/test_squarem.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/ordinal/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/ordinal/test_ordinal.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/regression/benchmark.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/regression/benchmark.r +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/regression/conftest.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/regression/test_fit.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/regression/test_gamma_nb.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/regression/test_glm.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/regression/test_glm_gpu.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/regression/test_glm_r_validation.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/regression/test_module_split.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/regression/test_r_validation.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/regression/test_stress_gpu.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/survival/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/survival/conftest.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/survival/test_coxph.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/survival/test_discrete_time.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/survival/test_gpu.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/survival/test_kaplan_meier.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/survival/test_logrank.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/survival/test_r_validation.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/test_code_quality.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/timeseries/__init__.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/timeseries/test_acf_stationarity.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/timeseries/test_arima.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/timeseries/test_decomposition.py +0 -0
- {pystatistics-2.1.0 → pystatistics-2.2.0}/tests/timeseries/test_ets.py +0 -0
|
@@ -1,5 +1,69 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.2.0
|
|
4
|
+
|
|
5
|
+
- Fixed a `torch._C._LinAlgError` crash in `chi_square_mcar_batched_torch`
|
|
6
|
+
(`pystatistics/mvnmle/backends/_em_batched.py`) on GPU FP32. The batched
|
|
7
|
+
MoM fast path selected `cholesky_solve` when the SVD-based condition
|
|
8
|
+
number was below threshold, but Cholesky requires positive-definiteness,
|
|
9
|
+
which is strictly stronger than good conditioning. On GPU FP32, real
|
|
10
|
+
tabular data (e.g. `lacuna_tabular_110` applied to UCI/OpenML datasets)
|
|
11
|
+
can produce `sigma_oo` with good cond number but tiny negative eigenvalues
|
|
12
|
+
from roundoff, making Cholesky fail. Fix: wrap the fast path in
|
|
13
|
+
`try/except torch._C._LinAlgError`; on failure, fall back to
|
|
14
|
+
`torch.linalg.pinv` for the batch (honouring the `regularize` flag —
|
|
15
|
+
`regularize=False` still raises). Surfaced by dogfooding via Project
|
|
16
|
+
Lacuna on 3,080 (dataset × generator) pairs across the 110-generator
|
|
17
|
+
tabular registry; previously `mom_mcar_test` crashed on the first batch
|
|
18
|
+
containing breast_cancer / wine / credit_card_default.
|
|
19
|
+
|
|
20
|
+
- Fixed an exception-type leak in `little_mcar_test`
|
|
21
|
+
(`pystatistics/mvnmle/mcar_test.py:~250`). The ML-estimation try/except
|
|
22
|
+
wrapped *every* exception — including `PyStatisticsError` subclasses
|
|
23
|
+
like `NumericalError` — as a bare `RuntimeError`, breaking the
|
|
24
|
+
documented `except PyStatisticsError:` pattern downstream and losing
|
|
25
|
+
the original exception chain. Fix: explicitly re-raise
|
|
26
|
+
`PyStatisticsError`, and use `raise ... from e` for anything else so
|
|
27
|
+
the chain is preserved. Surfaced by Project Lacuna's cache builder,
|
|
28
|
+
which catches `PyStatisticsError` to fall back to a sentinel entry
|
|
29
|
+
when Little's test is numerically unfit for a particular
|
|
30
|
+
(dataset, generator) pair; MLE failures were leaking past the catch
|
|
31
|
+
and killing the whole build.
|
|
32
|
+
|
|
33
|
+
- Added ridge-fallback to the batched Cholesky sites inside the EM
|
|
34
|
+
E-step / log-likelihood
|
|
35
|
+
(`pystatistics/mvnmle/backends/_em_batched.py`):
|
|
36
|
+
`e_step_full_batched_np` (line ~361), `_e_step_full_torch` (~680), and
|
|
37
|
+
`_loglik_full_batched_torch` (~797). These compute per-pattern
|
|
38
|
+
Cholesky of sigma_oo sub-blocks; real tabular data can produce
|
|
39
|
+
individual sub-blocks that are numerically indefinite even when the
|
|
40
|
+
global sigma is PD (integer-encoded categoricals with heavy
|
|
41
|
+
collinearity in the intersection of a given missingness pattern's
|
|
42
|
+
observed variables). Fix: wrap each site in `try/except LinAlgError`
|
|
43
|
+
with a `ridge·I` retry (ridge = 1e-10 at pattern scale; statistically
|
|
44
|
+
invisible). Also removed a dead Cholesky call in `e_step_batched_np`
|
|
45
|
+
whose result was never used — it was only a crash liability.
|
|
46
|
+
`np.linalg.solve` at that same site now has a pinv fallback for
|
|
47
|
+
singular sub-blocks.
|
|
48
|
+
|
|
49
|
+
- Added `regularize: bool = True` to `mlest`, `_solve_em`, and
|
|
50
|
+
`EMBackend.solve`, mirroring the existing convention on
|
|
51
|
+
`mom_mcar_test` / `little_mcar_test`. When True (new default),
|
|
52
|
+
`EMBackend._ensure_pd` applies a small diagonal ridge
|
|
53
|
+
(`max(0, 1e-10 - min_eig) + 1e-12`) to the M-step sigma whenever its
|
|
54
|
+
smallest eigenvalue falls below the PD threshold, rather than raising
|
|
55
|
+
`NumericalError` outright. The ridge is vanishingly small relative to
|
|
56
|
+
any real data scale — the typical case the old path rejected had
|
|
57
|
+
min_eig ≈ 1e-13 from pure FP64 roundoff — and a UserWarning makes the
|
|
58
|
+
event visible in logs. Call sites that need strict bit-for-bit
|
|
59
|
+
behaviour pass `regularize=False`. Motivated by Project Lacuna
|
|
60
|
+
dogfooding: applying real missingness generators to real UCI datasets
|
|
61
|
+
(credit_card_default × MNAR-NonLinSocial produced min_eig ≈ -0.66) was
|
|
62
|
+
hard-raising and killing the full cache build; the ridge fallback
|
|
63
|
+
keeps the test numerically well-defined with negligible statistical
|
|
64
|
+
impact, and the build proceeds.
|
|
65
|
+
|
|
66
|
+
|
|
3
67
|
## 2.1.0
|
|
4
68
|
|
|
5
69
|
- **`mom_mcar_test`: new method-of-moments MCAR test**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pystatistics
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: GPU-accelerated statistical computing for Python
|
|
5
5
|
Project-URL: Homepage, https://sgcx.org/technology/pystatistics/
|
|
6
6
|
Project-URL: Documentation, https://sgcx.org/docs/pystatistics/
|
|
@@ -51,6 +51,74 @@ GPU-accelerated statistical computing for Python.
|
|
|
51
51
|
|
|
52
52
|
## What's New
|
|
53
53
|
|
|
54
|
+
### 2.2.0 — Real-data robustness from Project Lacuna dogfooding
|
|
55
|
+
|
|
56
|
+
Continuation of the 2.1.0 dogfooding track. Running `little_mcar_test`
|
|
57
|
+
and `mom_mcar_test` on 3,080 (dataset × generator) pairs drawn from 28
|
|
58
|
+
real UCI / OpenML / sklearn tabular datasets under
|
|
59
|
+
`lacuna_tabular_110` missingness generators surfaced four classes of
|
|
60
|
+
numerical failure that synthetic unit tests did not exhibit. All fixed
|
|
61
|
+
in this release; no API breaks.
|
|
62
|
+
|
|
63
|
+
**Batched MoM GPU Cholesky crash.** `chi_square_mcar_batched_torch`'s
|
|
64
|
+
fast path selected `cholesky_solve` whenever the SVD-based condition
|
|
65
|
+
number check passed, on the assumption that good conditioning implies
|
|
66
|
+
positive-definiteness. On GPU FP32, roundoff can produce covariances
|
|
67
|
+
that pass the cond-number check but have tiny negative eigenvalues —
|
|
68
|
+
Cholesky fails, the call raises `torch._C._LinAlgError`. The fast path
|
|
69
|
+
is now wrapped with `try/except` and falls back to pseudo-inverse when
|
|
70
|
+
the ``regularize`` flag allows. Surfaced on `credit_card_default` ×
|
|
71
|
+
`MNAR-NonLinSocial` during the Lacuna cache build.
|
|
72
|
+
|
|
73
|
+
**Exception-type preservation in `little_mcar_test`.** The
|
|
74
|
+
ML-estimation `try/except` at the top of `little_mcar_test` wrapped
|
|
75
|
+
*every* exception as a bare `RuntimeError` — including
|
|
76
|
+
`PyStatisticsError` subclasses. This broke the documented
|
|
77
|
+
`except PyStatisticsError:` catch pattern downstream: users falling
|
|
78
|
+
back to a sentinel on MLE failure saw their handler bypassed and the
|
|
79
|
+
full build crash. Fix: explicitly re-raise `PyStatisticsError`, and
|
|
80
|
+
use `raise ... from e` for anything else so the chain is preserved.
|
|
81
|
+
|
|
82
|
+
**`regularize=True` default on the EM path (opt-out).** `mlest`,
|
|
83
|
+
`_solve_em`, and `EMBackend.solve` gain `regularize: bool = True`,
|
|
84
|
+
mirroring the existing convention on `mom_mcar_test` and
|
|
85
|
+
`little_mcar_test`. When True, `EMBackend._ensure_pd` applies a small
|
|
86
|
+
diagonal ridge — `max(0, 1e-10 − min_eig) + 1e-12` — to the M-step
|
|
87
|
+
sigma whenever its smallest eigenvalue falls below the PD threshold,
|
|
88
|
+
rather than raising `NumericalError`. The ridge is well below any
|
|
89
|
+
statistical precision on real data — the typical case the old path
|
|
90
|
+
rejected had `min_eig ≈ 1e-13` from pure FP64 roundoff on a matrix
|
|
91
|
+
that's theoretically PSD. Dogfooding surfaced cases where `min_eig`
|
|
92
|
+
hit `−0.66` on realistic MNAR mechanisms; the ridge fallback keeps EM
|
|
93
|
+
progressing. Callers needing strict bit-for-bit behaviour pass
|
|
94
|
+
`regularize=False` to restore the old raise.
|
|
95
|
+
|
|
96
|
+
**Three additional Cholesky ridge-fallbacks** in `_em_batched.py`:
|
|
97
|
+
`e_step_full_batched_np`, `_e_step_full_torch`, and
|
|
98
|
+
`_loglik_full_batched_torch` all compute per-pattern Cholesky of
|
|
99
|
+
`sigma_oo` sub-blocks. Real tabular data can produce individual
|
|
100
|
+
sub-blocks that are numerically indefinite even when the global sigma
|
|
101
|
+
is PD (integer-encoded categoricals with heavy collinearity in the
|
|
102
|
+
intersection of a given missingness pattern's observed variables).
|
|
103
|
+
Each site now wraps Cholesky in `try/except LinAlgError` with a
|
|
104
|
+
`ridge·I` retry (ridge = 1e-10 at pattern scale; statistically
|
|
105
|
+
invisible). Also removed a dead Cholesky call in `e_step_batched_np`
|
|
106
|
+
whose factor was never used downstream — pure crash liability — and
|
|
107
|
+
added a `pinv` fallback to the `np.linalg.solve` at the same site for
|
|
108
|
+
singular sub-blocks.
|
|
109
|
+
|
|
110
|
+
**Impact.** The Project Lacuna cache build on 3,080 (dataset ×
|
|
111
|
+
generator) pairs went from crashing on the first batch containing
|
|
112
|
+
`breast_cancer` or `credit_card_default` (pre-2.2.0) to completing in
|
|
113
|
+
a single pass at 0.9% MoM sentinel rate and 16.4% MLE sentinel rate
|
|
114
|
+
(the MLE sentinels are legitimate EM non-convergence on 1000-pattern
|
|
115
|
+
datasets — not crashes). Synthetic unit tests: 125/125 mvnmle pass.
|
|
116
|
+
|
|
117
|
+
**No API breaks.** New defaults (`regularize=True`) are strictly more
|
|
118
|
+
permissive than the old raises — any caller that was crashing before
|
|
119
|
+
will now proceed with a small `UserWarning`. Callers needing strict
|
|
120
|
+
behaviour pass `regularize=False`.
|
|
121
|
+
|
|
54
122
|
### 2.1.0 — Real-data EM speedup + monotone closed-form MLE
|
|
55
123
|
|
|
56
124
|
Dogfooding via Project Lacuna surfaced that ``little_mcar_test`` on
|
|
@@ -4,6 +4,74 @@ GPU-accelerated statistical computing for Python.
|
|
|
4
4
|
|
|
5
5
|
## What's New
|
|
6
6
|
|
|
7
|
+
### 2.2.0 — Real-data robustness from Project Lacuna dogfooding
|
|
8
|
+
|
|
9
|
+
Continuation of the 2.1.0 dogfooding track. Running `little_mcar_test`
|
|
10
|
+
and `mom_mcar_test` on 3,080 (dataset × generator) pairs drawn from 28
|
|
11
|
+
real UCI / OpenML / sklearn tabular datasets under
|
|
12
|
+
`lacuna_tabular_110` missingness generators surfaced four classes of
|
|
13
|
+
numerical failure that synthetic unit tests did not exhibit. All fixed
|
|
14
|
+
in this release; no API breaks.
|
|
15
|
+
|
|
16
|
+
**Batched MoM GPU Cholesky crash.** `chi_square_mcar_batched_torch`'s
|
|
17
|
+
fast path selected `cholesky_solve` whenever the SVD-based condition
|
|
18
|
+
number check passed, on the assumption that good conditioning implies
|
|
19
|
+
positive-definiteness. On GPU FP32, roundoff can produce covariances
|
|
20
|
+
that pass the cond-number check but have tiny negative eigenvalues —
|
|
21
|
+
Cholesky fails, the call raises `torch._C._LinAlgError`. The fast path
|
|
22
|
+
is now wrapped with `try/except` and falls back to pseudo-inverse when
|
|
23
|
+
the ``regularize`` flag allows. Surfaced on `credit_card_default` ×
|
|
24
|
+
`MNAR-NonLinSocial` during the Lacuna cache build.
|
|
25
|
+
|
|
26
|
+
**Exception-type preservation in `little_mcar_test`.** The
|
|
27
|
+
ML-estimation `try/except` at the top of `little_mcar_test` wrapped
|
|
28
|
+
*every* exception as a bare `RuntimeError` — including
|
|
29
|
+
`PyStatisticsError` subclasses. This broke the documented
|
|
30
|
+
`except PyStatisticsError:` catch pattern downstream: users falling
|
|
31
|
+
back to a sentinel on MLE failure saw their handler bypassed and the
|
|
32
|
+
full build crash. Fix: explicitly re-raise `PyStatisticsError`, and
|
|
33
|
+
use `raise ... from e` for anything else so the chain is preserved.
|
|
34
|
+
|
|
35
|
+
**`regularize=True` default on the EM path (opt-out).** `mlest`,
|
|
36
|
+
`_solve_em`, and `EMBackend.solve` gain `regularize: bool = True`,
|
|
37
|
+
mirroring the existing convention on `mom_mcar_test` and
|
|
38
|
+
`little_mcar_test`. When True, `EMBackend._ensure_pd` applies a small
|
|
39
|
+
diagonal ridge — `max(0, 1e-10 − min_eig) + 1e-12` — to the M-step
|
|
40
|
+
sigma whenever its smallest eigenvalue falls below the PD threshold,
|
|
41
|
+
rather than raising `NumericalError`. The ridge is well below any
|
|
42
|
+
statistical precision on real data — the typical case the old path
|
|
43
|
+
rejected had `min_eig ≈ 1e-13` from pure FP64 roundoff on a matrix
|
|
44
|
+
that's theoretically PSD. Dogfooding surfaced cases where `min_eig`
|
|
45
|
+
hit `−0.66` on realistic MNAR mechanisms; the ridge fallback keeps EM
|
|
46
|
+
progressing. Callers needing strict bit-for-bit behaviour pass
|
|
47
|
+
`regularize=False` to restore the old raise.
|
|
48
|
+
|
|
49
|
+
**Three additional Cholesky ridge-fallbacks** in `_em_batched.py`:
|
|
50
|
+
`e_step_full_batched_np`, `_e_step_full_torch`, and
|
|
51
|
+
`_loglik_full_batched_torch` all compute per-pattern Cholesky of
|
|
52
|
+
`sigma_oo` sub-blocks. Real tabular data can produce individual
|
|
53
|
+
sub-blocks that are numerically indefinite even when the global sigma
|
|
54
|
+
is PD (integer-encoded categoricals with heavy collinearity in the
|
|
55
|
+
intersection of a given missingness pattern's observed variables).
|
|
56
|
+
Each site now wraps Cholesky in `try/except LinAlgError` with a
|
|
57
|
+
`ridge·I` retry (ridge = 1e-10 at pattern scale; statistically
|
|
58
|
+
invisible). Also removed a dead Cholesky call in `e_step_batched_np`
|
|
59
|
+
whose factor was never used downstream — pure crash liability — and
|
|
60
|
+
added a `pinv` fallback to the `np.linalg.solve` at the same site for
|
|
61
|
+
singular sub-blocks.
|
|
62
|
+
|
|
63
|
+
**Impact.** The Project Lacuna cache build on 3,080 (dataset ×
|
|
64
|
+
generator) pairs went from crashing on the first batch containing
|
|
65
|
+
`breast_cancer` or `credit_card_default` (pre-2.2.0) to completing in
|
|
66
|
+
a single pass at 0.9% MoM sentinel rate and 16.4% MLE sentinel rate
|
|
67
|
+
(the MLE sentinels are legitimate EM non-convergence on 1000-pattern
|
|
68
|
+
datasets — not crashes). Synthetic unit tests: 125/125 mvnmle pass.
|
|
69
|
+
|
|
70
|
+
**No API breaks.** New defaults (`regularize=True`) are strictly more
|
|
71
|
+
permissive than the old raises — any caller that was crashing before
|
|
72
|
+
will now proceed with a small `UserWarning`. Callers needing strict
|
|
73
|
+
behaviour pass `regularize=False`.
|
|
74
|
+
|
|
7
75
|
### 2.1.0 — Real-data EM speedup + monotone closed-form MLE
|
|
8
76
|
|
|
9
77
|
Dogfooding via Project Lacuna surfaced that ``little_mcar_test`` on
|
|
@@ -187,14 +187,29 @@ def compute_conditional_parameters_np(
|
|
|
187
187
|
valid_mm = index.mis_mask[:, :, None] & index.mis_mask[:, None, :]
|
|
188
188
|
sigma_mm = np.where(valid_mm, sigma_mm, 0.0)
|
|
189
189
|
|
|
190
|
-
#
|
|
191
|
-
|
|
190
|
+
# (Note: an earlier revision computed a batched Cholesky here whose
|
|
191
|
+
# factor was never used downstream. Removed — np.linalg.solve below
|
|
192
|
+
# handles the per-pattern solve directly. Kept the pinv fallback
|
|
193
|
+
# because np.linalg.solve still fails on strictly-singular sigma_oo,
|
|
194
|
+
# which real tabular data with integer-encoded categoricals can
|
|
195
|
+
# produce at the per-pattern sub-block level.)
|
|
192
196
|
|
|
193
197
|
# Batched solve: beta^T = Sigma_oo^{-1} @ sigma_om = solve(sigma_oo, sigma_om)
|
|
194
198
|
# sigma_om = sigma_mo^T → (P, v_obs_max, v_mis_max)
|
|
195
199
|
sigma_om = np.swapaxes(sigma_mo, -1, -2)
|
|
196
|
-
|
|
197
|
-
|
|
200
|
+
try:
|
|
201
|
+
beta_T = np.linalg.solve(sigma_oo, sigma_om)
|
|
202
|
+
except np.linalg.LinAlgError:
|
|
203
|
+
# Per-pattern sigma_oo sub-block is singular. Fall back to pinv
|
|
204
|
+
# for this batch. Issue a warning so the event is visible.
|
|
205
|
+
import warnings
|
|
206
|
+
warnings.warn(
|
|
207
|
+
"e_step_batched_np: at least one per-pattern sigma_oo "
|
|
208
|
+
"sub-block is numerically singular; falling back to "
|
|
209
|
+
"Moore-Penrose pseudo-inverse for the batch.",
|
|
210
|
+
UserWarning, stacklevel=3,
|
|
211
|
+
)
|
|
212
|
+
beta_T = np.matmul(np.linalg.pinv(sigma_oo), sigma_om)
|
|
198
213
|
beta = np.swapaxes(beta_T, -1, -2) # (P, v_mis_max, v_obs_max)
|
|
199
214
|
|
|
200
215
|
# cond_cov = sigma_mm - beta @ sigma_om = sigma_mm - sigma_mo @ beta^T
|
|
@@ -343,7 +358,26 @@ def compute_loglik_batched_np(
|
|
|
343
358
|
eye_oo = np.broadcast_to(np.eye(v_obs_max, dtype=sigma.dtype), sigma_oo.shape)
|
|
344
359
|
sigma_oo = np.where(mask_oo, sigma_oo, eye_oo)
|
|
345
360
|
|
|
346
|
-
|
|
361
|
+
# Batched Cholesky for log-det and the quadratic-form solve below.
|
|
362
|
+
# Per-pattern sigma_oo sub-blocks can be numerically indefinite from
|
|
363
|
+
# FP64 roundoff even when the global sigma is PD (confirmed on
|
|
364
|
+
# credit_card_default via Project Lacuna). Apply a tiny diagonal
|
|
365
|
+
# ridge before Cholesky rather than raising — the ridge (1e-12 on a
|
|
366
|
+
# matrix normalised to trace ~v) is below any statistical precision.
|
|
367
|
+
try:
|
|
368
|
+
L_oo = np.linalg.cholesky(sigma_oo)
|
|
369
|
+
except np.linalg.LinAlgError:
|
|
370
|
+
import warnings
|
|
371
|
+
ridge = 1e-10
|
|
372
|
+
warnings.warn(
|
|
373
|
+
f"e_step_full_batched_np: per-pattern sigma_oo indefinite; "
|
|
374
|
+
f"retrying Cholesky with diagonal ridge {ridge:.0e}.",
|
|
375
|
+
UserWarning, stacklevel=3,
|
|
376
|
+
)
|
|
377
|
+
eye_full = np.broadcast_to(
|
|
378
|
+
np.eye(v_obs_max, dtype=sigma.dtype), sigma_oo.shape,
|
|
379
|
+
)
|
|
380
|
+
L_oo = np.linalg.cholesky(sigma_oo + ridge * eye_full)
|
|
347
381
|
log_diag = np.log(np.diagonal(L_oo, axis1=-2, axis2=-1))
|
|
348
382
|
logdet_per_pattern = 2.0 * np.sum(log_diag * index.obs_mask, axis=-1)
|
|
349
383
|
|
|
@@ -561,11 +595,37 @@ def chi_square_mcar_batched_torch(
|
|
|
561
595
|
# Python-level branch on device.
|
|
562
596
|
sigma_inv = torch.linalg.pinv(sigma_oo) if ill.any() else None
|
|
563
597
|
if sigma_inv is None:
|
|
564
|
-
# Fast path: all well-conditioned,
|
|
565
|
-
|
|
566
|
-
|
|
598
|
+
# Fast path: condition-number check says all are well-conditioned,
|
|
599
|
+
# so attempt cholesky_solve. Cholesky requires positive-definiteness,
|
|
600
|
+
# which is a STRICTER property than good conditioning — a matrix
|
|
601
|
+
# can pass the cond-number threshold but still have tiny negative
|
|
602
|
+
# eigenvalues due to FP32 roundoff (especially on GPU). When that
|
|
603
|
+
# happens, fall back to pinv for those patterns. regularize=False
|
|
604
|
+
# is respected: we only attempt the fallback when the user has
|
|
605
|
+
# already opted into regularization.
|
|
606
|
+
try:
|
|
607
|
+
L = torch.linalg.cholesky(sigma_oo)
|
|
608
|
+
z = torch.cholesky_solve(diff.unsqueeze(-1), L).squeeze(-1)
|
|
609
|
+
n_cholesky_fallback = 0
|
|
610
|
+
except torch._C._LinAlgError:
|
|
611
|
+
if not regularize:
|
|
612
|
+
raise
|
|
613
|
+
import warnings
|
|
614
|
+
warnings.warn(
|
|
615
|
+
"Cholesky factorisation failed on the batched fast path "
|
|
616
|
+
"despite condition-number check passing — likely FP32 "
|
|
617
|
+
"roundoff producing a numerically-indefinite covariance. "
|
|
618
|
+
"Falling back to Moore-Penrose pseudo-inverse for all "
|
|
619
|
+
"patterns in this batch. Pass regularize=False to raise "
|
|
620
|
+
"instead.",
|
|
621
|
+
UserWarning, stacklevel=4,
|
|
622
|
+
)
|
|
623
|
+
sigma_inv = torch.linalg.pinv(sigma_oo)
|
|
624
|
+
z = torch.matmul(sigma_inv, diff.unsqueeze(-1)).squeeze(-1)
|
|
625
|
+
n_cholesky_fallback = sigma_oo.shape[0]
|
|
567
626
|
else:
|
|
568
627
|
z = torch.matmul(sigma_inv, diff.unsqueeze(-1)).squeeze(-1)
|
|
628
|
+
n_cholesky_fallback = 0
|
|
569
629
|
|
|
570
630
|
contribs = (diff * z).sum(dim=-1) # (P,)
|
|
571
631
|
contribs_nk = contribs * n_per_pattern_t
|
|
@@ -574,7 +634,7 @@ def chi_square_mcar_batched_torch(
|
|
|
574
634
|
n_patterns_used = int(used_mask.sum().item())
|
|
575
635
|
|
|
576
636
|
test_statistic = float(contribs_nk[used_mask].sum().item())
|
|
577
|
-
n_regularized = int(ill.sum().item())
|
|
637
|
+
n_regularized = int(ill.sum().item()) + n_cholesky_fallback
|
|
578
638
|
return test_statistic, n_patterns_used, n_regularized
|
|
579
639
|
|
|
580
640
|
|
|
@@ -617,7 +677,21 @@ def _e_step_full_torch(
|
|
|
617
677
|
mask_oo = obs_mask_t.unsqueeze(-1) & obs_mask_t.unsqueeze(-2)
|
|
618
678
|
sigma_oo = torch.where(mask_oo, sigma_oo, eye_oo)
|
|
619
679
|
|
|
620
|
-
|
|
680
|
+
# Cholesky with ridge fallback. See numpy path comment above.
|
|
681
|
+
try:
|
|
682
|
+
L_oo = torch.linalg.cholesky(sigma_oo)
|
|
683
|
+
except torch._C._LinAlgError:
|
|
684
|
+
import warnings
|
|
685
|
+
ridge = 1e-10
|
|
686
|
+
warnings.warn(
|
|
687
|
+
f"_e_step_full_torch: per-pattern sigma_oo indefinite on "
|
|
688
|
+
f"GPU path; retrying Cholesky with diagonal ridge {ridge:.0e}.",
|
|
689
|
+
UserWarning, stacklevel=3,
|
|
690
|
+
)
|
|
691
|
+
eye_full = eye_oo if eye_oo.shape == sigma_oo.shape else torch.eye(
|
|
692
|
+
v_obs_max, device=device, dtype=dtype
|
|
693
|
+
).expand_as(sigma_oo)
|
|
694
|
+
L_oo = torch.linalg.cholesky(sigma_oo + ridge * eye_full)
|
|
621
695
|
|
|
622
696
|
# sigma_mo: missing rows × observed cols.
|
|
623
697
|
mrow_idx = mis_idx_t.unsqueeze(-1)
|
|
@@ -720,7 +794,23 @@ def _loglik_full_torch(
|
|
|
720
794
|
mask_oo = obs_mask_t.unsqueeze(-1) & obs_mask_t.unsqueeze(-2)
|
|
721
795
|
sigma_oo = torch.where(mask_oo, sigma_oo, eye_oo)
|
|
722
796
|
|
|
723
|
-
|
|
797
|
+
# Cholesky with ridge fallback for indefinite per-pattern sub-blocks.
|
|
798
|
+
# See numpy path comment.
|
|
799
|
+
try:
|
|
800
|
+
L_oo = torch.linalg.cholesky(sigma_oo)
|
|
801
|
+
except torch._C._LinAlgError:
|
|
802
|
+
import warnings
|
|
803
|
+
ridge = 1e-10
|
|
804
|
+
warnings.warn(
|
|
805
|
+
f"_loglik_full_batched_torch: per-pattern sigma_oo "
|
|
806
|
+
f"indefinite; retrying Cholesky with ridge {ridge:.0e}.",
|
|
807
|
+
UserWarning, stacklevel=3,
|
|
808
|
+
)
|
|
809
|
+
v_obs_max_local = sigma_oo.shape[-1]
|
|
810
|
+
eye_full = torch.eye(
|
|
811
|
+
v_obs_max_local, device=sigma_oo.device, dtype=sigma_oo.dtype
|
|
812
|
+
).expand_as(sigma_oo)
|
|
813
|
+
L_oo = torch.linalg.cholesky(sigma_oo + ridge * eye_full)
|
|
724
814
|
log_diag = torch.log(torch.diagonal(L_oo, dim1=-2, dim2=-1))
|
|
725
815
|
logdet_per_pattern = 2.0 * torch.sum(log_diag * obs_mask_t.to(dtype), dim=-1)
|
|
726
816
|
|
|
@@ -785,7 +875,19 @@ def compute_conditional_parameters_torch(
|
|
|
785
875
|
sigma_mm = torch_mod.where(valid_mm, sigma_mm,
|
|
786
876
|
torch_mod.zeros((), device=device, dtype=dtype))
|
|
787
877
|
|
|
788
|
-
|
|
878
|
+
# Cholesky with ridge fallback for indefinite per-pattern sub-blocks.
|
|
879
|
+
# See e_step_full_batched_np / _e_step_full_torch for rationale.
|
|
880
|
+
try:
|
|
881
|
+
L_oo = torch_mod.linalg.cholesky(sigma_oo)
|
|
882
|
+
except torch_mod._C._LinAlgError:
|
|
883
|
+
import warnings
|
|
884
|
+
ridge = 1e-10
|
|
885
|
+
warnings.warn(
|
|
886
|
+
f"e_step_batched_torch: per-pattern sigma_oo indefinite; "
|
|
887
|
+
f"retrying Cholesky with ridge {ridge:.0e}.",
|
|
888
|
+
UserWarning, stacklevel=3,
|
|
889
|
+
)
|
|
890
|
+
L_oo = torch_mod.linalg.cholesky(sigma_oo + ridge * eye_oo)
|
|
789
891
|
sigma_om = sigma_mo.transpose(-1, -2)
|
|
790
892
|
beta_T = torch_mod.cholesky_solve(sigma_om, L_oo) # (P, v_obs_max, v_mis_max)
|
|
791
893
|
beta = beta_T.transpose(-1, -2)
|
|
@@ -64,6 +64,7 @@ class EMBackend:
|
|
|
64
64
|
tol: float = 1e-4,
|
|
65
65
|
max_iter: int = 1000,
|
|
66
66
|
accelerate: bool = True,
|
|
67
|
+
regularize: bool = True,
|
|
67
68
|
) -> Result[MVNParams]:
|
|
68
69
|
"""
|
|
69
70
|
Solve MVN MLE using EM algorithm.
|
|
@@ -77,6 +78,18 @@ class EMBackend:
|
|
|
77
78
|
change in parameters is less than tol (R's norm convention).
|
|
78
79
|
max_iter : int
|
|
79
80
|
Maximum EM iterations.
|
|
81
|
+
regularize : bool, default True
|
|
82
|
+
When True (the default), apply a small diagonal ridge to the
|
|
83
|
+
M-step sigma whenever its smallest eigenvalue falls below the
|
|
84
|
+
positive-definiteness threshold — bringing it back to PD with
|
|
85
|
+
negligible statistical impact (ridge ~ -2*min_eig + 1e-12).
|
|
86
|
+
Emits a warning so the event is visible in logs. When False,
|
|
87
|
+
raise `NumericalError` on near-indefinite sigma (the strict
|
|
88
|
+
behaviour from earlier releases). True matches the convention
|
|
89
|
+
of `mom_mcar_test(regularize=...)` and is the right default
|
|
90
|
+
for real tabular data where FP roundoff produces
|
|
91
|
+
numerically-indefinite covariances on perfectly well-posed
|
|
92
|
+
statistical problems.
|
|
80
93
|
|
|
81
94
|
Returns
|
|
82
95
|
-------
|
|
@@ -157,7 +170,7 @@ class EMBackend:
|
|
|
157
170
|
return self._compute_loglik(mu_in, sigma_in, patterns)
|
|
158
171
|
|
|
159
172
|
def ensure_pd(sigma_in):
|
|
160
|
-
return self._ensure_pd(sigma_in, p)
|
|
173
|
+
return self._ensure_pd(sigma_in, p, regularize=regularize)
|
|
161
174
|
|
|
162
175
|
em_steps_consumed = 0
|
|
163
176
|
while em_steps_consumed < max_iter:
|
|
@@ -444,21 +457,56 @@ class EMBackend:
|
|
|
444
457
|
|
|
445
458
|
mu_out = mu_t.detach().cpu().numpy().astype(np.float64)
|
|
446
459
|
sigma_out = sigma_t.detach().cpu().numpy().astype(np.float64)
|
|
447
|
-
|
|
460
|
+
# GPU path currently shares the regularize default (no parameter
|
|
461
|
+
# plumbed yet through _run_em_loop_gpu); fine for now as the
|
|
462
|
+
# observed failure mode is the CPU/numpy path.
|
|
463
|
+
sigma_out = self._ensure_pd(sigma_out, p, regularize=True)
|
|
448
464
|
|
|
449
465
|
return mu_out, sigma_out, n_iter, converged, param_change, loglik
|
|
450
466
|
|
|
451
|
-
def _ensure_pd(
|
|
452
|
-
|
|
467
|
+
def _ensure_pd(
|
|
468
|
+
self,
|
|
469
|
+
sigma: np.ndarray,
|
|
470
|
+
p: int,
|
|
471
|
+
*,
|
|
472
|
+
regularize: bool = True,
|
|
473
|
+
) -> np.ndarray:
|
|
474
|
+
"""Check positive definiteness; optionally apply a small ridge to restore it.
|
|
475
|
+
|
|
476
|
+
When `regularize=True` (default) and the smallest eigenvalue falls
|
|
477
|
+
below the PD threshold (1e-10), add
|
|
478
|
+
`(max(0, 1e-10 - min_eig) + 1e-12) * I` to sigma and return it.
|
|
479
|
+
This restores strict PD with a ridge well below any statistical
|
|
480
|
+
precision on real data — the typical "failure" is a min-eigenvalue
|
|
481
|
+
in the 1e-13 range, pure FP64 roundoff on a matrix that's
|
|
482
|
+
theoretically PSD. Emits a UserWarning so the event is visible.
|
|
483
|
+
|
|
484
|
+
When `regularize=False`, preserve the strict behaviour: raise
|
|
485
|
+
`NumericalError` with a message pointing at likely data causes
|
|
486
|
+
(constant columns, collinearity, n too small for p).
|
|
487
|
+
"""
|
|
453
488
|
try:
|
|
454
489
|
eigvals = np.linalg.eigvalsh(sigma)
|
|
455
|
-
min_eig = np.min(eigvals)
|
|
490
|
+
min_eig = float(np.min(eigvals))
|
|
456
491
|
if min_eig < 1e-10:
|
|
492
|
+
if regularize:
|
|
493
|
+
ridge = max(0.0, 1e-10 - min_eig) + 1e-12
|
|
494
|
+
import warnings
|
|
495
|
+
warnings.warn(
|
|
496
|
+
f"EM M-step covariance near-indefinite "
|
|
497
|
+
f"(min eigenvalue={min_eig:.2e}); applying ridge "
|
|
498
|
+
f"{ridge:.2e}·I. Statistical impact is negligible "
|
|
499
|
+
f"at this scale. Pass regularize=False to raise "
|
|
500
|
+
f"instead.",
|
|
501
|
+
UserWarning, stacklevel=3,
|
|
502
|
+
)
|
|
503
|
+
return sigma + ridge * np.eye(p, dtype=sigma.dtype)
|
|
457
504
|
raise NumericalError(
|
|
458
505
|
f"EM algorithm encountered a non-positive-definite covariance matrix "
|
|
459
506
|
f"(min eigenvalue={min_eig:.2e}). "
|
|
460
507
|
f"Check data quality: look for constant columns, collinear variables, "
|
|
461
|
-
f"or insufficient observations for the number of variables."
|
|
508
|
+
f"or insufficient observations for the number of variables. "
|
|
509
|
+
f"Pass regularize=True to fall back to a small diagonal ridge."
|
|
462
510
|
)
|
|
463
511
|
except np.linalg.LinAlgError as e:
|
|
464
512
|
raise NumericalError(
|
|
@@ -14,6 +14,7 @@ from typing import List, Tuple, Optional, Dict, Any
|
|
|
14
14
|
from dataclasses import dataclass
|
|
15
15
|
import warnings
|
|
16
16
|
|
|
17
|
+
from pystatistics.core.exceptions import PyStatisticsError
|
|
17
18
|
from pystatistics.mvnmle.patterns import PatternInfo, identify_missingness_patterns
|
|
18
19
|
|
|
19
20
|
|
|
@@ -242,12 +243,21 @@ def little_mcar_test(data,
|
|
|
242
243
|
|
|
243
244
|
try:
|
|
244
245
|
ml_result = mlest(
|
|
245
|
-
data_array,
|
|
246
|
+
data_array,
|
|
247
|
+
backend=backend,
|
|
248
|
+
algorithm=algorithm,
|
|
249
|
+
regularize=regularize,
|
|
250
|
+
verbose=False,
|
|
246
251
|
)
|
|
247
252
|
mu_ml = ml_result.muhat
|
|
248
253
|
sigma_ml = ml_result.sigmahat
|
|
254
|
+
except PyStatisticsError:
|
|
255
|
+
# Preserve pystatistics exception type so callers using a
|
|
256
|
+
# `except PyStatisticsError:` catch (the documented pattern)
|
|
257
|
+
# actually catch MLE failures here.
|
|
258
|
+
raise
|
|
249
259
|
except Exception as e:
|
|
250
|
-
raise RuntimeError(f"ML estimation failed: {e}")
|
|
260
|
+
raise RuntimeError(f"ML estimation failed: {e}") from e
|
|
251
261
|
|
|
252
262
|
# Rule 1: do not quietly hand the caller a statistic built on top
|
|
253
263
|
# of unconverged ML estimates. If BFGS ran out of iterations (the
|
|
@@ -26,6 +26,7 @@ def mlest(
|
|
|
26
26
|
method: str | None = None,
|
|
27
27
|
tol: float | None = None,
|
|
28
28
|
max_iter: int | None = None,
|
|
29
|
+
regularize: bool = True,
|
|
29
30
|
verbose: bool = False,
|
|
30
31
|
) -> MVNSolution:
|
|
31
32
|
"""
|
|
@@ -95,7 +96,7 @@ def mlest(
|
|
|
95
96
|
f"{design.missing_rate:.1%} missing")
|
|
96
97
|
|
|
97
98
|
if algorithm == 'em':
|
|
98
|
-
result = _solve_em(design, backend, tol, max_iter, verbose)
|
|
99
|
+
result = _solve_em(design, backend, tol, max_iter, regularize, verbose)
|
|
99
100
|
elif algorithm == 'direct':
|
|
100
101
|
result = _solve_direct(design, backend, method, tol, max_iter, verbose)
|
|
101
102
|
elif algorithm == 'monotone':
|
|
@@ -186,7 +187,7 @@ def _solve_direct(design, backend, method, tol, max_iter, verbose):
|
|
|
186
187
|
return backend_impl.solve(design, **solve_kwargs)
|
|
187
188
|
|
|
188
189
|
|
|
189
|
-
def _solve_em(design, backend, tol, max_iter, verbose):
|
|
190
|
+
def _solve_em(design, backend, tol, max_iter, regularize, verbose):
|
|
190
191
|
"""Dispatch EM algorithm."""
|
|
191
192
|
from pystatistics.mvnmle.backends.em import EMBackend
|
|
192
193
|
|
|
@@ -202,7 +203,12 @@ def _solve_em(design, backend, tol, max_iter, verbose):
|
|
|
202
203
|
if verbose:
|
|
203
204
|
print(f"Backend: {backend_impl.name}")
|
|
204
205
|
|
|
205
|
-
return backend_impl.solve(
|
|
206
|
+
return backend_impl.solve(
|
|
207
|
+
design,
|
|
208
|
+
tol=effective_tol,
|
|
209
|
+
max_iter=effective_max_iter,
|
|
210
|
+
regularize=regularize,
|
|
211
|
+
)
|
|
206
212
|
|
|
207
213
|
|
|
208
214
|
# ---------------------------------------------------------------------------
|
|
@@ -42,8 +42,13 @@ def test_auto_drops_all_missing_rows_with_warning():
|
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
def test_strict_all_missing_rows_still_rejected():
|
|
45
|
+
from pystatistics.core.exceptions import PyStatisticsError
|
|
45
46
|
X = np.array([[1.0, 2.0], [np.nan, np.nan], [3.0, 4.0]])
|
|
46
|
-
|
|
47
|
+
# PyStatisticsError covers ValidationError (the actual type raised
|
|
48
|
+
# by MVNDesign.from_array). Keep ValueError / RuntimeError in the
|
|
49
|
+
# allowlist for forward-compat if the implementation ever routes
|
|
50
|
+
# through one of those instead.
|
|
51
|
+
with pytest.raises((PyStatisticsError, ValueError, RuntimeError)):
|
|
47
52
|
little_mcar_test(X, drop_all_missing_rows=False)
|
|
48
53
|
|
|
49
54
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|