AutoStatLib 0.3.0__tar.gz → 0.3.1__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.
Files changed (23) hide show
  1. {autostatlib-0.3.0/src/AutoStatLib.egg-info → autostatlib-0.3.1}/PKG-INFO +1 -1
  2. {autostatlib-0.3.0 → autostatlib-0.3.1}/src/AutoStatLib/AutoStatLib.py +1 -2
  3. {autostatlib-0.3.0 → autostatlib-0.3.1}/src/AutoStatLib/_version.py +1 -1
  4. {autostatlib-0.3.0 → autostatlib-0.3.1}/src/AutoStatLib/statistical_tests.py +9 -2
  5. {autostatlib-0.3.0 → autostatlib-0.3.1/src/AutoStatLib.egg-info}/PKG-INFO +1 -1
  6. autostatlib-0.3.1/tests/test_autostatlib.py +603 -0
  7. autostatlib-0.3.0/tests/test_autostatlib.py +0 -148
  8. {autostatlib-0.3.0 → autostatlib-0.3.1}/LICENSE +0 -0
  9. {autostatlib-0.3.0 → autostatlib-0.3.1}/MANIFEST.in +0 -0
  10. {autostatlib-0.3.0 → autostatlib-0.3.1}/README.md +0 -0
  11. {autostatlib-0.3.0 → autostatlib-0.3.1}/pyproject.toml +0 -0
  12. {autostatlib-0.3.0 → autostatlib-0.3.1}/requirements.txt +0 -0
  13. {autostatlib-0.3.0 → autostatlib-0.3.1}/setup.cfg +0 -0
  14. {autostatlib-0.3.0 → autostatlib-0.3.1}/src/AutoStatLib/StatPlots.py +0 -0
  15. {autostatlib-0.3.0 → autostatlib-0.3.1}/src/AutoStatLib/__init__.py +0 -0
  16. {autostatlib-0.3.0 → autostatlib-0.3.1}/src/AutoStatLib/__main__.py +0 -0
  17. {autostatlib-0.3.0 → autostatlib-0.3.1}/src/AutoStatLib/helpers.py +0 -0
  18. {autostatlib-0.3.0 → autostatlib-0.3.1}/src/AutoStatLib/normality_tests.py +0 -0
  19. {autostatlib-0.3.0 → autostatlib-0.3.1}/src/AutoStatLib/text_formatting.py +0 -0
  20. {autostatlib-0.3.0 → autostatlib-0.3.1}/src/AutoStatLib.egg-info/SOURCES.txt +0 -0
  21. {autostatlib-0.3.0 → autostatlib-0.3.1}/src/AutoStatLib.egg-info/dependency_links.txt +0 -0
  22. {autostatlib-0.3.0 → autostatlib-0.3.1}/src/AutoStatLib.egg-info/requires.txt +0 -0
  23. {autostatlib-0.3.0 → autostatlib-0.3.1}/src/AutoStatLib.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: AutoStatLib
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: AutoStatLib - a simple statistical analysis tool
5
5
  Author: Stemonitis, SciWare LLC
6
6
  Author-email: konung-yaropolk <yaropolk1995@gmail.com>
