MCPower 0.4.0__tar.gz → 0.4.2__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.
- {mcpower-0.4.0 → mcpower-0.4.2}/.github/workflows/release.yml +1 -1
- {mcpower-0.4.0 → mcpower-0.4.2}/.github/workflows/test-release.yml +1 -1
- {mcpower-0.4.0 → mcpower-0.4.2}/.github/workflows/tests.yml +3 -3
- mcpower-0.4.2/CHANGELOG.md +130 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/CMakeLists.txt +27 -4
- {mcpower-0.4.0 → mcpower-0.4.2}/PKG-INFO +95 -46
- {mcpower-0.4.0 → mcpower-0.4.2}/README.md +92 -43
- {mcpower-0.4.0 → mcpower-0.4.2}/cpp/src/bindings.cpp +83 -0
- mcpower-0.4.2/cpp/src/lme_solver.cpp +540 -0
- mcpower-0.4.2/cpp/src/lme_solver.hpp +154 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/mcpower/backends/__init__.py +21 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/mcpower/backends/jit.py +34 -4
- {mcpower-0.4.0 → mcpower-0.4.2}/mcpower/backends/native.py +51 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/mcpower/backends/python.py +32 -2
- {mcpower-0.4.0 → mcpower-0.4.2}/mcpower/core/simulation.py +155 -22
- {mcpower-0.4.0 → mcpower-0.4.2}/mcpower/core/variables.py +235 -60
- {mcpower-0.4.0 → mcpower-0.4.2}/mcpower/model.py +593 -97
- mcpower-0.4.2/mcpower/stats/__init__.py +6 -0
- {mcpower-0.4.0/mcpower/utils → mcpower-0.4.2/mcpower/stats}/data_generation.py +232 -1
- mcpower-0.4.2/mcpower/stats/lme_solver.py +1433 -0
- {mcpower-0.4.0/mcpower/utils → mcpower-0.4.2/mcpower/stats}/mixed_models.py +401 -11
- {mcpower-0.4.0/mcpower/utils → mcpower-0.4.2/mcpower/stats}/ols.py +183 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/mcpower/utils/__init__.py +5 -2
- mcpower-0.4.2/mcpower/utils/data_generation.py +13 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/mcpower/utils/formatters.py +46 -17
- mcpower-0.4.2/mcpower/utils/mixed_models.py +7 -0
- mcpower-0.4.2/mcpower/utils/ols.py +9 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/mcpower/utils/parsers.py +66 -18
- {mcpower-0.4.0 → mcpower-0.4.2}/mcpower/utils/updates.py +7 -1
- {mcpower-0.4.0 → mcpower-0.4.2}/mcpower/utils/validators.py +26 -21
- {mcpower-0.4.0 → mcpower-0.4.2}/mcpower/utils/visualization.py +9 -1
- {mcpower-0.4.0 → mcpower-0.4.2}/pyproject.toml +2 -2
- {mcpower-0.4.0 → mcpower-0.4.2}/tests/backend/test_backend_parity.py +0 -2
- {mcpower-0.4.0 → mcpower-0.4.2}/tests/backend/test_datagen_isolation.py +0 -1
- {mcpower-0.4.0 → mcpower-0.4.2}/tests/backend/test_ols_isolation.py +0 -1
- mcpower-0.4.2/tests/cars.csv +33 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/tests/conftest.py +1 -1
- {mcpower-0.4.0 → mcpower-0.4.2}/tests/helpers/power_helpers.py +1 -1
- {mcpower-0.4.0/tests → mcpower-0.4.2/tests/integration}/test_edge_cases.py +3 -2
- {mcpower-0.4.0/tests → mcpower-0.4.2/tests/integration}/test_model.py +56 -0
- {mcpower-0.4.0/tests → mcpower-0.4.2/tests/integration}/test_parallel.py +1 -0
- mcpower-0.4.2/tests/integration/test_posthoc_integration.py +576 -0
- {mcpower-0.4.0/tests → mcpower-0.4.2/tests/integration}/test_upload_data.py +251 -9
- {mcpower-0.4.0 → mcpower-0.4.2}/tests/mixed_models/test_cluster_validators.py +0 -2
- mcpower-0.4.2/tests/mixed_models/test_integration_phase2.py +218 -0
- mcpower-0.4.2/tests/mixed_models/test_lme_nested.py +236 -0
- mcpower-0.4.2/tests/mixed_models/test_lme_slopes.py +218 -0
- mcpower-0.4.2/tests/mixed_models/test_lme_solver.py +465 -0
- mcpower-0.4.2/tests/mixed_models/test_lme_vs_statsmodels.py +244 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/tests/mixed_models/test_mixed_models.py +7 -3
- {mcpower-0.4.0 → mcpower-0.4.2}/tests/mixed_models/test_mixed_models_diagnostics.py +1 -2
- {mcpower-0.4.0 → mcpower-0.4.2}/tests/mixed_models/test_mixed_models_extended.py +2 -1
- {mcpower-0.4.0 → mcpower-0.4.2}/tests/mixed_models/test_mixed_models_validation.py +7 -7
- mcpower-0.4.2/tests/specs/__init__.py +0 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/tests/specs/test_alpha_levels.py +1 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/tests/specs/test_corrections.py +1 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/tests/specs/test_monotonicity.py +1 -0
- mcpower-0.4.2/tests/unit/__init__.py +0 -0
- {mcpower-0.4.0/tests → mcpower-0.4.2/tests/unit}/test_data_generation.py +28 -28
- {mcpower-0.4.0/tests → mcpower-0.4.2/tests/unit}/test_formatters.py +117 -0
- {mcpower-0.4.0/tests → mcpower-0.4.2/tests/unit}/test_ols.py +10 -10
- {mcpower-0.4.0/tests → mcpower-0.4.2/tests/unit}/test_parsers.py +6 -3
- mcpower-0.4.2/tests/unit/test_posthoc.py +213 -0
- {mcpower-0.4.0/tests → mcpower-0.4.2/tests/unit}/test_updates.py +1 -1
- {mcpower-0.4.0/tests → mcpower-0.4.2/tests/unit}/test_validators.py +6 -0
- {mcpower-0.4.0/tests → mcpower-0.4.2/tests/unit}/test_variables.py +119 -0
- mcpower-0.4.0/CHANGELOG.md +0 -95
- mcpower-0.4.0/scripts/test_quick.py +0 -213
- mcpower-0.4.0/tests/LME_BENCHMARK_TESTS.md +0 -208
- mcpower-0.4.0/tests/cars.csv +0 -33
- {mcpower-0.4.0 → mcpower-0.4.2}/.github/FUNDING.yml +0 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/.gitignore +0 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/.pre-commit-config.yaml +0 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/LICENSE +0 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/cpp/src/data_generation.cpp +0 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/cpp/src/data_generation.hpp +0 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/cpp/src/ols.cpp +0 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/cpp/src/ols.hpp +0 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/examples/01_basic_power.py +0 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/examples/02_sample_size.py +0 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/examples/03_interactions.py +0 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/examples/04_correlations.py +0 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/examples/05_multiple_testing.py +0 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/examples/06_own_data.py +0 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/examples/cars.csv +0 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/mcpower/__init__.py +0 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/mcpower/core/__init__.py +0 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/mcpower/core/results.py +0 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/mcpower/core/scenarios.py +0 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/mcpower/progress.py +0 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/mcpower/tables/__init__.py +0 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/mcpower/tables/data/norm_cdf.npz +0 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/mcpower/tables/data/t3_ppf.npz +0 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/mcpower/tables/lookup.py +0 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/mcpower/utils/upload_data_utils.py +0 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/scripts/generate_tables.py +0 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/tests/backend/__init__.py +0 -0
- {mcpower-0.4.0/tests → mcpower-0.4.2/tests/backend}/test_jit_backend.py +0 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/tests/config.py +0 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/tests/helpers/__init__.py +0 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/tests/helpers/analytical.py +0 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/tests/helpers/mc_margins.py +0 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/tests/integration/__init__.py +0 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/tests/integration/test_find_power_api.py +0 -0
- {mcpower-0.4.0/tests → mcpower-0.4.2/tests/integration}/test_scenarios.py +0 -0
- {mcpower-0.4.0/tests → mcpower-0.4.2/tests/integration}/test_visualization.py +0 -0
- {mcpower-0.4.0/tests/specs → mcpower-0.4.2/tests/mixed_models}/__init__.py +0 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/tests/specs/test_monotonicity_lme.py +0 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/tests/specs/test_power_accuracy.py +0 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/tests/specs/test_power_accuracy_lme.py +0 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/tests/specs/test_type1_error.py +0 -0
- {mcpower-0.4.0 → mcpower-0.4.2}/tests/specs/test_type1_error_lme.py +0 -0
- {mcpower-0.4.0/tests → mcpower-0.4.2/tests/unit}/test_lookup.py +0 -0
- {mcpower-0.4.0/tests → mcpower-0.4.2/tests/unit}/test_progress.py +0 -0
- {mcpower-0.4.0/tests → mcpower-0.4.2/tests/unit}/test_simulation_unit.py +0 -0
|
@@ -69,7 +69,7 @@ jobs:
|
|
|
69
69
|
CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28
|
|
70
70
|
CIBW_MANYLINUX_AARCH64_IMAGE: manylinux_2_28
|
|
71
71
|
CIBW_TEST_REQUIRES: "pytest numpy pandas matplotlib scipy statsmodels"
|
|
72
|
-
CIBW_TEST_COMMAND: "pytest {project}/tests -v -x --ignore={project}/tests/backend/ -m \"not slow\""
|
|
72
|
+
CIBW_TEST_COMMAND: "pytest {project}/tests -v -x --ignore={project}/tests/backend/ -m \"not slow and not lme\""
|
|
73
73
|
|
|
74
74
|
- name: Verify native extension in wheels
|
|
75
75
|
shell: bash
|
|
@@ -34,7 +34,7 @@ jobs:
|
|
|
34
34
|
CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28
|
|
35
35
|
CIBW_MANYLINUX_AARCH64_IMAGE: manylinux_2_28
|
|
36
36
|
CIBW_TEST_REQUIRES: "pytest numpy pandas matplotlib scipy statsmodels"
|
|
37
|
-
CIBW_TEST_COMMAND: "pytest {project}/tests -v -x --ignore={project}/tests/backend/ -m \"not slow\""
|
|
37
|
+
CIBW_TEST_COMMAND: "pytest {project}/tests -v -x --ignore={project}/tests/backend/ -m \"not slow and not lme\""
|
|
38
38
|
|
|
39
39
|
- name: Verify native extension in wheels
|
|
40
40
|
shell: bash
|
|
@@ -37,12 +37,12 @@ jobs:
|
|
|
37
37
|
run: |
|
|
38
38
|
python -m pip install --upgrade pip
|
|
39
39
|
pip install pytest pytest-cov
|
|
40
|
-
pip install pandas joblib matplotlib
|
|
40
|
+
pip install pandas joblib matplotlib
|
|
41
41
|
pip install numba || echo "Numba not available for Python ${{ matrix.python-version }}, will use Python fallback"
|
|
42
42
|
pip install -e .
|
|
43
43
|
|
|
44
44
|
- name: Run tests with coverage
|
|
45
|
-
run: pytest tests/ -v --tb=short --cov=mcpower --cov-report=xml -m "not slow"
|
|
45
|
+
run: pytest tests/ -v --tb=short --cov=mcpower --cov-report=xml -m "not slow and not lme"
|
|
46
46
|
|
|
47
47
|
- name: Upload coverage to Codecov
|
|
48
48
|
uses: codecov/codecov-action@v4
|
|
@@ -98,7 +98,7 @@ jobs:
|
|
|
98
98
|
- name: Test Python fallback mode
|
|
99
99
|
run: |
|
|
100
100
|
python -c "import mcpower; print('Python fallback mode working')"
|
|
101
|
-
pytest tests/ -v --tb=short -m "not slow"
|
|
101
|
+
pytest tests/ -v --tb=short -m "not slow and not lme"
|
|
102
102
|
|
|
103
103
|
test-performance:
|
|
104
104
|
runs-on: ubuntu-latest
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [0.4.2] - 2026-02-17
|
|
6
|
+
|
|
7
|
+
### Major changes
|
|
8
|
+
- **Custom LME solver**: Replaced statsmodels MixedLM with a custom profiled-deviance REML solver (Python + C++ + JIT backends), eliminating the statsmodels runtime dependency for mixed models
|
|
9
|
+
- **C++ LME backend**: LME analysis exposed via PyBind11/Eigen (`mcpower_native.lme_analysis`) for maximum throughput
|
|
10
|
+
- **Post-hoc pairwise comparisons**: `target_test="group[1] vs group[2]"` syntax for contrasts between factor levels
|
|
11
|
+
- **Tukey HSD correction**: New `correction="tukey"` option for pairwise comparisons using studentized range critical values
|
|
12
|
+
|
|
13
|
+
### Minor changes
|
|
14
|
+
- `target_test` default changed from `"overall"` to `"all"` (overall F-test + all individual fixed effects)
|
|
15
|
+
- `target_test` now supports keyword expansion (`"all"`, `"all-posthoc"`), exclusion prefixes (`"-overall"`), and comma-separated combinations
|
|
16
|
+
- Renamed optional dependency extra from `[mixed]` to `[lme]`
|
|
17
|
+
- Formatter handles `NaN` power values gracefully (displays `"-"` instead of crashing)
|
|
18
|
+
- README rewritten with clearer examples and MCPower GUI cross-link
|
|
19
|
+
|
|
20
|
+
### Technical
|
|
21
|
+
- New `mcpower/stats/` package: statistical routines (`ols.py`, `lme_solver.py`, `mixed_models.py`, `data_generation.py`) extracted from `utils/`
|
|
22
|
+
- Reclassified several `@pytest.mark.slow` tests to run in the default (non-slow) suite
|
|
23
|
+
- Removed old test files and `scripts/test_quick.py`; moved `LME_BENCHMARK_TESTS.md` to `docs/`
|
|
24
|
+
|
|
25
|
+
### Experimental
|
|
26
|
+
- **Random slopes**: Support for `(1 + x|group)` syntax with configurable slope variance and slope-intercept correlation
|
|
27
|
+
- **Nested random effects**: Support for `(1|A/B)` syntax with automatic expansion to parent and child grouping terms
|
|
28
|
+
|
|
29
|
+
## [0.4.1] - 2026-02-14
|
|
30
|
+
|
|
31
|
+
### Minor changes
|
|
32
|
+
- README badges now link to their respective pages
|
|
33
|
+
- Added MCPower GUI desktop application section to README
|
|
34
|
+
|
|
35
|
+
### Technical
|
|
36
|
+
- Reclassified several backend and mixed-model tests from `@pytest.mark.slow` to default suite
|
|
37
|
+
- Moved slow markers to individual test methods for finer-grained control
|
|
38
|
+
|
|
39
|
+
## [0.4.0] - 2026-02-12
|
|
40
|
+
|
|
41
|
+
Releasing pipeline fixed
|
|
42
|
+
|
|
43
|
+
## [0.4.0b0] - 2026-02-12
|
|
44
|
+
|
|
45
|
+
The package has been almost entirely rebuilt from the ground up.
|
|
46
|
+
|
|
47
|
+
### Major changes
|
|
48
|
+
- **C++ native backend**: New PyBind11/Eigen-based compute backend for OLS and data generation (~3x speedup), removed Numba AOT
|
|
49
|
+
- **Multi-backend architecture**: Automatic selection between C++ native, Numba JIT, and pure Python backends with graceful fallback
|
|
50
|
+
- **Mixed-effects models**: Random intercept models via `(1|group)` syntax with statsmodels MixedLM
|
|
51
|
+
- **Build system migration**: Moved from setuptools to scikit-build-core with CMake for C++ compilation
|
|
52
|
+
- **Python 3.14 support**
|
|
53
|
+
- **Lookup tables reduced and reorganized**: removed lookup tables from OLS, precomputed critical values added
|
|
54
|
+
|
|
55
|
+
### Minor changes
|
|
56
|
+
- `joblib` is now a core dependency; `statsmodels` moved to optional `[lme]` extra
|
|
57
|
+
- Progress callback system for GUI/notebook integration
|
|
58
|
+
|
|
59
|
+
### Technical
|
|
60
|
+
- Complete code reorganization: monolithic `base.py` split into modular structure (`mcpower/core/`, `mcpower/backends/`, `mcpower/model.py`)
|
|
61
|
+
- New CI/CD pipeline: separate workflows for stable releases (PyPI) and pre-releases (TestPyPI)
|
|
62
|
+
- Comprehensive test suite overhaul (650 tests across unit, integration, spec, and mixed-model categories)
|
|
63
|
+
- Pre-commit hooks with ruff for linting and formatting
|
|
64
|
+
- Removed legacy `setup.py`, `pytest.ini`, and `paper/` directory
|
|
65
|
+
|
|
66
|
+
## [0.3.3] - 2025-07-27
|
|
67
|
+
|
|
68
|
+
### Technical
|
|
69
|
+
- Wheels for Python 3.13
|
|
70
|
+
|
|
71
|
+
## [0.3.2] - 2025-07-27
|
|
72
|
+
|
|
73
|
+
### Major changes
|
|
74
|
+
- Factors are now supported
|
|
75
|
+
- The workflow requires that `set_variable_types` be set before correlation and effects
|
|
76
|
+
|
|
77
|
+
## [0.3.1] - 2025-07-17
|
|
78
|
+
|
|
79
|
+
### Major changes
|
|
80
|
+
- Added weekly update checker
|
|
81
|
+
|
|
82
|
+
### Minor changes
|
|
83
|
+
- Small changes in documentation
|
|
84
|
+
|
|
85
|
+
## [0.3.0] - 2025-07-16
|
|
86
|
+
|
|
87
|
+
### Major changes
|
|
88
|
+
- AOT compiled files added
|
|
89
|
+
- PyPI release
|
|
90
|
+
|
|
91
|
+
### Minor changes
|
|
92
|
+
- Small changes in documentation, added fallback to compilation
|
|
93
|
+
|
|
94
|
+
## [0.2.4] - 2025-07-16
|
|
95
|
+
- Test PyPI release
|
|
96
|
+
|
|
97
|
+
## [0.2.3] - 2025-07-16
|
|
98
|
+
- Test PyPI release
|
|
99
|
+
|
|
100
|
+
## [0.2.2] - 2025-07-11
|
|
101
|
+
|
|
102
|
+
### Major changes
|
|
103
|
+
- Moved seed setting from method arguments to `model.set_seed()` for better control
|
|
104
|
+
- Added public method to customize scenario configurations
|
|
105
|
+
- Added support for 3-way and n-way interactions in formulas
|
|
106
|
+
|
|
107
|
+
### Minor changes
|
|
108
|
+
- Improved lookup table performance with extended range and faster fallbacks
|
|
109
|
+
- Fixed import error when uploading user data
|
|
110
|
+
- Fixed minor cache creation error
|
|
111
|
+
- Adjusted vulnerability thresholds from 30% to 20% power drop
|
|
112
|
+
- Added warning for inflated Type I error risk
|
|
113
|
+
|
|
114
|
+
### Technical
|
|
115
|
+
- Added comprehensive test suite
|
|
116
|
+
- Improved code documentation with clearer comments and global variables
|
|
117
|
+
- Clarified correlation behavior during variable transformations
|
|
118
|
+
|
|
119
|
+
## [0.2.1] - 2025-06-26
|
|
120
|
+
|
|
121
|
+
### Minor changes
|
|
122
|
+
- Fixed import statements to use relative imports instead of absolute imports
|
|
123
|
+
- Added changelog
|
|
124
|
+
- Extended docstrings
|
|
125
|
+
|
|
126
|
+
## [0.2.0] - 2025-06-26
|
|
127
|
+
- Initial release
|
|
128
|
+
|
|
129
|
+
## [0.1.0] - 2025-06-07
|
|
130
|
+
- Proof of concept release
|
|
@@ -6,11 +6,32 @@ set(CMAKE_CXX_STANDARD 17)
|
|
|
6
6
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
7
7
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
|
8
8
|
|
|
9
|
-
# Find pybind11 —
|
|
10
|
-
find_package(pybind11 CONFIG)
|
|
9
|
+
# Find pybind11 — try CONFIG first, then query the Python module as fallback
|
|
10
|
+
find_package(pybind11 CONFIG QUIET)
|
|
11
11
|
if(NOT pybind11_FOUND)
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
# Fallback: ask the pybind11 Python module where its cmake files live
|
|
13
|
+
# (scikit-build-core may not always add site-packages to CMAKE_PREFIX_PATH on Windows)
|
|
14
|
+
find_package(Python COMPONENTS Interpreter QUIET)
|
|
15
|
+
if(Python_FOUND)
|
|
16
|
+
execute_process(
|
|
17
|
+
COMMAND "${Python_EXECUTABLE}" -m pybind11 --cmakedir
|
|
18
|
+
OUTPUT_VARIABLE _pybind11_cmake_dir
|
|
19
|
+
OUTPUT_STRIP_TRAILING_WHITESPACE
|
|
20
|
+
ERROR_QUIET
|
|
21
|
+
RESULT_VARIABLE _pybind11_result
|
|
22
|
+
)
|
|
23
|
+
if(_pybind11_result EQUAL 0 AND _pybind11_cmake_dir)
|
|
24
|
+
find_package(pybind11 CONFIG REQUIRED PATHS "${_pybind11_cmake_dir}" NO_DEFAULT_PATH)
|
|
25
|
+
endif()
|
|
26
|
+
endif()
|
|
27
|
+
endif()
|
|
28
|
+
if(NOT pybind11_FOUND)
|
|
29
|
+
if(DEFINED ENV{CIBUILDWHEEL})
|
|
30
|
+
message(FATAL_ERROR "pybind11 not found in CI — cannot build wheel without native extension")
|
|
31
|
+
else()
|
|
32
|
+
message(WARNING "pybind11 not found — building Python-only package (no C++ backend)")
|
|
33
|
+
return()
|
|
34
|
+
endif()
|
|
14
35
|
endif()
|
|
15
36
|
|
|
16
37
|
# Try to find Eigen3, make it optional
|
|
@@ -47,6 +68,7 @@ endif()
|
|
|
47
68
|
set(MCPOWER_SOURCES
|
|
48
69
|
cpp/src/ols.cpp
|
|
49
70
|
cpp/src/data_generation.cpp
|
|
71
|
+
cpp/src/lme_solver.cpp
|
|
50
72
|
cpp/src/bindings.cpp
|
|
51
73
|
)
|
|
52
74
|
|
|
@@ -78,6 +100,7 @@ elseif(MSVC)
|
|
|
78
100
|
/fp:fast
|
|
79
101
|
/DNDEBUG
|
|
80
102
|
)
|
|
103
|
+
target_compile_definitions(mcpower_native PRIVATE _USE_MATH_DEFINES)
|
|
81
104
|
endif()
|
|
82
105
|
|
|
83
106
|
# OpenMP support (optional)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: MCPower
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: Monte Carlo Power Analysis for Statistical Models
|
|
5
5
|
Keywords: power analysis,statistics,monte carlo,linear regression
|
|
6
6
|
Author-Email: =?utf-8?q?Pawe=C5=82_Lenartowicz?= <pawellenartowicz@europe.com>
|
|
@@ -28,8 +28,8 @@ Requires-Dist: scipy>=1.11.0
|
|
|
28
28
|
Requires-Dist: joblib>=1.3.0
|
|
29
29
|
Provides-Extra: jit
|
|
30
30
|
Requires-Dist: numba>=0.61.0; extra == "jit"
|
|
31
|
-
Provides-Extra:
|
|
32
|
-
Requires-Dist: statsmodels>=0.14.0; extra == "
|
|
31
|
+
Provides-Extra: lme
|
|
32
|
+
Requires-Dist: statsmodels>=0.14.0; extra == "lme"
|
|
33
33
|
Provides-Extra: dev
|
|
34
34
|
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
35
35
|
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
@@ -45,11 +45,11 @@ Requires-Dist: pytest>=7.0.0; extra == "all"
|
|
|
45
45
|
Requires-Dist: pytest-cov>=4.0.0; extra == "all"
|
|
46
46
|
Description-Content-Type: text/markdown
|
|
47
47
|
|
|
48
|
-

