CognitiveScience commited on
Commit
5e6f65e
·
verified ·
1 Parent(s): f8f0751

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +319 -129
app.py CHANGED
@@ -1,163 +1,353 @@
1
- # mcp_tools.py
 
 
2
 
3
- import numpy as np
4
- import pingouin as pg
 
5
 
6
- import matplotlib.pyplot as plt
7
- import io
8
- import base64
9
- import json
10
- def plot_group_distribution(groups_str):
11
- groups = [np.array([float(x) for x in g.split(",")]) for g in groups_str.split(";")]
12
-
13
- fig, axes = plt.subplots(1, 3, figsize=(12, 4))
14
-
15
- # Histogram
16
- for g in groups:
17
- axes[0].hist(g, alpha=0.5)
18
- axes[0].set_title("Histogram")
19
 
20
- # Boxplot
21
- axes[1].boxplot(groups)
22
- axes[1].set_title("Boxplot")
23
-
24
- # QQ plot (first group)
25
- pg.qqplot(groups[0], ax=axes[2])
26
- axes[2].set_title("QQ Plot (Group 1)")
27
-
28
- buf = io.BytesIO()
29
- fig.savefig(buf, format="png")
30
- buf.seek(0)
31
- return base64.b64encode(buf.read()).decode()
32
 
33
- def assumption_checks(groups_str):
34
- import pandas as pd
35
 
36
- groups = [np.array([float(x) for x in g.split(",")]) for g in groups_str.split(";")]
 
 
 
 
37
 
 
38
 
39
- # Normality (Shapiro) with safety guard
40
- normality = []
41
-
42
- for i, g in enumerate(groups):
43
- if len(g) < 4:
44
- normality.append({
45
- "group": f"G{i+1}",
46
- "error": "Normality test requires at least 4 samples."
47
- })
48
- else:
49
- result = pg.normality(g).to_dict(orient="records")[0]
50
- normality.append({
51
- "group": f"G{i+1}",
52
- **result
53
- })
54
-
55
-
56
- # Homogeneity (Levene)
57
- levene = pg.homoscedasticity(groups, method="levene").to_dict(orient="records")[0]
58
-
59
- return {
60
- "normality": normality,
61
- "homogeneity": levene
62
- }
63
 
64
- def posthoc_tests(groups_str, method="games-howell"):
65
- import pandas as pd
66
 
67
- groups = [np.array([float(x) for x in g.split(",")]) for g in groups_str.split(";")]
68
 
69
- data = []
70
- for i, g in enumerate(groups):
71
- for val in g:
72
- data.append({"value": val, "group": f"G{i+1}"})
73
 
74
- df = pd.DataFrame(data)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
76
- result = pg.pairwise_ttests(
77
- dv="value",
78
- between="group",
79
- data=df,
80
- padjust="bonf",
81
- parametric=(method == "tukey"),
82
- effsize="hedges"
83
- )
84
 
85
- return result.to_dict(orient="records")
86
 
87
  # -----------------------------
88
- # Helper: convert Pingouin DF → JSON
89
  # -----------------------------
90
- def pg_to_json(df):
91
- """Convert Pingouin single-row DataFrame to JSON dict."""
92
- return df.to_dict(orient="records")[0]
93
 
 
 
94
 
95
- # -----------------------------
96
- # Welch t-test (independent, unequal variances)
97
- # -----------------------------
98
- def welch_t_test(group1_str, group2_str, alternative="two-sided"):
99
- g1 = np.array([float(x) for x in group1_str.split(",")])
100
- g2 = np.array([float(x) for x in group2_str.split(",")])
101
 
102
- result = pg.ttest(g1, g2, paired=False, correction=True, alternative=alternative)
103
- return pg_to_json(result)
104
 
 
 
 
 
 
 
 
105
 
106
- # -----------------------------
107
- # Student t-test (independent, equal variances)
108
- # -----------------------------
109
- def student_t_test(group1_str, group2_str, alternative="two-sided"):
110
- g1 = np.array([float(x) for x in group1_str.split(",")])
111
- g2 = np.array([float(x) for x in group2_str.split(",")])
112
 
