correlation-tost 0.1.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.
- correlation_tost-0.1.0/LICENSE +21 -0
- correlation_tost-0.1.0/PKG-INFO +79 -0
- correlation_tost-0.1.0/README.md +65 -0
- correlation_tost-0.1.0/pyproject.toml +23 -0
- correlation_tost-0.1.0/setup.cfg +4 -0
- correlation_tost-0.1.0/src/correlation_tost/__init__.py +3 -0
- correlation_tost-0.1.0/src/correlation_tost/_tost.py +96 -0
- correlation_tost-0.1.0/src/correlation_tost.egg-info/PKG-INFO +79 -0
- correlation_tost-0.1.0/src/correlation_tost.egg-info/SOURCES.txt +11 -0
- correlation_tost-0.1.0/src/correlation_tost.egg-info/dependency_links.txt +1 -0
- correlation_tost-0.1.0/src/correlation_tost.egg-info/requires.txt +5 -0
- correlation_tost-0.1.0/src/correlation_tost.egg-info/top_level.txt +1 -0
- correlation_tost-0.1.0/tests/test_tost.py +51 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Arijana Bohr
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: correlation-tost
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: TOST equivalence testing for Pearson correlations.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Requires-Python: >=3.9
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: numpy>=1.23
|
|
10
|
+
Requires-Dist: scipy>=1.9
|
|
11
|
+
Provides-Extra: test
|
|
12
|
+
Requires-Dist: pytest>=7; extra == "test"
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
# correlation-tost
|
|
16
|
+
|
|
17
|
+
A lightweight Python implementation of the two one-sided tests (TOST) procedure for equivalence testing of Pearson correlations.
|
|
18
|
+
|
|
19
|
+
The test uses Fisher's z transformation and has been numerically validated against R's `TOSTER::corsum_test`.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
Install directly from GitHub with `pip`:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install git+https://github.com/ArijanaBohr/correlation-tost.git
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Requirements:** Python ≥ 3.9, NumPy, SciPy
|
|
30
|
+
|
|
31
|
+
## Quick start
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from correlation_tost import pearson_tost
|
|
36
|
+
|
|
37
|
+
result = pearson_tost(
|
|
38
|
+
observed=0.30, # observed Pearson correlation
|
|
39
|
+
target=0.30, # target Pearson correlation
|
|
40
|
+
n=10_000, # sample size
|
|
41
|
+
margin=0.10, # equivalence margin
|
|
42
|
+
alpha=0.05, # significance level
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
print(result["equivalent"]) # True / False
|
|
46
|
+
print(result["p_value"]) # overall TOST p-value
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The equivalence bounds are defined in Pearson correlation units as
|
|
50
|
+
`[target - margin, target + margin]`. Fisher's z transformation is applied
|
|
51
|
+
internally when performing the test.
|
|
52
|
+
|
|
53
|
+
## Output
|
|
54
|
+
|
|
55
|
+
`pearson_tost` returns a dictionary with the following keys:
|
|
56
|
+
|
|
57
|
+
| Key | Description |
|
|
58
|
+
|----------------|---------------------------------------------------------------|
|
|
59
|
+
| `p_value` | Overall TOST p-value |
|
|
60
|
+
| `p_lower` | p-value for the lower-bound test |
|
|
61
|
+
| `p_upper` | p-value for the upper-bound test |
|
|
62
|
+
| `z_stat_lower` | Test statistic for the lower-bound test |
|
|
63
|
+
| `z_stat_upper` | Test statistic for the upper-bound test |
|
|
64
|
+
| `ci_lower` | Lower confidence interval bound |
|
|
65
|
+
| `ci_upper` | Upper confidence interval bound |
|
|
66
|
+
| `equivalent` | Whether equivalence is concluded at the specified `alpha` |
|
|
67
|
+
|
|
68
|
+
## Validation
|
|
69
|
+
|
|
70
|
+
The implementation was independently validated against
|
|
71
|
+
`TOSTER::corsum_test(method = "pearson", alternative = "equivalence")`
|
|
72
|
+
across:
|
|
73
|
+
|
|
74
|
+
- positive, negative, and zero correlations
|
|
75
|
+
- multiple sample sizes
|
|
76
|
+
- multiple equivalence margins
|
|
77
|
+
- a range of correlation strengths
|
|
78
|
+
|
|
79
|
+
The package itself does **not** depend on R, rpy2, or TOSTER.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# correlation-tost
|
|
2
|
+
|
|
3
|
+
A lightweight Python implementation of the two one-sided tests (TOST) procedure for equivalence testing of Pearson correlations.
|
|
4
|
+
|
|
5
|
+
The test uses Fisher's z transformation and has been numerically validated against R's `TOSTER::corsum_test`.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Install directly from GitHub with `pip`:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install git+https://github.com/ArijanaBohr/correlation-tost.git
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
**Requirements:** Python ≥ 3.9, NumPy, SciPy
|
|
16
|
+
|
|
17
|
+
## Quick start
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from correlation_tost import pearson_tost
|
|
22
|
+
|
|
23
|
+
result = pearson_tost(
|
|
24
|
+
observed=0.30, # observed Pearson correlation
|
|
25
|
+
target=0.30, # target Pearson correlation
|
|
26
|
+
n=10_000, # sample size
|
|
27
|
+
margin=0.10, # equivalence margin
|
|
28
|
+
alpha=0.05, # significance level
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
print(result["equivalent"]) # True / False
|
|
32
|
+
print(result["p_value"]) # overall TOST p-value
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The equivalence bounds are defined in Pearson correlation units as
|
|
36
|
+
`[target - margin, target + margin]`. Fisher's z transformation is applied
|
|
37
|
+
internally when performing the test.
|
|
38
|
+
|
|
39
|
+
## Output
|
|
40
|
+
|
|
41
|
+
`pearson_tost` returns a dictionary with the following keys:
|
|
42
|
+
|
|
43
|
+
| Key | Description |
|
|
44
|
+
|----------------|---------------------------------------------------------------|
|
|
45
|
+
| `p_value` | Overall TOST p-value |
|
|
46
|
+
| `p_lower` | p-value for the lower-bound test |
|
|
47
|
+
| `p_upper` | p-value for the upper-bound test |
|
|
48
|
+
| `z_stat_lower` | Test statistic for the lower-bound test |
|
|
49
|
+
| `z_stat_upper` | Test statistic for the upper-bound test |
|
|
50
|
+
| `ci_lower` | Lower confidence interval bound |
|
|
51
|
+
| `ci_upper` | Upper confidence interval bound |
|
|
52
|
+
| `equivalent` | Whether equivalence is concluded at the specified `alpha` |
|
|
53
|
+
|
|
54
|
+
## Validation
|
|
55
|
+
|
|
56
|
+
The implementation was independently validated against
|
|
57
|
+
`TOSTER::corsum_test(method = "pearson", alternative = "equivalence")`
|
|
58
|
+
across:
|
|
59
|
+
|
|
60
|
+
- positive, negative, and zero correlations
|
|
61
|
+
- multiple sample sizes
|
|
62
|
+
- multiple equivalence margins
|
|
63
|
+
- a range of correlation strengths
|
|
64
|
+
|
|
65
|
+
The package itself does **not** depend on R, rpy2, or TOSTER.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "correlation-tost"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
license = "MIT"
|
|
9
|
+
description = "TOST equivalence testing for Pearson correlations."
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"numpy>=1.23",
|
|
14
|
+
"scipy>=1.9",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.optional-dependencies]
|
|
18
|
+
test = [
|
|
19
|
+
"pytest>=7",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[tool.setuptools.packages.find]
|
|
23
|
+
where = ["src"]
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from scipy.stats import norm
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def pearson_tost(
|
|
8
|
+
observed: float,
|
|
9
|
+
target: float,
|
|
10
|
+
n: int,
|
|
11
|
+
margin: float = 0.10,
|
|
12
|
+
alpha: float = 0.05,
|
|
13
|
+
) -> dict[str, float | bool]:
|
|
14
|
+
"""Test equivalence between an observed and target Pearson correlation.
|
|
15
|
+
|
|
16
|
+
Equivalence is assessed using two one-sided tests (TOST) after applying
|
|
17
|
+
Fisher's z transformation to the observed correlation and equivalence
|
|
18
|
+
bounds.
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
observed
|
|
23
|
+
Observed Pearson correlation coefficient.
|
|
24
|
+
target
|
|
25
|
+
Target Pearson correlation coefficient.
|
|
26
|
+
n
|
|
27
|
+
Sample size used to estimate the observed correlation.
|
|
28
|
+
margin
|
|
29
|
+
Equivalence margin in Pearson correlation units. The equivalence
|
|
30
|
+
interval is ``[target - margin, target + margin]``.
|
|
31
|
+
alpha
|
|
32
|
+
Significance level for each one-sided test.
|
|
33
|
+
|
|
34
|
+
Returns
|
|
35
|
+
-------
|
|
36
|
+
dict
|
|
37
|
+
Test p-value, one-sided p-values, test statistics, confidence
|
|
38
|
+
interval, and equivalence decision.
|
|
39
|
+
"""
|
|
40
|
+
if n <= 3:
|
|
41
|
+
raise ValueError("n must be greater than 3.")
|
|
42
|
+
|
|
43
|
+
if not -1 <= observed <= 1:
|
|
44
|
+
raise ValueError("observed must be between -1 and 1.")
|
|
45
|
+
|
|
46
|
+
if not -1 <= target <= 1:
|
|
47
|
+
raise ValueError("target must be between -1 and 1.")
|
|
48
|
+
|
|
49
|
+
if margin <= 0:
|
|
50
|
+
raise ValueError("margin must be greater than 0.")
|
|
51
|
+
|
|
52
|
+
if not 0 < alpha < 0.5:
|
|
53
|
+
raise ValueError("alpha must be between 0 and 0.5.")
|
|
54
|
+
|
|
55
|
+
eps = 1e-12
|
|
56
|
+
|
|
57
|
+
observed = float(np.clip(observed, -1 + eps, 1 - eps))
|
|
58
|
+
lower = float(np.clip(target - margin, -1 + eps, 1 - eps))
|
|
59
|
+
upper = float(np.clip(target + margin, -1 + eps, 1 - eps))
|
|
60
|
+
|
|
61
|
+
z_observed = np.arctanh(observed)
|
|
62
|
+
z_lower = np.arctanh(lower)
|
|
63
|
+
z_upper = np.arctanh(upper)
|
|
64
|
+
|
|
65
|
+
se = 1.0 / np.sqrt(n - 3)
|
|
66
|
+
|
|
67
|
+
# Lower one-sided test:
|
|
68
|
+
# H0: rho <= lower
|
|
69
|
+
# H1: rho > lower
|
|
70
|
+
z_stat_lower = (z_observed - z_lower) / se
|
|
71
|
+
p_lower = norm.sf(z_stat_lower)
|
|
72
|
+
|
|
73
|
+
# Upper one-sided test:
|
|
74
|
+
# H0: rho >= upper
|
|
75
|
+
# H1: rho < upper
|
|
76
|
+
z_stat_upper = (z_observed - z_upper) / se
|
|
77
|
+
p_upper = norm.cdf(z_stat_upper)
|
|
78
|
+
|
|
79
|
+
# Both one-sided tests must reject for equivalence.
|
|
80
|
+
p_value = max(p_lower, p_upper)
|
|
81
|
+
|
|
82
|
+
# 100 * (1 - 2 * alpha)% confidence interval.
|
|
83
|
+
z_crit = norm.ppf(1 - alpha)
|
|
84
|
+
ci_lower = np.tanh(z_observed - z_crit * se)
|
|
85
|
+
ci_upper = np.tanh(z_observed + z_crit * se)
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
"p_value": float(p_value),
|
|
89
|
+
"p_lower": float(p_lower),
|
|
90
|
+
"p_upper": float(p_upper),
|
|
91
|
+
"z_stat_lower": float(z_stat_lower),
|
|
92
|
+
"z_stat_upper": float(z_stat_upper),
|
|
93
|
+
"ci_lower": float(ci_lower),
|
|
94
|
+
"ci_upper": float(ci_upper),
|
|
95
|
+
"equivalent": bool(p_value < alpha),
|
|
96
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: correlation-tost
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: TOST equivalence testing for Pearson correlations.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Requires-Python: >=3.9
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: numpy>=1.23
|
|
10
|
+
Requires-Dist: scipy>=1.9
|
|
11
|
+
Provides-Extra: test
|
|
12
|
+
Requires-Dist: pytest>=7; extra == "test"
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
# correlation-tost
|
|
16
|
+
|
|
17
|
+
A lightweight Python implementation of the two one-sided tests (TOST) procedure for equivalence testing of Pearson correlations.
|
|
18
|
+
|
|
19
|
+
The test uses Fisher's z transformation and has been numerically validated against R's `TOSTER::corsum_test`.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
Install directly from GitHub with `pip`:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install git+https://github.com/ArijanaBohr/correlation-tost.git
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Requirements:** Python ≥ 3.9, NumPy, SciPy
|
|
30
|
+
|
|
31
|
+
## Quick start
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from correlation_tost import pearson_tost
|
|
36
|
+
|
|
37
|
+
result = pearson_tost(
|
|
38
|
+
observed=0.30, # observed Pearson correlation
|
|
39
|
+
target=0.30, # target Pearson correlation
|
|
40
|
+
n=10_000, # sample size
|
|
41
|
+
margin=0.10, # equivalence margin
|
|
42
|
+
alpha=0.05, # significance level
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
print(result["equivalent"]) # True / False
|
|
46
|
+
print(result["p_value"]) # overall TOST p-value
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The equivalence bounds are defined in Pearson correlation units as
|
|
50
|
+
`[target - margin, target + margin]`. Fisher's z transformation is applied
|
|
51
|
+
internally when performing the test.
|
|
52
|
+
|
|
53
|
+
## Output
|
|
54
|
+
|
|
55
|
+
`pearson_tost` returns a dictionary with the following keys:
|
|
56
|
+
|
|
57
|
+
| Key | Description |
|
|
58
|
+
|----------------|---------------------------------------------------------------|
|
|
59
|
+
| `p_value` | Overall TOST p-value |
|
|
60
|
+
| `p_lower` | p-value for the lower-bound test |
|
|
61
|
+
| `p_upper` | p-value for the upper-bound test |
|
|
62
|
+
| `z_stat_lower` | Test statistic for the lower-bound test |
|
|
63
|
+
| `z_stat_upper` | Test statistic for the upper-bound test |
|
|
64
|
+
| `ci_lower` | Lower confidence interval bound |
|
|
65
|
+
| `ci_upper` | Upper confidence interval bound |
|
|
66
|
+
| `equivalent` | Whether equivalence is concluded at the specified `alpha` |
|
|
67
|
+
|
|
68
|
+
## Validation
|
|
69
|
+
|
|
70
|
+
The implementation was independently validated against
|
|
71
|
+
`TOSTER::corsum_test(method = "pearson", alternative = "equivalence")`
|
|
72
|
+
across:
|
|
73
|
+
|
|
74
|
+
- positive, negative, and zero correlations
|
|
75
|
+
- multiple sample sizes
|
|
76
|
+
- multiple equivalence margins
|
|
77
|
+
- a range of correlation strengths
|
|
78
|
+
|
|
79
|
+
The package itself does **not** depend on R, rpy2, or TOSTER.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/correlation_tost/__init__.py
|
|
5
|
+
src/correlation_tost/_tost.py
|
|
6
|
+
src/correlation_tost.egg-info/PKG-INFO
|
|
7
|
+
src/correlation_tost.egg-info/SOURCES.txt
|
|
8
|
+
src/correlation_tost.egg-info/dependency_links.txt
|
|
9
|
+
src/correlation_tost.egg-info/requires.txt
|
|
10
|
+
src/correlation_tost.egg-info/top_level.txt
|
|
11
|
+
tests/test_tost.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
correlation_tost
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from correlation_tost import pearson_tost
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_matches_toster_reference_case():
|
|
7
|
+
"""Match a reference result from TOSTER::corsum_test."""
|
|
8
|
+
result = pearson_tost(
|
|
9
|
+
observed=0.30,
|
|
10
|
+
target=0.30,
|
|
11
|
+
n=10_000,
|
|
12
|
+
margin=0.10,
|
|
13
|
+
alpha=0.05,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
assert result["p_value"] == pytest.approx(
|
|
17
|
+
6.513681924396496e-27,
|
|
18
|
+
rel=1e-12,
|
|
19
|
+
)
|
|
20
|
+
assert result["ci_lower"] == pytest.approx(
|
|
21
|
+
0.2849567,
|
|
22
|
+
abs=1e-7,
|
|
23
|
+
)
|
|
24
|
+
assert result["ci_upper"] == pytest.approx(
|
|
25
|
+
0.3148956,
|
|
26
|
+
abs=1e-7,
|
|
27
|
+
)
|
|
28
|
+
assert result["equivalent"] is True
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.mark.parametrize(
|
|
32
|
+
"kwargs, message",
|
|
33
|
+
[
|
|
34
|
+
({"n": 3}, "n must be greater than 3"),
|
|
35
|
+
({"observed": 1.1}, "observed must be between -1 and 1"),
|
|
36
|
+
({"target": -1.1}, "target must be between -1 and 1"),
|
|
37
|
+
({"margin": 0.0}, "margin must be greater than 0"),
|
|
38
|
+
({"alpha": 0.0}, "alpha must be between 0 and 0.5"),
|
|
39
|
+
({"alpha": 0.5}, "alpha must be between 0 and 0.5"),
|
|
40
|
+
],
|
|
41
|
+
)
|
|
42
|
+
def test_invalid_inputs(kwargs, message):
|
|
43
|
+
params = {
|
|
44
|
+
"observed": 0.30,
|
|
45
|
+
"target": 0.30,
|
|
46
|
+
"n": 100,
|
|
47
|
+
}
|
|
48
|
+
params.update(kwargs)
|
|
49
|
+
|
|
50
|
+
with pytest.raises(ValueError, match=message):
|
|
51
|
+
pearson_tost(**params)
|