|
|
49
|
-

|
|
50
|
-

|
|
51
|
-

|
|
52
|
-
[](https://github.com/pawlenartowicz/MCPower/actions)
|
|
49
|
+
[](https://pypi.org/project/MCPower/)
|
|
50
|
+
[](https://pypi.org/project/MCPower/)
|
|
51
|
+
[](https://www.gnu.org/licenses/gpl-3.0)
|
|
52
|
+
[](https://doi.org/10.5281/zenodo.16502734)
|
|
53
53
|
|
|
54
54
|
```
|
|
55
55
|
███╗ ███╗ ██████╗ ██████╗
|
|
@@ -64,16 +64,23 @@ Description-Content-Type: text/markdown
|
|
|
64
64
|
|
|
65
65
|
**Simple Monte Carlo power analysis for complex models.** Find the sample size you need or check if your study has enough power - even with complex models that traditional power analysis can't handle.
|
|
66
66
|
|
|
67
|
+
## Desktop Application
|
|
68
|
+
|
|
69
|
+
It it Python package, prefer a graphical interface? **[MCPower GUI](https://github.com/pawlenartowicz/mcpower-gui)** is a standalone desktop app — no Python installation required. Download ready-to-run executables for Windows, Linux, and macOS from the [releases page](https://github.com/pawlenartowicz/mcpower-gui/releases/latest).
|
|
70
|
+
|
|
67
71
|
## Why MCPower?
|
|
68
72
|
|
|
69
|
-
**Traditional power analysis breaks down** with interactions, correlated predictors, categorical variables, or non-normal data. MCPower uses simulation instead of formulas
|
|
73
|
+
**Traditional power analysis breaks down** with interactions, correlated predictors, categorical variables, or non-normal data. MCPower uses simulation instead of formulas — it generates thousands of datasets exactly like yours, then sees how often your analysis finds real effects.
|
|
74
|
+
|
|
75
|
+
**Just write your formula.** Define your model the way you'd write it in R — `outcome = treatment + covariate + treatment*covariate`. MCPower parses the formula, sets up the simulation, and handles interactions, factor coding, and dummy variables automatically. You focus on the research question, not the mechanics. More model types (logistic regression, ANOVA) are on the way.
|
|
76
|
+
|
|
77
|
+
**Test your assumptions with scenarios.** Real studies rarely match textbook conditions — effect sizes may be smaller than expected, distributions may be skewed, or variance may not be constant. Turn on `scenarios=True` and MCPower automatically tests your power under optimistic, realistic, and worst-case conditions. Instead of a single number, you get a range that shows how sensitive your design is to violated assumptions — so you plan for reality, not just the best case.
|
|
78
|
+
|
|
79
|
+
**Use your own data.** Upload a CSV and MCPower auto-detects variable types (continuous, binary, or categorical), preserves real distributions, and handles correlations between predictors. No need to overthink whether your data is normal, skewed, or categorical — just upload it and MCPower samples from the empirical distribution. This is especially useful when you have pilot data or a related dataset and want your power analysis to reflect actual conditions rather than idealized ones.
|
|
70
80
|
|
|
71
|
-
✅ **Works with complexity**: Interactions, correlations, factors, any distribution
|
|
72
|
-
✅ **
|
|
73
|
-
✅ **
|
|
74
|
-
✅ **Two simple commands**: Find sample size or check power
|
|
75
|
-
✅ **Scenario analysis**: Test robustness under realistic conditions
|
|
76
|
-
✅ **Minimal math required**: Just specify your model and effects
|
|
81
|
+
✅ **Works with complexity**: Interactions, correlations, multi-level factors, any distribution
|
|
82
|
+
✅ **Two simple commands**: `find_sample_size()` or `find_power()` — that's the entire API
|
|
83
|
+
✅ **Minimal math required**: Just specify your model formula and expected effect sizes
|
|
77
84
|
|
|
78
85
|
## Get Started in 2 Minutes
|
|
79
86
|
|
|
@@ -91,10 +98,10 @@ pip install --upgrade mcpower
|
|
|
91
98
|
```python
|
|
92
99
|
|
|
93
100
|
# 0. Import installed package
|
|
94
|
-
import
|
|
101
|
+
from mcpower import MCPower
|
|
95
102
|
|
|
96
103
|
# 1. Define your model (just like R)
|
|
97
|
-
model =
|
|
104
|
+
model = MCPower("satisfaction = treatment + motivation")
|
|
98
105
|
|
|
99
106
|
# 2. Set effect sizes (how big you expect effects to be)
|
|
100
107
|
model.set_effects("treatment=0.5, motivation=0.3")
|
|
@@ -175,10 +182,10 @@ model.set_effects("treatment=0.5, age=0.3, income=0.2")
|
|
|
175
182
|
|
|
176
183
|
### Randomized Controlled Trial
|
|
177
184
|
```python
|
|
178
|
-
import
|
|
185
|
+
from mcpower import MCPower
|
|
179
186
|
|
|
180
187
|
# RCT with treatment + control variables
|
|
181
|
-
model =
|
|
188
|
+
model = MCPower("outcome = treatment + age + baseline_score")
|
|
182
189
|
model.set_effects("treatment=0.6, age=0.2, baseline_score=0.8")
|
|
183
190
|
model.set_variable_type("treatment=binary") # 0/1 treatment
|
|
184
191
|
|
|
@@ -189,10 +196,10 @@ model.find_sample_size(target_test="treatment", from_size=100, to_size=500,
|
|
|
189
196
|
|
|
190
197
|
### A/B Test with Interaction
|
|
191
198
|
```python
|
|
192
|
-
import
|
|
199
|
+
from mcpower import MCPower
|
|
193
200
|
|
|
194
201
|
# Test if treatment effect depends on user type
|
|
195
|
-
model =
|
|
202
|
+
model = MCPower("conversion = treatment + user_type + treatment*user_type")
|
|
196
203
|
model.set_effects("treatment=0.4, user_type=0.3, treatment:user_type=0.5")
|
|
197
204
|
model.set_variable_type("treatment=binary, user_type=binary")
|
|
198
205
|
|
|
@@ -202,10 +209,10 @@ model.find_power(sample_size=400, target_test="treatment:user_type", scenarios=T
|
|
|
202
209
|
|
|
203
210
|
### Multi-Group Study with Categorical Variables
|
|
204
211
|
```python
|
|
205
|
-
import
|
|
212
|
+
from mcpower import MCPower
|
|
206
213
|
|
|
207
214
|
# Study with 3 treatment groups and 4 education levels
|
|
208
|
-
model =
|
|
215
|
+
model = MCPower("wellbeing = treatment + education + age")
|
|
209
216
|
model.set_variable_type("treatment=(factor,3), education=(factor,4)")
|
|
210
217
|
|
|
211
218
|
# Set effects for each factor level (vs. reference level 1)
|
|
@@ -217,10 +224,10 @@ model.find_sample_size(target_test="treatment[2], treatment[3]", scenarios=True)
|
|
|
217
224
|
|
|
218
225
|
### Survey with Correlated Predictors
|
|
219
226
|
```python
|
|
220
|
-
import
|
|
227
|
+
from mcpower import MCPower
|
|
221
228
|
|
|
222
229
|
# Predictors are often correlated in real data
|
|
223
|
-
model =
|
|
230
|
+
model = MCPower("wellbeing = income + education + social_support")
|
|
224
231
|
model.set_effects("income=0.4, education=0.3, social_support=0.6")
|
|
225
232
|
model.set_correlations("corr(income, education)=0.5, corr(income, social_support)=0.3")
|
|
226
233
|
|
|
@@ -246,7 +253,7 @@ model.set_variable_type("condition=(factor,0.2,0.5,0.3)")
|
|
|
246
253
|
### Working with Factors (Categorical Variables)
|
|
247
254
|
```python
|
|
248
255
|
# Factors automatically create dummy variables
|
|
249
|
-
model =
|
|
256
|
+
model = MCPower("outcome = treatment + education")
|
|
250
257
|
model.set_variable_type("treatment=(factor,3), education=(factor,4)")
|
|
251
258
|
|
|
252
259
|
# Set effects for specific levels (level 1 is always reference)
|
|
@@ -264,7 +271,7 @@ import pandas as pd
|
|
|
264
271
|
data = pd.read_csv("my_data.csv")
|
|
265
272
|
|
|
266
273
|
# Upload with automatic type detection
|
|
267
|
-
model =
|
|
274
|
+
model = MCPower("mpg = hp + wt + cyl")
|
|
268
275
|
model.upload_data(data[["hp", "wt", "cyl"]])
|
|
269
276
|
model.set_effects("hp=0.5, wt=0.3, cyl[2]=0.2, cyl[3]=0.4")
|
|
270
277
|
model.find_power(sample_size=100)
|
|
@@ -308,13 +315,28 @@ model.upload_data(
|
|
|
308
315
|
```python
|
|
309
316
|
# Testing multiple effects? Control false positives
|
|
310
317
|
model.find_power(
|
|
311
|
-
sample_size=200,
|
|
318
|
+
sample_size=200,
|
|
312
319
|
target_test="treatment,covariate,treatment:covariate",
|
|
313
320
|
correction="Benjamini-Hochberg",
|
|
314
321
|
scenarios=True # Test robustness too!
|
|
315
322
|
)
|
|
316
323
|
```
|
|
317
324
|
|
|
325
|
+
### Post-Hoc Pairwise Comparisons (Tukey HSD)
|
|
326
|
+
```python
|
|
327
|
+
# Compare specific factor levels with Tukey correction
|
|
328
|
+
model = MCPower("outcome = group + covariate")
|
|
329
|
+
model.set_variable_type("group=(factor,3)")
|
|
330
|
+
model.set_effects("group[2]=0.4, group[3]=0.6, covariate=0.3")
|
|
331
|
+
|
|
332
|
+
# Use "vs" syntax for pairwise comparisons + correction="tukey"
|
|
333
|
+
model.find_power(
|
|
334
|
+
sample_size=150,
|
|
335
|
+
target_test="group[0] vs group[1], group[0] vs group[2]",
|
|
336
|
+
correction="tukey"
|
|
337
|
+
)
|
|
338
|
+
```
|
|
339
|
+
|
|
318
340
|
### Test the single violation of assumptions.
|
|
319
341
|
```python
|
|
320
342
|
# Customize how much "messiness" to add in scenarios
|
|
@@ -325,27 +347,37 @@ model.set_heteroskedasticity(0.15) # Violation of equal variance assumption
|
|
|
325
347
|
model.find_sample_size(target_test="treatment", scenarios=False)
|
|
326
348
|
```
|
|
327
349
|
|
|
328
|
-
### Mixed-Effects Models (
|
|
350
|
+
### Mixed-Effects Models (Experimental)
|
|
329
351
|
```python
|
|
330
|
-
import
|
|
352
|
+
from mcpower import MCPower
|
|
331
353
|
|
|
332
|
-
#
|
|
333
|
-
model =
|
|
354
|
+
# Random intercept — clustered data
|
|
355
|
+
model = MCPower("satisfaction ~ treatment + motivation + (1|school)")
|
|
334
356
|
model.set_cluster("school", ICC=0.2, n_clusters=20)
|
|
335
357
|
model.set_effects("treatment=0.5, motivation=0.3")
|
|
336
358
|
model.set_variable_type("treatment=binary")
|
|
337
|
-
|
|
338
|
-
#
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
359
|
+
model.set_max_failed_simulations(0.10)
|
|
360
|
+
model.find_power(sample_size=1000) # 1000/20 = 50 per cluster
|
|
361
|
+
|
|
362
|
+
# Random slopes — effect varies across clusters
|
|
363
|
+
model = MCPower("y ~ x1 + (1 + x1|school)")
|
|
364
|
+
model.set_cluster("school", ICC=0.2, n_clusters=20,
|
|
365
|
+
random_slopes=["x1"], slope_variance=0.1,
|
|
366
|
+
slope_intercept_corr=0.3)
|
|
367
|
+
model.set_effects("x1=0.5")
|
|
368
|
+
model.set_max_failed_simulations(0.30)
|
|
342
369
|
model.find_power(sample_size=1000)
|
|
370
|
+
|
|
371
|
+
# Nested random effects — students in classrooms in schools
|
|
372
|
+
model = MCPower("y ~ treatment + (1|school/classroom)")
|
|
373
|
+
model.set_cluster("school", ICC=0.15, n_clusters=10)
|
|
374
|
+
model.set_cluster("classroom", ICC=0.10, n_per_parent=3) # 3 classrooms per school
|
|
375
|
+
model.set_effects("treatment=0.5")
|
|
376
|
+
model.set_max_failed_simulations(0.30)
|
|
377
|
+
model.find_power(sample_size=1500)
|
|
343
378
|
```
|
|
344
379
|
|
|
345
|
-
|
|
346
|
-
- Only random intercepts `(1|group)` — random slopes are not yet supported
|
|
347
|
-
- At least 25 observations per cluster and 10 observations per model parameter
|
|
348
|
-
- ICC must be between 0.1 and 0.9 (or exactly 0)
|
|
380
|
+
See the [Mixed-Effects Models wiki page](https://github.com/pawlenartowicz/MCPower/wiki/Mixed-Effects-Models) for detailed documentation on all model types, parameters, and design recommendations.
|
|
349
381
|
|
|
350
382
|
### More precision
|
|
351
383
|
```python
|
|
@@ -392,8 +424,11 @@ model.find_power(sample_size=200, progress_callback=False)
|
|
|
392
424
|
| **Factor variables** | **`model.set_variable_type("var=(factor,3)")`** |
|
|
393
425
|
| **Factor effects** | **`model.set_effects("var[2]=0.5, var[3]=0.7")`** |
|
|
394
426
|
| Correlated predictors | `model.set_correlations("corr(var1, var2)=0.4")` |
|
|
395
|
-
| Multiple testing correction | Add `correction="FDR"`, `"Holm"`, or `"
|
|
396
|
-
|
|
|
427
|
+
| Multiple testing correction | Add `correction="FDR"`, `"Holm"`, `"Bonferroni"`, or `"Tukey"`|
|
|
428
|
+
| Post-hoc pairwise comparison | `target_test="group[0] vs group[1]"` with `correction="tukey"` |
|
|
429
|
+
| Mixed model (random intercept) | `MCPower("y ~ x + (1\|group)")` + `model.set_cluster(...)` (experimental) |
|
|
430
|
+
| Random slopes | `MCPower("y ~ x + (1+x\|group)")` + `set_cluster(..., random_slopes=["x"], slope_variance=0.1)` (experimental) |
|
|
431
|
+
| Nested random effects | `MCPower("y ~ x + (1\|A/B)")` + two `set_cluster()` calls (experimental) |
|
|
397
432
|
| Reproducible results | `model.set_seed(42)` |
|
|
398
433
|
| Get results as dict | Add `return_results=True` to either method |
|
|
399
434
|
| Stricter significance | `model.set_alpha(0.01)` |
|
|
@@ -465,7 +500,7 @@ model.set_variable_type("""
|
|
|
465
500
|
### Factor Variables in Detail
|
|
466
501
|
```python
|
|
467
502
|
# Factor variables are categorical with multiple levels
|
|
468
|
-
model =
|
|
503
|
+
model = MCPower("outcome = treatment + education")
|
|
469
504
|
|
|
470
505
|
# Create factors
|
|
471
506
|
model.set_variable_type("treatment=(factor,3), education=(factor,4)")
|
|
@@ -539,9 +574,23 @@ model.set_correlations("(x1, x2)=0.3, (x1, x3)=-0.2")
|
|
|
539
574
|
- Python ≥ 3.10
|
|
540
575
|
- NumPy, SciPy, matplotlib, Pandas, joblib
|
|
541
576
|
- C++ compiler (automatically used during install for native backend; falls back to Python if unavailable)
|
|
542
|
-
- statsmodels (optional, for mixed-effects models — install with `pip install mcpower[
|
|
577
|
+
- statsmodels (optional, for mixed-effects models — install with `pip install mcpower[lme]`)
|
|
543
578
|
- Numba (optional, for JIT compilation fallback — install with `pip install mcpower[JIT]`)
|
|
544
579
|
|
|
580
|
+
|
|
581
|
+
## Documentation
|
|
582
|
+
|
|
583
|
+
Full documentation is available on the **[MCPower Wiki](https://github.com/pawlenartowicz/MCPower/wiki)**, including:
|
|
584
|
+
|
|
585
|
+
- [Quick Start](https://github.com/pawlenartowicz/MCPower/wiki/Quick-Start)
|
|
586
|
+
- [Model Specification](https://github.com/pawlenartowicz/MCPower/wiki/Model-Specification)
|
|
587
|
+
- [Variable Types](https://github.com/pawlenartowicz/MCPower/wiki/Variable-Types)
|
|
588
|
+
- [Effect Sizes](https://github.com/pawlenartowicz/MCPower/wiki/Effect-Sizes)
|
|
589
|
+
- [Mixed-Effects Models](https://github.com/pawlenartowicz/MCPower/wiki/Mixed-Effects-Models) (random intercepts, slopes, nested effects)
|
|
590
|
+
- [ANOVA & Post-Hoc Tests](https://github.com/pawlenartowicz/MCPower/wiki/ANOVA-and-Post-Hoc-Tests)
|
|
591
|
+
- [Scenario Analysis](https://github.com/pawlenartowicz/MCPower/wiki/Scenario-Analysis)
|
|
592
|
+
- [API Reference](https://github.com/pawlenartowicz/MCPower/wiki/API-Reference)
|
|
593
|
+
|
|
545
594
|
## Need Help?
|
|
546
595
|
|
|
547
596
|
- **Issues**: [GitHub Issues](https://github.com/pawlenartowicz/MCPower/issues)
|
|
@@ -552,7 +601,7 @@ model.set_correlations("(x1, x2)=0.3, (x1, x3)=-0.2")
|
|
|
552
601
|
- ✅ Scenarios, robustness analysis
|
|
553
602
|
- ✅ Factor variables (categorical predictors)
|
|
554
603
|
- ✅ C++ native backend (pybind11 + Eigen, 3x speedup)
|
|
555
|
-
-
|
|
604
|
+
- ⚠️ Mixed Effects Models (random intercepts, random slopes, nested effects) — experimental
|
|
556
605
|
- 🚧 Logistic Regression (coming soon)
|
|
557
606
|
- 🚧 ANOVA (coming soon)
|
|
558
607
|
- 🚧 Guide about methods, corrections (coming soon)
|