113
- result = pg.ttest(g1, g2, paired=False, correction=False, alternative=alternative)
114
- return pg_to_json(result)
 
115
 
 
 
 
116
 
117
- # -----------------------------
118
- # Paired t-test (same subjects measured twice)
119
- # -----------------------------
120
- def paired_t_test(group1_str, group2_str, alternative="two-sided"):
121
- g1 = np.array([float(x) for x in group1_str.split(",")])
122
- g2 = np.array([float(x) for x in group2_str.split(",")])
 
 
 
 
123
 
124
- result = pg.ttest(g1, g2, paired=True, alternative=alternative)
125
- return pg_to_json(result)
126
 
127
 
128
  # -----------------------------
129
- # One-sample t-test (sample vs population mean)
130
  # -----------------------------
131
- def one_sample_t_test(group_str, population_mean, alternative="two-sided"):
132
- g = np.array([float(x) for x in group_str.split(",")])
 
 
 
133
 
134
- # Adjust sample for one-sample t-test
135
- adjusted = g - float(population_mean)
136
 
137
- # Pingouin one-sample t-test: y=None, popmean implicitly = 0
138
- result = pg.ttest(adjusted, y=None, paired=False, alternative=alternative)
 
 
139
 
140
- return pg_to_json(result)
 
 
 
 
 
 
 
 
 
 
 
 
 
141
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
 
143
- # -----------------------------
144
- # One-way ANOVA (multiple independent groups)
145
- # -----------------------------
146
- def one_way_anova(groups_str):
147
- """
148
- groups_str format:
149
- "1,2,3; 4,5,6; 7,8,9"
150
- """
151
- groups = [np.array([float(x) for x in g.split(",")]) for g in groups_str.split(";")]
152
-
153
- # Build DataFrame for Pingouin
154
- import pandas as pd
155
- data = []
156
- for i, g in enumerate(groups):
157
- for val in g:
158
- data.append({"value": val, "group": f"G{i+1}"})
159
-
160
- df = pd.DataFrame(data)
161
-
162
- result = pg.anova(dv="value", between="group", data=df)
163
- return pg_to_json(result)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py
2
+ import asyncio
3
+ asyncio.set_event_loop_policy(asyncio.DefaultEventLoopPolicy())
4
 
5
+ import gradio as gr
6
+ import mcp_tools
7
+ from mcp_tools import posthoc_tests, assumption_checks
8
 
9
+ import spaces
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
+ @spaces.GPU
12
+ def init_gpu():
13
+ return "GPU initialized"
 
 
 
 
 
 
 
 
 
14
 
15
+ import json, tempfile, os
 
16
 
17
+ def save_json_to_file(data, filename):
18
+ path = os.path.join(tempfile.gettempdir(), filename)
19
+ with open(path, "w") as f:
20
+ json.dump(data, f)
21
+ return path
22
 
23
+ import base64
24
 
25
+ def save_png_to_file(img, filename):
26
+ if img is None:
27
+ return None
28
+ img.save(filename, format="PNG")
29
+ return filename
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
 
 
31
 
 
32
 
33
+ from io import BytesIO
34
+ from PIL import Image
35
+ import matplotlib.pyplot as plt
 
36
 
37
+ def ui_plot(groups_str):
38
+ # Parse semicolon-separated groups
39
+ groups = []
40
+ for g in groups_str.split(";"):
41
+ nums = [float(x) for x in g.split(",")]
42
+ groups.append(nums)
43
+
44
+ # Example plot: boxplot of groups
45
+ fig, ax = plt.subplots()
46
+ ax.boxplot(groups)
47
+ ax.set_title("Group Plot")
48
+ ax.set_xlabel("Group")
49
+ ax.set_ylabel("Value")
50
+
51
+ buf = BytesIO()
52
+ fig.savefig(buf, format="png")
53
+ buf.seek(0)
54
 
55
+ return Image.open(buf)
 
 
 
 
 
 
 
56
 
 
57
 
58
  # -----------------------------
59
+ # UI helper wrappers
60
  # -----------------------------
61
+ def ui_welch(g1, g2, alt):
62
+ return mcp_tools.welch_t_test(g1, g2, alt)
 
