Spaces:
Runtime error
Runtime error
Update src/streamlit_app.py
Browse files- src/streamlit_app.py +13 -1297
src/streamlit_app.py
CHANGED
|
@@ -4273,1301 +4273,17 @@ Required Total N: {n_total}
|
|
| 4273 |
- Bonferroni, C. E. (1936). Teoria statistica delle classi e calcolo delle probabilità. *Pubblicazioni del R Istituto Superiore di Scienze Economiche e Commerciali di Firenze*, 8, 3–62.
|
| 4274 |
""")
|
| 4275 |
|
| 4276 |
-
|
| 4277 |
-
|
| 4278 |
-
|
| 4279 |
-
|
| 4280 |
-
|
| 4281 |
-
|
| 4282 |
-
|
| 4283 |
-
|
| 4284 |
-
["A Priori", "Post Hoc", "Sensitivity", "Compromise", "Criterion"],
|
| 4285 |
-
index=0,
|
| 4286 |
-
help="Choose the power analysis goal",
|
| 4287 |
-
)
|
| 4288 |
-
with st.expander("What do these mean?"):
|
| 4289 |
-
st.markdown("""
|
| 4290 |
-
- :orange[**A Priori**] — Compute required sample size *N* from α, power, and effect size
|
| 4291 |
-
- :orange[**Post Hoc**] — Compute achieved power from *N*, α, and effect size
|
| 4292 |
-
- :orange[**Sensitivity**] — Compute minimum detectable effect size from *N*, α, and power
|
| 4293 |
-
- :orange[**Compromise**] — Compute adjusted α and achieved power from *N*, effect size, and cost ratio *q* = β/α
|
| 4294 |
-
- :orange[**Criterion**] — Compute required α from *N*, effect size, and power
|
| 4295 |
-
""")
|
| 4296 |
-
|
| 4297 |
-
col_left, col_right = st.columns([1, 1.2], gap="large")
|
| 4298 |
-
|
| 4299 |
-
with col_left:
|
| 4300 |
-
|
| 4301 |
-
ss_at_opts = [
|
| 4302 |
-
"One-sample Mean (t/z-test)",
|
| 4303 |
-
"Two Independent Means (t-test)",
|
| 4304 |
-
"Paired Means (t-test)",
|
| 4305 |
-
"One-sample Proportion",
|
| 4306 |
-
"Two Proportions",
|
| 4307 |
-
"One-way ANOVA",
|
| 4308 |
-
"Correlation (Pearson)",
|
| 4309 |
-
"Multiple Linear Regression",
|
| 4310 |
-
"Logistic Regression",
|
| 4311 |
-
"Chi-Square Test",
|
| 4312 |
-
"Mann-Whitney / Wilcoxon (Non-parametric)",
|
| 4313 |
-
"Log-Rank Test (Survival)",
|
| 4314 |
-
"Cox Regression",
|
| 4315 |
-
"Equivalence / Non-Inferiority",
|
| 4316 |
-
"Repeated Measures ANOVA",
|
| 4317 |
-
"Two-way / Factorial ANOVA",
|
| 4318 |
-
"ROC / AUC Analysis",
|
| 4319 |
-
"Cohen's Kappa / ICC Agreement",
|
| 4320 |
-
"Cluster-RCT / Multilevel",
|
| 4321 |
-
"Precision-based (CI Width)",
|
| 4322 |
-
"Pilot / Feasibility Study",
|
| 4323 |
-
"Wilcoxon Signed-Rank (paired)",
|
| 4324 |
-
"Kruskal-Wallis Test",
|
| 4325 |
-
"Friedman Test",
|
| 4326 |
-
"McNemar's Test",
|
| 4327 |
-
"Fisher's Exact Test",
|
| 4328 |
-
"MANOVA (Multivariate ANOVA)",
|
| 4329 |
-
"Binomial Exact Test",
|
| 4330 |
-
"Simulation-based Power (Monte Carlo)",
|
| 4331 |
-
]
|
| 4332 |
-
analysis_type = st.selectbox("Type of Analysis", ss_at_opts, index=0)
|
| 4333 |
-
|
| 4334 |
-
st.markdown("##### :orange[Common Parameters]")
|
| 4335 |
-
is_a_priori = analysis_mode == "A Priori"
|
| 4336 |
-
is_post_hoc = analysis_mode == "Post Hoc"
|
| 4337 |
-
is_sensitivity = analysis_mode == "Sensitivity"
|
| 4338 |
-
is_compromise = analysis_mode == "Compromise"
|
| 4339 |
-
is_criterion = analysis_mode == "Criterion"
|
| 4340 |
-
|
| 4341 |
-
col_a, col_b = st.columns(2)
|
| 4342 |
-
with col_a:
|
| 4343 |
-
alpha_ss = st.slider(
|
| 4344 |
-
"Significance Level (α)",
|
| 4345 |
-
0.001,
|
| 4346 |
-
0.10,
|
| 4347 |
-
0.05,
|
| 4348 |
-
0.001,
|
| 4349 |
-
format="%.3f",
|
| 4350 |
-
disabled=is_criterion or is_compromise,
|
| 4351 |
-
)
|
| 4352 |
-
with col_b:
|
| 4353 |
-
power_ss = st.slider(
|
| 4354 |
-
"Power (1 − β)",
|
| 4355 |
-
0.50,
|
| 4356 |
-
0.99,
|
| 4357 |
-
0.80,
|
| 4358 |
-
0.01,
|
| 4359 |
-
format="%.2f",
|
| 4360 |
-
disabled=is_post_hoc or is_compromise,
|
| 4361 |
-
)
|
| 4362 |
-
tails_ss = st.radio(
|
| 4363 |
-
"Test Direction",
|
| 4364 |
-
["Two-tailed", "One-tailed"],
|
| 4365 |
-
horizontal=True,
|
| 4366 |
-
)
|
| 4367 |
-
|
| 4368 |
-
if not is_a_priori:
|
| 4369 |
-
n_total_input = st.number_input(
|
| 4370 |
-
"Total Sample Size (N)",
|
| 4371 |
-
2,
|
| 4372 |
-
10000000,
|
| 4373 |
-
100,
|
| 4374 |
-
1,
|
| 4375 |
-
help="Enter the total sample size for the study.",
|
| 4376 |
-
)
|
| 4377 |
-
else:
|
| 4378 |
-
n_total_input = None
|
| 4379 |
-
|
| 4380 |
-
if is_compromise:
|
| 4381 |
-
cost_ratio = st.number_input(
|
| 4382 |
-
"Cost Ratio (β/α)",
|
| 4383 |
-
0.01,
|
| 4384 |
-
100.0,
|
| 4385 |
-
1.0,
|
| 4386 |
-
0.1,
|
| 4387 |
-
help="Relative cost of Type II vs Type I error. q = 1 means both errors weighted equally.",
|
| 4388 |
-
)
|
| 4389 |
-
else:
|
| 4390 |
-
cost_ratio = 1.0
|
| 4391 |
-
|
| 4392 |
-
st.markdown("##### :orange[Test-Specific Parameters]")
|
| 4393 |
-
ss_params = {}
|
| 4394 |
-
|
| 4395 |
-
if analysis_type == "One-sample Mean (t/z-test)":
|
| 4396 |
-
c1, c2 = st.columns(2)
|
| 4397 |
-
with c1:
|
| 4398 |
-
mean_diff = st.number_input(
|
| 4399 |
-
"Expected Mean Difference (μ − μ₀)", 0.0, 100.0, 1.0, 0.1
|
| 4400 |
-
)
|
| 4401 |
-
with c2:
|
| 4402 |
-
std_dev_1s = st.number_input(
|
| 4403 |
-
"Standard Deviation (σ)", 0.1, 100.0, 2.0, 0.1
|
| 4404 |
-
)
|
| 4405 |
-
d_1s = mean_diff / std_dev_1s if std_dev_1s > 0 else 0
|
| 4406 |
-
st.caption(
|
| 4407 |
-
f"Cohen's d = {d_1s:.3f} — Small: 0.20 | Medium: 0.50 | Large: 0.80"
|
| 4408 |
-
)
|
| 4409 |
-
ss_params = {"type": "one_mean", "effect_size": d_1s}
|
| 4410 |
-
|
| 4411 |
-
elif analysis_type == "Two Independent Means (t-test)":
|
| 4412 |
-
c1, c2, c3 = st.columns(3)
|
| 4413 |
-
with c1:
|
| 4414 |
-
m1 = st.number_input("Mean of Group 1", 0.0, 100.0, 0.0, 0.1)
|
| 4415 |
-
with c2:
|
| 4416 |
-
m2 = st.number_input("Mean of Group 2", 0.0, 100.0, 1.0, 0.1)
|
| 4417 |
-
with c3:
|
| 4418 |
-
sd_2s = st.number_input("Pooled SD", 0.1, 100.0, 1.0, 0.1)
|
| 4419 |
-
ratio_2s = st.number_input("Allocation Ratio (n₂/n₁)", 0.1, 10.0, 1.0, 0.1)
|
| 4420 |
-
d_2s = abs(m1 - m2) / sd_2s if sd_2s > 0 else 0
|
| 4421 |
-
st.caption(
|
| 4422 |
-
f"Cohen's d = {d_2s:.3f} — Small: 0.20 | Medium: 0.50 | Large: 0.80"
|
| 4423 |
-
)
|
| 4424 |
-
ss_params = {
|
| 4425 |
-
"type": "two_means",
|
| 4426 |
-
"effect_size": d_2s,
|
| 4427 |
-
"ratio": ratio_2s,
|
| 4428 |
-
}
|
| 4429 |
-
|
| 4430 |
-
elif analysis_type == "Paired Means (t-test)":
|
| 4431 |
-
c1, c2 = st.columns(2)
|
| 4432 |
-
with c1:
|
| 4433 |
-
pdiff = st.number_input(
|
| 4434 |
-
"Expected Mean Difference", 0.0, 100.0, 1.0, 0.1
|
| 4435 |
-
)
|
| 4436 |
-
with c2:
|
| 4437 |
-
sddiff = st.number_input("SD of Differences", 0.1, 100.0, 1.5, 0.1)
|
| 4438 |
-
d_pd = pdiff / sddiff if sddiff > 0 else 0
|
| 4439 |
-
st.caption(
|
| 4440 |
-
f"Cohen's d_z = {d_pd:.3f} — Small: 0.20 | Medium: 0.50 | Large: 0.80"
|
| 4441 |
-
)
|
| 4442 |
-
ss_params = {"type": "paired", "effect_size": d_pd}
|
| 4443 |
-
|
| 4444 |
-
elif analysis_type == "One-sample Proportion":
|
| 4445 |
-
c1, c2 = st.columns(2)
|
| 4446 |
-
with c1:
|
| 4447 |
-
p0 = st.number_input("Null Proportion (p₀)", 0.01, 0.99, 0.5, 0.01)
|
| 4448 |
-
with c2:
|
| 4449 |
-
p1 = st.number_input("Expected Proportion (p₁)", 0.01, 0.99, 0.7, 0.01)
|
| 4450 |
-
ss_params = {
|
| 4451 |
-
"type": "one_prop",
|
| 4452 |
-
"prop_null": p0,
|
| 4453 |
-
"prop_alt": p1,
|
| 4454 |
-
}
|
| 4455 |
-
|
| 4456 |
-
elif analysis_type == "Two Proportions":
|
| 4457 |
-
c1, c2, c3 = st.columns(3)
|
| 4458 |
-
with c1:
|
| 4459 |
-
prop1 = st.number_input("Proportion in Group 1", 0.01, 0.99, 0.3, 0.01)
|
| 4460 |
-
with c2:
|
| 4461 |
-
prop2 = st.number_input("Proportion in Group 2", 0.01, 0.99, 0.5, 0.01)
|
| 4462 |
-
with c3:
|
| 4463 |
-
ratio_prop = st.number_input(
|
| 4464 |
-
"Allocation Ratio (n₂/n₁)", 0.1, 10.0, 1.0, 0.1
|
| 4465 |
-
)
|
| 4466 |
-
ss_params = {
|
| 4467 |
-
"type": "two_prop",
|
| 4468 |
-
"p1": prop1,
|
| 4469 |
-
"p2": prop2,
|
| 4470 |
-
"ratio": ratio_prop,
|
| 4471 |
-
}
|
| 4472 |
-
|
| 4473 |
-
elif analysis_type == "One-way ANOVA":
|
| 4474 |
-
c1, c2 = st.columns(2)
|
| 4475 |
-
with c1:
|
| 4476 |
-
k_anova = st.number_input("Number of Groups", 3, 20, 3, 1)
|
| 4477 |
-
with c2:
|
| 4478 |
-
f_anova = st.number_input(
|
| 4479 |
-
"Cohen's f (effect size)",
|
| 4480 |
-
0.01,
|
| 4481 |
-
2.0,
|
| 4482 |
-
0.25,
|
| 4483 |
-
0.01,
|
| 4484 |
-
)
|
| 4485 |
-
st.caption("Small: 0.10 | Medium: 0.25 | Large: 0.40")
|
| 4486 |
-
ss_params = {"type": "anova", "k": int(k_anova), "effect_size": f_anova}
|
| 4487 |
-
|
| 4488 |
-
elif analysis_type == "Correlation (Pearson)":
|
| 4489 |
-
r_val = st.number_input(
|
| 4490 |
-
"Expected Correlation (r)",
|
| 4491 |
-
0.01,
|
| 4492 |
-
0.99,
|
| 4493 |
-
0.3,
|
| 4494 |
-
0.01,
|
| 4495 |
-
)
|
| 4496 |
-
st.caption("Small: 0.10 | Medium: 0.30 | Large: 0.50")
|
| 4497 |
-
ss_params = {"type": "correlation", "effect_size": r_val}
|
| 4498 |
-
|
| 4499 |
-
elif analysis_type == "Multiple Linear Regression":
|
| 4500 |
-
c1, c2 = st.columns(2)
|
| 4501 |
-
with c1:
|
| 4502 |
-
k_reg = st.number_input("Number of Predictors", 1, 50, 3, 1)
|
| 4503 |
-
with c2:
|
| 4504 |
-
r2_reg = st.number_input("Expected R²", 0.01, 0.99, 0.15, 0.01)
|
| 4505 |
-
f2_reg = r2_reg / (1 - r2_reg) if r2_reg < 1 else 0
|
| 4506 |
-
st.caption(
|
| 4507 |
-
f"Cohen's f² = {f2_reg:.3f} — Small: 0.02 | Medium: 0.15 | Large: 0.35"
|
| 4508 |
-
)
|
| 4509 |
-
ss_params = {
|
| 4510 |
-
"type": "regression",
|
| 4511 |
-
"k": int(k_reg),
|
| 4512 |
-
"effect_size": f2_reg,
|
| 4513 |
-
}
|
| 4514 |
-
|
| 4515 |
-
elif analysis_type == "Logistic Regression":
|
| 4516 |
-
c1, c2 = st.columns(2)
|
| 4517 |
-
with c1:
|
| 4518 |
-
k_log = st.number_input("Number of Predictors", 1, 50, 3, 1)
|
| 4519 |
-
with c2:
|
| 4520 |
-
ev_rate = st.number_input(
|
| 4521 |
-
"Baseline Event Rate",
|
| 4522 |
-
0.01,
|
| 4523 |
-
0.99,
|
| 4524 |
-
0.3,
|
| 4525 |
-
0.01,
|
| 4526 |
-
)
|
| 4527 |
-
or_val = st.number_input("Odds Ratio to Detect", 1.1, 10.0, 2.0, 0.1)
|
| 4528 |
-
ss_params = {
|
| 4529 |
-
"type": "logistic",
|
| 4530 |
-
"k": int(k_log),
|
| 4531 |
-
"event_rate": ev_rate,
|
| 4532 |
-
"or": or_val,
|
| 4533 |
-
}
|
| 4534 |
-
|
| 4535 |
-
elif analysis_type == "Chi-Square Test":
|
| 4536 |
-
c1, c2 = st.columns(2)
|
| 4537 |
-
with c1:
|
| 4538 |
-
df_cs = st.number_input("Degrees of Freedom", 1, 50, 2, 1)
|
| 4539 |
-
with c2:
|
| 4540 |
-
w_cs = st.number_input(
|
| 4541 |
-
"Cohen's w (effect size)",
|
| 4542 |
-
0.01,
|
| 4543 |
-
2.0,
|
| 4544 |
-
0.3,
|
| 4545 |
-
0.01,
|
| 4546 |
-
)
|
| 4547 |
-
st.caption("Small: 0.10 | Medium: 0.30 | Large: 0.50")
|
| 4548 |
-
ss_params = {"type": "chisq", "df": int(df_cs), "effect_size": w_cs}
|
| 4549 |
-
|
| 4550 |
-
elif analysis_type == "Mann-Whitney / Wilcoxon (Non-parametric)":
|
| 4551 |
-
c1, c2 = st.columns(2)
|
| 4552 |
-
with c1:
|
| 4553 |
-
P_val = st.number_input(
|
| 4554 |
-
"P(X>Y) probability",
|
| 4555 |
-
0.51,
|
| 4556 |
-
0.99,
|
| 4557 |
-
0.65,
|
| 4558 |
-
0.01,
|
| 4559 |
-
)
|
| 4560 |
-
st.caption("Small: ~0.56 | Medium: ~0.64 | Large: ~0.71")
|
| 4561 |
-
with c2:
|
| 4562 |
-
are_val = st.number_input("ARE", 0.5, 1.5, 0.955, 0.001)
|
| 4563 |
-
ratio_mw = st.number_input("Allocation Ratio (n₂/n₁)", 0.1, 10.0, 1.0, 0.1)
|
| 4564 |
-
st.caption("ARE = 0.955 at normality, lower for heavy-tailed distributions")
|
| 4565 |
-
ss_params = {
|
| 4566 |
-
"type": "mannwhitney",
|
| 4567 |
-
"effect_size": P_val,
|
| 4568 |
-
"ratio": ratio_mw,
|
| 4569 |
-
"are": are_val,
|
| 4570 |
-
}
|
| 4571 |
-
|
| 4572 |
-
elif analysis_type == "Log-Rank Test (Survival)":
|
| 4573 |
-
c1, c2 = st.columns(2)
|
| 4574 |
-
with c1:
|
| 4575 |
-
hr_val = st.number_input("Hazard Ratio", 1.1, 10.0, 2.0, 0.1)
|
| 4576 |
-
with c2:
|
| 4577 |
-
ratio_lr = st.number_input(
|
| 4578 |
-
"Allocation Ratio (n₂/n₁)",
|
| 4579 |
-
0.1,
|
| 4580 |
-
10.0,
|
| 4581 |
-
1.0,
|
| 4582 |
-
0.1,
|
| 4583 |
-
)
|
| 4584 |
-
c1, c2 = st.columns(2)
|
| 4585 |
-
with c1:
|
| 4586 |
-
med_val = st.number_input(
|
| 4587 |
-
"Median Survival Control (months)",
|
| 4588 |
-
1,
|
| 4589 |
-
120,
|
| 4590 |
-
12,
|
| 4591 |
-
1,
|
| 4592 |
-
)
|
| 4593 |
-
with c2:
|
| 4594 |
-
dur_val = st.number_input(
|
| 4595 |
-
"Total Study Duration (months)",
|
| 4596 |
-
1,
|
| 4597 |
-
240,
|
| 4598 |
-
36,
|
| 4599 |
-
1,
|
| 4600 |
-
)
|
| 4601 |
-
ss_params = {
|
| 4602 |
-
"type": "logrank",
|
| 4603 |
-
"hr": hr_val,
|
| 4604 |
-
"ratio": ratio_lr,
|
| 4605 |
-
"median_survival": med_val,
|
| 4606 |
-
"study_duration": dur_val,
|
| 4607 |
-
}
|
| 4608 |
-
|
| 4609 |
-
elif analysis_type == "Cox Regression":
|
| 4610 |
-
c1, c2 = st.columns(2)
|
| 4611 |
-
with c1:
|
| 4612 |
-
hr_val = st.number_input("Hazard Ratio", 1.1, 10.0, 2.0, 0.1)
|
| 4613 |
-
with c2:
|
| 4614 |
-
k_val = st.number_input("Number of Predictors", 1, 50, 3, 1)
|
| 4615 |
-
c1, c2 = st.columns(2)
|
| 4616 |
-
with c1:
|
| 4617 |
-
sd_val = st.number_input("SD of Predictor", 0.1, 10.0, 1.0, 0.1)
|
| 4618 |
-
with c2:
|
| 4619 |
-
r2_val = st.number_input(
|
| 4620 |
-
"R-squared with other covariates",
|
| 4621 |
-
0.0,
|
| 4622 |
-
0.99,
|
| 4623 |
-
0.0,
|
| 4624 |
-
0.01,
|
| 4625 |
-
)
|
| 4626 |
-
ev_val = st.number_input("Event Rate", 0.01, 0.99, 0.5, 0.01)
|
| 4627 |
-
ss_params = {
|
| 4628 |
-
"type": "cox",
|
| 4629 |
-
"hr": hr_val,
|
| 4630 |
-
"k": int(k_val),
|
| 4631 |
-
"sd_x": sd_val,
|
| 4632 |
-
"r2_x": r2_val,
|
| 4633 |
-
"event_rate": ev_val,
|
| 4634 |
-
}
|
| 4635 |
-
|
| 4636 |
-
elif analysis_type == "Equivalence / Non-Inferiority":
|
| 4637 |
-
equiv_param_type = st.radio(
|
| 4638 |
-
"Parameter type",
|
| 4639 |
-
["Mean", "Proportion"],
|
| 4640 |
-
horizontal=True,
|
| 4641 |
-
)
|
| 4642 |
-
c1, c2 = st.columns(2)
|
| 4643 |
-
with c1:
|
| 4644 |
-
margin = st.number_input("Margin (delta)", 0.001, 10.0, 1.0, 0.001)
|
| 4645 |
-
with c2:
|
| 4646 |
-
d_exp = st.number_input(
|
| 4647 |
-
"Expected Difference",
|
| 4648 |
-
-10.0,
|
| 4649 |
-
10.0,
|
| 4650 |
-
0.0,
|
| 4651 |
-
0.01,
|
| 4652 |
-
)
|
| 4653 |
-
c1, c2 = st.columns(2)
|
| 4654 |
-
p1_eq = 0.5
|
| 4655 |
-
p2_eq = 0.5
|
| 4656 |
-
with c1:
|
| 4657 |
-
if equiv_param_type == "Mean":
|
| 4658 |
-
sd_val = st.number_input("SD", 0.1, 100.0, 1.0, 0.1)
|
| 4659 |
-
else:
|
| 4660 |
-
p1_eq = st.number_input(
|
| 4661 |
-
"Expected proportion (Group 1)",
|
| 4662 |
-
0.01,
|
| 4663 |
-
0.99,
|
| 4664 |
-
0.2,
|
| 4665 |
-
0.01,
|
| 4666 |
-
)
|
| 4667 |
-
sd_val = 1.0
|
| 4668 |
-
with c2:
|
| 4669 |
-
ratio_eq = st.number_input(
|
| 4670 |
-
"Allocation Ratio (n₂/n₁)",
|
| 4671 |
-
0.1,
|
| 4672 |
-
10.0,
|
| 4673 |
-
1.0,
|
| 4674 |
-
0.1,
|
| 4675 |
-
)
|
| 4676 |
-
if equiv_param_type == "Proportion":
|
| 4677 |
-
p2_eq = st.number_input(
|
| 4678 |
-
"Expected proportion (Group 2)",
|
| 4679 |
-
0.01,
|
| 4680 |
-
0.99,
|
| 4681 |
-
0.2,
|
| 4682 |
-
0.01,
|
| 4683 |
-
)
|
| 4684 |
-
ss_params = {
|
| 4685 |
-
"type": "equiv",
|
| 4686 |
-
"margin": margin,
|
| 4687 |
-
"expected_diff": d_exp,
|
| 4688 |
-
"sd": sd_val,
|
| 4689 |
-
"ratio": ratio_eq,
|
| 4690 |
-
"equiv_param_type": equiv_param_type,
|
| 4691 |
-
"p1_eq": p1_eq,
|
| 4692 |
-
"p2_eq": p2_eq,
|
| 4693 |
-
}
|
| 4694 |
-
|
| 4695 |
-
elif analysis_type == "Repeated Measures ANOVA":
|
| 4696 |
-
c1, c2 = st.columns(2)
|
| 4697 |
-
with c1:
|
| 4698 |
-
f_val = st.number_input("Cohen's f", 0.01, 2.0, 0.25, 0.01)
|
| 4699 |
-
st.caption("Small: 0.10 | Medium: 0.25 | Large: 0.40")
|
| 4700 |
-
with c2:
|
| 4701 |
-
k_val = st.number_input("Number of Groups", 2, 20, 2, 1)
|
| 4702 |
-
c1, c2 = st.columns(2)
|
| 4703 |
-
with c1:
|
| 4704 |
-
m_val = st.number_input("Number of Measurements", 2, 20, 3, 1)
|
| 4705 |
-
with c2:
|
| 4706 |
-
rho_val = st.number_input(
|
| 4707 |
-
"Correlation between measurements",
|
| 4708 |
-
0.0,
|
| 4709 |
-
0.99,
|
| 4710 |
-
0.5,
|
| 4711 |
-
0.01,
|
| 4712 |
-
)
|
| 4713 |
-
eps_val = st.number_input(
|
| 4714 |
-
"Sphericity correction epsilon",
|
| 4715 |
-
0.1,
|
| 4716 |
-
1.0,
|
| 4717 |
-
0.75,
|
| 4718 |
-
0.01,
|
| 4719 |
-
)
|
| 4720 |
-
ss_params = {
|
| 4721 |
-
"type": "rm_anova",
|
| 4722 |
-
"effect_size": f_val,
|
| 4723 |
-
"k": int(k_val),
|
| 4724 |
-
"m": int(m_val),
|
| 4725 |
-
"rho": rho_val,
|
| 4726 |
-
"epsilon": eps_val,
|
| 4727 |
-
}
|
| 4728 |
-
|
| 4729 |
-
elif analysis_type == "Two-way / Factorial ANOVA":
|
| 4730 |
-
c1, c2 = st.columns(2)
|
| 4731 |
-
with c1:
|
| 4732 |
-
r_val = st.number_input("Rows (Factor A levels)", 2, 10, 2, 1)
|
| 4733 |
-
with c2:
|
| 4734 |
-
c_val = st.number_input("Columns (Factor B levels)", 2, 10, 2, 1)
|
| 4735 |
-
c1, c2, c3 = st.columns(3)
|
| 4736 |
-
with c1:
|
| 4737 |
-
f_a = st.number_input(
|
| 4738 |
-
"Cohen's f for Factor A",
|
| 4739 |
-
0.01,
|
| 4740 |
-
2.0,
|
| 4741 |
-
0.25,
|
| 4742 |
-
0.01,
|
| 4743 |
-
)
|
| 4744 |
-
st.caption("Small: 0.10 | Medium: 0.25 | Large: 0.40")
|
| 4745 |
-
with c2:
|
| 4746 |
-
f_b = st.number_input(
|
| 4747 |
-
"Cohen's f for Factor B",
|
| 4748 |
-
0.01,
|
| 4749 |
-
2.0,
|
| 4750 |
-
0.25,
|
| 4751 |
-
0.01,
|
| 4752 |
-
)
|
| 4753 |
-
with c3:
|
| 4754 |
-
f_ab = st.number_input(
|
| 4755 |
-
"Cohen's f for interaction",
|
| 4756 |
-
0.01,
|
| 4757 |
-
2.0,
|
| 4758 |
-
0.25,
|
| 4759 |
-
0.01,
|
| 4760 |
-
)
|
| 4761 |
-
focus = st.radio(
|
| 4762 |
-
"Effect of interest",
|
| 4763 |
-
["Main Effect A", "Main Effect B", "Interaction"],
|
| 4764 |
-
horizontal=True,
|
| 4765 |
-
)
|
| 4766 |
-
ss_params = {
|
| 4767 |
-
"type": "twoway_anova",
|
| 4768 |
-
"f_a": f_a,
|
| 4769 |
-
"f_b": f_b,
|
| 4770 |
-
"f_ab": f_ab,
|
| 4771 |
-
"rows": int(r_val),
|
| 4772 |
-
"cols": int(c_val),
|
| 4773 |
-
"focus": focus,
|
| 4774 |
-
}
|
| 4775 |
-
|
| 4776 |
-
elif analysis_type == "ROC / AUC Analysis":
|
| 4777 |
-
c1, c2 = st.columns(2)
|
| 4778 |
-
with c1:
|
| 4779 |
-
auc_val = st.number_input("Expected AUC", 0.5, 0.99, 0.7, 0.01)
|
| 4780 |
-
with c2:
|
| 4781 |
-
st.number_input("Null AUC", 0.5, 0.5, 0.5, disabled=True)
|
| 4782 |
-
ratio_roc = st.number_input(
|
| 4783 |
-
"Ratio of controls to cases",
|
| 4784 |
-
0.1,
|
| 4785 |
-
10.0,
|
| 4786 |
-
1.0,
|
| 4787 |
-
0.1,
|
| 4788 |
-
)
|
| 4789 |
-
ss_params = {
|
| 4790 |
-
"type": "roc_auc",
|
| 4791 |
-
"auc": auc_val,
|
| 4792 |
-
"null_auc": 0.5,
|
| 4793 |
-
"ratio": ratio_roc,
|
| 4794 |
-
}
|
| 4795 |
-
|
| 4796 |
-
elif analysis_type == "Cohen's Kappa / ICC Agreement":
|
| 4797 |
-
atype = st.radio("Type", ["Cohen's Kappa", "ICC"], horizontal=True)
|
| 4798 |
-
c1, c2 = st.columns(2)
|
| 4799 |
-
with c1:
|
| 4800 |
-
kappa_val = st.number_input(
|
| 4801 |
-
"Expected Kappa",
|
| 4802 |
-
0.01,
|
| 4803 |
-
0.99,
|
| 4804 |
-
0.6,
|
| 4805 |
-
0.01,
|
| 4806 |
-
)
|
| 4807 |
-
with c2:
|
| 4808 |
-
null_kap = st.number_input("Null Kappa", 0.0, 0.5, 0.0, 0.01)
|
| 4809 |
-
c1, c2 = st.columns(2)
|
| 4810 |
-
with c1:
|
| 4811 |
-
raters = st.number_input("Number of Raters", 2, 10, 2, 1)
|
| 4812 |
-
with c2:
|
| 4813 |
-
cats = st.number_input("Number of Categories", 2, 10, 2, 1)
|
| 4814 |
-
ss_params = {
|
| 4815 |
-
"type": "kappa",
|
| 4816 |
-
"kappa": kappa_val,
|
| 4817 |
-
"null_kappa": null_kap,
|
| 4818 |
-
"raters": int(raters),
|
| 4819 |
-
"categories": int(cats),
|
| 4820 |
-
"agreement_type": atype,
|
| 4821 |
-
}
|
| 4822 |
-
|
| 4823 |
-
elif analysis_type == "Cluster-RCT / Multilevel":
|
| 4824 |
-
c1, c2 = st.columns(2)
|
| 4825 |
-
with c1:
|
| 4826 |
-
d_val = st.number_input("Effect size d", 0.1, 5.0, 0.5, 0.01)
|
| 4827 |
-
st.caption("Small: 0.20 | Medium: 0.50 | Large: 0.80")
|
| 4828 |
-
with c2:
|
| 4829 |
-
icc_val = st.number_input(
|
| 4830 |
-
"ICC",
|
| 4831 |
-
0.001,
|
| 4832 |
-
0.5,
|
| 4833 |
-
0.05,
|
| 4834 |
-
0.001,
|
| 4835 |
-
format="%.3f",
|
| 4836 |
-
)
|
| 4837 |
-
c1, c2 = st.columns(2)
|
| 4838 |
-
with c1:
|
| 4839 |
-
m_val = st.number_input("Cluster size (m)", 2, 1000, 30, 1)
|
| 4840 |
-
with c2:
|
| 4841 |
-
ratio_cl = st.number_input(
|
| 4842 |
-
"Allocation Ratio (n₂/n₁)",
|
| 4843 |
-
0.1,
|
| 4844 |
-
10.0,
|
| 4845 |
-
1.0,
|
| 4846 |
-
0.1,
|
| 4847 |
-
)
|
| 4848 |
-
ss_params = {
|
| 4849 |
-
"type": "cluster_rct",
|
| 4850 |
-
"effect_size": d_val,
|
| 4851 |
-
"icc": icc_val,
|
| 4852 |
-
"cluster_size": int(m_val),
|
| 4853 |
-
"ratio": ratio_cl,
|
| 4854 |
-
}
|
| 4855 |
-
|
| 4856 |
-
elif analysis_type == "Precision-based (CI Width)":
|
| 4857 |
-
ptype = st.radio(
|
| 4858 |
-
"Type of parameter",
|
| 4859 |
-
["Mean", "Proportion"],
|
| 4860 |
-
horizontal=True,
|
| 4861 |
-
)
|
| 4862 |
-
c1, c2 = st.columns(2)
|
| 4863 |
-
with c1:
|
| 4864 |
-
hw_val = st.number_input(
|
| 4865 |
-
"Desired half-width of CI",
|
| 4866 |
-
0.01,
|
| 4867 |
-
100.0,
|
| 4868 |
-
5.0,
|
| 4869 |
-
0.01,
|
| 4870 |
-
)
|
| 4871 |
-
with c2:
|
| 4872 |
-
cl_val = st.number_input("Confidence Level %", 80, 99, 95, 1)
|
| 4873 |
-
if ptype == "Mean":
|
| 4874 |
-
sd_val = st.number_input("SD", 0.1, 100.0, 10.0, 0.1)
|
| 4875 |
-
prop_val = 0.5
|
| 4876 |
-
else:
|
| 4877 |
-
sd_val = 1.0
|
| 4878 |
-
prop_val = st.number_input(
|
| 4879 |
-
"Expected Proportion",
|
| 4880 |
-
0.01,
|
| 4881 |
-
0.99,
|
| 4882 |
-
0.5,
|
| 4883 |
-
0.01,
|
| 4884 |
-
)
|
| 4885 |
-
ss_params = {
|
| 4886 |
-
"type": "precision",
|
| 4887 |
-
"half_width": hw_val,
|
| 4888 |
-
"conf_level": cl_val,
|
| 4889 |
-
"param_type": ptype,
|
| 4890 |
-
"sd": sd_val,
|
| 4891 |
-
"prop": prop_val,
|
| 4892 |
-
}
|
| 4893 |
-
|
| 4894 |
-
elif analysis_type == "Pilot / Feasibility Study":
|
| 4895 |
-
method = st.radio(
|
| 4896 |
-
"Method",
|
| 4897 |
-
["Rule of thumb", "Precision-based", "Fraction of main study"],
|
| 4898 |
-
horizontal=True,
|
| 4899 |
-
)
|
| 4900 |
-
if method == "Rule of thumb":
|
| 4901 |
-
npg_val = st.number_input("Participants per group", 5, 100, 12, 1)
|
| 4902 |
-
ss_params = {
|
| 4903 |
-
"type": "pilot",
|
| 4904 |
-
"method": method,
|
| 4905 |
-
"n_per_group": int(npg_val),
|
| 4906 |
-
}
|
| 4907 |
-
elif method == "Precision-based":
|
| 4908 |
-
c1, c2 = st.columns(2)
|
| 4909 |
-
with c1:
|
| 4910 |
-
hw_val = st.number_input(
|
| 4911 |
-
"Desired half-width of CI",
|
| 4912 |
-
0.01,
|
| 4913 |
-
100.0,
|
| 4914 |
-
5.0,
|
| 4915 |
-
0.01,
|
| 4916 |
-
)
|
| 4917 |
-
with c2:
|
| 4918 |
-
cl_val = st.number_input("Confidence Level %", 80, 99, 95, 1)
|
| 4919 |
-
sd_val = st.number_input("SD", 0.1, 100.0, 10.0, 0.1)
|
| 4920 |
-
ss_params = {
|
| 4921 |
-
"type": "pilot",
|
| 4922 |
-
"method": method,
|
| 4923 |
-
"half_width": hw_val,
|
| 4924 |
-
"conf_level": cl_val,
|
| 4925 |
-
"param_type": "Mean",
|
| 4926 |
-
"sd": sd_val,
|
| 4927 |
-
"prop": 0.5,
|
| 4928 |
-
}
|
| 4929 |
-
else:
|
| 4930 |
-
main_n = st.number_input(
|
| 4931 |
-
"Expected main study N",
|
| 4932 |
-
10,
|
| 4933 |
-
10000,
|
| 4934 |
-
100,
|
| 4935 |
-
1,
|
| 4936 |
-
)
|
| 4937 |
-
fraction = st.number_input("Fraction", 0.05, 0.5, 0.1, 0.01)
|
| 4938 |
-
ss_params = {
|
| 4939 |
-
"type": "pilot",
|
| 4940 |
-
"method": method,
|
| 4941 |
-
"fraction": fraction,
|
| 4942 |
-
"main_n": int(main_n),
|
| 4943 |
-
}
|
| 4944 |
-
|
| 4945 |
-
elif analysis_type == "Wilcoxon Signed-Rank (paired)":
|
| 4946 |
-
c1, c2 = st.columns(2)
|
| 4947 |
-
with c1:
|
| 4948 |
-
pr_pos = st.number_input(
|
| 4949 |
-
"Pr(positive difference)",
|
| 4950 |
-
0.51,
|
| 4951 |
-
0.99,
|
| 4952 |
-
0.65,
|
| 4953 |
-
0.01,
|
| 4954 |
-
)
|
| 4955 |
-
st.caption("Small: ~0.56 | Medium: ~0.64 | Large: ~0.71")
|
| 4956 |
-
with c2:
|
| 4957 |
-
are_wsr = st.number_input(
|
| 4958 |
-
"ARE vs paired t-test",
|
| 4959 |
-
0.5,
|
| 4960 |
-
1.5,
|
| 4961 |
-
0.955,
|
| 4962 |
-
0.001,
|
| 4963 |
-
)
|
| 4964 |
-
st.caption("ARE = 0.955 at normality, lower for heavy-tailed distributions")
|
| 4965 |
-
ss_params = {
|
| 4966 |
-
"type": "wilcoxon_sr",
|
| 4967 |
-
"effect_size": pr_pos,
|
| 4968 |
-
"are": are_wsr,
|
| 4969 |
-
}
|
| 4970 |
-
|
| 4971 |
-
elif analysis_type == "Kruskal-Wallis Test":
|
| 4972 |
-
c1, c2 = st.columns(2)
|
| 4973 |
-
with c1:
|
| 4974 |
-
k_kw = st.number_input("Number of Groups", 3, 20, 3, 1)
|
| 4975 |
-
with c2:
|
| 4976 |
-
f_kw = st.number_input(
|
| 4977 |
-
"Cohen's f (effect size)",
|
| 4978 |
-
0.01,
|
| 4979 |
-
2.0,
|
| 4980 |
-
0.25,
|
| 4981 |
-
0.01,
|
| 4982 |
-
)
|
| 4983 |
-
st.caption("Small: 0.10 | Medium: 0.25 | Large: 0.40")
|
| 4984 |
-
are_kw = st.number_input(
|
| 4985 |
-
"ARE vs ANOVA (asymptotic relative efficiency)",
|
| 4986 |
-
0.15,
|
| 4987 |
-
1.5,
|
| 4988 |
-
0.955,
|
| 4989 |
-
0.001,
|
| 4990 |
-
help="ARE = 0.955 at normality, lower for heavy-tailed distributions. Inflates N by 1/ARE.",
|
| 4991 |
-
)
|
| 4992 |
-
st.caption(
|
| 4993 |
-
f"Effective inflation = {1/are_kw:.2f}× (N_multiplier = {1/are_kw:.3f})"
|
| 4994 |
-
)
|
| 4995 |
-
ss_params = {
|
| 4996 |
-
"type": "kruskal",
|
| 4997 |
-
"k": int(k_kw),
|
| 4998 |
-
"effect_size": f_kw,
|
| 4999 |
-
"are": are_kw,
|
| 5000 |
-
}
|
| 5001 |
-
|
| 5002 |
-
elif analysis_type == "Friedman Test":
|
| 5003 |
-
c1, c2 = st.columns(2)
|
| 5004 |
-
with c1:
|
| 5005 |
-
k_fr = st.number_input("Number of Groups", 2, 20, 3, 1)
|
| 5006 |
-
with c2:
|
| 5007 |
-
m_fr = st.number_input("Number of Measurements", 2, 20, 3, 1)
|
| 5008 |
-
c1, c2 = st.columns(2)
|
| 5009 |
-
with c1:
|
| 5010 |
-
w_fr = st.number_input("Kendall's W", 0.01, 0.99, 0.3, 0.01)
|
| 5011 |
-
st.caption("Small: 0.10 | Medium: 0.30 | Large: 0.50")
|
| 5012 |
-
with c2:
|
| 5013 |
-
are_fr = st.number_input("ARE vs RM-ANOVA", 0.5, 1.5, 0.955, 0.001)
|
| 5014 |
-
ss_params = {
|
| 5015 |
-
"type": "friedman",
|
| 5016 |
-
"k": int(k_fr),
|
| 5017 |
-
"m": int(m_fr),
|
| 5018 |
-
"w": w_fr,
|
| 5019 |
-
"are": are_fr,
|
| 5020 |
-
}
|
| 5021 |
-
|
| 5022 |
-
elif analysis_type == "McNemar's Test":
|
| 5023 |
-
c1, c2 = st.columns(2)
|
| 5024 |
-
with c1:
|
| 5025 |
-
p_b = st.number_input("Discordant prop (b)", 0.01, 0.99, 0.2, 0.01)
|
| 5026 |
-
with c2:
|
| 5027 |
-
p_c = st.number_input("Discordant prop (c)", 0.01, 0.99, 0.4, 0.01)
|
| 5028 |
-
ss_params = {"type": "mcnemar", "p_b": p_b, "p_c": p_c}
|
| 5029 |
-
|
| 5030 |
-
elif analysis_type == "Fisher's Exact Test":
|
| 5031 |
-
c1, c2, c3 = st.columns(3)
|
| 5032 |
-
with c1:
|
| 5033 |
-
p1_fish = st.number_input(
|
| 5034 |
-
"Proportion Group 1",
|
| 5035 |
-
0.01,
|
| 5036 |
-
0.99,
|
| 5037 |
-
0.3,
|
| 5038 |
-
0.01,
|
| 5039 |
-
)
|
| 5040 |
-
with c2:
|
| 5041 |
-
p2_fish = st.number_input(
|
| 5042 |
-
"Proportion Group 2",
|
| 5043 |
-
0.01,
|
| 5044 |
-
0.99,
|
| 5045 |
-
0.5,
|
| 5046 |
-
0.01,
|
| 5047 |
-
)
|
| 5048 |
-
with c3:
|
| 5049 |
-
ratio_fish = st.number_input(
|
| 5050 |
-
"Allocation Ratio (n₂/n₁)",
|
| 5051 |
-
0.1,
|
| 5052 |
-
10.0,
|
| 5053 |
-
1.0,
|
| 5054 |
-
0.1,
|
| 5055 |
-
)
|
| 5056 |
-
are_fish = st.number_input(
|
| 5057 |
-
"ARE vs z-test (asymptotic relative efficiency)",
|
| 5058 |
-
0.5,
|
| 5059 |
-
1.0,
|
| 5060 |
-
0.833,
|
| 5061 |
-
0.001,
|
| 5062 |
-
help="ARE ≈ 0.833 is the standard adjustment for Fisher's exact vs z-test. Lower values increase N.",
|
| 5063 |
-
)
|
| 5064 |
-
st.caption(f"Effective inflation = {1/are_fish:.2f}×")
|
| 5065 |
-
ss_params = {
|
| 5066 |
-
"type": "fisher",
|
| 5067 |
-
"p1": p1_fish,
|
| 5068 |
-
"p2": p2_fish,
|
| 5069 |
-
"ratio": ratio_fish,
|
| 5070 |
-
"are": are_fish,
|
| 5071 |
-
}
|
| 5072 |
-
|
| 5073 |
-
elif analysis_type == "MANOVA (Multivariate ANOVA)":
|
| 5074 |
-
c1, c2 = st.columns(2)
|
| 5075 |
-
with c1:
|
| 5076 |
-
k_man = st.number_input("Number of Groups", 2, 20, 3, 1)
|
| 5077 |
-
with c2:
|
| 5078 |
-
dv_man = st.number_input("Number of DVs", 2, 20, 3, 1)
|
| 5079 |
-
manova_test = st.selectbox(
|
| 5080 |
-
"Test statistic",
|
| 5081 |
-
[
|
| 5082 |
-
"Pillai's Trace",
|
| 5083 |
-
"Wilks' Lambda",
|
| 5084 |
-
"Hotelling-Lawley Trace",
|
| 5085 |
-
"Roy's Largest Root",
|
| 5086 |
-
],
|
| 5087 |
-
help="Pillai: most robust, recommended. Wilks: traditional. Hotelling: more power when assumptions met. Roy: most powerful when one dimension dominates.",
|
| 5088 |
-
)
|
| 5089 |
-
c1, c2 = st.columns(2)
|
| 5090 |
-
with c1:
|
| 5091 |
-
f2_man = st.number_input(
|
| 5092 |
-
"Effect size f²(V)",
|
| 5093 |
-
0.01,
|
| 5094 |
-
2.0,
|
| 5095 |
-
0.0625,
|
| 5096 |
-
0.001,
|
| 5097 |
-
format="%.4f",
|
| 5098 |
-
)
|
| 5099 |
-
st.caption("Small: 0.01 | Medium: 0.0625 | Large: 0.16")
|
| 5100 |
-
with c2:
|
| 5101 |
-
corr_man = st.number_input(
|
| 5102 |
-
"Correlation among DVs",
|
| 5103 |
-
0.0,
|
| 5104 |
-
0.99,
|
| 5105 |
-
0.5,
|
| 5106 |
-
0.01,
|
| 5107 |
-
)
|
| 5108 |
-
ss_params = {
|
| 5109 |
-
"type": "manova",
|
| 5110 |
-
"k": int(k_man),
|
| 5111 |
-
"dv": int(dv_man),
|
| 5112 |
-
"f2": f2_man,
|
| 5113 |
-
"rho": corr_man,
|
| 5114 |
-
"manova_test": manova_test,
|
| 5115 |
-
}
|
| 5116 |
-
|
| 5117 |
-
elif analysis_type == "Binomial Exact Test":
|
| 5118 |
-
c1, c2 = st.columns(2)
|
| 5119 |
-
with c1:
|
| 5120 |
-
p0_bin = st.number_input(
|
| 5121 |
-
"Null proportion (π₀)",
|
| 5122 |
-
0.01,
|
| 5123 |
-
0.99,
|
| 5124 |
-
0.5,
|
| 5125 |
-
0.01,
|
| 5126 |
-
)
|
| 5127 |
-
with c2:
|
| 5128 |
-
p1_bin = st.number_input(
|
| 5129 |
-
"Expected proportion (π₁)",
|
| 5130 |
-
0.01,
|
| 5131 |
-
0.99,
|
| 5132 |
-
0.7,
|
| 5133 |
-
0.01,
|
| 5134 |
-
)
|
| 5135 |
-
ss_params = {"type": "binomial", "p0": p0_bin, "p1": p1_bin}
|
| 5136 |
-
|
| 5137 |
-
elif analysis_type == "Simulation-based Power (Monte Carlo)":
|
| 5138 |
-
sim_test = st.selectbox(
|
| 5139 |
-
"Statistical test to simulate",
|
| 5140 |
-
[
|
| 5141 |
-
"Independent t-test (pooled)",
|
| 5142 |
-
"Welch's t-test",
|
| 5143 |
-
"Mann-Whitney U test",
|
| 5144 |
-
"Two-proportion z-test",
|
| 5145 |
-
],
|
| 5146 |
-
)
|
| 5147 |
-
n_sim = st.number_input(
|
| 5148 |
-
"Number of simulations",
|
| 5149 |
-
100,
|
| 5150 |
-
10000,
|
| 5151 |
-
1000,
|
| 5152 |
-
100,
|
| 5153 |
-
help="Higher = more precise but slower.",
|
| 5154 |
-
)
|
| 5155 |
-
if sim_test in (
|
| 5156 |
-
"Independent t-test (pooled)",
|
| 5157 |
-
"Welch's t-test",
|
| 5158 |
-
"Mann-Whitney U test",
|
| 5159 |
-
):
|
| 5160 |
-
c1, c2, c3 = st.columns(3)
|
| 5161 |
-
with c1:
|
| 5162 |
-
mu1_s = st.number_input(
|
| 5163 |
-
"Mean of Group 1",
|
| 5164 |
-
-100.0,
|
| 5165 |
-
100.0,
|
| 5166 |
-
0.0,
|
| 5167 |
-
0.1,
|
| 5168 |
-
)
|
| 5169 |
-
with c2:
|
| 5170 |
-
mu2_s = st.number_input(
|
| 5171 |
-
"Mean of Group 2",
|
| 5172 |
-
-100.0,
|
| 5173 |
-
100.0,
|
| 5174 |
-
0.5,
|
| 5175 |
-
0.1,
|
| 5176 |
-
)
|
| 5177 |
-
with c3:
|
| 5178 |
-
sd_s = st.number_input("SD (both groups)", 0.1, 100.0, 1.0, 0.1)
|
| 5179 |
-
n_per_s = st.number_input("N per group", 5, 5000, 50, 5)
|
| 5180 |
-
dist_type = st.radio(
|
| 5181 |
-
"Distribution shape",
|
| 5182 |
-
["Normal", "Skewed (Exponential)", "Heavy-tailed (Uniform)"],
|
| 5183 |
-
horizontal=True,
|
| 5184 |
-
help="Normal = standard normal. Exponential = skewed right. Uniform = light tails.",
|
| 5185 |
-
)
|
| 5186 |
-
ss_params = {
|
| 5187 |
-
"type": "simulation",
|
| 5188 |
-
"sim_test": sim_test,
|
| 5189 |
-
"n_sim": int(n_sim),
|
| 5190 |
-
"mu1": mu1_s,
|
| 5191 |
-
"mu2": mu2_s,
|
| 5192 |
-
"sd": sd_s,
|
| 5193 |
-
"n_per": int(n_per_s),
|
| 5194 |
-
"dist": dist_type,
|
| 5195 |
-
}
|
| 5196 |
-
else:
|
| 5197 |
-
p1_s = st.number_input(
|
| 5198 |
-
"Proportion in Group 1",
|
| 5199 |
-
0.01,
|
| 5200 |
-
0.99,
|
| 5201 |
-
0.3,
|
| 5202 |
-
0.01,
|
| 5203 |
-
)
|
| 5204 |
-
p2_s = st.number_input(
|
| 5205 |
-
"Proportion in Group 2",
|
| 5206 |
-
0.01,
|
| 5207 |
-
0.99,
|
| 5208 |
-
0.5,
|
| 5209 |
-
0.01,
|
| 5210 |
-
)
|
| 5211 |
-
n_per_s = st.number_input("N per group", 5, 5000, 100, 5)
|
| 5212 |
-
ss_params = {
|
| 5213 |
-
"type": "simulation",
|
| 5214 |
-
"sim_test": sim_test,
|
| 5215 |
-
"n_sim": int(n_sim),
|
| 5216 |
-
"p1_s": p1_s,
|
| 5217 |
-
"p2_s": p2_s,
|
| 5218 |
-
"n_per": int(n_per_s),
|
| 5219 |
-
}
|
| 5220 |
-
|
| 5221 |
-
# Apply effect size converter value if present
|
| 5222 |
-
conv_es = st.session_state.pop("converted_es", None)
|
| 5223 |
-
conv_type = st.session_state.pop("converted_type", None)
|
| 5224 |
-
if conv_es is not None and conv_type is not None:
|
| 5225 |
-
atype_key = ss_params.get("type", "")
|
| 5226 |
-
if conv_type == "d" and atype_key in (
|
| 5227 |
-
"one_mean",
|
| 5228 |
-
"two_means",
|
| 5229 |
-
"paired",
|
| 5230 |
-
"cluster_rct",
|
| 5231 |
-
):
|
| 5232 |
-
ss_params["effect_size"] = conv_es
|
| 5233 |
-
elif conv_type == "r" and atype_key == "correlation":
|
| 5234 |
-
ss_params["effect_size"] = conv_es
|
| 5235 |
-
elif conv_type == "f" and atype_key in (
|
| 5236 |
-
"anova",
|
| 5237 |
-
"rm_anova",
|
| 5238 |
-
"twoway_anova",
|
| 5239 |
-
"kruskal",
|
| 5240 |
-
):
|
| 5241 |
-
ss_params["effect_size"] = conv_es
|
| 5242 |
-
elif conv_type == "f2" and atype_key == "regression":
|
| 5243 |
-
ss_params["effect_size"] = conv_es
|
| 5244 |
-
elif conv_type == "or" and atype_key == "logistic":
|
| 5245 |
-
ss_params["or"] = conv_es
|
| 5246 |
-
elif conv_type == "w" and atype_key == "chisq":
|
| 5247 |
-
ss_params["effect_size"] = conv_es
|
| 5248 |
-
elif conv_type == "d" and atype_key == "wilcoxon_sr":
|
| 5249 |
-
from scipy.stats import norm
|
| 5250 |
-
|
| 5251 |
-
p_conv = 0.5 + conv_es / (2 * np.sqrt(3))
|
| 5252 |
-
ss_params["effect_size"] = max(0.51, min(0.99, p_conv))
|
| 5253 |
-
|
| 5254 |
-
# =========================
|
| 5255 |
-
# STUDY ADJUSTMENTS
|
| 5256 |
-
# =========================
|
| 5257 |
-
with st.expander("⚙️ Study Adjustments"):
|
| 5258 |
-
col_d1, col_d2 = st.columns(2)
|
| 5259 |
-
with col_d1:
|
| 5260 |
-
adjust_attrition = st.checkbox(
|
| 5261 |
-
"Adjust for dropout rate",
|
| 5262 |
-
value=False,
|
| 5263 |
-
)
|
| 5264 |
-
with col_d2:
|
| 5265 |
-
dropout_rate = (
|
| 5266 |
-
st.slider(
|
| 5267 |
-
"Expected dropout rate",
|
| 5268 |
-
0.0,
|
| 5269 |
-
0.5,
|
| 5270 |
-
0.1,
|
| 5271 |
-
0.01,
|
| 5272 |
-
disabled=not adjust_attrition,
|
| 5273 |
-
)
|
| 5274 |
-
if adjust_attrition
|
| 5275 |
-
else 0.0
|
| 5276 |
-
)
|
| 5277 |
-
|
| 5278 |
-
adjust_multiple = st.checkbox("Multiple testing correction")
|
| 5279 |
-
if adjust_multiple:
|
| 5280 |
-
mc_method = st.selectbox(
|
| 5281 |
-
"Correction method",
|
| 5282 |
-
["Bonferroni", "Holm-Bonferroni", "Benjamini-Hochberg (FDR)"],
|
| 5283 |
-
help="Bonferroni: α/m (most conservative). Holm: sequential Bonferroni. BH-FDR: controls false discovery rate (less conservative).",
|
| 5284 |
-
)
|
| 5285 |
-
num_tests = st.number_input(
|
| 5286 |
-
"Number of tests/comparisons",
|
| 5287 |
-
1,
|
| 5288 |
-
100,
|
| 5289 |
-
1,
|
| 5290 |
-
1,
|
| 5291 |
-
)
|
| 5292 |
-
else:
|
| 5293 |
-
mc_method = "None"
|
| 5294 |
-
num_tests = 1
|
| 5295 |
-
|
| 5296 |
-
show_budget = st.checkbox("Show budget / feasibility estimates")
|
| 5297 |
-
if show_budget:
|
| 5298 |
-
c1, c2 = st.columns(2)
|
| 5299 |
-
with c1:
|
| 5300 |
-
cost_per = st.number_input(
|
| 5301 |
-
"Cost per participant ($)",
|
| 5302 |
-
0.0,
|
| 5303 |
-
100000.0,
|
| 5304 |
-
100.0,
|
| 5305 |
-
10.0,
|
| 5306 |
-
)
|
| 5307 |
-
with c2:
|
| 5308 |
-
recruitment_rate = st.number_input(
|
| 5309 |
-
"Recruitment rate (per month)",
|
| 5310 |
-
0.0,
|
| 5311 |
-
1000.0,
|
| 5312 |
-
10.0,
|
| 5313 |
-
1.0,
|
| 5314 |
-
)
|
| 5315 |
-
else:
|
| 5316 |
-
cost_per = 0.0
|
| 5317 |
-
recruitment_rate = 0.0
|
| 5318 |
-
|
| 5319 |
-
ss_params["dropout_rate"] = dropout_rate if adjust_attrition else 0.0
|
| 5320 |
-
ss_params["num_tests"] = num_tests if adjust_multiple else 1
|
| 5321 |
-
ss_params["mc_method"] = mc_method
|
| 5322 |
-
ss_params["cost_per"] = cost_per if show_budget else 0.0
|
| 5323 |
-
ss_params["recruitment_rate"] = recruitment_rate if show_budget else 0.0
|
| 5324 |
-
|
| 5325 |
-
# =========================
|
| 5326 |
-
# EFFECT SIZE CONVERTER
|
| 5327 |
-
# =========================
|
| 5328 |
-
with st.expander("📐 Effect Size Converter"):
|
| 5329 |
-
st.caption(
|
| 5330 |
-
"Convert between common effect size measures. Click Apply to use the converted value."
|
| 5331 |
-
)
|
| 5332 |
-
conv_tab = st.radio(
|
| 5333 |
-
"Conversion",
|
| 5334 |
-
[
|
| 5335 |
-
"Means → d",
|
| 5336 |
-
"d ↔ r",
|
| 5337 |
-
"d ↔ OR",
|
| 5338 |
-
"η² ↔ f",
|
| 5339 |
-
"R² ↔ f²",
|
| 5340 |
-
"2×2 Table → w/OR",
|
| 5341 |
-
"P(X>Y) ↔ d / Cliff's δ",
|
| 5342 |
-
],
|
| 5343 |
-
horizontal=True,
|
| 5344 |
-
label_visibility="collapsed",
|
| 5345 |
-
)
|
| 5346 |
-
import math as cmath
|
| 5347 |
-
|
| 5348 |
-
if conv_tab == "Means → d":
|
| 5349 |
-
c1, c2 = st.columns(2)
|
| 5350 |
-
with c1:
|
| 5351 |
-
m1_c = st.number_input(
|
| 5352 |
-
"Mean 1",
|
| 5353 |
-
0.0,
|
| 5354 |
-
100.0,
|
| 5355 |
-
0.0,
|
| 5356 |
-
0.1,
|
| 5357 |
-
key="conv_m1",
|
| 5358 |
-
)
|
| 5359 |
-
m2_c = st.number_input(
|
| 5360 |
-
"Mean 2",
|
| 5361 |
-
0.0,
|
| 5362 |
-
100.0,
|
| 5363 |
-
1.0,
|
| 5364 |
-
0.1,
|
| 5365 |
-
key="conv_m2",
|
| 5366 |
-
)
|
| 5367 |
-
with c2:
|
| 5368 |
-
sd_c = st.number_input(
|
| 5369 |
-
"Pooled SD",
|
| 5370 |
-
0.1,
|
| 5371 |
-
100.0,
|
| 5372 |
-
1.0,
|
| 5373 |
-
0.1,
|
| 5374 |
-
key="conv_sd",
|
| 5375 |
-
)
|
| 5376 |
-
d_c = abs(m1_c - m2_c) / sd_c if sd_c > 0 else 0
|
| 5377 |
-
st.metric("Cohen's d", f"{d_c:.4f}")
|
| 5378 |
-
if st.button("Apply d to current test", key="apply_d_means"):
|
| 5379 |
-
st.session_state.converted_es = d_c
|
| 5380 |
-
st.session_state.converted_type = "d"
|
| 5381 |
-
st.rerun()
|
| 5382 |
-
elif conv_tab == "d ↔ r":
|
| 5383 |
-
c1, c2 = st.columns(2)
|
| 5384 |
-
with c1:
|
| 5385 |
-
d_c = st.number_input(
|
| 5386 |
-
"Cohen's d",
|
| 5387 |
-
0.01,
|
| 5388 |
-
10.0,
|
| 5389 |
-
0.5,
|
| 5390 |
-
0.01,
|
| 5391 |
-
key="conv_dr_d",
|
| 5392 |
-
)
|
| 5393 |
-
r_c = d_c / cmath.sqrt(d_c**2 + 4)
|
| 5394 |
-
c2.metric("Correlation r", f"{r_c:.4f}")
|
| 5395 |
-
if st.button("Apply r to Correlation test", key="apply_dr"):
|
| 5396 |
-
st.session_state.converted_es = r_c
|
| 5397 |
-
st.session_state.converted_type = "r"
|
| 5398 |
-
st.rerun()
|
| 5399 |
-
elif conv_tab == "d ↔ OR":
|
| 5400 |
-
c1, c2 = st.columns(2)
|
| 5401 |
-
with c1:
|
| 5402 |
-
d_c = st.number_input(
|
| 5403 |
-
"Cohen's d",
|
| 5404 |
-
0.01,
|
| 5405 |
-
10.0,
|
| 5406 |
-
0.5,
|
| 5407 |
-
0.01,
|
| 5408 |
-
key="conv_do_d",
|
| 5409 |
-
)
|
| 5410 |
-
or_c = cmath.exp(d_c * cmath.pi / cmath.sqrt(3))
|
| 5411 |
-
c2.metric("Odds Ratio", f"{or_c:.4f}")
|
| 5412 |
-
if st.button("Apply OR to Logistic Regression", key="apply_do"):
|
| 5413 |
-
st.session_state.converted_es = or_c
|
| 5414 |
-
st.session_state.converted_type = "or"
|
| 5415 |
-
st.rerun()
|
| 5416 |
-
elif conv_tab == "η² ↔ f":
|
| 5417 |
-
c1, c2 = st.columns(2)
|
| 5418 |
-
with c1:
|
| 5419 |
-
eta2 = st.number_input(
|
| 5420 |
-
"η²",
|
| 5421 |
-
0.001,
|
| 5422 |
-
0.99,
|
| 5423 |
-
0.06,
|
| 5424 |
-
0.001,
|
| 5425 |
-
key="conv_eta",
|
| 5426 |
-
)
|
| 5427 |
-
f_c = cmath.sqrt(eta2 / (1 - eta2))
|
| 5428 |
-
c2.metric("Cohen's f", f"{f_c:.4f}")
|
| 5429 |
-
if st.button("Apply f to ANOVA tests", key="apply_eta"):
|
| 5430 |
-
st.session_state.converted_es = f_c
|
| 5431 |
-
st.session_state.converted_type = "f"
|
| 5432 |
-
st.rerun()
|
| 5433 |
-
elif conv_tab == "R² ↔ f²":
|
| 5434 |
-
c1, c2 = st.columns(2)
|
| 5435 |
-
with c1:
|
| 5436 |
-
r2_c = st.number_input(
|
| 5437 |
-
"R²",
|
| 5438 |
-
0.001,
|
| 5439 |
-
0.99,
|
| 5440 |
-
0.15,
|
| 5441 |
-
0.001,
|
| 5442 |
-
key="conv_r2",
|
| 5443 |
-
)
|
| 5444 |
-
f2_c = r2_c / (1 - r2_c) if r2_c < 1 else 0
|
| 5445 |
-
c2.metric("Cohen's f²", f"{f2_c:.4f}")
|
| 5446 |
-
if st.button("Apply f² to Regression test", key="apply_r2"):
|
| 5447 |
-
st.session_state.converted_es = f2_c
|
| 5448 |
-
st.session_state.converted_type = "f2"
|
| 5449 |
-
st.rerun()
|
| 5450 |
-
elif conv_tab == "2×2 Table → w/OR":
|
| 5451 |
-
c1, c2, c3, c4 = st.columns(4)
|
| 5452 |
-
with c1:
|
| 5453 |
-
a_t = st.number_input("Cell a", 0, 1000, 30, 1, key="conv_a")
|
| 5454 |
-
with c2:
|
| 5455 |
-
b_t = st.number_input("Cell b", 0, 1000, 20, 1, key="conv_b")
|
| 5456 |
-
with c3:
|
| 5457 |
-
c_t = st.number_input("Cell c", 0, 1000, 20, 1, key="conv_c")
|
| 5458 |
-
with c4:
|
| 5459 |
-
d_t = st.number_input("Cell d", 0, 1000, 30, 1, key="conv_d")
|
| 5460 |
-
n_t = a_t + b_t + c_t + d_t
|
| 5461 |
-
if n_t > 0:
|
| 5462 |
-
p_exp = (a_t + c_t) / n_t
|
| 5463 |
-
p_nexp = (b_t + d_t) / n_t
|
| 5464 |
-
prop_diff = (
|
| 5465 |
-
abs(a_t / (a_t + b_t) - c_t / (c_t + d_t))
|
| 5466 |
-
if (a_t + b_t) > 0 and (c_t + d_t) > 0
|
| 5467 |
-
else 0
|
| 5468 |
-
)
|
| 5469 |
-
or_t = (a_t * d_t) / (b_t * c_t) if b_t > 0 and c_t > 0 else None
|
| 5470 |
-
chi2_t = (
|
| 5471 |
-
n_t
|
| 5472 |
-
* (abs(a_t * d_t - b_t * c_t) - n_t / 2) ** 2
|
| 5473 |
-
/ ((a_t + b_t) * (c_t + d_t) * (a_t + c_t) * (b_t + d_t))
|
| 5474 |
-
if all(
|
| 5475 |
-
x > 0 for x in [a_t + b_t, c_t + d_t, a_t + c_t, b_t + d_t]
|
| 5476 |
-
)
|
| 5477 |
-
else 0
|
| 5478 |
-
)
|
| 5479 |
-
w_t = cmath.sqrt(chi2_t / n_t) if n_t > 0 else 0
|
| 5480 |
-
c1, c2 = st.columns(2)
|
| 5481 |
-
c1.metric("Cohen's w", f"{w_t:.4f}")
|
| 5482 |
-
if or_t:
|
| 5483 |
-
c2.metric("Odds Ratio", f"{or_t:.4f}")
|
| 5484 |
-
if st.button("Apply w to Chi-Square test", key="apply_2x2"):
|
| 5485 |
-
st.session_state.converted_es = w_t
|
| 5486 |
-
st.session_state.converted_type = "w"
|
| 5487 |
-
st.rerun()
|
| 5488 |
-
elif conv_tab == "P(X>Y) ↔ d / Cliff's δ":
|
| 5489 |
-
conv_dir = st.radio(
|
| 5490 |
-
"Direction",
|
| 5491 |
-
["P(X>Y) → d / Cliff's δ", "Cliff's δ → d / P(X>Y)"],
|
| 5492 |
-
horizontal=True,
|
| 5493 |
-
)
|
| 5494 |
-
if conv_dir == "P(X>Y) → d / Cliff's δ":
|
| 5495 |
-
p_xy = st.number_input(
|
| 5496 |
-
"P(X>Y) probability (common language effect size)",
|
| 5497 |
-
0.51,
|
| 5498 |
-
0.99,
|
| 5499 |
-
0.65,
|
| 5500 |
-
0.01,
|
| 5501 |
-
help="Probability that a random observation from Group 1 exceeds one from Group 2.",
|
| 5502 |
-
)
|
| 5503 |
-
d_np = np.sqrt(3) * (p_xy - 0.5) * 2
|
| 5504 |
-
cliff_d = 2 * p_xy - 1
|
| 5505 |
-
c1, c2 = st.columns(2)
|
| 5506 |
-
c1.metric("Cohen's d (approx)", f"{d_np:.4f}")
|
| 5507 |
-
c2.metric("Cliff's δ / Glass r_b", f"{cliff_d:.4f}")
|
| 5508 |
-
if st.button(
|
| 5509 |
-
"Apply d to Mann-Whitney/Wilcoxon",
|
| 5510 |
-
key="apply_pxy_d",
|
| 5511 |
-
):
|
| 5512 |
-
st.session_state.converted_es = d_np
|
| 5513 |
-
st.session_state.converted_type = "d"
|
| 5514 |
-
st.rerun()
|
| 5515 |
-
else:
|
| 5516 |
-
cliff_in = st.number_input(
|
| 5517 |
-
"Cliff's δ (or Glass rank-biserial r)",
|
| 5518 |
-
-1.0,
|
| 5519 |
-
1.0,
|
| 5520 |
-
0.3,
|
| 5521 |
-
0.01,
|
| 5522 |
-
)
|
| 5523 |
-
p_xy_out = (cliff_in + 1) / 2
|
| 5524 |
-
d_np_out = np.sqrt(3) * cliff_in
|
| 5525 |
-
c1, c2 = st.columns(2)
|
| 5526 |
-
c1.metric("P(X>Y)", f"{p_xy_out:.4f}")
|
| 5527 |
-
c2.metric("Cohen's d (approx)", f"{d_np_out:.4f}")
|
| 5528 |
-
if st.button(
|
| 5529 |
-
"Apply d to Mann-Whitney/Wilcoxon",
|
| 5530 |
-
key="apply_cliff_d",
|
| 5531 |
-
):
|
| 5532 |
-
st.session_state.converted_es = abs(d_np_out)
|
| 5533 |
-
st.session_state.converted_type = "d"
|
| 5534 |
-
st.rerun()
|
| 5535 |
-
|
| 5536 |
-
with col_right:
|
| 5537 |
-
btn_labels = {
|
| 5538 |
-
"A Priori": "Calculate Sample Size",
|
| 5539 |
-
"Post Hoc": "Calculate Achieved Power",
|
| 5540 |
-
"Sensitivity": "Calculate Minimum Detectable Effect",
|
| 5541 |
-
"Compromise": "Calculate Compromise Power",
|
| 5542 |
-
"Criterion": "Calculate Required Significance Level",
|
| 5543 |
-
}
|
| 5544 |
-
if st.button(
|
| 5545 |
-
btn_labels.get(analysis_mode, "Calculate"),
|
| 5546 |
-
use_container_width=True,
|
| 5547 |
-
type="primary",
|
| 5548 |
-
):
|
| 5549 |
-
params_dict = {
|
| 5550 |
-
"analysis_type": analysis_type,
|
| 5551 |
-
"alpha": alpha_ss,
|
| 5552 |
-
"power": power_ss,
|
| 5553 |
-
"tails": tails_ss,
|
| 5554 |
-
"analysis_mode": analysis_mode,
|
| 5555 |
-
**ss_params,
|
| 5556 |
-
}
|
| 5557 |
-
if not is_a_priori and n_total_input is not None:
|
| 5558 |
-
params_dict["n_total"] = n_total_input
|
| 5559 |
-
if is_compromise:
|
| 5560 |
-
params_dict["cost_ratio"] = cost_ratio
|
| 5561 |
-
st.session_state.power_params = params_dict
|
| 5562 |
-
st.session_state.results = None
|
| 5563 |
-
|
| 5564 |
-
if st.session_state.get("power_params"):
|
| 5565 |
-
render_power_calculator(
|
| 5566 |
-
st.session_state.power_params,
|
| 5567 |
-
st.session_state.power_params.get("analysis_mode", "A Priori"),
|
| 5568 |
-
)
|
| 5569 |
-
else:
|
| 5570 |
-
st.info("Select your parameters and click 'Calculate Sample Size'.")
|
| 5571 |
-
|
| 5572 |
_render_power_analysis()
|
| 5573 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4273 |
- Bonferroni, C. E. (1936). Teoria statistica delle classi e calcolo delle probabilità. *Pubblicazioni del R Istituto Superiore di Scienze Economiche e Commerciali di Firenze*, 8, 3–62.
|
| 4274 |
""")
|
| 4275 |
|
| 4276 |
+
|
| 4277 |
+
st.set_page_config(page_title="Power Analysis & Sample Size", page_icon="⚡", layout="wide")
|
| 4278 |
+
|
| 4279 |
+
from features.power_ui import _render_power_analysis
|
| 4280 |
+
from core.utils import render_footer
|
| 4281 |
+
|
| 4282 |
+
|
| 4283 |
+
def main():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4284 |
_render_power_analysis()
|
| 4285 |
+
render_footer()
|
| 4286 |
+
|
| 4287 |
+
|
| 4288 |
+
if __name__ == "__main__":
|
| 4289 |
+
main()
|