MuhammadNabeelSh commited on
Commit
c533ed4
·
verified ·
1 Parent(s): 90ab6f9

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. 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
- def _render_power_analysis():
4277
-
4278
- st.title("Power Analysis & Sample Size Estimation")
4279
-
4280
- with st.sidebar:
4281
- st.markdown("##### :orange[Power Analysis Type]")
4282
- analysis_mode = st.radio(
4283
- "Analysis Mode",
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()