63
 
64
+ def ui_student(g1, g2, alt):
65
+ return mcp_tools.student_t_test(g1, g2, alt)
66
 
67
+ def ui_paired(g1, g2, alt):
68
+ return mcp_tools.paired_t_test(g1, g2, alt)
 
 
 
 
69
 
70
+ #def ui_one_sample(g, mu, alt):
71
+ # return mcp_tools.one_sample_t_test(g, mu, alt)
72
 
73
+ def parse_group(text):
74
+ # Split on commas, strip whitespace, convert to float
75
+ return [float(x.strip()) for x in text.split(",") if x.strip()]
76
+
77
+ def ui_os(g, mu, alternative):
78
+ import numpy as np
79
+ import pingouin as pg
80
 
81
+ g = np.array(parse_group(g), dtype=float)
 
 
 
 
 
82
 
83
+ if mu is None:
84
+ mu = 0
85
+ mu = float(mu)
86
 
87
+ adjusted = g - mu
88
+ result = pg.ttest(adjusted, y=None, alternative=alternative)
89
+ print("DEBUG ui_os:", g, mu, alternative)
90
 
91
+ return {
92
+ "T": float(result["T"].iloc[0]),
93
+ "dof": int(result["dof"].iloc[0]),
94
+ "alternative": alternative,
95
+ "p_val": float(result["p-val"].iloc[0]),
96
+ "CI95": [float(result["CI95%"].iloc[0][0]), float(result["CI95%"].iloc[0][1])],
97
+ "cohen_d": float(result["cohen-d"].iloc[0]),
98
+ "power": float(result["power"].iloc[0]),
99
+ "BF10": str(result["BF10"].iloc[0])
100
+ }
101
 
102
+ def ui_anova(groups):
103
+ return mcp_tools.one_way_anova(groups)
104
 
105
 
106
  # -----------------------------
107
+ # Modern Light Theme
108
  # -----------------------------
109
+ theme = gr.themes.Soft(
110
+ primary_hue="blue",
111
+ secondary_hue="gray",
112
+ neutral_hue="gray",
113
+ )
114
 
 
 
115
 
116
+ # -----------------------------
117
+ # Main UI
118
+ # -----------------------------
119
+ with gr.Blocks(title="Stat3001 — Statistical Tests") as demo:
120
 
121
+ gr.Markdown(
122
+ """
123
+ # 📊 Stat3001 — Statistical Analysis Toolkit
124
+ Clean, modern, and powered by **Pingouin** for robust statistical results.
125
+ """
126
+ )
127
+ groups_input = gr.Textbox(
128
+ label="Groups (semicolon-separated)",
129
+ placeholder="Example: 1,2,3; 4,5,6; 7,8,9",
130
+ value="1,2,3; 4,5,6; 7,8,9"
131
+ )
132
+ load_example = gr.Button("Load example")
133
+ load_example.click(lambda: "1,2,3; 4,5,6; 7,8,9", None, groups_input)
134
+ # Shared input for both ANOVA and post-hoc
135
 
