AutoStatLib 0.4.2__tar.gz → 0.4.4__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.
- {autostatlib-0.4.2/src/AutoStatLib.egg-info → autostatlib-0.4.4}/PKG-INFO +3 -3
- {autostatlib-0.4.2 → autostatlib-0.4.4}/README.md +1 -1
- {autostatlib-0.4.2 → autostatlib-0.4.4}/requirements.txt +1 -1
- {autostatlib-0.4.2 → autostatlib-0.4.4}/src/AutoStatLib/StatPlots.py +5 -8
- {autostatlib-0.4.2 → autostatlib-0.4.4}/src/AutoStatLib/_version.py +1 -1
- {autostatlib-0.4.2 → autostatlib-0.4.4}/src/AutoStatLib/helpers.py +1 -1
- autostatlib-0.4.4/src/AutoStatLib/normality_tests.py +226 -0
- {autostatlib-0.4.2 → autostatlib-0.4.4/src/AutoStatLib.egg-info}/PKG-INFO +3 -3
- {autostatlib-0.4.2 → autostatlib-0.4.4}/src/AutoStatLib.egg-info/requires.txt +1 -1
- autostatlib-0.4.2/src/AutoStatLib/normality_tests.py +0 -90
- {autostatlib-0.4.2 → autostatlib-0.4.4}/LICENSE +0 -0
- {autostatlib-0.4.2 → autostatlib-0.4.4}/MANIFEST.in +0 -0
- {autostatlib-0.4.2 → autostatlib-0.4.4}/pyproject.toml +0 -0
- {autostatlib-0.4.2 → autostatlib-0.4.4}/setup.cfg +0 -0
- {autostatlib-0.4.2 → autostatlib-0.4.4}/src/AutoStatLib/AutoStatLib.py +0 -0
- {autostatlib-0.4.2 → autostatlib-0.4.4}/src/AutoStatLib/__init__.py +0 -0
- {autostatlib-0.4.2 → autostatlib-0.4.4}/src/AutoStatLib/__main__.py +0 -0
- {autostatlib-0.4.2 → autostatlib-0.4.4}/src/AutoStatLib/_protocol.py +0 -0
- {autostatlib-0.4.2 → autostatlib-0.4.4}/src/AutoStatLib/statistical_tests.py +0 -0
- {autostatlib-0.4.2 → autostatlib-0.4.4}/src/AutoStatLib/text_formatting.py +0 -0
- {autostatlib-0.4.2 → autostatlib-0.4.4}/src/AutoStatLib.egg-info/SOURCES.txt +0 -0
- {autostatlib-0.4.2 → autostatlib-0.4.4}/src/AutoStatLib.egg-info/dependency_links.txt +0 -0
- {autostatlib-0.4.2 → autostatlib-0.4.4}/src/AutoStatLib.egg-info/top_level.txt +0 -0
- {autostatlib-0.4.2 → autostatlib-0.4.4}/tests/test_autostatlib.py +0 -0
- {autostatlib-0.4.2 → autostatlib-0.4.4}/tests/test_statplots.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: AutoStatLib
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.4
|
|
4
4
|
Summary: AutoStatLib - a simple statistical analysis tool
|
|
5
5
|
Author: Stemonitis, SciWare LLC
|
|
6
6
|
Author-email: konung-yaropolk <yaropolk1995@gmail.com>
|
|
@@ -25,7 +25,7 @@ Requires-Python: >=3.10
|
|
|
25
25
|
Description-Content-Type: text/markdown
|
|
26
26
|
License-File: LICENSE
|
|
27
27
|
Requires-Dist: numpy
|
|
28
|
-
Requires-Dist: scipy
|
|
28
|
+
Requires-Dist: scipy>=1.9
|
|
29
29
|
Requires-Dist: statsmodels
|
|
30
30
|
Requires-Dist: matplotlib
|
|
31
31
|
Requires-Dist: seaborn
|
|
@@ -35,7 +35,7 @@ Dynamic: license-file
|
|
|
35
35
|
|
|
36
36
|
# AutoStatLib - python library for automated statistical analysis
|
|
37
37
|
|
|
38
|
-
[](https://github.com/konung-yaropolk/AutoStatLib/actions/workflows/tests.yml)
|
|
39
39
|
[](https://pypi.org/project/AutoStatLib)
|
|
40
40
|
[](https://github.com/konung-yaropolk/AutoStatLib)
|
|
41
41
|
[](https://pypi.org/project/AutoStatLib)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# AutoStatLib - python library for automated statistical analysis
|
|
2
2
|
|
|
3
|
-
[](https://github.com/konung-yaropolk/AutoStatLib/actions/workflows/tests.yml)
|
|
4
4
|
[](https://pypi.org/project/AutoStatLib)
|
|
5
5
|
[](https://github.com/konung-yaropolk/AutoStatLib)
|
|
6
6
|
[](https://pypi.org/project/AutoStatLib)
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from typing import Optional
|
|
4
|
-
import random
|
|
2
|
+
from typing import Literal, Optional
|
|
5
3
|
|
|
6
4
|
import matplotlib.axes
|
|
7
5
|
import matplotlib.colors as mcolors
|
|
@@ -9,7 +7,6 @@ import matplotlib.colors as color
|
|
|
9
7
|
import matplotlib.figure
|
|
10
8
|
import matplotlib.pyplot as plt
|
|
11
9
|
import numpy as np
|
|
12
|
-
import pandas as pd
|
|
13
10
|
import seaborn as sns
|
|
14
11
|
|
|
15
12
|
|
|
@@ -246,7 +243,7 @@ class BaseStatPlot(Helpers):
|
|
|
246
243
|
x: int,
|
|
247
244
|
linewidth: float = 2,
|
|
248
245
|
widths: float = 0.85,
|
|
249
|
-
|
|
246
|
+
orientation: Literal["vertical", "horizontal"] = "vertical",
|
|
250
247
|
showmeans: bool = False,
|
|
251
248
|
showmedians: bool = False,
|
|
252
249
|
showextrema: bool = False,
|
|
@@ -257,7 +254,7 @@ class BaseStatPlot(Helpers):
|
|
|
257
254
|
self.data_groups[x],
|
|
258
255
|
positions=[x],
|
|
259
256
|
widths=widths,
|
|
260
|
-
|
|
257
|
+
orientation=orientation,
|
|
261
258
|
showmeans=showmeans,
|
|
262
259
|
showmedians=showmedians,
|
|
263
260
|
showextrema=showextrema,
|
|
@@ -284,7 +281,7 @@ class BaseStatPlot(Helpers):
|
|
|
284
281
|
flierEdgeColor: Optional[str] = None,
|
|
285
282
|
flierLineWidth: float = 2,
|
|
286
283
|
flierLineStyle: Optional[str] = None,
|
|
287
|
-
|
|
284
|
+
orientation: Literal["vertical", "horizontal"] = "vertical",
|
|
288
285
|
# whiskers when one float is tukeys parameter, when a pair of percentages,
|
|
289
286
|
# defines the percentiles where the whiskers should be If a float,
|
|
290
287
|
# the lower whisker is at the lowest datum above Q1 - whis*(Q3-Q1),
|
|
@@ -403,7 +400,7 @@ class BaseStatPlot(Helpers):
|
|
|
403
400
|
conf_intervals=confidences,
|
|
404
401
|
sym=fliersMarker,
|
|
405
402
|
flierprops=flierProps,
|
|
406
|
-
|
|
403
|
+
orientation=orientation,
|
|
407
404
|
whis=whiskers,
|
|
408
405
|
whiskerprops=whiskersProps,
|
|
409
406
|
showcaps=showWhiskersCaps,
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# AutoStatLib package version:
|
|
2
|
-
__version__ = "0.4.
|
|
2
|
+
__version__ = "0.4.4"
|
|
@@ -74,7 +74,7 @@ class Helpers(StatAnalysisProtocol):
|
|
|
74
74
|
groups_mean = [float(a.mean()) for a in groups_arr]
|
|
75
75
|
groups_median = [float(np.median(a)) for a in groups_arr]
|
|
76
76
|
groups_sd = [float(a.std(ddof=1)) for a in groups_arr]
|
|
77
|
-
groups_se = [sd / np.sqrt(n) for sd, n in zip(groups_sd, groups_n)]
|
|
77
|
+
groups_se = [sd / np.sqrt(n).item() for sd, n in zip(groups_sd, groups_n)]
|
|
78
78
|
|
|
79
79
|
# --- Posthoc matrix representations — one pass over the matrix -----
|
|
80
80
|
# Previously built as three separate nested list comprehensions;
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from AutoStatLib._protocol import StatAnalysisProtocol
|
|
4
|
+
|
|
5
|
+
from typing import Literal, Optional
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
from numpy.random import Generator
|
|
9
|
+
from scipy.stats import (
|
|
10
|
+
MonteCarloMethod,
|
|
11
|
+
anderson,
|
|
12
|
+
goodness_of_fit,
|
|
13
|
+
norm,
|
|
14
|
+
normaltest,
|
|
15
|
+
shapiro,
|
|
16
|
+
)
|
|
17
|
+
from statsmodels.stats.diagnostic import lilliefors
|
|
18
|
+
|
|
19
|
+
NormalityMethod = Literal["interpolate", "monte_carlo"]
|
|
20
|
+
_NORMALITY_METHODS: tuple[str, ...] = ("interpolate", "monte_carlo")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class NormalityTests(StatAnalysisProtocol):
|
|
24
|
+
"""
|
|
25
|
+
Normality tests mixin.
|
|
26
|
+
|
|
27
|
+
Test selection follows:
|
|
28
|
+
Power comparisons of Shapiro-Wilk, Kolmogorov-Smirnov,
|
|
29
|
+
Lilliefors and Anderson-Darling tests,
|
|
30
|
+
Nornadiah Mohd Razali, Yap Bee Wah.
|
|
31
|
+
|
|
32
|
+
The Kolmogorov-Smirnov/Lilliefors and Anderson-Darling tests each
|
|
33
|
+
support two ways of getting a p-value, selected with `method`:
|
|
34
|
+
|
|
35
|
+
"interpolate" (default): fast, O(1), table/formula-based. Lilliefors
|
|
36
|
+
uses statsmodels' Dallal & Wilkinson (1986) table interpolation;
|
|
37
|
+
Anderson-Darling uses `scipy.stats.anderson`'s own critical-value
|
|
38
|
+
table together with the closed-form continuous approximation from
|
|
39
|
+
Lorentz Jantschi & Sorana D. Bolboaca, *Computation of Probability
|
|
40
|
+
Associated with Anderson-Darling Statistic*, Mathematics, 2018
|
|
41
|
+
(scipy's own table lookup is accurate but clipped to [0.01, 0.15];
|
|
42
|
+
this formula extrapolates beyond that range).
|
|
43
|
+
|
|
44
|
+
"monte_carlo": slower, (asymptotically) exact. Both loc and scale are
|
|
45
|
+
fit by MLE on the sample (that's what makes these "Lilliefors" /
|
|
46
|
+
a proper Anderson-Darling test rather than tests against a fully
|
|
47
|
+
specified normal), and the p-value comes from simulating that
|
|
48
|
+
statistic's true null distribution via parametric bootstrap
|
|
49
|
+
(`scipy.stats.goodness_of_fit` for KS, `scipy.stats.anderson` with
|
|
50
|
+
a `MonteCarloMethod` for AD), seeded from a NumPy `Generator`,
|
|
51
|
+
rather than read off a table/formula. Use this when the table
|
|
52
|
+
approximation is questionable (e.g. a statistic landing right on
|
|
53
|
+
a table boundary) or when you need a precise p-value rather than
|
|
54
|
+
just a pass/fail at alpha=0.05 — see `_N_MC_SAMPLES` for the
|
|
55
|
+
speed/precision trade-off.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
# Monte Carlo replicates used to build the null distribution when
|
|
59
|
+
# method="monte_carlo". 9999 is scipy's own default (p-values accurate
|
|
60
|
+
# to ~1e-4). Runtime grows with both this value and `n`; for n in the
|
|
61
|
+
# hundreds expect well under a second per test, for n in the thousands
|
|
62
|
+
# expect several seconds. Lower this if you need speed over precision.
|
|
63
|
+
_N_MC_SAMPLES: int = 9999
|
|
64
|
+
|
|
65
|
+
def check_normality(
|
|
66
|
+
self,
|
|
67
|
+
data: list[float],
|
|
68
|
+
method: NormalityMethod = "interpolate",
|
|
69
|
+
random_state: Optional[Generator] = None,
|
|
70
|
+
) -> tuple[Optional[bool], Optional[bool], Optional[bool], Optional[bool]]:
|
|
71
|
+
"""
|
|
72
|
+
Run up to four normality tests on a single group.
|
|
73
|
+
|
|
74
|
+
Parameters:
|
|
75
|
+
data: sample to test.
|
|
76
|
+
method: "interpolate" (default) for fast table/formula-based
|
|
77
|
+
p-values, or "monte_carlo" for slower, (asymptotically)
|
|
78
|
+
exact p-values. Applies to the Lilliefors and
|
|
79
|
+
Anderson-Darling tests; see the class docstring.
|
|
80
|
+
random_state: NumPy `Generator` driving the Monte Carlo
|
|
81
|
+
simulation when method="monte_carlo". Pass one in for
|
|
82
|
+
reproducible results across calls; otherwise a fresh
|
|
83
|
+
`default_rng()` is created. Ignored when
|
|
84
|
+
method="interpolate".
|
|
85
|
+
|
|
86
|
+
Returns a tuple (sw, lf, ad, ap) where each element is:
|
|
87
|
+
True — test passed (p > 0.05, distribution appears normal)
|
|
88
|
+
False — test failed
|
|
89
|
+
None — sample too small to run the test reliably (n < 20)
|
|
90
|
+
"""
|
|
91
|
+
sw: Optional[bool] = None
|
|
92
|
+
lf: Optional[bool] = None
|
|
93
|
+
ad: Optional[bool] = None
|
|
94
|
+
ap: Optional[bool] = None
|
|
95
|
+
n: int = len(data)
|
|
96
|
+
|
|
97
|
+
# Shapiro-Wilk test — scipy's Royston (AS R94) p-value is already
|
|
98
|
+
# the correct approach here, no change needed.
|
|
99
|
+
_sw_stat, sw_p_value = shapiro(data)
|
|
100
|
+
sw = bool(sw_p_value and sw_p_value > 0.05)
|
|
101
|
+
|
|
102
|
+
# Lilliefors test: KS statistic against a normal fit to the data.
|
|
103
|
+
_lf_stat, lf_p_value = self._ks_lilliefors(
|
|
104
|
+
data, method=method, random_state=random_state
|
|
105
|
+
)
|
|
106
|
+
lf = bool(lf_p_value and lf_p_value > 0.05)
|
|
107
|
+
|
|
108
|
+
# Anderson-Darling test (requires n >= 20)
|
|
109
|
+
if n >= 20:
|
|
110
|
+
_ad_stat, ad_p_value = self.anderson_get_p(
|
|
111
|
+
data, method=method, random_state=random_state
|
|
112
|
+
)
|
|
113
|
+
ad = bool(ad_p_value and ad_p_value > 0.05)
|
|
114
|
+
|
|
115
|
+
# D'Agostino-Pearson test (unreliable for n < 20). This is a
|
|
116
|
+
# skewness/kurtosis statistic with a known asymptotic chi-square
|
|
117
|
+
# null distribution — scipy's `normaltest` p-value is already the
|
|
118
|
+
# standard, correct approach and doesn't need Monte Carlo.
|
|
119
|
+
if n >= 20:
|
|
120
|
+
_ap_stat, ap_p_value = normaltest(data)
|
|
121
|
+
ap = bool(ap_p_value and ap_p_value > 0.05)
|
|
122
|
+
|
|
123
|
+
return (sw, lf, ad, ap)
|
|
124
|
+
|
|
125
|
+
def _ks_lilliefors(
|
|
126
|
+
self,
|
|
127
|
+
data: list[float],
|
|
128
|
+
method: NormalityMethod = "interpolate",
|
|
129
|
+
random_state: Optional[Generator] = None,
|
|
130
|
+
n_mc_samples: Optional[int] = None,
|
|
131
|
+
) -> tuple[float, float]:
|
|
132
|
+
"""
|
|
133
|
+
KS test for normality with the Lilliefors correction, i.e. loc/scale
|
|
134
|
+
estimated from `data` rather than assumed known.
|
|
135
|
+
|
|
136
|
+
method="interpolate" (default, fast): statsmodels' Lilliefors /
|
|
137
|
+
Dallal & Wilkinson (1986) table-interpolated p-value.
|
|
138
|
+
method="monte_carlo" (slow, exact): p-value from simulating the
|
|
139
|
+
statistic's true null distribution via parametric bootstrap
|
|
140
|
+
(`scipy.stats.goodness_of_fit`).
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
(ks_statistic, p_value)
|
|
144
|
+
"""
|
|
145
|
+
if method not in _NORMALITY_METHODS:
|
|
146
|
+
raise ValueError(
|
|
147
|
+
f"Unknown method {method!r}; expected one of {_NORMALITY_METHODS}."
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
if method == "monte_carlo":
|
|
151
|
+
rng: Generator = (
|
|
152
|
+
random_state if random_state is not None else np.random.default_rng()
|
|
153
|
+
)
|
|
154
|
+
result = goodness_of_fit(
|
|
155
|
+
norm,
|
|
156
|
+
data,
|
|
157
|
+
statistic="ks",
|
|
158
|
+
n_mc_samples=n_mc_samples or self._N_MC_SAMPLES,
|
|
159
|
+
random_state=rng,
|
|
160
|
+
)
|
|
161
|
+
return float(result.statistic), float(result.pvalue)
|
|
162
|
+
|
|
163
|
+
lf_stat, lf_p_value = lilliefors(data, dist="norm")
|
|
164
|
+
return float(lf_stat), float(lf_p_value)
|
|
165
|
+
|
|
166
|
+
def anderson_get_p(
|
|
167
|
+
self,
|
|
168
|
+
data: list[float],
|
|
169
|
+
method: NormalityMethod = "interpolate",
|
|
170
|
+
random_state: Optional[Generator] = None,
|
|
171
|
+
n_mc_samples: Optional[int] = None,
|
|
172
|
+
) -> tuple[float, Optional[float]]:
|
|
173
|
+
"""
|
|
174
|
+
Anderson-Darling test for normality, with loc/scale estimated from
|
|
175
|
+
`data`.
|
|
176
|
+
|
|
177
|
+
method="interpolate" (default, fast): the AD statistic comes from
|
|
178
|
+
`scipy.stats.anderson(..., method="interpolate")`, and is
|
|
179
|
+
converted to a continuous p-value with the closed-form
|
|
180
|
+
approximation from Lorentz Jantschi & Sorana D. Bolboaca,
|
|
181
|
+
*Computation of Probability Associated with Anderson-Darling
|
|
182
|
+
Statistic*, Mathematics, 2018 (scipy's own table lookup is
|
|
183
|
+
accurate but clipped to [0.01, 0.15]; this extrapolates
|
|
184
|
+
beyond that range).
|
|
185
|
+
method="monte_carlo" (slow, exact): both statistic and p-value
|
|
186
|
+
come from `scipy.stats.anderson` with a `MonteCarloMethod`,
|
|
187
|
+
which simulates the statistic's true null distribution
|
|
188
|
+
(refitting loc/scale on each simulated draw) instead of
|
|
189
|
+
reading off the table.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
(ad_statistic, p_value)
|
|
193
|
+
"""
|
|
194
|
+
if method not in _NORMALITY_METHODS:
|
|
195
|
+
raise ValueError(
|
|
196
|
+
f"Unknown method {method!r}; expected one of {_NORMALITY_METHODS}."
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
if method == "monte_carlo":
|
|
200
|
+
rng: Generator = (
|
|
201
|
+
random_state if random_state is not None else np.random.default_rng()
|
|
202
|
+
)
|
|
203
|
+
mc_method = MonteCarloMethod(
|
|
204
|
+
n_resamples=n_mc_samples or self._N_MC_SAMPLES, rng=rng
|
|
205
|
+
)
|
|
206
|
+
result = anderson(data, dist="norm", method=mc_method)
|
|
207
|
+
return float(result.statistic), float(result.pvalue)
|
|
208
|
+
|
|
209
|
+
n: int = len(data)
|
|
210
|
+
ad: float = float(anderson(data, dist="norm", method="interpolate").statistic)
|
|
211
|
+
|
|
212
|
+
# Adjust statistic for small sample sizes, then map to a
|
|
213
|
+
# continuous p-value (Jantschi & Bolboaca, 2018).
|
|
214
|
+
s: float = ad * (1 + 0.75 / n + 2.25 / (n**2))
|
|
215
|
+
|
|
216
|
+
p: float
|
|
217
|
+
if s >= 0.6:
|
|
218
|
+
p = np.e ** (1.2937 - 5.709 * s + 0.0186 * s**2)
|
|
219
|
+
elif s > 0.34:
|
|
220
|
+
p = np.e ** (0.9177 - 4.279 * s - 1.38 * s**2)
|
|
221
|
+
elif s > 0.2:
|
|
222
|
+
p = 1 - np.e ** (-8.318 + 42.796 * s - 59.938 * s**2)
|
|
223
|
+
else:
|
|
224
|
+
p = 1 - np.e ** (-13.436 + 101.14 * s - 223.73 * s**2)
|
|
225
|
+
|
|
226
|
+
return ad, p
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: AutoStatLib
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.4
|
|
4
4
|
Summary: AutoStatLib - a simple statistical analysis tool
|
|
5
5
|
Author: Stemonitis, SciWare LLC
|
|
6
6
|
Author-email: konung-yaropolk <yaropolk1995@gmail.com>
|
|
@@ -25,7 +25,7 @@ Requires-Python: >=3.10
|
|
|
25
25
|
Description-Content-Type: text/markdown
|
|
26
26
|
License-File: LICENSE
|
|
27
27
|
Requires-Dist: numpy
|
|
28
|
-
Requires-Dist: scipy
|
|
28
|
+
Requires-Dist: scipy>=1.9
|
|
29
29
|
Requires-Dist: statsmodels
|
|
30
30
|
Requires-Dist: matplotlib
|
|
31
31
|
Requires-Dist: seaborn
|
|
@@ -35,7 +35,7 @@ Dynamic: license-file
|
|
|
35
35
|
|
|
36
36
|
# AutoStatLib - python library for automated statistical analysis
|
|
37
37
|
|
|
38
|
-
[](https://github.com/konung-yaropolk/AutoStatLib/actions/workflows/tests.yml)
|
|
39
39
|
[](https://pypi.org/project/AutoStatLib)
|
|
40
40
|
[](https://github.com/konung-yaropolk/AutoStatLib)
|
|
41
41
|
[](https://pypi.org/project/AutoStatLib)
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from AutoStatLib._protocol import StatAnalysisProtocol
|
|
4
|
-
|
|
5
|
-
from typing import Optional
|
|
6
|
-
|
|
7
|
-
import numpy as np
|
|
8
|
-
from scipy.stats import shapiro, normaltest, anderson
|
|
9
|
-
from statsmodels.stats.diagnostic import lilliefors
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class NormalityTests(StatAnalysisProtocol):
|
|
13
|
-
"""
|
|
14
|
-
Normality tests mixin.
|
|
15
|
-
|
|
16
|
-
See the article about minimal sample size for tests:
|
|
17
|
-
Power comparisons of Shapiro-Wilk, Kolmogorov-Smirnov,
|
|
18
|
-
Lilliefors and Anderson-Darling tests,
|
|
19
|
-
Nornadiah Mohd Razali, Yap Bee Wah.
|
|
20
|
-
"""
|
|
21
|
-
|
|
22
|
-
def check_normality(
|
|
23
|
-
self, data: list[float]
|
|
24
|
-
) -> tuple[Optional[bool], Optional[bool], Optional[bool], Optional[bool]]:
|
|
25
|
-
"""
|
|
26
|
-
Run up to four normality tests on a single group.
|
|
27
|
-
|
|
28
|
-
Returns a tuple (sw, lf, ad, ap) where each element is:
|
|
29
|
-
True — test passed (p > 0.05, distribution appears normal)
|
|
30
|
-
False — test failed
|
|
31
|
-
None — sample too small to run the test reliably (n < 20)
|
|
32
|
-
"""
|
|
33
|
-
sw: Optional[bool] = None
|
|
34
|
-
lf: Optional[bool] = None
|
|
35
|
-
ad: Optional[bool] = None
|
|
36
|
-
ap: Optional[bool] = None
|
|
37
|
-
n: int = len(data)
|
|
38
|
-
|
|
39
|
-
# Shapiro-Wilk test
|
|
40
|
-
_sw_stat, sw_p_value = shapiro(data)
|
|
41
|
-
sw = bool(sw_p_value and sw_p_value > 0.05)
|
|
42
|
-
|
|
43
|
-
# Lilliefors test
|
|
44
|
-
_lf_stat, lf_p_value = lilliefors(data, dist="norm")
|
|
45
|
-
lf = bool(lf_p_value and lf_p_value > 0.05)
|
|
46
|
-
|
|
47
|
-
# Anderson-Darling test (requires n >= 20)
|
|
48
|
-
if n >= 20:
|
|
49
|
-
_ad_stat, ad_p_value = self.anderson_get_p(data, dist="norm")
|
|
50
|
-
ad = bool(ad_p_value and ad_p_value > 0.05)
|
|
51
|
-
|
|
52
|
-
# D'Agostino-Pearson test (unreliable for n < 20)
|
|
53
|
-
if n >= 20:
|
|
54
|
-
_ap_stat, ap_p_value = normaltest(data)
|
|
55
|
-
ap = bool(ap_p_value and ap_p_value > 0.05)
|
|
56
|
-
|
|
57
|
-
return (sw, lf, ad, ap)
|
|
58
|
-
|
|
59
|
-
def anderson_get_p(
|
|
60
|
-
self, data: list[float], dist: str = "norm"
|
|
61
|
-
) -> tuple[float, Optional[float]]:
|
|
62
|
-
"""
|
|
63
|
-
Calculate the p-value for the Anderson-Darling test using the method described in:
|
|
64
|
-
*Computation of Probability Associated with Anderson-Darling Statistic*,
|
|
65
|
-
Lorentz Jantschi and Sorana D. Bolboaca, Mathematics, 2018.
|
|
66
|
-
|
|
67
|
-
Returns:
|
|
68
|
-
(ad_statistic, p_value)
|
|
69
|
-
"""
|
|
70
|
-
e: float = 2.718281828459045
|
|
71
|
-
n: int = len(data)
|
|
72
|
-
|
|
73
|
-
ad, _critical_values, _significance_levels = anderson(data, dist=dist)
|
|
74
|
-
|
|
75
|
-
# Adjust statistic for small sample sizes
|
|
76
|
-
s: float = ad * (1 + 0.75 / n + 2.25 / (n**2))
|
|
77
|
-
|
|
78
|
-
p: Optional[float]
|
|
79
|
-
if s >= 0.6:
|
|
80
|
-
p = e ** (1.2937 - 5.709 * s + 0.0186 * s**2)
|
|
81
|
-
elif s > 0.34:
|
|
82
|
-
p = e ** (0.9177 - 4.279 * s - 1.38 * s**2)
|
|
83
|
-
elif s > 0.2:
|
|
84
|
-
p = 1 - e ** (-8.318 + 42.796 * s - 59.938 * s**2)
|
|
85
|
-
elif s <= 0.2:
|
|
86
|
-
p = 1 - e ** (-13.436 + 101.14 * s - 223.73 * s**2)
|
|
87
|
-
else:
|
|
88
|
-
p = None
|
|
89
|
-
|
|
90
|
-
return ad, p
|
|
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
|