csv-stats 0.1.0__py3-none-any.whl
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.
- csv-stats/__init__.py +1 -0
- csv-stats/anova.py +301 -0
- csv-stats/ttest.py +107 -0
- csv-stats/utils/load_data.py +45 -0
- csv-stats/utils/save_stats.py +65 -0
- csv-stats/utils/summary_stats.py +62 -0
- csv-stats/utils/test_assumptions.py +87 -0
- csv_stats-0.1.0.dist-info/METADATA +25 -0
- csv_stats-0.1.0.dist-info/RECORD +11 -0
- csv_stats-0.1.0.dist-info/WHEEL +5 -0
- csv_stats-0.1.0.dist-info/top_level.txt +1 -0
csv-stats/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
csv-stats/anova.py
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import statsmodels.api as sm
|
|
5
|
+
from statsmodels.formula.api import ols
|
|
6
|
+
from statsmodels.stats.anova import AnovaRM
|
|
7
|
+
import pandas as pd
|
|
8
|
+
|
|
9
|
+
from .utils.summary_stats import calculate_summary_statistics
|
|
10
|
+
from .utils.load_data import load_data_from_path
|
|
11
|
+
from .utils.test_assumptions import test_normality_assumption, test_variance_homogeneity_assumption, test_sphericity_assumption
|
|
12
|
+
from .utils.save_stats import dict_to_pdf
|
|
13
|
+
|
|
14
|
+
def anova1way(data: Union[Path, str, pd.DataFrame], group_column: str, data_column: str, repeated_measures_column: str = "", filename: Union[str, None] = 'anova1way_results.pdf') -> dict:
|
|
15
|
+
"""
|
|
16
|
+
Perform one-way ANOVA.
|
|
17
|
+
|
|
18
|
+
Parameters:
|
|
19
|
+
data : file path, str, or pd.DataFrame
|
|
20
|
+
The input data containing the groups and values.
|
|
21
|
+
group_column : str
|
|
22
|
+
The name of the column containing group labels.
|
|
23
|
+
data_column : str
|
|
24
|
+
The name of the column containing data values.
|
|
25
|
+
repeated_measures_column : str, optional
|
|
26
|
+
The name of the column containing the repeated measures identifiers. Defaults to "".
|
|
27
|
+
filename : str, optional
|
|
28
|
+
The filename to save the results to. Defaults to 'anova1way_results.pdf'. If None, results are not saved to a file.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
result: dict
|
|
32
|
+
A dictionary containing:
|
|
33
|
+
"F" : float
|
|
34
|
+
The computed F-statistic.
|
|
35
|
+
"p" : float
|
|
36
|
+
The associated p-value, rounded to four decimal places.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
if repeated_measures_column is None:
|
|
40
|
+
repeated_measures_column = ""
|
|
41
|
+
|
|
42
|
+
# Define a boolean flag for repeated measures ANOVA
|
|
43
|
+
is_repeated_measures = repeated_measures_column == ""
|
|
44
|
+
|
|
45
|
+
result = {}
|
|
46
|
+
result["group_column"] = group_column
|
|
47
|
+
result["data_column"] = data_column
|
|
48
|
+
result["repeated_measures_column"] = repeated_measures_column
|
|
49
|
+
|
|
50
|
+
# Load the data as a pandas DataFrame
|
|
51
|
+
data = load_data_from_path(data)
|
|
52
|
+
|
|
53
|
+
# Perform ANOVA
|
|
54
|
+
formula = f"{data_column} ~ C({group_column})"
|
|
55
|
+
anova_result = _perform_anova(data, formula, group_column, data_column, repeated_measures_column, is_repeated_measures)
|
|
56
|
+
|
|
57
|
+
# Extract F-statistic and p-value
|
|
58
|
+
F = anova_result["anova_table"].loc[f"C({group_column})", "F"]
|
|
59
|
+
p = anova_result["anova_table"].loc[f"C({group_column})", "PR(>F)"]
|
|
60
|
+
|
|
61
|
+
# Calculate degrees of freedom
|
|
62
|
+
df_between = len(data[group_column].unique()) - 1
|
|
63
|
+
df_within = len(data) - len(data[group_column].unique())
|
|
64
|
+
|
|
65
|
+
summary_stats = calculate_summary_statistics(data, group_column, data_column)
|
|
66
|
+
|
|
67
|
+
# Store results in the dictionary
|
|
68
|
+
result["F"] = F
|
|
69
|
+
result["p"] = round(p, 4)
|
|
70
|
+
result["df_between"] = df_between
|
|
71
|
+
result["df_within"] = df_within
|
|
72
|
+
result["summary_stats"] = summary_stats
|
|
73
|
+
result["normality_test"] = anova_result["normality_test"]
|
|
74
|
+
result["homogeneity_of_variance_test"] = anova_result["homogeneity_of_variance_test"]
|
|
75
|
+
result["sphericity_test"] = anova_result["sphericity_test"]
|
|
76
|
+
|
|
77
|
+
if filename is not None:
|
|
78
|
+
dict_to_pdf(result, filename=filename)
|
|
79
|
+
|
|
80
|
+
return result
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def anova2way(data: Union[Path, str, pd.DataFrame], group_column1: str, group_column2: str, data_column: str, repeated_measures_column: str = "", filename: Union[str, None] = 'anova2way_results.pdf') -> dict:
|
|
84
|
+
"""
|
|
85
|
+
Perform two-way ANOVA.
|
|
86
|
+
|
|
87
|
+
Parameters:
|
|
88
|
+
data : file path, str, or pd.DataFrame
|
|
89
|
+
The input data containing the groups and values.
|
|
90
|
+
group_column1 : str
|
|
91
|
+
The name of the first column containing group labels.
|
|
92
|
+
group_column2 : str
|
|
93
|
+
The name of the second column containing group labels.
|
|
94
|
+
data_column : str
|
|
95
|
+
The name of the column containing data values.
|
|
96
|
+
repeated_measures_column : str, optional
|
|
97
|
+
The name of the column containing the repeated measures identifiers. Defaults to "".
|
|
98
|
+
filename : str, optional
|
|
99
|
+
The filename to save the results to. Defaults to 'anova2way_results.pdf'. If None, results are not saved to a file.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
result: dict
|
|
103
|
+
A dictionary containing:
|
|
104
|
+
"F1" : float
|
|
105
|
+
The computed F-statistic for the first factor.
|
|
106
|
+
"p1" : float
|
|
107
|
+
The associated p-value for the first factor, rounded to four decimal places.
|
|
108
|
+
"F2" : float
|
|
109
|
+
The computed F-statistic for the second factor.
|
|
110
|
+
"p2" : float
|
|
111
|
+
The associated p-value for the second factor, rounded to four decimal places.
|
|
112
|
+
"F_interaction" : float
|
|
113
|
+
The computed F-statistic for the interaction between factors.
|
|
114
|
+
"p_interaction" : float
|
|
115
|
+
The associated p-value for the interaction, rounded to four decimal places.
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
if repeated_measures_column is None:
|
|
119
|
+
repeated_measures_column = ""
|
|
120
|
+
|
|
121
|
+
# Define a boolean flag for repeated measures ANOVA
|
|
122
|
+
is_repeated_measures = repeated_measures_column == ""
|
|
123
|
+
|
|
124
|
+
result = {}
|
|
125
|
+
result["group_column1"] = group_column1
|
|
126
|
+
result["group_column2"] = group_column2
|
|
127
|
+
result["data_column"] = data_column
|
|
128
|
+
result["repeated_measures_column"] = repeated_measures_column
|
|
129
|
+
|
|
130
|
+
# Load the data as a pandas DataFrame
|
|
131
|
+
data = load_data_from_path(data)
|
|
132
|
+
|
|
133
|
+
# Fit the model with interaction
|
|
134
|
+
formula = f"{data_column} ~ C({group_column1}) + C({group_column2}) + C({group_column1}):C({group_column2})"
|
|
135
|
+
anova_result = _perform_anova(data, formula, [group_column1, group_column2], data_column, repeated_measures_column, is_repeated_measures)
|
|
136
|
+
|
|
137
|
+
summary_stats_group1 = calculate_summary_statistics(data, group_column1, data_column)
|
|
138
|
+
summary_stats_group2 = calculate_summary_statistics(data, group_column2, data_column)
|
|
139
|
+
# Prepare the dataframe to calculate the summary statistics for the interaction effect
|
|
140
|
+
interaction_column_name = f"{group_column1}_{group_column2}"
|
|
141
|
+
data[interaction_column_name] = data[group_column1].astype(str) + '_' + data[group_column2].astype(str)
|
|
142
|
+
summary_stats_interaction = calculate_summary_statistics(data, interaction_column_name, data_column)
|
|
143
|
+
|
|
144
|
+
# Store results in the dictionary
|
|
145
|
+
result["main_effects"] = {}
|
|
146
|
+
result["main_effects"][group_column1] = {}
|
|
147
|
+
result["main_effects"][group_column2] = {}
|
|
148
|
+
result["interaction"] = {}
|
|
149
|
+
|
|
150
|
+
anova_table = anova_result["anova_table"]
|
|
151
|
+
result["main_effects"][group_column1]["F"] = anova_table.loc[f"C({group_column1})", "F"]
|
|
152
|
+
result["main_effects"][group_column1]["p"] = round(anova_table.loc[f"C({group_column1})", "PR(>F)"], 4)
|
|
153
|
+
result["main_effects"][group_column2]["F"] = anova_table.loc[f"C({group_column2})", "F"]
|
|
154
|
+
result["main_effects"][group_column2]["p"] = round(anova_table.loc[f"C({group_column2})", "PR(>F)"], 4)
|
|
155
|
+
|
|
156
|
+
interaction_key = f"C({group_column1}):C({group_column2})"
|
|
157
|
+
result["interaction"]["F"] = anova_table.loc[interaction_key, "F"]
|
|
158
|
+
result["interaction"]["p"] = round(anova_table.loc[interaction_key, "PR(>F)"], 4)
|
|
159
|
+
result[f"summary_stats_{group_column1}"] = summary_stats_group1
|
|
160
|
+
result[f"summary_stats_{group_column2}"] = summary_stats_group2
|
|
161
|
+
result["summary_stats_interaction"] = summary_stats_interaction
|
|
162
|
+
|
|
163
|
+
result["normality_test"] = anova_result["normality_test"]
|
|
164
|
+
result["homogeneity_of_variance_test"] = anova_result["homogeneity_of_variance_test"]
|
|
165
|
+
result["sphericity_test"] = anova_result["sphericity_test"]
|
|
166
|
+
|
|
167
|
+
if filename is not None:
|
|
168
|
+
dict_to_pdf(result, filename='anova2way_results.pdf')
|
|
169
|
+
|
|
170
|
+
return result
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def anova3way(data: Union[Path, str, pd.DataFrame], group_column1: str, group_column2: str, group_column3: str, data_column: str, repeated_measures_column: str, filename: Union[str, None] = 'anova3way_results.pdf') -> dict:
|
|
174
|
+
"""
|
|
175
|
+
Perform three-way ANOVA.
|
|
176
|
+
|
|
177
|
+
Parameters:
|
|
178
|
+
data : file path, str, or pd.DataFrame
|
|
179
|
+
The input data containing the groups and values.
|
|
180
|
+
group_column1 : str
|
|
181
|
+
The name of the first column containing group labels.
|
|
182
|
+
group_column2 : str
|
|
183
|
+
The name of the second column containing group labels.
|
|
184
|
+
group_column3 : str
|
|
185
|
+
The name of the third column containing group labels.
|
|
186
|
+
data_column : str
|
|
187
|
+
The name of the column containing data values.
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
result: dict
|
|
191
|
+
A dictionary containing:
|
|
192
|
+
"F1" : float
|
|
193
|
+
The computed F-statistic for the first factor.
|
|
194
|
+
"p1" : float
|
|
195
|
+
The associated p-value for the first factor, rounded to four decimal places.
|
|
196
|
+
"F2" : float
|
|
197
|
+
The computed F-statistic for the second factor.
|
|
198
|
+
"p2" : float
|
|
199
|
+
The associated p-value for the second factor, rounded to four decimal places.
|
|
200
|
+
"F3" : float
|
|
201
|
+
The computed F-statistic for the third factor.
|
|
202
|
+
"p3" : float
|
|
203
|
+
The associated p-value for the third factor, rounded to four decimal places.
|
|
204
|
+
"F_interaction" : float
|
|
205
|
+
The computed F-statistic for the interaction between factors.
|
|
206
|
+
"p_interaction" : float
|
|
207
|
+
The associated p-value for the interaction, rounded to four decimal places.
|
|
208
|
+
"""
|
|
209
|
+
|
|
210
|
+
result = {}
|
|
211
|
+
result["group_column1"] = group_column1
|
|
212
|
+
result["group_column2"] = group_column2
|
|
213
|
+
result["group_column3"] = group_column3
|
|
214
|
+
result["data_column"] = data_column
|
|
215
|
+
|
|
216
|
+
# Load the data as a pandas DataFrame
|
|
217
|
+
data = load_data_from_path(data)
|
|
218
|
+
|
|
219
|
+
# Perform ANOVA
|
|
220
|
+
formula = f"{data_column} ~ C({group_column1}) * C({group_column2}) * C({group_column3})"
|
|
221
|
+
anova_result = _perform_anova(data, formula, [group_column1, group_column2, group_column3], data_column, repeated_measures_column, False)
|
|
222
|
+
|
|
223
|
+
summary_stats_group1 = calculate_summary_statistics(data, group_column1, data_column)
|
|
224
|
+
summary_stats_group2 = calculate_summary_statistics(data, group_column2, data_column)
|
|
225
|
+
summary_stats_group3 = calculate_summary_statistics(data, group_column3, data_column)
|
|
226
|
+
# Prepare the dataframe to calculate the summary statistics for the interaction effect
|
|
227
|
+
interaction_column_name = f"{group_column1}_{group_column2}+{group_column3}"
|
|
228
|
+
data[interaction_column_name] = data[group_column1].astype(str) + '_' + data[group_column2].astype(str) + '_' + data[group_column3].astype(str)
|
|
229
|
+
summary_stats_interaction = calculate_summary_statistics(data, interaction_column_name, data_column)
|
|
230
|
+
|
|
231
|
+
# Store results in the dictionary
|
|
232
|
+
result["main_effects"] = {}
|
|
233
|
+
result["main_effects"][group_column1] = {}
|
|
234
|
+
result["main_effects"][group_column2] = {}
|
|
235
|
+
result["main_effects"][group_column3] = {}
|
|
236
|
+
result["interaction"] = {}
|
|
237
|
+
|
|
238
|
+
anova_table = anova_result["anova_table"]
|
|
239
|
+
result["main_effects"][group_column1]["F"] = anova_table.loc[f"C({group_column1})", "F"]
|
|
240
|
+
result["main_effects"][group_column1]["p"] = round(anova_table.loc[f"C({group_column1})", "PR(>F)"], 4)
|
|
241
|
+
result["main_effects"][group_column2]["F"] = anova_table.loc[f"C({group_column2})", "F"]
|
|
242
|
+
result["main_effects"][group_column2]["p"] = round(anova_table.loc[f"C({group_column2})", "PR(>F)"], 4)
|
|
243
|
+
result["main_effects"][group_column3]["F"] = anova_table.loc[f"C({group_column3})", "F"]
|
|
244
|
+
result["main_effects"][group_column3]["p"] = round(anova_table.loc[f"C({group_column3})", "PR(>F)"], 4)
|
|
245
|
+
|
|
246
|
+
interaction_key = f"C({group_column1}):C({group_column2}):C({group_column3})"
|
|
247
|
+
result["interaction"]["F"] = anova_table.loc[interaction_key, "F"]
|
|
248
|
+
result["interaction"]["p"] = round(anova_table.loc[interaction_key, "PR(>F)"], 4)
|
|
249
|
+
result[f"summary_stats_{group_column1}"] = summary_stats_group1
|
|
250
|
+
result[f"summary_stats_{group_column2}"] = summary_stats_group2
|
|
251
|
+
result[f"summary_stats_{group_column3}"] = summary_stats_group3
|
|
252
|
+
result["summary_stats_interaction"] = summary_stats_interaction
|
|
253
|
+
|
|
254
|
+
result["normality_test"] = anova_result["normality_test"]
|
|
255
|
+
result["homogeneity_of_variance_test"] = anova_result["homogeneity_of_variance_test"]
|
|
256
|
+
result["sphericity_test"] = anova_result["sphericity_test"]
|
|
257
|
+
|
|
258
|
+
if filename is not None:
|
|
259
|
+
dict_to_pdf(result, filename=filename)
|
|
260
|
+
|
|
261
|
+
return result
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _perform_anova(data: pd.DataFrame, formula: str, group_column: Union[list, str], data_column: str, repeated_measures_column: str, is_repeated_measures: bool) -> dict:
|
|
265
|
+
"""Perform the actual ANOVA computation. Works for all types of ANOVA.
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
data (pd.DataFrame): The input data
|
|
269
|
+
group_column (str): The name of the column containing group labels.
|
|
270
|
+
data_column (str): The name of the column containing data values.
|
|
271
|
+
repeated_measures_column (str): The name of the column containing the repeated measures identifiers.
|
|
272
|
+
is_repeated_measures (bool): The boolean flag indicating if this is a repeated measures ANOVA.
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
dict: The ANOVA results including F-statistic and p-value.
|
|
276
|
+
"""
|
|
277
|
+
result = {}
|
|
278
|
+
if not is_repeated_measures:
|
|
279
|
+
# Fit the one-way ANOVA model using statsmodels
|
|
280
|
+
model = ols(formula, data=data).fit()
|
|
281
|
+
anova_table = sm.stats.anova_lm(model, typ=2)
|
|
282
|
+
homogeneity_variances_result = test_variance_homogeneity_assumption(data, group_column, data_column)
|
|
283
|
+
mauchly_test_result = "Not applicable"
|
|
284
|
+
else:
|
|
285
|
+
model = AnovaRM(data,
|
|
286
|
+
depvar=data_column,
|
|
287
|
+
subject=repeated_measures_column,
|
|
288
|
+
within=[group_column]).fit()
|
|
289
|
+
anova_table = model.anova_table
|
|
290
|
+
mauchly_test_result = test_sphericity_assumption(data, group_column, repeated_measures_column, data_column)
|
|
291
|
+
homogeneity_variances_result = "Not applicable"
|
|
292
|
+
|
|
293
|
+
result["model"] = model
|
|
294
|
+
result["anova_table"] = anova_table
|
|
295
|
+
result["homogeneity_of_variance_test"] = homogeneity_variances_result
|
|
296
|
+
result["sphericity_test"] = mauchly_test_result
|
|
297
|
+
|
|
298
|
+
normality_result = test_normality_assumption(model)
|
|
299
|
+
result["normality_test"] = normality_result
|
|
300
|
+
|
|
301
|
+
return result
|
csv-stats/ttest.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from scipy import stats
|
|
6
|
+
from statsmodels.formula.api import ols
|
|
7
|
+
|
|
8
|
+
from .anova import anova1way
|
|
9
|
+
from .utils.load_data import load_data_from_path
|
|
10
|
+
from .utils.save_stats import dict_to_pdf
|
|
11
|
+
from .utils.summary_stats import calculate_summary_statistics
|
|
12
|
+
from .utils.test_assumptions import test_normality_assumption, test_variance_homogeneity_assumption
|
|
13
|
+
|
|
14
|
+
def ttest_ind(data: Union[Path, str, pd.DataFrame], group_column: str, data_column: str, filename: Union[str, None] = 'ttest_ind_results.pdf') -> dict:
|
|
15
|
+
"""Conduct a two-sample t-test (independent or paired) and save results to a PDF.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
data (Union[Path, str, pd.DataFrame]): The input data, which can be a file path, string, or DataFrame.
|
|
19
|
+
group_column (str): The name of the column containing group labels. If this is a one sample t-test, this should be an empty string.
|
|
20
|
+
data_column (str): The name of the column containing the data values.
|
|
21
|
+
repeated_measures_column (str, optional): The name of the. Defaults to "".
|
|
22
|
+
filename (str, optional): The filename to save the results to. Defaults to 'ttest_ind_results.pdf'. If None, results are not saved to a file.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
result = {} # Initialize result dictionary
|
|
26
|
+
|
|
27
|
+
# Load the data
|
|
28
|
+
data = load_data_from_path(data)
|
|
29
|
+
|
|
30
|
+
# Make sure there is a 'group_column' even for one-sample t-tests
|
|
31
|
+
if group_column is None:
|
|
32
|
+
group_column = ""
|
|
33
|
+
if group_column == "":
|
|
34
|
+
group_column = "one_sample"
|
|
35
|
+
data[group_column] = "all"
|
|
36
|
+
|
|
37
|
+
# Store metadata
|
|
38
|
+
result["group_column"] = group_column
|
|
39
|
+
result["data_column"] = data_column
|
|
40
|
+
|
|
41
|
+
groups = data[group_column].unique()
|
|
42
|
+
num_groups = len(groups)
|
|
43
|
+
if num_groups == 2:
|
|
44
|
+
result = anova1way(data, group_column, data_column, group_column, filename)
|
|
45
|
+
dict_to_pdf(result, filename=filename)
|
|
46
|
+
return result
|
|
47
|
+
|
|
48
|
+
if num_groups != 1:
|
|
49
|
+
raise ValueError("Data must contain exactly one or two groups for a t-test.")
|
|
50
|
+
|
|
51
|
+
t_stat, p_value = stats.ttest_1samp(data[data_column], popmean=0)
|
|
52
|
+
result["t_statistic"] = round(t_stat, 4)
|
|
53
|
+
result["p_value"] = round(p_value, 4)
|
|
54
|
+
|
|
55
|
+
summary_stats = calculate_summary_statistics(data, group_column, data_column)
|
|
56
|
+
result["summary_statistics"] = summary_stats
|
|
57
|
+
|
|
58
|
+
# Get the model for normality assumption testing
|
|
59
|
+
formula = f"{data_column} ~ C({group_column})"
|
|
60
|
+
model = ols(formula, data=data).fit()
|
|
61
|
+
|
|
62
|
+
# Test model assumptions
|
|
63
|
+
normality_test_result = test_normality_assumption(model)
|
|
64
|
+
homogeneity_variances_result = test_variance_homogeneity_assumption(data, group_column, data_column)
|
|
65
|
+
result["normality_test"] = normality_test_result
|
|
66
|
+
result["homogeneity_of_variance_test"] = homogeneity_variances_result
|
|
67
|
+
|
|
68
|
+
if filename is not None:
|
|
69
|
+
dict_to_pdf(result, filename=filename)
|
|
70
|
+
return result
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def ttest_dep(data: Union[Path, str, pd.DataFrame], group_column: str, data_column: str, repeated_measures_column: str, filename: Union[str, None] = 'ttest_dep_results.pdf') -> dict:
|
|
74
|
+
"""Conduct a paired t-test and save results to a PDF.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
data (Union[Path, str, pd.DataFrame]): The input data, which can be a file path, string, or DataFrame.
|
|
78
|
+
group_column (str): The name of the column containing group labels. If this is a one sample t-test, this should be an empty string.
|
|
79
|
+
data_column (str): The name of the column containing the data values.
|
|
80
|
+
repeated_measures_column (str, optional): The name of the column containing the repeated measures identifiers. Defaults to "".
|
|
81
|
+
filename (str, optional): The filename to save the results to. Defaults to 'ttest_dep_results.pdf'. If None, results are not saved to a file.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
# Load the data
|
|
85
|
+
data = load_data_from_path(data)
|
|
86
|
+
|
|
87
|
+
groups = data[group_column].unique()
|
|
88
|
+
num_groups = len(groups)
|
|
89
|
+
if num_groups != 2:
|
|
90
|
+
raise ValueError("Data must contain exactly two groups for a paired t-test.")
|
|
91
|
+
|
|
92
|
+
# Pivot the data to have one column per group
|
|
93
|
+
pivot_data = data.pivot(index=repeated_measures_column, columns=group_column, values=data_column)
|
|
94
|
+
delta_column_name = f"{groups[0]}_minus_{groups[1]}"
|
|
95
|
+
pivot_data[delta_column_name] = pivot_data[groups[0]] - pivot_data[groups[1]]
|
|
96
|
+
|
|
97
|
+
# Perform paired t-test on the difference column
|
|
98
|
+
ttest_ind_result = ttest_ind(pivot_data, "", delta_column_name, filename=None)
|
|
99
|
+
|
|
100
|
+
# Calculate and add summary statistics for the repeated measures
|
|
101
|
+
# i.e. if there was a pre and post test per subject, this will provide a mean per subject
|
|
102
|
+
# repeated_measures_summary = calculate_summary_statistics(data, repeated_measures_column, delta_column_name)
|
|
103
|
+
# ttest_ind_result["repeated_measures_summary_statistics"] = repeated_measures_summary
|
|
104
|
+
|
|
105
|
+
if filename is not None:
|
|
106
|
+
dict_to_pdf(ttest_ind_result, filename=filename)
|
|
107
|
+
return ttest_ind_result
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
|
|
6
|
+
def load_data_from_path(data: Union[Path, str, pd.DataFrame]) -> pd.DataFrame:
|
|
7
|
+
"""
|
|
8
|
+
Load data from a file path or return the DataFrame if already provided.
|
|
9
|
+
|
|
10
|
+
Parameters:
|
|
11
|
+
data : Union[Path, str, pd.DataFrame]
|
|
12
|
+
The file path to load data from or a DataFrame.
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
pd.DataFrame
|
|
16
|
+
The loaded DataFrame.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
if isinstance(data, pd.DataFrame):
|
|
20
|
+
return data
|
|
21
|
+
|
|
22
|
+
# Convert string paths to Path objects
|
|
23
|
+
if isinstance(data, str):
|
|
24
|
+
data = Path(data)
|
|
25
|
+
|
|
26
|
+
if isinstance(data, Path):
|
|
27
|
+
check_file_exists(data)
|
|
28
|
+
return pd.read_csv(data)
|
|
29
|
+
else:
|
|
30
|
+
raise ValueError("Input must be a file path or a pandas DataFrame.")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def check_file_exists(path: Union[Path, str]):
|
|
34
|
+
"""
|
|
35
|
+
Check that the provided file path exists.
|
|
36
|
+
|
|
37
|
+
Parameters:
|
|
38
|
+
path : Union[Path, str]
|
|
39
|
+
The file path to check.
|
|
40
|
+
"""
|
|
41
|
+
if isinstance(path, str):
|
|
42
|
+
path = Path(path)
|
|
43
|
+
|
|
44
|
+
if not path.exists():
|
|
45
|
+
raise ValueError(f"Unable to find a file at {path}")
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Union, Any
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from reportlab.lib.pagesizes import letter
|
|
6
|
+
from reportlab.pdfgen import canvas
|
|
7
|
+
from reportlab.lib.units import inch
|
|
8
|
+
from reportlab.pdfbase import pdfmetrics
|
|
9
|
+
from reportlab.pdfbase.ttfonts import TTFont
|
|
10
|
+
|
|
11
|
+
def dict_to_pdf(data: dict, filename: Union[str, None] = 'output.pdf'):
|
|
12
|
+
"""
|
|
13
|
+
Convert a dictionary to a formatted JSON string and save as PDF.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
data: The dictionary to convert
|
|
17
|
+
filename: Output PDF filename (default: 'output.pdf')
|
|
18
|
+
"""
|
|
19
|
+
# Convert NumPy types to native Python types for JSON serialization
|
|
20
|
+
def convert_types(obj: Any) -> Any:
|
|
21
|
+
if isinstance(obj, dict):
|
|
22
|
+
return {k: convert_types(v) for k, v in obj.items()}
|
|
23
|
+
elif isinstance(obj, (list, tuple)):
|
|
24
|
+
return [convert_types(item) for item in obj]
|
|
25
|
+
elif hasattr(obj, 'item'): # NumPy scalar types
|
|
26
|
+
return obj.item()
|
|
27
|
+
return obj
|
|
28
|
+
|
|
29
|
+
if filename is None:
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
filename = str(filename)
|
|
33
|
+
|
|
34
|
+
data = convert_types(data)
|
|
35
|
+
|
|
36
|
+
# Convert dictionary to formatted JSON string
|
|
37
|
+
json_str = json.dumps(data, indent=2)
|
|
38
|
+
|
|
39
|
+
# Create PDF
|
|
40
|
+
c = canvas.Canvas(filename, pagesize=letter)
|
|
41
|
+
width, height = letter
|
|
42
|
+
|
|
43
|
+
# Margins and setup
|
|
44
|
+
margin = 0.5 * inch
|
|
45
|
+
y_position = height - margin
|
|
46
|
+
line_height = 0.15 * inch
|
|
47
|
+
|
|
48
|
+
# Use monospace font for better JSON formatting
|
|
49
|
+
c.setFont("Courier", 9)
|
|
50
|
+
|
|
51
|
+
# Draw each line of the JSON
|
|
52
|
+
for line in json_str.split('\n'):
|
|
53
|
+
# Check if we need a new page
|
|
54
|
+
if y_position < margin:
|
|
55
|
+
c.showPage()
|
|
56
|
+
c.setFont("Courier", 9)
|
|
57
|
+
y_position = height - margin
|
|
58
|
+
|
|
59
|
+
# Draw the line
|
|
60
|
+
c.drawString(margin, y_position, line)
|
|
61
|
+
y_position -= line_height
|
|
62
|
+
|
|
63
|
+
# Save the PDF
|
|
64
|
+
c.save()
|
|
65
|
+
print(f"PDF saved as {filename}")
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
|
|
2
|
+
import numpy as np
|
|
3
|
+
import pandas as pd
|
|
4
|
+
|
|
5
|
+
def calculate_summary_statistics(data: pd.DataFrame, group_column: str, data_column: str) -> dict:
|
|
6
|
+
"""
|
|
7
|
+
Calculate summary statistics for each group in the data. If there is only one group, grouped statistics will still be calculated.
|
|
8
|
+
|
|
9
|
+
Parameters:
|
|
10
|
+
data : pd.DataFrame
|
|
11
|
+
The input data containing the groups and values.
|
|
12
|
+
group_column : str
|
|
13
|
+
The name of the column containing group labels.
|
|
14
|
+
data_column : str
|
|
15
|
+
The name of the column containing data values.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
summary_stats: dict
|
|
19
|
+
A dictionary where keys are group names and values are dictionaries of summary statistics:
|
|
20
|
+
"count", "mean", "std", "min", "25%", "50%", "75%", "max"
|
|
21
|
+
"""
|
|
22
|
+
summary_stats = {}
|
|
23
|
+
grouped = data.groupby(group_column)[data_column]
|
|
24
|
+
|
|
25
|
+
# Calculate summary statistics for each group
|
|
26
|
+
means = {name: group.mean() for name, group in grouped}
|
|
27
|
+
std_devs = {name: group.std() for name, group in grouped}
|
|
28
|
+
counts = {name: len(group) for name, group in grouped}
|
|
29
|
+
variances = {name: group.var() for name, group in grouped}
|
|
30
|
+
n_missing = {name: group.isnull().sum() for name, group in grouped}
|
|
31
|
+
mins = {name: group.min() for name, group in grouped}
|
|
32
|
+
maxs = {name: group.max() for name, group in grouped}
|
|
33
|
+
|
|
34
|
+
# Calculate quartiles
|
|
35
|
+
quartiles = {name: group.quantile([0.25, 0.5, 0.75]).to_dict() for name, group in grouped}
|
|
36
|
+
|
|
37
|
+
# Store group-wise statistics
|
|
38
|
+
summary_stats_grouped = {}
|
|
39
|
+
summary_stats_grouped["mean"] = means
|
|
40
|
+
summary_stats_grouped["std_dev"] = std_devs
|
|
41
|
+
summary_stats_grouped["count"] = counts
|
|
42
|
+
summary_stats_grouped["variance"] = variances
|
|
43
|
+
summary_stats_grouped["n_missing"] = n_missing
|
|
44
|
+
summary_stats_grouped["quartiles"] = quartiles
|
|
45
|
+
summary_stats_grouped["min"] = mins
|
|
46
|
+
summary_stats_grouped["max"] = maxs
|
|
47
|
+
|
|
48
|
+
# Store overall statistics
|
|
49
|
+
summary_stats_overall = {}
|
|
50
|
+
summary_stats_overall["mean"] = data[data_column].mean()
|
|
51
|
+
summary_stats_overall["std_dev"] = data[data_column].std()
|
|
52
|
+
summary_stats_overall["count"] = len(data)
|
|
53
|
+
summary_stats_overall["n_missing"] = data[data_column].isnull().sum()
|
|
54
|
+
summary_stats_overall["variance"] = data[data_column].var()
|
|
55
|
+
summary_stats_overall["quartiles"] = data[data_column].quantile([0.25,0.5, 0.75]).to_dict()
|
|
56
|
+
summary_stats_overall["min"] = data[data_column].min()
|
|
57
|
+
summary_stats_overall["max"] = data[data_column].max()
|
|
58
|
+
|
|
59
|
+
summary_stats["grouped"] = summary_stats_grouped
|
|
60
|
+
summary_stats["overall"] = summary_stats_overall
|
|
61
|
+
|
|
62
|
+
return summary_stats
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
|
|
3
|
+
from scipy import stats
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import pingouin as pg
|
|
6
|
+
|
|
7
|
+
def test_normality_assumption(model) -> dict:
|
|
8
|
+
"""Test the normality assumption of residuals using the Shapiro-Wilk test.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
model (_type_): The fitted statistical model from statsmodels.
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
dict: The result of the Shapiro-Wilk test containing the test statistic and p-value.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
# Test for normality
|
|
18
|
+
residuals = model.resid
|
|
19
|
+
normality_test_statistic, p_value_normality = stats.shapiro(residuals)
|
|
20
|
+
|
|
21
|
+
normality_result = {}
|
|
22
|
+
normality_result["shapiro_wilk"] = {"test_statistic": normality_test_statistic, "p_value": p_value_normality}
|
|
23
|
+
|
|
24
|
+
if p_value_normality < 0.05:
|
|
25
|
+
print(f"Warning: The Shapiro-Wilk test for normality returned a p-value of {p_value_normality:.4f}. A p-value less than 0.05 suggests that the residuals may not be normally distributed. Consider using a non-parametric test if normality is violated.")
|
|
26
|
+
|
|
27
|
+
return normality_result
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_variance_homogeneity_assumption(data: pd.DataFrame, group_columns: Union[str, list], data_column: str) -> dict:
|
|
31
|
+
"""Test the homogeneity of variances using Levene's test. For 2 or more factors, uses the interaction of all factors.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
data (pd.DataFrame): The input data containing the groups and values.
|
|
35
|
+
group_columns (Union[str, list]): The name(s) of the column(s) containing group labels.
|
|
36
|
+
data_column (str): The name of the column containing data values.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
dict: The result of Levene's test containing the test statistic and p-value.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
if isinstance(group_columns, str):
|
|
43
|
+
group_columns = [group_columns]
|
|
44
|
+
|
|
45
|
+
# Test for homogeneity of variances
|
|
46
|
+
data['combined_group'] = data[group_columns].astype(str).agg('_'.join, axis=1)
|
|
47
|
+
grouped_data = [group[data_column].values for name, group in data.groupby('combined_group')]
|
|
48
|
+
levene_statistic, p_value_levene = stats.levene(*grouped_data)
|
|
49
|
+
|
|
50
|
+
homogeneity_variances_result = {}
|
|
51
|
+
homogeneity_variances_result["levene"] = {"test_statistic": levene_statistic, "p_value": p_value_levene}
|
|
52
|
+
|
|
53
|
+
if p_value_levene < 0.05:
|
|
54
|
+
print(f"Warning: The Levene's test for homogeneity of variances returned a p-value of {p_value_levene:.4f}. A p-value less than 0.05 suggests that the variances across groups may not be equal. Consider using a non-parametric test if homogeneity of variances is violated.")
|
|
55
|
+
|
|
56
|
+
return homogeneity_variances_result
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_sphericity_assumption(data: pd.DataFrame, group_columns: Union[str, list], subject_column: str, data_column: str) -> dict:
|
|
60
|
+
"""Test the sphericity assumption using Mauchly's test.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
data (pd.DataFrame): The input data containing the groups, subjects, and values.
|
|
64
|
+
group_column (str): The name of the column containing group labels.
|
|
65
|
+
subject_column (str): The name of the column containing subject identifiers.
|
|
66
|
+
data_column (str): The name of the column containing data values.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
dict: The result of Mauchly's test containing the test statistic and p-value.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
if isinstance(group_columns, str):
|
|
73
|
+
group_columns = [group_columns]
|
|
74
|
+
|
|
75
|
+
# Perform Mauchly's test for sphericity using pingouin
|
|
76
|
+
mauchly_result = pg.sphericity(data, dv=data_column, subject=subject_column, within=group_columns)
|
|
77
|
+
|
|
78
|
+
sphericity_result = {}
|
|
79
|
+
sphericity_result["mauchly"] = {
|
|
80
|
+
"W": mauchly_result['W'].values[0],
|
|
81
|
+
"p_value": mauchly_result['pval'].values[0]
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if mauchly_result['pval'].values[0] < 0.05:
|
|
85
|
+
print(f"Warning: Mauchly's test for sphericity returned a p-value of {mauchly_result['pval'].values[0]:.4f}. A p-value less than 0.05 suggests that the sphericity assumption may be violated. Consider applying a correction (e.g., Greenhouse-Geisser or Huynh-Feldt) to the ANOVA results.")
|
|
86
|
+
|
|
87
|
+
return sphericity_result
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: csv-stats
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Python package for the life sciences to conduct hypothesis testing from CSV files.
|
|
5
|
+
Author-email: Mitchell Tillman <mtillman14@gmail.com>
|
|
6
|
+
Maintainer-email: Mitchell Tillman <mtillman14@gmail.com>
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/mtillman14/csv-stats
|
|
9
|
+
Project-URL: Documentation, https://github.com/mtillman14/csv-stats
|
|
10
|
+
Project-URL: Repository, https://github.com/mtillman14/csv-stats
|
|
11
|
+
Project-URL: Issues, https://github.com/mtillman14/csv-stats/issues
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: Development Status :: 3 - Alpha
|
|
17
|
+
Requires-Python: >=3.8
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Requires-Dist: pandas>=2.3.3
|
|
20
|
+
Requires-Dist: pingouin>=0.5.5
|
|
21
|
+
Requires-Dist: scipy>=1.16.2
|
|
22
|
+
Requires-Dist: statsmodels>=0.14.5
|
|
23
|
+
|
|
24
|
+
# csv-stats
|
|
25
|
+
Python package for rapid hypothesis testing on CSV files with data in long table format, which are common in the life sciences.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
csv-stats/__init__.py,sha256=Pru0BlFBASFCFo7McHdohtKkUtgMPDwbGfyUZlE2_Vw,21
|
|
2
|
+
csv-stats/anova.py,sha256=TGK1AE0fWGGz3NNn5N81ByuPyfNY-s7CRUjCtONAvh4,13822
|
|
3
|
+
csv-stats/ttest.py,sha256=BPk0PKMFngZ7eAw4kjC_CA3JYtqMZ_tG36RnK8BGdDg,4929
|
|
4
|
+
csv-stats/utils/load_data.py,sha256=S2jj1N8lsli6tJoz8GVIP1ya3AvwNBEVs8E8jzoqY8U,1106
|
|
5
|
+
csv-stats/utils/save_stats.py,sha256=miIKM6Skq5qPwRLwzF0q7ovzBcucm4XEKjTG1WGA_5A,1902
|
|
6
|
+
csv-stats/utils/summary_stats.py,sha256=Hz4lmj8Ar_evGENTODZ6PZDJcGkGhh6lyX2UJcBuuV4,2602
|
|
7
|
+
csv-stats/utils/test_assumptions.py,sha256=T9xeiuT5-_QjrI26zeoYPrXC6NnR_MzqcajSHtcHy5k,3932
|
|
8
|
+
csv_stats-0.1.0.dist-info/METADATA,sha256=k87MgZOEj-AjVpCgneniodLpd-5gzLCoPA5HVO3u7DI,1104
|
|
9
|
+
csv_stats-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
10
|
+
csv_stats-0.1.0.dist-info/top_level.txt,sha256=ddAwVrUJVKxXCfDSFFl62yHBrVf8uFpyuLpp_UlK_k8,10
|
|
11
|
+
csv_stats-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
csv-stats
|