136
+ # -----------------------------------------
137
+ # SHARED COMPONENTS (define BEFORE tabs)
138
+ # -----------------------------------------
139
+
140
+ # Shared input area
141
+
142
+ # ---------------------------------------------------
143
+ # Welch t-test
144
+ # ---------------------------------------------------
145
+ with gr.Tab("Welch t-test"):
146
+ with gr.Column():
147
+ gr.Markdown("### Compare two independent groups (unequal variances).")
148
+
149
+ g1 = gr.Textbox(label="Group 1 (comma-separated numbers)")
150
+ g2 = gr.Textbox(label="Group 2 (comma-separated numbers)")
151
+ alt = gr.Radio(["two-sided", "less", "greater"], value="two-sided", label="Alternative hypothesis")
152
+
153
+ run = gr.Button("Run Welch t-test", variant="primary")
154
+ out = gr.JSON(label="Results")
155
+
156
+ download_welch = gr.DownloadButton("Download results")
157
+ #run.click(lambda *args: ui_welch(*args), inputs=[g1, g2, alt], outputs=[out])
158
+ run.click(ui_welch, inputs=[g1, g2, alt], outputs=out)
159
+
160
+ run.click(
161
+ lambda r: save_json_to_file(r, "welch_results.json"),
162
+ inputs=[out],
163
+ outputs=[download_welch]
164
+ )
165
+
166
+
167
+
168
+
169
+ example_welch = "23,25,28,30,32", "18,20,22,25,27"
170
+ example_anova = "1,2,3; 2,3,4; 3,4,5"
171
+
172
+ load_example = gr.Button("Load example")
173
+ load_example.click(
174
+ lambda: example_welch,
175
+ None,
176
+ [g1, g2]
177
+ )
178
+
179
+
180
+
181
+ # ---------------------------------------------------
182
+ # Student t-test
183
+ # ---------------------------------------------------
184
+ with gr.Tab("Student t-test"):
185
+ gr.Markdown("### Compare two independent groups (equal variances).")
186
+ with gr.Column():
187
+ g1_s = gr.Textbox(label="Group 1 (comma-separated numbers)")
188
+ g2_s = gr.Textbox(label="Group 2 (comma-separated numbers)")
189
+ alt_s = gr.Radio(["two-sided", "less", "greater"], value="two-sided", label="Alternative hypothesis")
190
+
191
+ example_student = ("10,12,14,15,16", "9,11,13,14,15")
192
+ load_example_student = gr.Button("Load example")
193
+ load_example_student.click(lambda: example_student, None, [g1_s, g1_s])
194
+ run_s = gr.Button("Run Student t-test", variant="primary")
195
+ out_s = gr.JSON(label="Results")
196
+
197
+ download_student = gr.DownloadButton("Download results")
198
+ run_s.click(ui_student, inputs=[g1_s, g1_s, alt_s], outputs=[out_s])
199
+
200
+ run_s.click(
201
+ lambda r: save_json_to_file(r, "student_results.json"),
202
+ inputs=[out_s],
203
+ outputs=[download_student]
204
+ )
205
+
206
+ #run.click(lambda *args: ui_welch(*args), inputs=[g1, g2, alt], outputs=[out])
207
+ #run.click(lambda r: ("results.json", json.dumps(r)), inputs=[out], outputs=[download])
208
+
209
+
210
+
211
+ # ---------------------------------------------------
212
+ # Paired t-test
213
+ # ---------------------------------------------------
214
+ with gr.Tab("Paired t-test"):
215
+ gr.Markdown("### Compare two related measurements (same subjects).")
216
+ with gr.Column():
217
+ g1_p = gr.Textbox(label="Group 1 (comma-separated numbers)")
218
+ g2_p = gr.Textbox(label="Group 2 (comma-separated numbers)")
219
+ alt_p = gr.Radio(["two-sided", "less", "greater"], value="two-sided", label="Alternative hypothesis")
220
+
221
+ example_paired = ("5,6,7,8,9", "6,7,8,9,10")
222
+ load_example_paired = gr.Button("Load example")
223
+ load_example_paired.click(lambda: example_paired, None, [g1_p, g2_p])
224
+ run_p = gr.Button("Run Paired t-test", variant="primary")
225
+ out_p = gr.JSON(label="Results")
226
+
227
+ download_paired = gr.DownloadButton("Download results")
228
+
229
+ run_p.click(ui_paired, inputs=[g1_p, g2_p, alt_p], outputs=[out_p])
230
+
231
+ run_p.click(
232
+ lambda r: save_json_to_file(r, "paired_results.json"),
233
+ inputs=[out_p],
234
+ outputs=[download_paired]
235
+ )
236
+
237
+
238
+ #run.click(lambda *args: ui_welch(*args), inputs=[g1, g2, alt], outputs=[out])
239
+ #run.click(lambda r: ("results.json", json.dumps(r)), inputs=[out], outputs=[download])
240
+
241
+ #run_p.click(ui_paired, inputs=[g1_p, g2_p, alt_p], outputs=out_p)
242
+
243
+
244
+ # ---------------------------------------------------
245
+ # One-sample t-test
246
+ # ---------------------------------------------------
247
+ with gr.Tab("One-sample t-test"):
248
+ with gr.Column():
249
+ g_os = gr.Textbox(label="Sample values (comma-separated)")
250
+ mu_os = gr.Number(label="Population mean", value=0)
251
+ alt_one = gr.Radio(["two-sided", "less", "greater"], value="two-sided")
252
+
253
+ out_os = gr.JSON(label="Results")
254
+ run_os = gr.Button("Run One-sample t-test", variant="primary")
255
+ download_os = gr.DownloadButton("Download results")
256
+
257
+ example_one_sample = "5,6,7,8,9"
258
+ load_example_one = gr.Button("Load example")
259
+ load_example_one.click(lambda: example_one_sample, None, [g_os])
260
+
261
+ run_os.click(ui_os, inputs=[g_os, mu_os, alt_one], outputs=[out_os])
262
+
263
+ run_os.click(
264
+ lambda r: save_json_to_file(r, "one_sample_results.json"),
265
+ inputs=[out_os],
266
+ outputs=[download_os]
267
+ )
268
+
269
+ # ---------------------------------------------------
270
+ # One-way ANOVA
271
+ # ---------------------------------------------------
272
 