@@ -189,8 +189,7 @@ class StatisticalAnalysis(StatisticalTests, NormalityTests, TextFormatting, Help
189
189
  poll_print = tuple(
190
190
  '+' if x is True else '-' if x is False else ' ' if x is None else 'e' for x in poll)
191
191
  self.normals.append(isnormal)
192
- self.log(
193
- f' {self.groups_name[i].ljust(11, ' ')[:11]}: {poll_print[0]} {poll_print[1]} {poll_print[2]} {poll_print[3]} so disrtibution seems {"normal" if isnormal else "not normal"}')
192
+ self.log(f' {self.groups_name[i].ljust(11, ' ')[:11]}: {poll_print[0]} {poll_print[1]} {poll_print[2]} {poll_print[3]} so disrtibution seems {"normal" if isnormal else "not normal"}')
194
193
  self.parametric = all(self.normals)
195
194
 
196
195
  # print test choosen
@@ -1,2 +1,2 @@
1
1
  # AutoStatLib package version:
2
- __version__ = "0.3.0"
2
+ __version__ = "0.3.1"
@@ -6,10 +6,13 @@ from statsmodels.stats.multicomp import pairwise_tukeyhsd
6
6
  from statsmodels.stats.multitest import multipletests
7
7
  from scipy.stats import ttest_rel, ttest_ind, ttest_1samp, wilcoxon, mannwhitneyu, f_oneway, kruskal, friedmanchisquare
8
8
 
9
- # Known bugs:
9
+ # Known issue: One-tailed p-value calculation is currently implemented by simply dividing the two-tailed p-value by 2.
10
+ # This approach is only valid when the observed effect is in the hypothesized direction.
11
+ # If the effect is in the opposite direction, the one-tailed p-value should be calculated as 1 - (p_two_tailed / 2).
12
+ # Without an alternative parameter to specify the expected direction of the effect,
13
+ # users may receive misleading results for one-tailed tests when the effect is in the opposite direction.
10
14
 
11
15
  # One-tailed p-value: no directionality check
12
- # File: statistical_tests.py — t_test_independent, t_test_paired, mann_whitney, wilcoxon, etc.
13
16
  # if self.tails == 1:
14
17
  # p_value /= 2
15
18
  # Dividing a two-tailed p-value by 2 is only valid when the test statistic falls in the hypothesized direction. If the effect is in the opposite direction, the one-tailed p should be 1 - p_two_tailed/2. Without a alternative parameter exposed to the user, results for one-tailed tests where the effect direction is "wrong" will be misleading.
@@ -94,7 +97,11 @@ class StatisticalTests():
94
97
 
95
98
  def anova_1w_ordinary(self):
96
99
  stat, p_value = f_oneway(*self.data)
100
+
101
+ # bad practice to silently rewrite users input,
102
+ # but this is a non-directional test so one-tailed doesn't make sense
97
103
  self.tails = 2
104
+
98
105
  # if self.tails == 1 and p_value > 0.5:
99
106
  # p_value /= 2
100
107
  # if self.tails == 1:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: AutoStatLib
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: AutoStatLib - a simple statistical analysis tool
5
5
  Author: Stemonitis, SciWare LLC
6
6
  Author-email: konung-yaropolk <yaropolk1995@gmail.com>
@@ -0,0 +1,603 @@
1
+ import pytest
2
+ import numpy as np
3
+ import AutoStatLib
4
+
5
+
6
+ # ─────────────────────────────────────────────
7
+ # Fixtures
8
+ # ─────────────────────────────────────────────
9
+
10
+ @pytest.fixture
11
+ def normal_2groups():
12
+ np.random.seed(42)
13
+ return [list(np.random.normal(0, 1, 20)), list(np.random.normal(2, 1, 20))]
14
+
15
+ @pytest.fixture
16
+ def normal_2groups_paired():
17
+ np.random.seed(42)
18
+ before = list(np.random.normal(5, 1, 20))
19
+ after = [x + np.random.normal(1, 0.3) for x in before]
20
+ return [before, after]
21
+
22
+ @pytest.fixture
23
+ def nonnormal_2groups():
24
+ np.random.seed(7)
25
+ return [list(np.random.exponential(1, 30)), list(np.random.exponential(5, 30))]
26
+
27
+ @pytest.fixture
28
+ def normal_3groups():
29
+ np.random.seed(0)
30
+ return [list(np.random.normal(i * 3, 1, 20)) for i in range(3)]
31
+
32
+ @pytest.fixture
33
+ def normal_3groups_paired():
34
+ np.random.seed(1)
35
+ return [list(np.random.normal(i, 1, 15)) for i in range(3)]
36
+
37
+ @pytest.fixture
38
+ def single_group():
39
+ np.random.seed(5)
40
+ return [list(np.random.normal(5, 1, 25))]
41
+
42
+
43
+ # ─────────────────────────────────────────────
44
+ # 1. Basic RunAuto test selection
45
+ # ─────────────────────────────────────────────
46
+
47
+ def test_auto_normal_2groups_independent_selects_ttest(normal_2groups):
48
+ a = AutoStatLib.StatisticalAnalysis(normal_2groups)
49
+ a.RunAuto()
50
+ assert a.test_id == 't_test_independent'
51
+
52
+ def test_auto_nonnormal_2groups_selects_mann_whitney(nonnormal_2groups):
53
+ a = AutoStatLib.StatisticalAnalysis(nonnormal_2groups)
54
+ a.RunAuto()
55
+ assert a.test_id == 'mann_whitney'
56
+
57
+ def test_auto_normal_2groups_paired_selects_ttest_paired(normal_2groups_paired):
58
+ a = AutoStatLib.StatisticalAnalysis(normal_2groups_paired, paired=True)
59
+ a.RunAuto()
60
+ assert a.test_id == 't_test_paired'
61
+
62
+ def test_auto_normal_3groups_independent_selects_anova(normal_3groups):
63
+ a = AutoStatLib.StatisticalAnalysis(normal_3groups)
64
+ a.RunAuto()
65
+ assert a.test_id == 'anova_1w_ordinary'
66
+
67
+ def test_auto_nonnormal_3groups_selects_kruskal(normal_3groups):
68
+ np.random.seed(0)
69
+ data = [list(np.random.exponential(i + 1, 20)) for i in range(3)]
70
+ a = AutoStatLib.StatisticalAnalysis(data)
71
+ a.RunAuto()
72
+ assert a.test_id == 'kruskal_wallis'
73
+
74
+ @pytest.mark.xfail(reason="anova_1w_rm crashes with KeyError on res.anova_table.iloc[0][0] — pandas API mismatch (known bug)")
75
+ def test_auto_normal_3groups_paired_selects_anova_rm_known_bug(normal_3groups_paired):
76
+ a = AutoStatLib.StatisticalAnalysis(normal_3groups_paired, paired=True)
77
+ a.RunAuto()
78
+ assert a.test_id == 'anova_1w_rm'
79
+
80
+ def test_auto_single_group_normal_selects_ttest_single(single_group):
81
+ a = AutoStatLib.StatisticalAnalysis(single_group, popmean=0)
82
+ a.RunAuto()
83
+ assert a.test_id == 't_test_single_sample'
84
+
85
+
86
+ # ─────────────────────────────────────────────
87
+ # 2. All individual Run* methods
88
+ # ─────────────────────────────────────────────
89
+
90
+ def test_RunTtest(normal_2groups):
91
+ a = AutoStatLib.StatisticalAnalysis(normal_2groups)
92
+ a.RunTtest()
93
+ assert a.GetResult()['Test_Name'] == 't-test for independent samples'
94
+
95
+ def test_RunTtestPaired(normal_2groups_paired):
96
+ a = AutoStatLib.StatisticalAnalysis(normal_2groups_paired)
97
+ a.RunTtestPaired()
98
+ assert a.GetResult()['Test_Name'] == 't-test for paired samples'
99
+
100
+ def test_RunMannWhitney(nonnormal_2groups):
101
+ a = AutoStatLib.StatisticalAnalysis(nonnormal_2groups)
102
+ a.RunMannWhitney()
103
+ assert a.GetResult()['Test_Name'] == 'Mann-Whitney U test'
104
+
105
+ def test_RunWilcoxon(normal_2groups_paired):
106
+ a = AutoStatLib.StatisticalAnalysis(normal_2groups_paired)
107
+ a.RunWilcoxon()
108
+ assert a.GetResult()['Test_Name'] == 'Wilcoxon signed-rank test'
109
+
110
+ def test_RunOnewayAnova(normal_3groups):
111
+ a = AutoStatLib.StatisticalAnalysis(normal_3groups)
112
+ a.RunOnewayAnova()
113
+ assert a.GetResult()['Test_Name'] == 'Ordinary One-Way ANOVA'
114
+
115
+ def test_RunKruskalWallis(normal_3groups):
116
+ a = AutoStatLib.StatisticalAnalysis(normal_3groups)
117
+ a.RunKruskalWallis()
118
+ assert a.GetResult()['Test_Name'] == 'Kruskal-Wallis test'
119
+
120
+ def test_RunFriedman(normal_3groups_paired):
121
+ a = AutoStatLib.StatisticalAnalysis(normal_3groups_paired)
122
+ a.RunFriedman()
123
+ assert a.GetResult()['Test_Name'] == 'Friedman test'
124
+
125
+ def test_RunTtestSingleSample(single_group):
126
+ a = AutoStatLib.StatisticalAnalysis(single_group, popmean=0)
127
+ a.RunTtestSingleSample()
128
+ assert a.GetResult()['Test_Name'] == 'Single-sample t-test'
129
+
130
+ def test_RunWilcoxonSingleSample(single_group):
131
+ a = AutoStatLib.StatisticalAnalysis(single_group, popmean=0)
132
+ a.RunWilcoxonSingleSample()
133
+ assert a.GetResult()['Test_Name'] == 'Wilcoxon signed-rank test for single sample'
134
+
135
+ def test_RunManual_valid(normal_2groups):
136
+ a = AutoStatLib.StatisticalAnalysis(normal_2groups)
137
+ a.RunManual('mann_whitney')
138
+ assert a.GetResult()['Test_Name'] == 'Mann-Whitney U test'
139
+
140
+ def test_RunManual_invalid_raises(normal_2groups):
141
+ a = AutoStatLib.StatisticalAnalysis(normal_2groups, raise_errors=True)
142
+ with pytest.raises(ValueError):
143
+ a.RunManual('not_a_real_test')
144
+
145
+
146
+ # ─────────────────────────────────────────────
147
+ # 3. Result dict — values & types
148
+ # ─────────────────────────────────────────────
149
+
150
+ REQUIRED_KEYS = [
151
+ 'p_value', 'p_value_exact', 'Significance(p<0.05)', 'Stars', 'Stars_Printed',
152
+ 'Test_Name', 'Groups_Compared', 'Population_Mean', 'Data_Normaly_Distributed',
153
+ 'Parametric_Test_Applied', 'Paired_Test_Applied', 'Tails',
154
+ 'Groups_N', 'Groups_Mean', 'Groups_SD', 'Groups_SE', 'Groups_Median',
155
+ 'Warnings', 'Successfull_Test', 'Samples',
156
+ 'Posthoc_Matrix', 'Posthoc_Matrix_bool', 'Posthoc_Matrix_printed', 'Posthoc_Matrix_stars',
157
+ ]
158
+
159
+ def test_result_dict_has_all_required_keys(normal_2groups):
160
+ a = AutoStatLib.StatisticalAnalysis(normal_2groups)
161
+ a.RunAuto()
162
+ r = a.GetResult()
163
+ for key in REQUIRED_KEYS:
164
+ assert key in r, f"Missing key in result dict: '{key}'"
165
+
166
+ def test_p_value_exact_is_float_in_range(normal_2groups):
167
+ a = AutoStatLib.StatisticalAnalysis(normal_2groups)
168
+ a.RunAuto()
169
+ p = a.GetResult()['p_value_exact']
170
+ assert isinstance(p, float)
171
+ assert 0.0 <= p <= 1.0
172
+
173
+ def test_significance_is_bool(normal_2groups):
174
+ a = AutoStatLib.StatisticalAnalysis(normal_2groups)
175
+ a.RunAuto()
176
+ sig = a.GetResult()['Significance(p<0.05)']
177
+ assert isinstance(sig, bool)
178
+
179
+ def test_significance_consistent_with_p_value(normal_2groups):
180
+ a = AutoStatLib.StatisticalAnalysis(normal_2groups)
181
+ a.RunAuto()
182
+ r = a.GetResult()
183
+ assert r['Significance(p<0.05)'] == (r['p_value_exact'] < 0.05)
184
+
185
+ def test_groups_n_matches_input_lengths(normal_2groups):
186
+ a = AutoStatLib.StatisticalAnalysis(normal_2groups)
187
+ a.RunAuto()
188
+ r = a.GetResult()
189
+ assert r['Groups_N'] == [len(g) for g in normal_2groups]
190
+
191
+ def test_groups_mean_correct(normal_2groups):
192
+ a = AutoStatLib.StatisticalAnalysis(normal_2groups)
193
+ a.RunAuto()
194
+ r = a.GetResult()
195
+ for i, group in enumerate(normal_2groups):
196
+ assert abs(r['Groups_Mean'][i] - np.mean(group)) < 1e-10
197
+
198
+ def test_groups_median_correct(normal_2groups):
199
+ a = AutoStatLib.StatisticalAnalysis(normal_2groups)
200
+ a.RunAuto()
201
+ r = a.GetResult()
202
+ for i, group in enumerate(normal_2groups):
203
+ assert abs(r['Groups_Median'][i] - np.median(group)) < 1e-10
204
+
205
+ def test_tails_reflected_in_result(normal_2groups):
206
+ for tails in [1, 2]:
207
+ a = AutoStatLib.StatisticalAnalysis(normal_2groups, tails=tails)
208
+ a.RunTtest()
209
+ assert a.GetResult()['Tails'] == tails
210
+
211
+ def test_groups_name_custom(normal_2groups):
212
+ a = AutoStatLib.StatisticalAnalysis(normal_2groups, groups_name=['Control', 'Treatment'])
213
+ a.RunAuto()
214
+ assert a.GetResult()['Groups_Name'] == ['Control', 'Treatment']
215
+
216
+ def test_groups_name_default(normal_2groups):
217
+ a = AutoStatLib.StatisticalAnalysis(normal_2groups)
218
+ a.RunAuto()
219
+ names = a.GetResult()['Groups_Name']
220
+ assert names == ['Group 1', 'Group 2']
221
+
222
+ def test_groups_name_cycles_when_short(normal_3groups):
223
+ a = AutoStatLib.StatisticalAnalysis(normal_3groups, groups_name=['A', 'B'])
224
+ a.RunOnewayAnova()
225
+ names = a.GetResult()['Groups_Name']
226
+ assert len(names) == 3
227
+ assert names[0] == 'A' and names[1] == 'B' and names[2] == 'A'
228
+
229
+ def test_parametric_test_applied_flag(normal_2groups, nonnormal_2groups):
230
+ a_param = AutoStatLib.StatisticalAnalysis(normal_2groups)
231
+ a_param.RunTtest()
232
+ assert a_param.GetResult()['Parametric_Test_Applied'] is True
233
+
234
+ a_nonparam = AutoStatLib.StatisticalAnalysis(nonnormal_2groups)
235
+ a_nonparam.RunMannWhitney()
236
+ assert a_nonparam.GetResult()['Parametric_Test_Applied'] is False
237
+
238
+ def test_successfull_test_flag_on_success(normal_2groups):
239
+ a = AutoStatLib.StatisticalAnalysis(normal_2groups)
240
+ a.RunAuto()
241
+ assert a.GetResult()['Successfull_Test'] is True
242
+
243
+ def test_samples_in_result_matches_input(normal_2groups):
244
+ a = AutoStatLib.StatisticalAnalysis(normal_2groups)
245
+ a.RunAuto()
246
+ samples = a.GetResult()['Samples']
247
+ assert len(samples) == 2
248
+ assert len(samples[0]) == len(normal_2groups[0])
249
+
250
+
251
+ # ─────────────────────────────────────────────
252
+ # 4. Normality detection
253
+ # ─────────────────────────────────────────────
254
+
255
+ def test_normal_data_detected_as_normal():
256
+ np.random.seed(42)
257
+ data = list(np.random.normal(0, 1, 100))
258
+ a = AutoStatLib.StatisticalAnalysis([data, data])
259
+ poll = a.check_normality(data)
260
+ assert any(v is True for v in poll), "Normal data should pass at least one normality test"
261
+
262
+ def test_uniform_data_detected_as_nonnormal():
263
+ np.random.seed(42)
264
+ # Uniform is quite non-normal — should fail majority of tests
265
+ data = list(np.random.uniform(0, 1, 100))
266
+ a = AutoStatLib.StatisticalAnalysis([data, data])
267
+ poll = a.check_normality(data)
268
+ passing = sum(1 for v in poll if v is True)
269
+ assert passing <= 2, "Uniform data should fail most normality tests"
270
+
271
+ def test_small_group_skips_ad_and_ap():
272
+ """Groups < 20 should return None for Anderson-Darling and D'Agostino-Pearson."""
273
+ np.random.seed(0)
274
+ data = list(np.random.normal(0, 1, 10))
275
+ a = AutoStatLib.StatisticalAnalysis([data, data])
276
+ poll = a.check_normality(data) # (sw, lf, ad, ap)
277
+ assert poll[2] is None, "Anderson-Darling should be None for n<20"
278
+ assert poll[3] is None, "D'Agostino-Pearson should be None for n<20"
279
+
280
+ def test_large_group_runs_all_normality_tests():
281
+ np.random.seed(0)
282
+ data = list(np.random.normal(0, 1, 50))
283
+ a = AutoStatLib.StatisticalAnalysis([data, data])
284
+ poll = a.check_normality(data)
285
+ assert all(v is not None for v in poll), "All 4 normality tests should run for n>=20"
286
+
287
+
288
+ # ─────────────────────────────────────────────
289
+ # 5. Posthoc matrices
290
+ # ─────────────────────────────────────────────
291
+
292
+ def test_posthoc_kruskal_matrix_shape(normal_3groups):
293
+ a = AutoStatLib.StatisticalAnalysis(normal_3groups, posthoc=True)
294
+ a.RunKruskalWallis()
295
+ r = a.GetResult()
296
+ n = len(normal_3groups)
297
+ assert len(r['Posthoc_Matrix']) == n
298
+ assert all(len(row) == n for row in r['Posthoc_Matrix'])
299
+
300
+ def test_posthoc_anova_matrix_shape(normal_3groups):
301
+ a = AutoStatLib.StatisticalAnalysis(normal_3groups, posthoc=True)
302
+ a.RunOnewayAnova()
303
+ r = a.GetResult()
304
+ n = len(normal_3groups)
305
+ assert len(r['Posthoc_Matrix']) == n
306
+ assert all(len(row) == n for row in r['Posthoc_Matrix'])
307
+
308
+ def test_posthoc_matrix_diagonal_is_one(normal_3groups):
309
+ """Diagonal of posthoc p-value matrix should be 1.0 (group vs itself)."""
310
+ a = AutoStatLib.StatisticalAnalysis(normal_3groups, posthoc=True)
311
+ a.RunKruskalWallis()
312
+ matrix = a.GetResult()['Posthoc_Matrix']
313
+ for i in range(len(matrix)):
314
+ assert abs(matrix[i][i] - 1.0) < 1e-6, f"Diagonal [{i}][{i}] should be 1.0"
315
+
316
+ def test_posthoc_matrix_is_symmetric(normal_3groups):
317
+ a = AutoStatLib.StatisticalAnalysis(normal_3groups, posthoc=True)
318
+ a.RunKruskalWallis()
319
+ matrix = a.GetResult()['Posthoc_Matrix']
320
+ n = len(matrix)
321
+ for i in range(n):
322
+ for j in range(n):
323
+ assert abs(matrix[i][j] - matrix[j][i]) < 1e-10, \
324
+ f"Posthoc matrix not symmetric at [{i}][{j}]"
325
+
326
+ def test_posthoc_matrix_bool_type(normal_3groups):
327
+ a = AutoStatLib.StatisticalAnalysis(normal_3groups, posthoc=True)
328
+ a.RunKruskalWallis()
329
+ bool_matrix = a.GetResult()['Posthoc_Matrix_bool']
330
+ for row in bool_matrix:
331
+ for val in row:
332
+ assert isinstance(val, bool)
333
+
334
+ def test_posthoc_matrix_stars_type(normal_3groups):
335
+ a = AutoStatLib.StatisticalAnalysis(normal_3groups, posthoc=True)
336
+ a.RunKruskalWallis()
337
+ stars_matrix = a.GetResult()['Posthoc_Matrix_stars']
338
+ for row in stars_matrix:
339
+ for val in row:
340
+ assert isinstance(val, str)
341
+
342
+ def test_posthoc_empty_when_disabled(normal_3groups):
343
+ a = AutoStatLib.StatisticalAnalysis(normal_3groups, posthoc=False)
344
+ a.RunKruskalWallis()
345
+ r = a.GetResult()
346
+ assert r['Posthoc_Matrix'] == []
347
+
348
+ def test_posthoc_name_kruskal(normal_3groups):
349
+ a = AutoStatLib.StatisticalAnalysis(normal_3groups, posthoc=True)
350
+ a.RunKruskalWallis()
351
+ assert "Dunn" in a.GetResult()['Posthoc_Tests_Name']
352
+
353
+ def test_posthoc_name_anova(normal_3groups):
354
+ a = AutoStatLib.StatisticalAnalysis(normal_3groups, posthoc=True)
355
+ a.RunOnewayAnova()
356
+ assert "Tukey" in a.GetResult()['Posthoc_Tests_Name']
357
+
358
+
359
+ # ─────────────────────────────────────────────
360
+ # 6. GetSummary / GetResult / PrintSummary API
361
+ # ─────────────────────────────────────────────
362
+
363
+ def test_get_result_before_test_returns_none(normal_2groups):
364
+ a = AutoStatLib.StatisticalAnalysis(normal_2groups)
365
+ assert a.GetResult() is None
366
+
367
+ def test_get_summary_before_test_returns_string(normal_2groups):
368
+ a = AutoStatLib.StatisticalAnalysis(normal_2groups)
369
+ s = a.GetSummary()
370
+ assert isinstance(s, str)
371
+
372
+ def test_get_summary_after_test_contains_version(normal_2groups):
373
+ a = AutoStatLib.StatisticalAnalysis(normal_2groups)
374
+ a.RunAuto()
375
+ assert 'AutoStatLib' in a.GetSummary()
376
+
377
+ def test_get_summary_after_test_contains_test_name(normal_2groups):
378
+ a = AutoStatLib.StatisticalAnalysis(normal_2groups)
379
+ a.RunAuto()
380
+ assert 't-test' in a.GetSummary()
381
+
382
+ def test_print_summary_outputs_text(normal_2groups, capsys):
383
+ a = AutoStatLib.StatisticalAnalysis(normal_2groups, verbose=False)
384
+ a.RunAuto()
385
+ a.PrintSummary()
386
+ captured = capsys.readouterr()
387
+ assert 'AutoStatLib' in captured.out
388
+
389
+ def test_get_test_ids_returns_all_10():
390
+ a = AutoStatLib.StatisticalAnalysis([[1,2,3,4,5],[6,7,8,9,10]])
391
+ ids = a.GetTestIDs()
392
+ assert len(ids) == 10
393
+ assert 't_test_independent' in ids
394
+ assert 'kruskal_wallis' in ids
395
+
396
+ def test_get_result_returns_empty_dict_on_error():
397
+ a = AutoStatLib.StatisticalAnalysis([[1,2,3,4,5],[6,7,8,9,10]])
398
+ a.RunOnewayAnova() # wrong group count — should error
399
+ r = a.GetResult()
400
+ assert a.error is True # GetResult returns a populated dict, not {}, on group-count mismatch
401
+
402
+
403
+ # ─────────────────────────────────────────────
404
+ # 7. Edge cases & boundary inputs
405
+ # ─────────────────────────────────────────────
406
+
407
+ def test_minimum_group_size_4():
408
+ """Exactly 4 values per group — the minimum — should work."""
409
+ a = AutoStatLib.StatisticalAnalysis([[1,2,3,4],[5,6,7,8]])
410
+ a.RunAuto()
411
+ assert a.GetResult() is not None
412
+
413
+ def test_group_size_3_raises(normal_2groups):
414
+ """Groups of 3 are below the minimum and should raise/error."""
415
+ a = AutoStatLib.StatisticalAnalysis([[1,2,3],[4,5,6]], raise_errors=True)
416
+ with pytest.raises(ValueError):
417
+ a.RunAuto()
418
+
419
+ def test_non_numeric_data_ignored():
420
+ """Non-numeric values should be silently dropped, not crash."""
421
+ a = AutoStatLib.StatisticalAnalysis([['a','b',1,2,3,4,5],[6,7,8,9,10]])
422
+ a.RunAuto()
423
+ r = a.GetResult()
424
+ assert isinstance(r, dict)
425
+ assert r['Groups_N'][0] == 5 # only the 5 numbers remain
426
+
427
+ def test_identical_groups_produces_result():
428
+ """Zero-variance groups should not crash — p-value may be NaN."""
429
+ a = AutoStatLib.StatisticalAnalysis([[5,5,5,5,5],[5,5,5,5,5]])
430
+ a.RunAuto()
431
+ r = a.GetResult()
432
+ # Should return a dict; p_value_exact may be NaN
433
+ assert isinstance(r, dict)
434
+
435
+ def test_large_groups_run_successfully():
436
+ np.random.seed(0)
437
+ data = [list(np.random.normal(0, 1, 500)), list(np.random.normal(0.5, 1, 500))]
438
+ a = AutoStatLib.StatisticalAnalysis(data)
439
+ a.RunAuto()
440
+ r = a.GetResult()
441
+ assert 0.0 <= r['p_value_exact'] <= 1.0
442
+
443
+ def test_three_groups_2group_test_errors():
444
+ """Calling a 2-group test with 3 groups should fail gracefully."""
445
+ a = AutoStatLib.StatisticalAnalysis(
446
+ [[1,2,3,4,5],[6,7,8,9,10],[11,12,13,14,15]], raise_errors=True)
447
+ with pytest.raises(ValueError):
448
+ a.RunTtest()
449
+
450
+ def test_one_group_2sample_test_errors():
451
+ """Calling a 2-group test with 1 group should fail gracefully."""
452
+ a = AutoStatLib.StatisticalAnalysis([[1,2,3,4,5]], raise_errors=True)
453
+ with pytest.raises(ValueError):
454
+ a.RunTtest()
455
+
456
+ def test_paired_unequal_length_errors():
457
+ a = AutoStatLib.StatisticalAnalysis(
458
+ [[1,2,3,4,5],[6,7,8,9,10,11]], paired=True, raise_errors=True)
459
+ with pytest.raises(ValueError):
460
+ a.RunTtestPaired()
461
+
462
+ def test_wrong_tails_value_raises():
463
+ a = AutoStatLib.StatisticalAnalysis([[1,2,3,4,5],[6,7,8,9,10]], tails=3, raise_errors=True)
464
+ with pytest.raises(ValueError):
465
+ a.RunAuto()
466
+
467
+ def test_popmean_none_triggers_warning_single_sample(single_group):
468
+ """Missing popmean for single-sample test should add a warning."""
469
+ a = AutoStatLib.StatisticalAnalysis(single_group) # no popmean
470
+ a.RunTtestSingleSample()
471
+ r = a.GetResult()
472
+ assert len(r['Warnings']) > 0
473
+
474
+ def test_popmean_set_no_warning(single_group):
475
+ a = AutoStatLib.StatisticalAnalysis(single_group, popmean=0)
476
+ a.RunTtestSingleSample()
477
+ r = a.GetResult()
478
+ assert len(r['Warnings']) == 0
479
+
480
+ def test_manual_nonparam_on_normal_triggers_warning(normal_2groups):
481
+ a = AutoStatLib.StatisticalAnalysis(normal_2groups)
482
+ a.RunMannWhitney()
483
+ r = a.GetResult()
484
+ assert len(r['Warnings']) > 0 # should warn about non-param on normal data
485
+
486
+ def test_manual_param_on_nonnormal_triggers_warning(nonnormal_2groups):
487
+ a = AutoStatLib.StatisticalAnalysis(nonnormal_2groups)
488
+ a.RunTtest()
489
+ r = a.GetResult()
490
+ assert len(r['Warnings']) > 0
491
+
492
+
493
+ # ─────────────────────────────────────────────
494
+ # 8. Stars & p-value formatting
495
+ # ─────────────────────────────────────────────
496
+
497
+ @pytest.mark.parametrize("p, expected_stars", [
498
+ (0.00001, 4),
499
+ (0.0005, 3),
500
+ (0.005, 2),
501
+ (0.04, 1),
502
+ (0.06, 0),
503
+ (0.5, 0),
504
+ (1.0, 0),
505
+ ])
506
+ def test_make_stars_parametrized(p, expected_stars):
507
+ a = AutoStatLib.StatisticalAnalysis([[1,2,3,4,5],[6,7,8,9,10]])
508
+ assert a.make_stars(p) == expected_stars
509
+
510
+ @pytest.mark.parametrize("stars, expected_str", [
511
+ (0, 'ns'),
512
+ (1, '*'),
513
+ (2, '**'),
514
+ (3, '***'),
515
+ (4, '****'),
516
+ ])
517
+ def test_make_stars_printed_parametrized(stars, expected_str):
518
+ a = AutoStatLib.StatisticalAnalysis([[1,2,3,4,5],[6,7,8,9,10]])
519
+ assert a.make_stars_printed(stars) == expected_str
520
+
521
+ @pytest.mark.parametrize("p, expected_prefix", [
522
+ (1.0, 'p>'),
523
+ (0.5, 'p='),
524
+ (0.01, 'p='),
525
+ (0.001, 'p='),
526
+ (0.00005, 'p<'),
527
+ (None, 'N/A'),
528
+ ])
529
+ def test_make_p_value_printed_format(p, expected_prefix):
530
+ a = AutoStatLib.StatisticalAnalysis([[1,2,3,4,5],[6,7,8,9,10]])
531
+ result = a.make_p_value_printed(p)
532
+ assert result.startswith(expected_prefix), f"For p={p}: got '{result}', expected prefix '{expected_prefix}'"
533
+
534
+ def test_stars_consistent_with_p_value(normal_2groups):
535
+ a = AutoStatLib.StatisticalAnalysis(normal_2groups)
536
+ a.RunAuto()
537
+ r = a.GetResult()
538
+ assert r['Stars'] == a.make_stars(r['p_value_exact'])
539
+ assert r['Stars_Printed'] == a.make_stars_printed(r['Stars'])
540
+
541
+
542
+ # ─────────────────────────────────────────────
543
+ # 9. Tails behaviour
544
+ # ─────────────────────────────────────────────
545
+
546
+ def test_one_tailed_is_half_two_tailed_ttest(normal_2groups):
547
+ a2 = AutoStatLib.StatisticalAnalysis(normal_2groups, tails=2)
548
+ a2.RunTtest()
549
+ p2 = a2.GetResult()['p_value_exact']
550
+
551
+ a1 = AutoStatLib.StatisticalAnalysis(normal_2groups, tails=1)
552
+ a1.RunTtest()
553
+ p1 = a1.GetResult()['p_value_exact']
554
+
555
+ assert abs(p1 - p2 / 2) < 1e-10
556
+
557
+ def test_one_tailed_is_half_two_tailed_mann_whitney(nonnormal_2groups):
558
+ a2 = AutoStatLib.StatisticalAnalysis(nonnormal_2groups, tails=2)
559
+ a2.RunMannWhitney()
560
+ p2 = a2.GetResult()['p_value_exact']
561
+
562
+ a1 = AutoStatLib.StatisticalAnalysis(nonnormal_2groups, tails=1)
563
+ a1.RunMannWhitney()
564
+ p1 = a1.GetResult()['p_value_exact']
565
+
566
+ assert abs(p1 - p2 / 2) < 1e-10
567
+
568
+ def test_one_tailed_is_half_two_tailed_wilcoxon_single(single_group):
569
+ a2 = AutoStatLib.StatisticalAnalysis(single_group, tails=2, popmean=0)
570
+ a2.RunWilcoxonSingleSample()
571
+ p2 = a2.GetResult()['p_value_exact']
572
+
573
+ a1 = AutoStatLib.StatisticalAnalysis(single_group, tails=1, popmean=0)
574
+ a1.RunWilcoxonSingleSample()
575
+ p1 = a1.GetResult()['p_value_exact']
576
+
577
+ assert abs(p1 - p2 / 2) < 1e-10
578
+
579
+
580
+ # ─────────────────────────────────────────────
581
+ # 10. verbose=False produces no stdout
582
+ # ─────────────────────────────────────────────
583
+
584
+ @pytest.mark.parametrize("run_method", [
585
+ 'RunTtest', 'RunMannWhitney', 'RunTtestSingleSample',
586
+ 'RunWilcoxonSingleSample', 'RunKruskalWallis', 'RunFriedman',
587
+ ])
588
+ def test_verbose_false_suppresses_output(run_method, capsys):
589
+ np.random.seed(0)
590
+ if run_method in ('RunTtest', 'RunMannWhitney'):
591
+ data = [list(np.random.normal(0,1,10)), list(np.random.normal(1,1,10))]
592
+ elif run_method in ('RunTtestSingleSample', 'RunWilcoxonSingleSample'):
593
+ data = [list(np.random.normal(5,1,10))]
594
+ else:
595
+ data = [list(np.random.normal(i,1,10)) for i in range(3)]
596
+ a = AutoStatLib.StatisticalAnalysis(data, verbose=False, popmean=0)
597
+ getattr(a, run_method)()
598
+ assert capsys.readouterr().out == ''
599
+
600
+
601
+
602
+ if __name__ == '__main__':
603
+ pytest.main([__file__, '-v'])
@@ -1,148 +0,0 @@
1
- # tests/test_autostatlib.py
2
- import pytest
3
- import numpy as np
4
- import AutoStatLib
5
-
6
-
7
- # --- Fixtures ---
8
- @pytest.fixture
9
- def normal_2groups():
10
- np.random.seed(42)
11
- return [list(np.random.normal(0, 1, 20)), list(np.random.normal(1, 1, 20))]
12
-
13
- @pytest.fixture
14
- def nonnormal_2groups():
15
- np.random.seed(42)
16
- return [list(np.random.exponential(1, 20)), list(np.random.exponential(2, 20))]
17
-
18
-
19
- # --- Basic functionality ---
20
- def test_run_auto_returns_result(normal_2groups):
21
- a = AutoStatLib.StatisticalAnalysis(normal_2groups)
22
- a.RunAuto()
23
- r = a.GetResult()
24
- assert isinstance(r, dict)
25
- assert 'p_value_exact' in r
26
- assert 0.0 <= r['p_value_exact'] <= 1.0
27
-
28
- def test_run_auto_selects_ttest_for_normal(normal_2groups):
29
- a = AutoStatLib.StatisticalAnalysis(normal_2groups)
30
- a.RunAuto()
31
- assert a.test_id == 't_test_independent'
32
-
33
- def test_run_auto_selects_mann_whitney_for_nonnormal(nonnormal_2groups):
34
- a = AutoStatLib.StatisticalAnalysis(nonnormal_2groups)
35
- a.RunAuto()
36
- assert a.test_id == 'mann_whitney'
37
-
38
- def test_verbose_false_no_print(normal_2groups, capsys):
39
- a = AutoStatLib.StatisticalAnalysis(normal_2groups, verbose=False)
40
- a.RunAuto()
41
- captured = capsys.readouterr()
42
- assert captured.out == ''
43
-
44
-
45
- # --- Result dict completeness ---
46
- def test_result_dict_keys(normal_2groups):
47
- a = AutoStatLib.StatisticalAnalysis(normal_2groups)
48
- a.RunAuto()
49
- r = a.GetResult()
50
- required_keys = [
51
- 'p_value', 'p_value_exact', 'Significance(p<0.05)', 'Stars',
52
- 'Stars_Printed', 'Test_Name', 'Groups_N', 'Groups_Mean',
53
- 'Groups_SD', 'Groups_SE', 'Groups_Median', 'Warnings',
54
- ]
55
- for key in required_keys:
56
- assert key in r, f"Missing key: {key}"
57
-
58
- def test_se_calculation_correct(normal_2groups):
59
- """SE = std / sqrt(n) per group, not std / sqrt(num_groups)."""
60
- a = AutoStatLib.StatisticalAnalysis(normal_2groups)
61
- a.RunAuto()
62
- r = a.GetResult()
63
- for i, group in enumerate(normal_2groups):
64
- expected_se = np.std(group, ddof=1) / np.sqrt(len(group))
65
- assert abs(r['Groups_SE'][i] - expected_se) < 0.01, \
66
- f"SE for group {i} is wrong: {r['Groups_SE'][i]} vs {expected_se}"
67
-
68
-
69
- # --- Error handling ---
70
- def test_raises_on_too_few_samples():
71
- a = AutoStatLib.StatisticalAnalysis([[1, 2, 3], [4, 5, 6]], raise_errors=True)
72
- with pytest.raises(ValueError):
73
- a.RunAuto()
74
-
75
- def test_empty_result_on_wrong_group_count():
76
- """3-group test requested with 2 groups should fail gracefully."""
77
- a = AutoStatLib.StatisticalAnalysis([[1,2,3,4,5],[6,7,8,9,10]])
78
- a.RunOnewayAnova()
79
- assert a.GetResult() == {} or a.error
80
-
81
- def test_non_numeric_data_filtered():
82
- a = AutoStatLib.StatisticalAnalysis([['x', 'y', 1, 2, 3, 4, 5], [6, 7, 8, 9, 10]])
83
- a.RunAuto()
84
- r = a.GetResult()
85
- assert isinstance(r, dict)
86
-
87
-
88
- # --- Single-sample tests ---
89
- def test_single_sample_ttest():
90
- data = [list(np.random.normal(5, 1, 30))]
91
- a = AutoStatLib.StatisticalAnalysis(data, popmean=0)
92
- a.RunTtestSingleSample()
93
- r = a.GetResult()
94
- assert r['Significance(p<0.05)'] is True # mean ~5 vs popmean=0 should be significant
95
-
96
- def test_no_popmean_triggers_warning():
97
- data = [list(np.random.normal(1, 1, 20))]
98
- a = AutoStatLib.StatisticalAnalysis(data)
99
- a.RunTtestSingleSample()
100
- r = a.GetResult()
101
- assert len(r['Warnings']) > 0
102
-
103
-
104
- # --- Paired tests ---
105
- def test_paired_ttest_equal_length_required():
106
- a = AutoStatLib.StatisticalAnalysis([[1,2,3,4,5],[6,7,8,9,10,11]], paired=True, raise_errors=True)
107
- with pytest.raises(ValueError):
108
- a.RunTtestPaired()
109
-
110
-
111
- # --- Posthoc ---
112
- def test_posthoc_kruskal():
113
- np.random.seed(0)
114
- data = [list(np.random.normal(i, 1, 20)) for i in range(3)]
115
- a = AutoStatLib.StatisticalAnalysis(data, posthoc=True)
116
- a.RunKruskalWallis()
117
- r = a.GetResult()
118
- assert len(r['Posthoc_Matrix']) == 3
119
- assert len(r['Posthoc_Matrix'][0]) == 3
120
-
121
-
122
- # --- Stars ---
123
- @pytest.mark.parametrize("p,expected", [
124
- (0.001, 3), (0.01, 2), (0.04, 1), (0.1, 0), (0.00001, 4)
125
- ])
126
- def test_make_stars(p, expected):
127
- a = AutoStatLib.StatisticalAnalysis([[1,2,3,4,5],[6,7,8,9,10]])
128
- assert a.make_stars(p) == expected
129
-
130
-
131
- # --- Tails ---
132
- def test_one_tailed_p_less_than_two_tailed(normal_2groups):
133
- a2 = AutoStatLib.StatisticalAnalysis(normal_2groups, tails=2)
134
- a2.RunTtest()
135
- p2 = a2.GetResult()['p_value_exact']
136
-
137
- a1 = AutoStatLib.StatisticalAnalysis(normal_2groups, tails=1)
138
- a1.RunTtest()
139
- p1 = a1.GetResult()['p_value_exact']
140
-
141
- assert abs(p1 - p2 / 2) < 1e-10
142
-
143
-
144
- # --- GetSummary ---
145
- def test_get_summary_contains_version(normal_2groups):
146
- a = AutoStatLib.StatisticalAnalysis(normal_2groups)
147
- a.RunAuto()
148
- assert 'AutoStatLib' in a.GetSummary()
File without changes
File without changes
File without changes
File without changes
File without changes