273
+
274
+ # -----------------------------------------
275
+ # ANOVA TAB
276
+ # -----------------------------------------
277
+
278
+ with gr.Tab("ANOVA"):
279
+ with gr.Column():
280
+ run_anova = gr.Button("Run ANOVA")
281
+ out_anova = gr.JSON(label="Results")
282
+
283
+ run_anova.click(ui_anova, inputs=[groups_input], outputs=[out_anova])
284
+
285
+ # -----------------------------------------
286
+ # POST-HOC TAB
287
+ # -----------------------------------------
288
+
289
+ with gr.Tab("Post-hoc"):
290
+ with gr.Column():
291
+ method = gr.Radio(["games-howell", "tukey"], value="games-howell")
292
+ run_post = gr.Button("Run post-hoc tests")
293
+ out_post = gr.JSON(label="Results")
294
+ download_post = gr.DownloadButton("Download results")
295
+
296
+ run_post.click(lambda g, m: posthoc_tests(g, m),
297
+ inputs=[groups_input, method],
298
+ outputs=[out_post])
299
+
300
+ run_post.click(lambda r: save_json_to_file(r, "posthoc.json"),
301
+ inputs=[out_post],
302
+ outputs=[download_post])
303
+
304
+ # ---------------------------------------------------
305
+ # Assumption Checks
306
+ # ---------------------------------------------------
307
+ with gr.Tab("Assumption Checks"):
308
+ with gr.Column():
309
+ gr.Markdown("### Normality & Homogeneity")
310
+ run_assump = gr.Button("Run assumption checks", variant="primary")
311
+ out_assump = gr.JSON(label="Results")
312
+ download_assump = gr.DownloadButton("Download results")
313
+
314
+ # Use the shared groups_input
315
+ run_assump.click(
316
+ lambda g: assumption_checks(g),
317
+ inputs=[groups_input],
318
+ outputs=[out_assump]
319
+ )
320
+
321
+ run_assump.click(
322
+ lambda r: save_json_to_file(r, "assumption_checks.json"),
323
+ inputs=[out_assump],
324
+ outputs=[download_assump]
325
+ )
326
+
327
+ # ---------------------------------------------------
328
+ # Plots
329
+ # ---------------------------------------------------
330
+ with gr.Tab("Plot"):
331
+ with gr.Column():
332
+ run_plot = gr.Button("Generate Plot")
333
+ plot_output = gr.Image(type="pil", label="Plot")
334
+ download_plot = gr.DownloadButton("Download plot")
335
+
336
+ run_plot.click(ui_plot, inputs=[groups_input], outputs=[plot_output])
337
+
338
+ download_plot.click(
339
+ lambda img: save_png_to_file(img, "plot.png"),
340
+ inputs=[plot_output],
341
+ outputs=[download_plot]
342
+ )
343
+
344
+
345
+
346
+
347
+ # Main execution
348
+ if __name__ == "__main__":
349
+ demo.launch(
350
+ theme=theme,
351
+ server_name="0.0.0.0",
352
+ server_port=7860
353
+ )