From 33e9543b15a1133c37584dc05bdff46b332bb3a0 Mon Sep 17 00:00:00 2001 From: admin Date: Sat, 2 Aug 2025 05:15:23 +0700 Subject: [PATCH] Upload to Server Uploading to server --- sample_size_site_full/anova_oneway.html | 1 + sample_size_site_full/anova_oneway_guide.html | 711 ++++++++++++++++++ sample_size_site_full/anova_repeated.html | 1 + .../anova_repeated_guide.html | 154 ++++ sample_size_site_full/anova_twoway.html | 1 + sample_size_site_full/anova_twoway_guide.html | 125 +++ sample_size_site_full/chi_gof.html | 1 + sample_size_site_full/chi_gof_guide.html | 71 ++ sample_size_site_full/chi_independence.html | 1 + .../chi_independence_guide.html | 66 ++ sample_size_site_full/cor_pearson.html | 1 + sample_size_site_full/cor_pearson_guide.html | 103 +++ sample_size_site_full/cor_spearman.html | 1 + sample_size_site_full/cor_spearman_guide.html | 99 +++ sample_size_site_full/f_test.html | 1 + sample_size_site_full/f_test_guide.html | 126 ++++ sample_size_site_full/index.html | Bin 0 -> 30232 bytes sample_size_site_full/kruskal_wallis.html | 1 + .../kruskal_wallis_guide.html | 114 +++ sample_size_site_full/levene_test.html | 1 + sample_size_site_full/levene_test_guide.html | 126 ++++ sample_size_site_full/mann_whitney.html | 1 + sample_size_site_full/mann_whitney_guide.html | 101 +++ sample_size_site_full/overview.html | 113 +++ sample_size_site_full/prop_one_sample.html | 1 + .../prop_one_sample_guide.html | 33 + sample_size_site_full/prop_two_sample.html | 1 + .../prop_two_sample_guide.html | 33 + sample_size_site_full/shapiro_wilk.html | 1 + sample_size_site_full/shapiro_wilk_guide.html | 102 +++ sample_size_site_full/t_one_sample.html | 1 + sample_size_site_full/t_one_sample_guide.html | 81 ++ sample_size_site_full/t_paired.html | 1 + sample_size_site_full/t_paired_guide.html | 85 +++ sample_size_site_full/t_two_sample.html | 1 + sample_size_site_full/t_two_sample_guide.html | 116 +++ sample_size_site_full/wilcoxon_signed.html | 1 + .../wilcoxon_signed_guide.html | 124 +++ sample_size_site_full/z_one_sample.html | 1 + sample_size_site_full/z_one_sample_guide.html | 101 +++ sample_size_site_full/z_paired.html | 1 + sample_size_site_full/z_paired_guide.html | 89 +++ sample_size_site_full/z_two_sample.html | 1 + sample_size_site_full/z_two_sample_guide.html | 103 +++ samplesize/1ProportionTest/app.R | 238 ++++++ samplesize/2ProportionTest/app.R | 234 ++++++ samplesize/F-test/app.R | 215 ++++++ samplesize/Kruskal-Wallis-Test/app.R | 207 +++++ samplesize/LevenesTest/app.R | 251 +++++++ samplesize/MannWhitneyUtest/app.R | 206 +++++ samplesize/Onesampleztest/app.R | 211 ++++++ samplesize/PearsonCorrelationTest/app.R | 222 ++++++ samplesize/RepeatAnova/app.R | 244 ++++++ samplesize/Shapiro-Wilk/app.R | 239 ++++++ samplesize/SpearmanCorrelationTest/app.R | 214 ++++++ samplesize/TwowayANOVA/app.R | 253 +++++++ samplesize/Wilcoxon-Signed-Rank/app.R | 203 +++++ samplesize/aa/app.R | 66 ++ samplesize/anova/app.R | 218 ++++++ samplesize/chisquare/app.R | 242 ++++++ samplesize/chisquaregoodfit/app.R | 242 ++++++ samplesize/onesamplettest/app.R | 208 +++++ samplesize/pairedztest/app.R | 221 ++++++ samplesize/pairsamplettest/app.R | 223 ++++++ samplesize/twosamplettest/app.R | 209 +++++ samplesize/twosampleztest/app.R | 227 ++++++ 66 files changed, 7590 insertions(+) create mode 100644 sample_size_site_full/anova_oneway.html create mode 100644 sample_size_site_full/anova_oneway_guide.html create mode 100644 sample_size_site_full/anova_repeated.html create mode 100644 sample_size_site_full/anova_repeated_guide.html create mode 100644 sample_size_site_full/anova_twoway.html create mode 100644 sample_size_site_full/anova_twoway_guide.html create mode 100644 sample_size_site_full/chi_gof.html create mode 100644 sample_size_site_full/chi_gof_guide.html create mode 100644 sample_size_site_full/chi_independence.html create mode 100644 sample_size_site_full/chi_independence_guide.html create mode 100644 sample_size_site_full/cor_pearson.html create mode 100644 sample_size_site_full/cor_pearson_guide.html create mode 100644 sample_size_site_full/cor_spearman.html create mode 100644 sample_size_site_full/cor_spearman_guide.html create mode 100644 sample_size_site_full/f_test.html create mode 100644 sample_size_site_full/f_test_guide.html create mode 100644 sample_size_site_full/index.html create mode 100644 sample_size_site_full/kruskal_wallis.html create mode 100644 sample_size_site_full/kruskal_wallis_guide.html create mode 100644 sample_size_site_full/levene_test.html create mode 100644 sample_size_site_full/levene_test_guide.html create mode 100644 sample_size_site_full/mann_whitney.html create mode 100644 sample_size_site_full/mann_whitney_guide.html create mode 100644 sample_size_site_full/overview.html create mode 100644 sample_size_site_full/prop_one_sample.html create mode 100644 sample_size_site_full/prop_one_sample_guide.html create mode 100644 sample_size_site_full/prop_two_sample.html create mode 100644 sample_size_site_full/prop_two_sample_guide.html create mode 100644 sample_size_site_full/shapiro_wilk.html create mode 100644 sample_size_site_full/shapiro_wilk_guide.html create mode 100644 sample_size_site_full/t_one_sample.html create mode 100644 sample_size_site_full/t_one_sample_guide.html create mode 100644 sample_size_site_full/t_paired.html create mode 100644 sample_size_site_full/t_paired_guide.html create mode 100644 sample_size_site_full/t_two_sample.html create mode 100644 sample_size_site_full/t_two_sample_guide.html create mode 100644 sample_size_site_full/wilcoxon_signed.html create mode 100644 sample_size_site_full/wilcoxon_signed_guide.html create mode 100644 sample_size_site_full/z_one_sample.html create mode 100644 sample_size_site_full/z_one_sample_guide.html create mode 100644 sample_size_site_full/z_paired.html create mode 100644 sample_size_site_full/z_paired_guide.html create mode 100644 sample_size_site_full/z_two_sample.html create mode 100644 sample_size_site_full/z_two_sample_guide.html create mode 100644 samplesize/1ProportionTest/app.R create mode 100644 samplesize/2ProportionTest/app.R create mode 100644 samplesize/F-test/app.R create mode 100644 samplesize/Kruskal-Wallis-Test/app.R create mode 100644 samplesize/LevenesTest/app.R create mode 100644 samplesize/MannWhitneyUtest/app.R create mode 100644 samplesize/Onesampleztest/app.R create mode 100644 samplesize/PearsonCorrelationTest/app.R create mode 100644 samplesize/RepeatAnova/app.R create mode 100644 samplesize/Shapiro-Wilk/app.R create mode 100644 samplesize/SpearmanCorrelationTest/app.R create mode 100644 samplesize/TwowayANOVA/app.R create mode 100644 samplesize/Wilcoxon-Signed-Rank/app.R create mode 100644 samplesize/aa/app.R create mode 100644 samplesize/anova/app.R create mode 100644 samplesize/chisquare/app.R create mode 100644 samplesize/chisquaregoodfit/app.R create mode 100644 samplesize/onesamplettest/app.R create mode 100644 samplesize/pairedztest/app.R create mode 100644 samplesize/pairsamplettest/app.R create mode 100644 samplesize/twosamplettest/app.R create mode 100644 samplesize/twosampleztest/app.R diff --git a/sample_size_site_full/anova_oneway.html b/sample_size_site_full/anova_oneway.html new file mode 100644 index 0000000..d3ea945 --- /dev/null +++ b/sample_size_site_full/anova_oneway.html @@ -0,0 +1 @@ + diff --git a/sample_size_site_full/anova_oneway_guide.html b/sample_size_site_full/anova_oneway_guide.html new file mode 100644 index 0000000..60df47e --- /dev/null +++ b/sample_size_site_full/anova_oneway_guide.html @@ -0,0 +1,711 @@ + + + + + + Hướng dẫn One Way ANOVA + + + + +

Hướng dẫn tính toán cỡ mẫu cho kiểm định ANOVA (Phân tích phương + sai)

+
+

1. Kiểm định ANOVA là gì?

+

Kiểm định ANOVA (Analysis of Variance) là một phương pháp thống kê dùng để + kiểm tra xem có sự khác biệt có ý nghĩa thống kê giữa trung bình của nhiều + nhóm độc lập hay không.

+
+

Phạm vi ứng dụng:

+

Kiểm định ANOVA được sử dụng rộng rãi trong các nghiên cứu y tế, khoa học xã + hội và giáo dục, đặc biệt khi cần so sánh trung bình của hơn 2 nhóm độc + lập.
Ví dụ:

+ +
+

2. Công thức tính cỡ mẫu cho kiểm định ANOVA

+

Công thức tổng quát để tính cỡ mẫu cho kiểm định ANOVA dựa trên kích thước + hiệu ứng chuẩn hóa (

+ + + + f + + f + + +

), mức ý nghĩa (

+ + + + α + + \alpha + + +

), và độ mạnh kiểm định (

+ + + + 1 + + β + + 1-\beta + + +

):

+ + + + n + = + + + ( + η + + + 1 + ) + ( + + Z + + 1 + + α + + + + + + Z + + 1 + + β + + + + ) + 2 + + + + f + 2 + + + + n = + \frac{{(\eta + 1)(Z_{1-\alpha} + Z_{1-\beta})^2}}{{f^2}} + + +

+

Trong đó:

+ +
+

3. Đặc biệt khi chỉ có 2 nhóm

+

Khi số nhóm

+ + + + k + = + 2 + + k = 2 + + +

, kiểm định ANOVA trở thành kiểm + định t-test hai mẫu độc lập.
Trong trường hợp này, công thức + trên được đơn giản hóa thành:

+ + + + n + = + + + 2 + ( + + Z + + 1 + + α + + + + + + Z + + 1 + + β + + + + ) + 2 + + + + f + 2 + + + + n = + \frac{{2(Z_{1-\alpha} + Z_{1-\beta})^2}}{{f^2}} + + +

+

Nhận xét:

+ +
+

4. Hướng dẫn tính toán

+

Cách tính cỡ mẫu

+

Để tính cỡ mẫu cho kiểm định ANOVA, bạn cần cung cấp:

+
    +
  1. Kích thước hiệu ứng ( + + + f + + f + + ): +
      +
    • + + + f + = + 0.1 + + f = 0.1 + + : Hiệu ứng nhỏ.
    • +
    • + + + f + = + 0.25 + + f = 0.25 + + : Hiệu ứng trung bình.
    • +
    • + + + f + = + 0.4 + + f = 0.4 + + : Hiệu ứng lớn.
    • +
    +
  2. +
  3. Mức ý nghĩa ( + + + α + + \alpha + + ): Thường là 0.05. +
  4. +
  5. Độ mạnh kiểm định ( + + + 1 + + β + + 1-\beta + + ): Thường là 0.8 + hoặc 0.9.
  6. +
  7. Số nhóm ( + + + k + + k + + ): Nhập số nhóm cần + so sánh.
  8. +
+

Tổng cỡ mẫu

+

Tổng cỡ mẫu cho toàn bộ nghiên cứu được tính bằng:

+ + + + + n + + t + o + t + a + l + + + = + n + × + k + + n_{total} = n \times k + + +

+

Với

+ + + + n + + n + + +

là số mẫu cần thiết cho mỗi nhóm, và

+ + + + k + + k + + +

là số nhóm.

+
+

5. Ví dụ minh họa

+

Bài toán:

+

Bạn muốn kiểm tra sự khác biệt trung bình cân nặng giữa 3 nhóm trẻ em được bổ + sung dinh dưỡng ở mức độ khác nhau với:

+ +

Áp dụng công thức:

+
    +
  1. +

    Bậc tự do giữa các nhóm:

    + + + + η + = + k + + 1 + = + 3 + + 1 + = + 2 + + \eta = k - 1 = 3 - 1 = 2 + + + +
  2. +
  3. +

    Tra bảng phân phối chuẩn:

    +
      +
    • + + + + Z + + 1 + + α + + + = + + Z + 0.95 + + = + 1.96 + + Z_{1-\alpha} = Z_{0.95} = + 1.96 + + .
    • +
    • + + + + Z + + 1 + + β + + + = + + Z + 0.8 + + = + 0.84 + + Z_{1-\beta} = Z_{0.8} = + 0.84 + + .
    • +
    +
  4. +
  5. +

    Tính cỡ mẫu cho mỗi nhóm:

    + + + + n + = + + + ( + η + + + 1 + ) + ( + + Z + + 1 + + α + + + + + + Z + + 1 + + β + + + + ) + 2 + + + + f + 2 + + + + n = + \frac{{(\eta + 1)(Z_{1-\alpha} + Z_{1-\beta})^2}}{{f^2}} + + + + + n + = + + + ( + 2 + + + 1 + ) + ( + 1.96 + + + 0.84 + + ) + 2 + + + + 0.2 + + 5 + 2 + + + + = + + + 3 + × + 7.84 + + 0.0625 + + = + + 23.52 + 0.0625 + + = + 376.32 + + n = + \frac{{(2 + 1)(1.96 + 0.84)^2}}{{0.25^2}} = + \frac{{3 \times 7.84}}{{0.0625}} = \frac{{23.52}}{{0.0625}} = 376.32 + + + +
  6. +
  7. +

    Tổng cỡ mẫu:

    + + + + + n + + t + o + t + a + l + + + = + n + × + k + = + 376.32 + × + 3 + = + 1129 + + n_{total} = n \times k = 376.32 + \times 3 = 1129 + + +
  8. +
+

Kết luận:

+

Cần ít nhất 376 mẫu cho mỗi nhóm và tổng cộng 1129 + mẫu cho cả nghiên cứu.

+
+

6. Công cụ tính toán bằng R

+

Bạn có thể tính cỡ mẫu cho kiểm định ANOVA bằng hàm + pwr.anova.test trong R:

+
+
R
+
+
+
+ + +
+
+
+
library(pwr) + pwr.anova.test(k = 3, f = 0.25, sig.level = 0.05, power = 0.8) +
+
+

Kết quả trả về:

+ +

+ + diff --git a/sample_size_site_full/anova_repeated.html b/sample_size_site_full/anova_repeated.html new file mode 100644 index 0000000..4e83aa8 --- /dev/null +++ b/sample_size_site_full/anova_repeated.html @@ -0,0 +1 @@ + diff --git a/sample_size_site_full/anova_repeated_guide.html b/sample_size_site_full/anova_repeated_guide.html new file mode 100644 index 0000000..0d343a3 --- /dev/null +++ b/sample_size_site_full/anova_repeated_guide.html @@ -0,0 +1,154 @@ + + + + + Hướng dẫn tính cỡ mẫu Repeated Measures ANOVA + + + + + + + +

 

+

Hướng dẫn tính toán cỡ mẫu cho kiểm định Repeated Measures ANOVA

+
+

1. Repeated Measures ANOVA là gì?

+

Repeated Measures ANOVA (Phân tích phương sai với số đo lặp lại) là một phương pháp thống kê được sử dụng khi cùng một nhóm đối tượng được đo nhiều lần trên cùng một biến phụ thuộc trong các điều kiện hoặc thời điểm khác nhau.

+

Phạm vi ứng dụng:

+

Repeated Measures ANOVA được áp dụng rộng rãi trong các nghiên cứu y sinh và y tế công cộng, khi cần so sánh sự thay đổi của một biến qua thời gian hoặc qua các điều kiện khác nhau:

+ +
+

Ví dụ trong nghiên cứu:

+
    +
  1. Nghiên cứu lâm sàng: +
      +
    • Theo dõi huyết áp của bệnh nhân tại 3 thời điểm: trước điều trị, sau 1 tháng, và sau 3 tháng.
    • +
    +
  2. +
  3. Nghiên cứu dinh dưỡng: +
      +
    • So sánh mức đường huyết của một nhóm bệnh nhân sau khi sử dụng 3 chế độ ăn khác nhau.
    • +
    +
  4. +
  5. Y tế công cộng: +
      +
    • Đánh giá tác động của chiến dịch truyền thông sức khỏe đối với kiến thức người dân qua các thời điểm khảo sát khác nhau.
    • +
    +
  6. +
+
+

2. Công thức tính cỡ mẫu cho Repeated Measures ANOVA

+

Cỡ mẫu cho kiểm định Repeated Measures ANOVA được tính toán dựa trên các tham số chính:

+ +

Công thức tổng quát:

+

+\[ +n = \frac{(\eta + 1)(Z_{1-\alpha} + Z_{1-\beta})^2}{f^2 (1 - \rho)} +\] +

+

Trong đó:

+ +

Tổng cỡ mẫu cho toàn bộ nghiên cứu:

+

+\[ +n_{\text{total}} = n \times \text{số nhóm} +\] +

+
+

3. Ví dụ minh họa

+

Bài toán:

+

Bạn muốn đánh giá sự thay đổi điểm huyết áp của một nhóm bệnh nhân tại 3 thời điểm:

+
    +
  1. Trước khi điều trị.
  2. +
  3. Sau 1 tháng điều trị.
  4. +
  5. Sau 3 tháng điều trị.
  6. +
+

Các tham số:

+ +

Tính toán từng bước sẽ cho kết quả \(n \approx 753\)

+
+

4. Hướng dẫn tính toán cỡ mẫu bằng R

+

Mã R mẫu:

+
library(pwr)
+
+# Hiệu ứng điều chỉnh với hệ số tương quan
+adjusted_effect_size <- 0.25 * sqrt(1 - 0.5)
+
+# Tính cỡ mẫu
+result <- pwr.anova.test(
+  k = 3,                # Số thời điểm
+  f = adjusted_effect_size,
+  sig.level = 0.05,
+  power = 0.8
+)
+
+print(result)
+

Kết quả trả về:

+
Balanced one-way analysis of variance power calculation
+
+k = 3
+n = 752.64
+f = 0.1767767
+sig.level = 0.05
+power = 0.8
+

Kết luận:

+ +
+

5. Kết luận

+

Repeated Measures ANOVA là một công cụ mạnh mẽ để phân tích dữ liệu lặp lại trên cùng một đối tượng, giúp kiểm tra hiệu quả của các can thiệp qua thời gian hoặc trong các điều kiện khác nhau. Việc tính toán cỡ mẫu đúng cách giúp đảm bảo nghiên cứu có đủ sức mạnh thống kê để phát hiện sự khác biệt thực sự.

+ + + \ No newline at end of file diff --git a/sample_size_site_full/anova_twoway.html b/sample_size_site_full/anova_twoway.html new file mode 100644 index 0000000..a6a0439 --- /dev/null +++ b/sample_size_site_full/anova_twoway.html @@ -0,0 +1 @@ + diff --git a/sample_size_site_full/anova_twoway_guide.html b/sample_size_site_full/anova_twoway_guide.html new file mode 100644 index 0000000..a098b4a --- /dev/null +++ b/sample_size_site_full/anova_twoway_guide.html @@ -0,0 +1,125 @@ + + + + + Hướng dẫn tính cỡ mẫu Two-way ANOVA + + + + +

Hướng dẫn tính toán cỡ mẫu cho kiểm định Two-way ANOVA

+
+

1. Two-way ANOVA là gì?

+

Two-way ANOVA (Phân tích phương sai hai chiều) là một phương pháp thống kê dùng để kiểm tra:

+
    +
  1. Hiệu ứng chính (Main effects): +
      +
    • Xem liệu từng yếu tố độc lập (factor) có ảnh hưởng có ý nghĩa đến biến phụ thuộc hay không.
    • +
    +
  2. +
  3. Hiệu ứng tương tác (Interaction effects): +
      +
    • Xem liệu hai yếu tố độc lập có ảnh hưởng tương tác đến biến phụ thuộc hay không.
    • +
    +
  4. +
+

Phạm vi ứng dụng:

+

Two-way ANOVA được sử dụng khi bạn cần phân tích tác động của hai yếu tố độc lập (có nhiều mức) đến biến phụ thuộc. Phương pháp này phổ biến trong nghiên cứu y học, khoa học xã hội, và kỹ thuật.

+

Ví dụ:

+ +
+

2. Công thức tính cỡ mẫu

+

Cỡ mẫu trong Two-way ANOVA phụ thuộc vào:

+ +

Công thức tổng quát:

+

+ \[ + n = \frac{(\eta_1 \times \eta_2)(Z_{1-\alpha} + Z_{1-\beta})^2}{f^2} + \] +

+

Trong đó:

+ +

Tổng số mẫu:

+

+ \[ + n_{total} = n \times (\eta_1 \times \eta_2) + \] +

+
+

3. Ví dụ minh họa

+

Bài toán: Kiểm tra ảnh hưởng của 3 loại thuốc (\( \eta_1 = 3 \)) và 2 giới tính (\( \eta_2 = 2 \)) đến huyết áp.

+ +

+ \[ + n = \frac{6 \times (1.96 + 0.84)^2}{0.25^2} = \frac{6 \times 7.84}{0.0625} = \frac{47.04}{0.0625} = 752.64 + \] +

+

+ \[ + n_{total} = 752.64 \times 6 = 4515.84 + \] +

+

Kết luận: Cỡ mẫu mỗi tổ hợp: 753, tổng toàn nghiên cứu: 4516 (làm tròn).

+
+

4. Tính toán trong R

+
library(pwr)
+
+# Tổng số tổ hợp (3 x 2 = 6)
+total_groups <- 3 * 2
+
+result <- pwr.anova.test(
+  k = total_groups,
+  f = 0.25,
+  sig.level = 0.05,
+  power = 0.8
+)
+
+print(result)
+

Kết quả:

+
Balanced one-way analysis of variance power calculation
+
+k = 6
+n = 753.64
+f = 0.25
+sig.level = 0.05
+power = 0.8
+
+

5. Kết luận

+

Two-way ANOVA giúp kiểm tra đồng thời ảnh hưởng của hai yếu tố và tương tác giữa chúng đến biến phụ thuộc. Việc tính đúng cỡ mẫu là điều kiện then chốt đảm bảo độ tin cậy cho kết quả phân tích.

+ + \ No newline at end of file diff --git a/sample_size_site_full/chi_gof.html b/sample_size_site_full/chi_gof.html new file mode 100644 index 0000000..92e3d2e --- /dev/null +++ b/sample_size_site_full/chi_gof.html @@ -0,0 +1 @@ + diff --git a/sample_size_site_full/chi_gof_guide.html b/sample_size_site_full/chi_gof_guide.html new file mode 100644 index 0000000..c937edf --- /dev/null +++ b/sample_size_site_full/chi_gof_guide.html @@ -0,0 +1,71 @@ + + + + + Tính Cỡ Mẫu Kiểm định Chi-squared Goodness-of-Fit + + + + + + + +

Tính Cỡ Mẫu Kiểm định Chi-squared Goodness-of-Fit

+ +

Mục đích sử dụng

+

Kiểm định Chi-squared Goodness-of-Fit dùng để đánh giá xem dữ liệu quan sát có phù hợp với phân phối lý thuyết hay không.

+ +

Công thức tính cỡ mẫu

+

Cỡ mẫu được tính dựa trên kích thước hiệu ứng Cohen's \( w \) và giá trị tới hạn Chi-squared:

+ +
+ \[ + n = \frac{\chi^2_{1-\alpha, df} + \chi^2_{1-\beta, df}}{w^2} + \] +
+ +

Trong đó:

+ + +

Ví dụ

+

Giả sử bạn muốn kiểm định goodness-of-fit với 4 nhóm phân loại, mức ý nghĩa \( \alpha = 0.05 \), power 0.80, và Cohen's \( w = 0.25 \) (hiệu ứng trung bình):

+ + +
+ \[ + n = \frac{7.81 + 4.11}{0.25^2} = \frac{11.92}{0.0625} = 190.72 + \] +
+ +

Nên cần khoảng 191 quan sát để kiểm định hiệu quả với các tham số này.

+ +

Ứng dụng

+

Bạn có thể nhập các thông số vào app để tính nhanh cỡ mẫu phù hợp với nghiên cứu của mình.

+ + + diff --git a/sample_size_site_full/chi_independence.html b/sample_size_site_full/chi_independence.html new file mode 100644 index 0000000..93fd100 --- /dev/null +++ b/sample_size_site_full/chi_independence.html @@ -0,0 +1 @@ + diff --git a/sample_size_site_full/chi_independence_guide.html b/sample_size_site_full/chi_independence_guide.html new file mode 100644 index 0000000..b3b75e6 --- /dev/null +++ b/sample_size_site_full/chi_independence_guide.html @@ -0,0 +1,66 @@ + + + + + Hướng dẫn Tính Cỡ Mẫu Kiểm định Chi-squared Độc lập + + + + + + + +

Tính Cỡ Mẫu Kiểm định Chi-squared Độc lập

+ +

Mục đích sử dụng

+

Tính cỡ mẫu giúp xác định số quan sát cần thiết để kiểm định sự độc lập giữa hai biến danh mục bằng kiểm định Chi-squared với mức ý nghĩa và sức mạnh kiểm định mong muốn.

+ +

Công thức tính cỡ mẫu

+

Cỡ mẫu được tính theo công thức:

+
+ \[ + n = \frac{\chi^2_{1-\alpha, df} + \chi^2_{1-\beta, df}}{w^2} + \] +
+

Trong đó:

+ + +

Ví dụ

+

Giả sử kiểm định độc lập bảng 2x2, với mức ý nghĩa \( \alpha = 0.05 \), sức mạnh kiểm định (power) \( 1-\beta = 0.80 \), và kích thước hiệu ứng Cohen's \( w = 0.3 \) (mức trung bình).

+ +
+ \[ + n = \frac{3.84 + 1.64}{0.3^2} = \frac{5.48}{0.09} \approx 61 + \] +
+

Vậy bạn cần khoảng 61 quan sát để phát hiện hiệu ứng với độ tin cậy và sức mạnh này.

+ +

Ứng dụng

+

Trong ứng dụng, bạn nhập kích thước hiệu ứng và các tham số để tính cỡ mẫu tự động.

+ + + diff --git a/sample_size_site_full/cor_pearson.html b/sample_size_site_full/cor_pearson.html new file mode 100644 index 0000000..abe9ed1 --- /dev/null +++ b/sample_size_site_full/cor_pearson.html @@ -0,0 +1 @@ + diff --git a/sample_size_site_full/cor_pearson_guide.html b/sample_size_site_full/cor_pearson_guide.html new file mode 100644 index 0000000..ea962e6 --- /dev/null +++ b/sample_size_site_full/cor_pearson_guide.html @@ -0,0 +1,103 @@ + + + + + + Kiểm định Tương quan Pearson + + + + + + +

Kiểm định Tương quan Pearson

+ +

+ Kiểm định tương quan Pearson được sử dụng để đo mức độ và hướng của mối quan hệ tuyến tính giữa hai biến định lượng liên tục. +

+ +

1. Hệ số tương quan Pearson (\( r \))

+ +

Hệ số tương quan Pearson có giá trị trong khoảng từ -1 đến 1:

+ + +
+\[ +r = \frac{\sum (x_i - \bar{x})(y_i - \bar{y})}{\sqrt{\sum (x_i - \bar{x})^2 \sum (y_i - \bar{y})^2}} +\] +
+ +

Trong đó:

+ + +

2. Công thức tính cỡ mẫu cần thiết

+ +

+ Để xác định cỡ mẫu đủ lớn nhằm phát hiện một hệ số tương quan \( r \) với mức ý nghĩa \( \alpha \) và công suất \( 1 - \beta \), công thức gần đúng là: +

+ +
+\[ +n = \left( \frac{Z_{1-\alpha/2} + Z_{1-\beta}}{0.5 \cdot \ln\left(\frac{1 + r}{1 - r}\right)} \right)^2 + 3 +\] +
+ +

Trong đó:

+ + +

3. Ứng dụng trong y tế công cộng

+ +

+ Tương quan Pearson được ứng dụng rộng rãi để: +

+ + +

+ Những phân tích này giúp định hướng các chiến lược can thiệp và dự phòng bệnh trong cộng đồng. +

+ + + diff --git a/sample_size_site_full/cor_spearman.html b/sample_size_site_full/cor_spearman.html new file mode 100644 index 0000000..353b905 --- /dev/null +++ b/sample_size_site_full/cor_spearman.html @@ -0,0 +1 @@ + diff --git a/sample_size_site_full/cor_spearman_guide.html b/sample_size_site_full/cor_spearman_guide.html new file mode 100644 index 0000000..61bd03d --- /dev/null +++ b/sample_size_site_full/cor_spearman_guide.html @@ -0,0 +1,99 @@ + + + + + + Kiểm định Tương quan hạng Spearman + + + + + + +

Kiểm định Tương quan hạng Spearman (\(\rho_s\))

+ +

+ Kiểm định tương quan hạng Spearman là một phương pháp phi tham số được sử dụng để đo lường mức độ và hướng của mối quan hệ đơn điệu (monotonic) giữa hai biến. +

+ +

1. Hệ số tương quan hạng Spearman (\(r_s\))

+ +

+ Không giống như Pearson, Spearman không đo lường mối quan hệ tuyến tính. Thay vào đó, nó đánh giá xem khi giá trị của một biến tăng lên, giá trị của biến kia có xu hướng tăng (hoặc giảm) một cách nhất quán hay không, ngay cả khi mối quan hệ đó không phải là một đường thẳng. +

+

Hệ số \(r_s\) được tính bằng cách áp dụng công thức Pearson trên dữ liệu đã được xếp hạng (rank). Công thức tính toán phổ biến là:

+ +
+\[ +r_s = 1 - \frac{6 \sum d_i^2}{n(n^2 - 1)} +\] +
+ +

Trong đó:

+ + +

2. Khi nào nên sử dụng Tương quan Spearman?

+
+

Spearman là một lựa chọn thay thế mạnh mẽ cho Pearson trong các trường hợp sau:

+ +
+ + +

3. Ứng dụng trong y tế công cộng

+ +

+ Tương quan Spearman rất hữu ích trong các nghiên cứu y tế công cộng, đặc biệt khi làm việc với các thang đo hoặc các biến không tuân theo phân phối chuẩn: +

+ + + + diff --git a/sample_size_site_full/f_test.html b/sample_size_site_full/f_test.html new file mode 100644 index 0000000..d20c5cd --- /dev/null +++ b/sample_size_site_full/f_test.html @@ -0,0 +1 @@ + diff --git a/sample_size_site_full/f_test_guide.html b/sample_size_site_full/f_test_guide.html new file mode 100644 index 0000000..2c2fe27 --- /dev/null +++ b/sample_size_site_full/f_test_guide.html @@ -0,0 +1,126 @@ + + + + + + Kiểm định F về Phương sai (F-test for Variance) + + + + + + +

Kiểm định F để so sánh phương sai (F-test for Variance)

+ +

+ Kiểm định F (F-test) được sử dụng để so sánh phương sai của hai quần thể, thường là để kiểm tra giả thuyết rằng hai biến có độ biến thiên bằng nhau. Đây là nền tảng cho các kiểm định như ANOVA, hoặc kiểm tra giả định về tính đồng nhất phương sai trước khi thực hiện các phân tích khác. +

+ +

1. Giả thuyết kiểm định

+ +

+ Giả sử có hai mẫu độc lập với phương sai lần lượt là \( \sigma_1^2 \) và \( \sigma_2^2 \), ta muốn kiểm tra: +

+ + + +

2. Công thức tính thống kê kiểm định F

+ +
+\[ +F = \frac{S_1^2}{S_2^2} +\] +
+ +

Trong đó:

+ + +

+ Giá trị \( F \) tuân theo phân phối F với \( (n_1 - 1, n_2 - 1) \) bậc tự do. +

+ +

3. Công thức xấp xỉ tính cỡ mẫu

+ +

+ Mặc dù không phổ biến như trong t-test hay chi-square, cỡ mẫu để phát hiện sự khác biệt về phương sai có thể ước tính bằng: +

+ +
+\[ +n = \left( \frac{Z_{1-\alpha/2} + Z_{1-\beta}}{\ln(\sigma_1 / \sigma_2) \cdot \sqrt{2}} \right)^2 + 2 +\] +
+ +

Trong đó:

+ + +
+ Lưu ý: Công thức trên chỉ là xấp xỉ. Để có kết quả chính xác, bạn nên dùng phần mềm chuyên dụng như G*Power, R (gói `pwr`), hoặc các công cụ tính toán trực tuyến dựa trên phân phối F chính xác. +
+ +

4. Ứng dụng trong y tế công cộng

+ +

+ Kiểm định F được sử dụng để đánh giá sự khác biệt về độ biến thiên của một biến định lượng giữa hai nhóm, ví dụ: +

+ + + +

+ Trong phân tích ANOVA, kiểm định F còn giúp đánh giá sự khác biệt trung bình giữa nhiều nhóm — một bước quan trọng trong phân tích y tế cộng đồng có nhiều can thiệp. +

+ + + diff --git a/sample_size_site_full/index.html b/sample_size_site_full/index.html new file mode 100644 index 0000000000000000000000000000000000000000..4b23324fd445a37d64e19bf083d346490919b9e0 GIT binary patch literal 30232 zcmeHQ%WoUU9iChZ^bqva3$ck1#4bwFs+}fvSfh#`iIF-sVkPMVML_c*F{DV9lq|_b z|DvKmZaL=AW6wnq^b{05^w2|rp4)!k{5YD~*;#Uzq)4v35F}E&^Z31AGdug=|Eaoq zP2E$E)m^oxeyN_Q`zlfg>R27BzPh8ntGbdITuWi%Dz1HOa z2&2?7MjInF@V$vK8fv7*`U-o0g83e(s(J_CBUMZ3H$X3rV&-eBQ+1?<80lE|YvTI= z-`n~xTb%-`7OotiPYc%xWfPxO=gto?V;4QTXxGIIecT(;d9oSaB#oxoqAJhq`P}8%RzNDy(*w--h@ZSN(YpagN zlC(Ysj$?HjJ!^ofg)z6)7q~)v*y}mkZ{sSdTf^rOXjD_rK#L)+jPXnOj?~Z8TWEJv z|JKmEtM75lF@8clwlNYZvps1;-VnCFhK(biYZ{CJb6d|Jr;=@9X-8}cTMK<7^*b$7 zyk>qn6CPGU0dl*G8P0)yq;@g4@P`~5YnqQWuiE-fN4=XE?GUX-;0&?g7&iiKj?kv1 z=|LV=fz=SyV(WL%{z#pn4`uQP_`8dCUx1Iqtc~vxprriqvx}B;jnv?ngeyY7S^_S% z=wdeVmox2PrW@eoK>$AC)Dh^=m{6jlsZO|z{8;KyX3u~Hdy!X7%+S>|tE8ug7x7i(`@Gl+`VZ!5iMFgzFjLZmJ%z9bxtp%-qsAZ)u$0R<~ereiG;>u#&nQ z{}i;L&b0$A;~Zq`A6;wgfslD0xD|H{zNip6~uOof-?-kbFmP#F`UD4JWffO z#mG=d;>e>8VBjdB`!4_;C6*F&rdx@8_-$wL>;Q5V(y=UBVfB!BzW^jc*IN^fNT;jV zn*r!0x^bv`o}k|mMj1gC8o1~4F^LPUj@a{triN%!QV!?Q58|>D;PVhV$a$z?L&zN4 zQSN9TC?D1mJV7h^6OkI)qb9CUyTp>y8xVR*95qRFj}qo$&^<;&+xbz%z} zuqd?HNgbq@8EL+apilH2uD#_fuUD`^9ap zSm?Jvjc14(;yvONnGNY};7CcirlSeb6GkPp1TA$Pb0;O75QQYNnt-#d-UiM)`1PZJ zIGpovPV~8_IYg^Wdq`^#1-KL9p=fY*Vi$Vaw$lqyQo=ZgxRCBANhRym2${%P16Qr* zC-oU4PUm7ethZssj1H#3JsS$~{c!fGs!GZC~VM2FRIB~nShw56rYW?Ny7NHm(}Z7(|!kqOlnD177MH>o#yxSg(>>Hf>Nnm3`5zSr`p>1KO7 z+D^<96ELf{KHtIF3P*20p zD4!g9g1`IV#5MIMu1F5RS`jNu+n|e7z}}o_G9!P|exxbSh`0}E>C0FliBl+q39~!u z9wUJ1_nD>I44!e_DJWocfOOca`~^@@I(`d?Scf7f8&0*#P(|o1m7nK<`-a!?_F?`c z<5FVIxQJCZ;rK2fmq^4!ylH%^qhCn6lCxOKrHv(69zaTbPJ~8tC4Sn7GO8P|XBpjZ z??=6)88Zdu#iX0tp7&bdBlAUaoRuzS3j6qxy5#Gcy399C93Ac>6);LH=McZ`_S!=Q zv#qs_6gbu}$^Voj7pAZd8@VFQr?oIVHc}$?+CuNbk%hW`fA)z}I7AP6A?h!!me3=w zv>N@WL*M!dDGTXynwGQ5z*^z~_>xXJGoR3zQz?#A4A!{gM&UIqr~Z zHjbmLQC1ielS<5TI6muSc1%j6TQSQDzriKVLdR`qdNxANX0ztU>T}()jZfB$520M$;**_YVz=sWS^^ev__jR`yncnvVYa> zK7GBcg0+@r?^#3sCwvI>wel4{r$yVoy)*x>%Hq5D7eU%dOU6{`{(vncjtsZ)?=P0q z*m!4Je8W7w%r;DhGArBoRV>?h0XTj6HRa{b zHeOs}wn1GfMz)bgg(CPM>!Zs|n-$9JIVQOtinH_Pm7z?VvR{mBBaI5B>|f<_+5S;$**lJVtJ5f3p5HkGt?DUG;Rb zX2$gZLdLaLUg!RASxu8W*)tQN8CO!Lt%XRdS?eKT7_;!e&rDXGs|bFpUmcNouSVP^ zxP;fSmtQI76B%9jFth8JE0VJRm@DQ>SuMf3$u}4aYhjf?&RI9hP7}MTVfTz>k7Q?2 zW4+!@sE}vnq|}Q6_IubGMSe-xN3ExmyN3KedU3}O_g!+wOjVCk#ePBV|DadNyH>br z4G_GGB=57MI-@^%>d5Tl{)8jD*T7-!{Cl7ts$byQxqJBj6rX&W?jfG4`w0KDMOy9J zLA8j+>dMT(R*Ry(+FmCx|9u9LUk@g&wef`tsp@Me3+ z5FLu-DJeH1J7vaX{b|;HWwe~sgYktX@OhE^n()-?Uu0eIX;CC?SWVo>^A#jUypL#* z@!?}ci&-PaaMW3(s8Q^V9|>NK$Z;3i&u0f3x{|mH{ytWps@*k3g)TIHjI*hcph(Ah z>V{X%6AC`7iVErZME1>Y>r?2#dG5IIF~;HPET8G#Ja5C*qIs$%+3B!Y%`lAu>-T{| zfxPv?QXd!<&s#Tir9t*Y%%=}~diQqG^}(e;sSk2`&^n*Kp>B(QufspEwkFjB?)=QZ zGy7w&f21Da3ZF^k6Pc{E&s#UhPLt*N3ZEK9)Q`)_oyu&7Pv7|!+D+L(!Sj!J#ucj| zZk0TAJ-XQQO-XUHzwkg8fvR<94s*eCu0V!p+mKD8XpLV;!JLkRaG>O&dz8Tm*YLO^^=XS zdZf{yh`!J0N2w9>R>*nYVqaG`&a_WqZX~A+%~KQn%o~mSF70Sl^|98Q-p1?5bgGn^ z@-o)Wdx)@zcMme$*B*qQ;&t=xyZg``OM4MJ)}~j@r_rH^no;IY#)~FDuWJ6Z3q9sK z6;GjatLCBW>(7}&P?gzIQd+}T%Ghw;*pMe;@=Q~n-Nm!(Br59V-c9t>9Y=dR#+;iU z1*YL&L@#G_qtu3ZwSlLV%6n#bhN(F%D{tiYb301(efS{jT{b=L3*%qv!poQobr#S7 zzF*L2k9{xCr|>lLUO9QvebcqUr9csN;BvAa<7N2C8A8wY^19S=W!H(jzsR0I@19O> zPsI*BXIn>n;=WVF^kkirJ4fw3#>`_#SyP`lp8kFT&O#{NapOB=Lb&6KeUp+%TI>K|xd-(glj=cEJHal7^FrK|D??3D6yvD_L6>qTQ+c3lL zujHy4&#{mbzYjHE diff --git a/sample_size_site_full/kruskal_wallis_guide.html b/sample_size_site_full/kruskal_wallis_guide.html new file mode 100644 index 0000000..722177d --- /dev/null +++ b/sample_size_site_full/kruskal_wallis_guide.html @@ -0,0 +1,114 @@ + + + + + + Kiểm định Kruskal-Wallis + + + + + + +

Kiểm định Kruskal-Wallis (Kruskal-Wallis Test)

+ +

+ Kiểm định Kruskal-Wallis là một phương pháp phi tham số được sử dụng để xác định xem liệu có sự khác biệt đáng kể về mặt thống kê giữa hai hoặc nhiều nhóm độc lập hay không. Nó được coi là phiên bản thay thế cho Phân tích phương sai một yếu tố (One-Way ANOVA) khi các giả định của ANOVA không được đáp ứng. +

+ +

1. Giả thuyết kiểm định

+

+ Kiểm định này so sánh phân phối của các nhóm dựa trên thứ hạng của dữ liệu. +

+
    +
  • \( H_0 \): Phân phối của tất cả các nhóm là như nhau.
  • +
  • \( H_1 \): Phân phối của ít nhất một nhóm khác biệt so với các nhóm còn lại.
  • +
+ +

2. Khi nào nên sử dụng Kruskal-Wallis?

+
+

Sử dụng kiểm định này thay cho One-Way ANOVA khi:

+
    +
  • Dữ liệu không tuân theo phân phối chuẩn: Đây là lý do phổ biến nhất.
  • +
  • Phương sai không đồng nhất: Khi giả định về tính đồng nhất của phương sai bị vi phạm.
  • +
  • Dữ liệu ở dạng thứ hạng (Ordinal Data): Khi biến phụ thuộc của bạn là dữ liệu thứ hạng (ví dụ: mức độ hài lòng, thang điểm Likert).
  • +
  • Cỡ mẫu nhỏ và dữ liệu không chuẩn.
  • +
+
+ +

3. Thống kê kiểm định (H-statistic)

+ +

+ Thống kê kiểm định H được tính toán dựa trên tổng hạng của mỗi nhóm. +

+ +
+\[ +H = \frac{12}{N(N+1)} \sum_{i=1}^{k} \frac{R_i^2}{n_i} - 3(N+1) +\] +
+ +

+ Trong đó: +

    +
  • \( N \): là tổng số quan sát trong tất cả các nhóm.
  • +
  • \( k \): là số lượng nhóm.
  • +
  • \( R_i \): là tổng hạng của các quan sát trong nhóm \(i\).
  • +
  • \( n_i \): là số lượng quan sát trong nhóm \(i\).
  • +
+

+

+ Khi cỡ mẫu đủ lớn, thống kê H xấp xỉ tuân theo phân phối Chi-bình phương với \(k-1\) bậc tự do. +

+ +

4. Tính toán cỡ mẫu

+

+ Không giống như các kiểm định tham số, không có công thức giải tích đơn giản để tính toán cỡ mẫu cho kiểm định Kruskal-Wallis. Việc này thường đòi hỏi phải sử dụng các phần mềm thống kê chuyên dụng (như R, SAS) để chạy mô phỏng Monte Carlo, dựa trên các giả định về hình dạng phân phối và độ lớn của hiệu ứng mong muốn. +

+ +

5. Ứng dụng trong y tế công cộng

+
    +
  • So sánh mức độ hài lòng: So sánh mức độ hài lòng của bệnh nhân (đo bằng thang điểm từ 1-5) đối với ba chương trình can thiệp sức khỏe cộng đồng khác nhau.
  • +
  • Đánh giá nhận thức: So sánh mức độ nhận thức về nguy cơ bệnh tật (thấp, trung bình, cao) giữa các nhóm có trình độ học vấn khác nhau.
  • +
  • Phân tích dữ liệu lâm sàng không chuẩn: So sánh thời gian nằm viện (thường bị lệch phải) giữa các nhóm bệnh nhân được điều trị bằng các phác đồ khác nhau.
  • +
+ + + diff --git a/sample_size_site_full/levene_test.html b/sample_size_site_full/levene_test.html new file mode 100644 index 0000000..ed564cf --- /dev/null +++ b/sample_size_site_full/levene_test.html @@ -0,0 +1 @@ + diff --git a/sample_size_site_full/levene_test_guide.html b/sample_size_site_full/levene_test_guide.html new file mode 100644 index 0000000..551285c --- /dev/null +++ b/sample_size_site_full/levene_test_guide.html @@ -0,0 +1,126 @@ + + + + + + Kiểm định Levene về phương sai + + + + + + +

Kiểm định Levene (Levene’s Test)

+ +

+ Kiểm định Levene được sử dụng để đánh giá sự bằng nhau của phương sai (homogeneity of variance) giữa hai hay nhiều nhóm. Đây là kiểm định phổ biến hơn F-test khi dữ liệu không tuân theo phân phối chuẩn, vì nó ít nhạy cảm với sự vi phạm giả định chuẩn. +

+ +

1. Giả thuyết kiểm định

+
    +
  • \( H_0: \sigma_1^2 = \sigma_2^2 = \dots = \sigma_k^2 \) (phương sai của các nhóm là bằng nhau)
  • +
  • \( H_1: \) Có ít nhất một cặp nhóm có phương sai khác nhau
  • +
+ +

2. Ý tưởng phương pháp

+ +

+ Thay vì dùng giá trị gốc \( Y_{ij} \), Levene’s Test hoạt động trên độ lệch tuyệt đối giữa mỗi quan sát và trung vị hoặc trung bình của nhóm đó: +

+ +
+\[ +Z_{ij} = | Y_{ij} - \tilde{Y}_{\cdot j} | +\] +
+ +

+ Trong đó: +

    +
  • \( Y_{ij} \): giá trị của quan sát thứ \( i \) trong nhóm \( j \)
  • +
  • \( \tilde{Y}_{\cdot j} \): trung vị (hoặc trung bình) của nhóm \( j \)
  • +
+

+ +

+ Sau đó, một kiểm định ANOVA một yếu tố được thực hiện trên biến mới \( Z_{ij} \). Nếu có sự khác biệt đáng kể về trung bình của \(Z_{ij}\) giữa các nhóm, điều đó ngụ ý rằng phương sai của các giá trị gốc không đồng nhất. +

+ +

3. Thống kê kiểm định (W-statistic)

+ +

+ Thống kê Levene (W) có công thức tương tự như thống kê F trong ANOVA: +

+ +
+\[ +W = \frac{(N - k)}{(k - 1)} \cdot \frac{\sum_{j=1}^k n_j (\bar{Z}_{\cdot j} - \bar{Z}_{\cdot\cdot})^2}{\sum_{j=1}^k \sum_{i=1}^{n_j} (Z_{ij} - \bar{Z}_{\cdot j})^2} +\] +
+ +

Trong đó:

+
    +
  • \( N \): tổng số quan sát, \( k \): số nhóm
  • +
  • \( \bar{Z}_{\cdot j} \): trung bình của \( Z_{ij} \) trong nhóm \( j \)
  • +
  • \( \bar{Z}_{\cdot\cdot} \): trung bình của tất cả các giá trị \( Z_{ij} \)
  • +
+ +

+ Giá trị \( W \) được so sánh với phân phối \( F_{k-1, N-k} \). Nếu p-value nhỏ hơn mức ý nghĩa (thường là 0.05), chúng ta bác bỏ \( H_0 \). +

+ +

4. Ứng dụng trong y tế công cộng

+ +
    +
  • So sánh sự biến động của huyết áp giữa ba khu vực dân cư khác nhau.
  • +
  • Kiểm tra độ ổn định của chỉ số đường huyết giữa các nhóm bệnh nhân dùng các loại thuốc khác nhau.
  • +
  • Đánh giá tính đồng nhất phương sai trước khi thực hiện phân tích ANOVA, đặc biệt khi nghiên cứu hiệu quả của nhiều can thiệp.
  • +
+ +

5. Ghi chú

+
+
    +
  • Levene’s Test có thể dùng trung vị hoặc trung bình trong công thức. Phiên bản dùng trung vị (thường được gọi là Brown–Forsythe test) được khuyến nghị khi dữ liệu có các giá trị ngoại lai (outliers).
  • +
  • Đây là một kiểm định giả định rất quan trọng trước khi thực hiện ANOVA hoặc các mô hình hồi quy có biến phân loại.
  • +
+
+ + + diff --git a/sample_size_site_full/mann_whitney.html b/sample_size_site_full/mann_whitney.html new file mode 100644 index 0000000..668c6cd --- /dev/null +++ b/sample_size_site_full/mann_whitney.html @@ -0,0 +1 @@ + diff --git a/sample_size_site_full/mann_whitney_guide.html b/sample_size_site_full/mann_whitney_guide.html new file mode 100644 index 0000000..ee9b464 --- /dev/null +++ b/sample_size_site_full/mann_whitney_guide.html @@ -0,0 +1,101 @@ + + + + + Giới thiệu về Mann-Whitney U Test + + + + + + +

Giới thiệu về Mann-Whitney U Test

+ +

1. Mann-Whitney U Test là gì?

+

Kiểm định Mann-Whitney U là một kiểm định phi tham số được sử dụng để so sánh trung vị giữa hai nhóm độc lập, thay thế cho t-test hai mẫu độc lập khi:

+
    +
  • Dữ liệu không tuân theo phân phối chuẩn.
  • +
  • Dữ liệu dạng thứ tự hoặc liên tục nhưng không chuẩn.
  • +
+ +
+ +

2. Vai trò và Ứng dụng

+
    +
  • Y sinh: So sánh sự thay đổi giữa hai nhóm bệnh nhân dùng hai thuốc.
  • +
  • Thử nghiệm lâm sàng: Đánh giá hiệu quả thuốc qua trung vị chỉ số sinh học.
  • +
  • Y tế công cộng: So sánh trung vị mức độ hài lòng giữa hai cộng đồng.
  • +
+ +

Ví dụ

+
    +
  • So sánh trung vị BMI giữa nam và nữ.
  • +
  • So sánh mức độ đau (1–10) giữa hai nhóm thuốc.
  • +
+ +
+ +

Hướng dẫn Tính Cỡ Mẫu

+ +

1. Công thức cỡ mẫu

+

Cỡ mẫu mỗi nhóm được tính theo:

+ +

+ $$ n = \frac{(Z_{1-\alpha/2} + Z_{1-\beta})^2}{r^2} $$ +

+ +

Trong đó:

+
    +
  • \( Z_{1-\alpha/2} \): Giá trị Z theo mức ý nghĩa \( \alpha \)
  • +
  • \( Z_{1-\beta} \): Giá trị Z theo độ mạnh kiểm định (power)
  • +
  • \( r \): Kích thước hiệu ứng (effect size), tính theo: + $$ r = \frac{\mu_1 - \mu_2}{\sigma} $$ + với \( \mu_1, \mu_2 \): trung vị hai nhóm, và \( \sigma \): độ lệch chuẩn tổng hợp. +
  • +
+ +
+ +

2. Các bước tính toán

+
    +
  1. Xác định tham số: \( \alpha, 1 - \beta, r \)
  2. +
  3. Tính: +
      +
    • \( Z_{1-\alpha/2} = qnorm(1 - \alpha/2) \)
    • +
    • \( Z_{1-\beta} = qnorm(1 - \beta) \)
    • +
    +
  4. +
  5. Thay vào công thức để tính \( n \)
  6. +
+ +
+ +

3. Ví dụ minh họa

+ +

Giả sử:

+
    +
  • \( \mu_1 = 5 \), \( \mu_2 = 7 \), \( \sigma = 2 \)
  • +
  • \( \alpha = 0.05 \Rightarrow Z_{1-\alpha/2} = 1.96 \)
  • +
  • \( 1 - \beta = 0.8 \Rightarrow Z_{1-\beta} = 0.84 \)
  • +
+ +

Bước 1: Tính effect size:

+

+ $$ r = \frac{5 - 7}{2} = -1 \Rightarrow |r| = 1 $$ +

+ +

Bước 2: Thay vào công thức:

+

+ $$ n = \frac{(1.96 + 0.84)^2}{1^2} = \frac{7.84}{1} = 7.84 $$ +

+ +

Kết luận: Cỡ mẫu cần thiết mỗi nhóm: 8 đối tượng.

+ + + diff --git a/sample_size_site_full/overview.html b/sample_size_site_full/overview.html new file mode 100644 index 0000000..c23b108 --- /dev/null +++ b/sample_size_site_full/overview.html @@ -0,0 +1,113 @@ + + + + + + + Phương pháp luận Xác định Cỡ mẫu Nghiên cứu + + + + +
+

PHƯƠNG PHÁP LUẬN XÁC ĐỊNH CỠ MẪU NGHIÊN CỨU

+ +

Xác định cỡ mẫu là một bước thiết yếu và mang tính nền tảng trong đề cương nghiên cứu khoa học. Một cỡ mẫu được tính toán hợp lý không chỉ đảm bảo tính đại diện của mẫu cho quần thể nghiên cứu mà còn giúp tối ưu hóa việc sử dụng nguồn lực, thời gian và chi phí. Quan trọng hơn, nó đảm bảo nghiên cứu có đủ công suất thống kê (statistical power) để phát hiện ra các mối liên hệ hoặc sự khác biệt có ý nghĩa, từ đó gia tăng độ tin cậy và giá trị khoa học của kết quả nghiên cứu.

+ +

Công cụ tính toán cỡ mẫu được sử dụng trong nghiên cứu này được phát triển trên nền tảng ngôn ngữ lập trình R, một công cụ chuẩn mực và mạnh mẽ trong phân tích thống kê, kết hợp với Shiny framework để xây dựng giao diện người dùng tương tác và trực quan.

+ +
+ +

Mối quan hệ giữa Cỡ mẫu Nghiên cứu và Cỡ mẫu cho Kiểm định Giả thuyết

+ +

Về bản chất, việc tính toán cỡ mẫu cho một nghiên cứu chính là quá trình xác định số lượng quan sát tối thiểu cần thiết để thực hiện các kiểm định giả thuyết thống kê (statistical hypothesis testing) đã đề ra trong mục tiêu nghiên cứu.

+ +

Mỗi nghiên cứu khoa học đều nhằm trả lời một hoặc nhiều câu hỏi nghiên cứu, và các câu hỏi này được cụ thể hóa thành các giả thuyết thống kê (ví dụ: giả thuyết H₀ và giả thuyết Hₐ). Để có thể đưa ra kết luận bác bỏ hay không bác bỏ một giả thuyết, chúng ta cần sử dụng các phép kiểm định thống kê tương ứng (ví dụ: kiểm định t, khi bình phương, ANOVA).

+ +

Do đó, cỡ mẫu của nghiên cứu phải đủ lớn để phép kiểm định thống kê có đủ "năng lực" hay công suất thống kê (1-β) để phát hiện ra một ảnh hưởng (effect size) thực sự tồn tại trong quần thể ở một mức ý nghĩa (α) đã định trước. Nói cách khác, không có sự tách biệt giữa "cỡ mẫu nghiên cứu" và "cỡ mẫu cho kiểm định", chúng là một. Cỡ mẫu nghiên cứu được quyết định bởi yêu cầu của các kiểm định giả thuyết chính.

+ +
+ +

Xử lý trường hợp Nghiên cứu sử dụng nhiều Kiểm định Giả thuyết

+ +

Trong thực tế, một nghiên cứu thường có nhiều hơn một mục tiêu và do đó cần thực hiện nhiều phép kiểm định giả thuyết khác nhau. Ví dụ, một nghiên cứu có thể vừa so sánh tỷ lệ giữa hai nhóm, vừa so sánh giá trị trung bình giữa ba nhóm, và vừa xem xét một mối tương quan. Mỗi phép kiểm định này, với các tham số khác nhau (mức ý nghĩa α, công suất 1-β, độ lớn ảnh hưởng dự kiến), sẽ yêu cầu một cỡ mẫu tối thiểu khác nhau.

+ +

Trong trường hợp này, quy trình chuẩn mực cần thực hiện như sau:

+ +
    +
  1. Xác định các mục tiêu chính: Liệt kê tất cả các giả thuyết thống kê chính yếu mà nghiên cứu cần phải trả lời. Đây là những mục tiêu quan trọng nhất, quyết định sự thành công của nghiên cứu.
  2. +
  3. Tính toán cỡ mẫu riêng lẻ: Thực hiện tính toán cỡ mẫu cho từng giả thuyết chính một cách độc lập.
  4. +
  5. Lựa chọn cỡ mẫu cuối cùng: Cỡ mẫu cuối cùng cho toàn bộ nghiên cứu sẽ là cỡ mẫu lớn nhất trong số các cỡ mẫu đã tính toán ở bước 2.
  6. +
+ +
+

Lý do: Việc lựa chọn cỡ mẫu lớn nhất đảm bảo rằng nghiên cứu có đủ công suất thống kê cho tất cả các phân tích quan trọng. Nếu chọn một cỡ mẫu nhỏ hơn, nghiên cứu có thể có đủ năng lực để trả lời một số câu hỏi, nhưng sẽ không đủ năng lực (underpowered) để đưa ra kết luận đáng tin cậy cho các câu hỏi nghiên cứu khác yêu cầu cỡ mẫu lớn hơn. Điều này giúp tránh được sai lầm loại II (type II error) – không phát hiện được sự khác biệt trong khi nó thực sự tồn tại.

+
+
+
+
+ + +``` \ No newline at end of file diff --git a/sample_size_site_full/prop_one_sample.html b/sample_size_site_full/prop_one_sample.html new file mode 100644 index 0000000..27e5fd0 --- /dev/null +++ b/sample_size_site_full/prop_one_sample.html @@ -0,0 +1 @@ + diff --git a/sample_size_site_full/prop_one_sample_guide.html b/sample_size_site_full/prop_one_sample_guide.html new file mode 100644 index 0000000..541f140 --- /dev/null +++ b/sample_size_site_full/prop_one_sample_guide.html @@ -0,0 +1,33 @@ + + + + + Hướng dẫn Proportion Test một mẫu + + + + + +

Hướng dẫn Proportion Test một mẫu

+

Đây là hướng dẫn ngắn gọn cho việc tính toán cỡ mẫu trong kiểm định Proportion Test một mẫu.

+ +

Giả thuyết kiểm định

+

\( H_0: p = p_0 \quad \text{{vs}} \quad H_1: p \ne p_0 \)

+ +

Công thức tính cỡ mẫu

+

Cỡ mẫu \( n \) có thể được ước lượng bằng công thức:

+

\[ n = \frac{{Z_{{1 - \alpha/2}}^2 \cdot p_0(1 - p_0)}}{{(p - p_0)^2}} \]

+ +

Tham số

+
    +
  • \( p_0 \): tỷ lệ giả định trong giả thuyết không
  • +
  • \( p \): tỷ lệ kỳ vọng
  • +
  • \( \alpha \): mức ý nghĩa
  • +
  • \( Z \): giá trị tới hạn của phân phối chuẩn
  • +
+ + diff --git a/sample_size_site_full/prop_two_sample.html b/sample_size_site_full/prop_two_sample.html new file mode 100644 index 0000000..1ce685a --- /dev/null +++ b/sample_size_site_full/prop_two_sample.html @@ -0,0 +1 @@ + diff --git a/sample_size_site_full/prop_two_sample_guide.html b/sample_size_site_full/prop_two_sample_guide.html new file mode 100644 index 0000000..8480627 --- /dev/null +++ b/sample_size_site_full/prop_two_sample_guide.html @@ -0,0 +1,33 @@ + + + + + Hướng dẫn Proportion Test hai mẫu + + + + + +

Hướng dẫn Proportion Test hai mẫu

+

Đây là hướng dẫn ngắn gọn cho việc tính toán cỡ mẫu trong kiểm định Proportion Test hai mẫu.

+ +

Giả thuyết kiểm định

+

\( H_0: p = p_0 \quad \text{{vs}} \quad H_1: p \ne p_0 \)

+ +

Công thức tính cỡ mẫu

+

Cỡ mẫu \( n \) có thể được ước lượng bằng công thức:

+

\[ n = \frac{{Z_{{1 - \alpha/2}}^2 \cdot p_0(1 - p_0)}}{{(p - p_0)^2}} \]

+ +

Tham số

+
    +
  • \( p_0 \): tỷ lệ giả định trong giả thuyết không
  • +
  • \( p \): tỷ lệ kỳ vọng
  • +
  • \( \alpha \): mức ý nghĩa
  • +
  • \( Z \): giá trị tới hạn của phân phối chuẩn
  • +
+ + diff --git a/sample_size_site_full/shapiro_wilk.html b/sample_size_site_full/shapiro_wilk.html new file mode 100644 index 0000000..9b5f3f1 --- /dev/null +++ b/sample_size_site_full/shapiro_wilk.html @@ -0,0 +1 @@ + diff --git a/sample_size_site_full/shapiro_wilk_guide.html b/sample_size_site_full/shapiro_wilk_guide.html new file mode 100644 index 0000000..2a5ad73 --- /dev/null +++ b/sample_size_site_full/shapiro_wilk_guide.html @@ -0,0 +1,102 @@ + + + + + + Kiểm định Shapiro-Wilk + + + + + + +

Kiểm định Shapiro-Wilk (Shapiro-Wilk Test)

+ +

+ Kiểm định Shapiro-Wilk là một trong những kiểm định thống kê mạnh mẽ và phổ biến nhất được sử dụng để kiểm tra xem một mẫu dữ liệu có tuân theo phân phối chuẩn (normal distribution) hay không. +

+ +

1. Giả thuyết kiểm định

+
    +
  • \( H_0 \): Dữ liệu của mẫu tuân theo phân phối chuẩn.
  • +
  • \( H_1 \): Dữ liệu của mẫu không tuân theo phân phối chuẩn.
  • +
+ +
+ Lưu ý quan trọng: Trong kiểm định này, chúng ta thường mong muốn có một p-value lớn (p > 0.05) để không bác bỏ giả thuyết \(H_0\), từ đó có thể kết luận rằng dữ liệu tuân theo phân phối chuẩn và đủ điều kiện để sử dụng các kiểm định tham số. +
+ +

2. Thống kê kiểm định (W-statistic)

+ +

+ Thống kê kiểm định W được tính toán dựa trên sự so sánh giữa các giá trị đã được sắp xếp của mẫu với các giá trị kỳ vọng tương ứng từ một phân phối chuẩn. +

+ +
+\[ +W = \frac{(\sum_{i=1}^n a_i x_{(i)})^2}{\sum_{i=1}^n (x_i - \bar{x})^2} +\] +
+ +

+ Trong đó: +

    +
  • \( x_{(i)} \): là giá trị thống kê thứ tự thứ \(i\) (dữ liệu đã được sắp xếp từ nhỏ đến lớn).
  • +
  • \( \bar{x} \): là trung bình mẫu.
  • +
  • \( a_i \): là các hằng số được tính toán từ trung bình, phương sai và hiệp phương sai của các thống kê thứ tự từ một mẫu có phân phối chuẩn.
  • +
+

+

+ Giá trị W nằm trong khoảng từ 0 đến 1. Giá trị W càng gần 1 thì dữ liệu càng gần với phân phối chuẩn. +

+ +

3. Ứng dụng trong y tế công cộng

+ +

+ Kiểm tra giả định về tính chuẩn của dữ liệu là một bước cực kỳ quan trọng trước khi thực hiện nhiều phân tích thống kê tham số. +

+
    +
  • Trước khi thực hiện T-test hoặc ANOVA: Các kiểm định này đều yêu cầu dữ liệu (hoặc phần dư) phải tuân theo phân phối chuẩn. Kiểm định Shapiro-Wilk giúp xác nhận giả định này.
  • +
  • Đánh giá dữ liệu lâm sàng: Kiểm tra xem các chỉ số sinh học như huyết áp, cholesterol, BMI trong một mẫu dân số có tuân theo phân phối chuẩn hay không. Nếu không, các phương pháp phi tham số hoặc phép biến đổi dữ liệu có thể cần được xem xét.
  • +
  • Kiểm tra phần dư của mô hình hồi quy: Trong hồi quy tuyến tính, một giả định quan trọng là phần dư (residuals) của mô hình phải tuân theo phân phối chuẩn. Shapiro-Wilk là một công cụ hiệu quả để kiểm tra giả định này.
  • +
+ + + diff --git a/sample_size_site_full/t_one_sample.html b/sample_size_site_full/t_one_sample.html new file mode 100644 index 0000000..390645c --- /dev/null +++ b/sample_size_site_full/t_one_sample.html @@ -0,0 +1 @@ + diff --git a/sample_size_site_full/t_one_sample_guide.html b/sample_size_site_full/t_one_sample_guide.html new file mode 100644 index 0000000..0fd9b4c --- /dev/null +++ b/sample_size_site_full/t_one_sample_guide.html @@ -0,0 +1,81 @@ + + + + + Hướng dẫn tính cỡ mẫu kiểm định t một mẫu + + +

Hướng dẫn tính toán cỡ mẫu cho kiểm định t một mẫu

+
+

1. Kiểm định t một mẫu là gì?

+

Kiểm định t một mẫu (One-sample t-test) là một phương pháp thống kê dùng để kiểm tra xem giá trị trung bình của một mẫu có khác biệt so với một giá trị đã biết hoặc giả thuyết hay không.

+

Ví dụ: kiểm tra xem chiều cao trung bình của một nhóm học sinh có khác biệt so với chiều cao chuẩn quốc gia (ví dụ 165 cm) không.

+

Công thức kiểm định:

+

t = (x̄ - μ₀) / (s / √n)

+

Trong đó:

+
    +
  • : Giá trị trung bình mẫu
  • +
  • μ₀: Giá trị trung bình giả thuyết
  • +
  • s: Độ lệch chuẩn mẫu
  • +
  • n: Kích thước mẫu
  • +
+ +
+

2. Phạm vi ứng dụng trong y tế công cộng

+

Trong y tế công cộng, kiểm định t một mẫu thường được dùng để:

+
    +
  • So sánh trung bình của biến sức khỏe (BMI, huyết áp...) với giá trị chuẩn.
  • +
  • Kiểm tra sự khác biệt giữa dữ liệu khảo sát và tiêu chuẩn y tế cộng đồng.
  • +
+

Ví dụ:

+
    +
  • Kiểm tra cân nặng trung bình của trẻ em so với chuẩn WHO.
  • +
  • So sánh mức ô nhiễm trung bình với giới hạn khuyến cáo.
  • +
+ +
+

3. Công thức tính cỡ mẫu

+

Công thức:

+

n = ((Z₁₋⍺/2 + Z₁₋β)² × s²) / (μ₁ - μ₀)²

+

Trong đó:

+
    +
  • Z₁₋⍺/2: Giá trị Z theo mức ý nghĩa
  • +
  • Z₁₋β: Giá trị Z theo độ mạnh kiểm định (power)
  • +
  • s: Độ lệch chuẩn dự kiến
  • +
  • μ₁: Giá trị trung bình kỳ vọng
  • +
  • μ₀: Giá trị trung bình giả thuyết
  • +
+ +
+

4. Hướng dẫn tính toán

+

Bạn cần cung cấp:

+
    +
  1. Mức ý nghĩa (): thường là 0.05
  2. +
  3. Độ mạnh kiểm định (1 - β): thường là 0.8 hoặc 0.9
  4. +
  5. Độ lệch chuẩn (s): lấy từ nghiên cứu trước
  6. +
  7. Giá trị kỳ vọng (μ₁) và giá trị giả thuyết (μ₀)
  8. +
+

Ví dụ:

+
    +
  • ⍺ = 0.05
  • +
  • 1 - β = 0.8
  • +
  • s = 2
  • +
  • μ₁ = 16
  • +
  • μ₀ = 15
  • +
+

Tính:

+

Tra bảng Z:

+
    +
  • Z₀.975 = 1.96
  • +
  • Z₀.8 = 0.84
  • +
+

Áp dụng công thức:

+

n = ((1.96 + 0.84)² × 4) / (1)² = (7.84 × 4) = 31.36

+

⇒ Cần ít nhất 32 trẻ em.

+ +
+

5. Công cụ tính bằng R hoặc Shiny

+

Bạn có thể dùng ứng dụng Shiny để tự động tính cỡ mẫu kiểm định t một mẫu. Ứng dụng này giúp thực hiện các bước tính toán nhanh chóng và trực quan.

+ + + diff --git a/sample_size_site_full/t_paired.html b/sample_size_site_full/t_paired.html new file mode 100644 index 0000000..6850116 --- /dev/null +++ b/sample_size_site_full/t_paired.html @@ -0,0 +1 @@ + diff --git a/sample_size_site_full/t_paired_guide.html b/sample_size_site_full/t_paired_guide.html new file mode 100644 index 0000000..0924fb8 --- /dev/null +++ b/sample_size_site_full/t_paired_guide.html @@ -0,0 +1,85 @@ + + + + + Hướng dẫn kiểm định t ghép cặp + + + + + + +

Hướng dẫn tính toán cỡ mẫu cho kiểm định t ghép cặp (Paired t-test)

+
+ +

1. Kiểm định t ghép cặp là gì?

+

Kiểm định t ghép cặp (Paired t-test) là phương pháp thống kê dùng để kiểm tra sự khác biệt trung bình giữa hai điều kiện, được đo lặp lại trên cùng đối tượng.

+

Khác với t-test hai mẫu độc lập, t ghép cặp tập trung vào sự khác biệt giữa hai giá trị trong mỗi cặp, giúp giảm sai số và cần cỡ mẫu nhỏ hơn.

+ +

2. Phạm vi ứng dụng trong y tế công cộng

+
    +
  • So sánh huyết áp trước và sau điều trị.
  • +
  • Đo ô nhiễm không khí sáng vs chiều cùng địa điểm.
  • +
  • So sánh cân nặng trẻ em trước và sau chương trình dinh dưỡng.
  • +
+ +

3. Công thức kiểm định t ghép cặp

+

Giá trị thống kê t được tính như sau:

+

+ $$ t = \frac{\bar{d} - \mu_d}{s_d / \sqrt{n}} $$ +

+ +

Trong đó:

+
    +
  • \(\bar{d}\): Trung bình của các hiệu số giữa hai điều kiện
  • +
  • \(\mu_d\): Giá trị kỳ vọng của hiệu số (thường là 0)
  • +
  • \(s_d\): Độ lệch chuẩn của hiệu số
  • +
  • \(n\): Số cặp quan sát
  • +
+ +

4. Công thức tính cỡ mẫu

+

Cỡ mẫu \(n\) được tính theo công thức:

+

+ $$ n = \frac{(Z_{1-\alpha/2} + Z_{1-\beta})^2 \cdot s^2}{\mu_d^2} $$ +

+ +

Trong đó:

+
    +
  • \(Z_{1-\alpha/2}\): Giá trị tới hạn theo mức ý nghĩa \(\alpha\)
  • +
  • \(Z_{1-\beta}\): Giá trị tới hạn theo độ mạnh kiểm định
  • +
  • \(s\): Độ lệch chuẩn của hiệu số
  • +
  • \(\mu_d\): Khác biệt mong muốn phát hiện
  • +
+ +

5. Ví dụ minh họa

+

Giả sử:

+
    +
  • \(\alpha = 0.05 \Rightarrow Z_{1-\alpha/2} = 1.96\)
  • +
  • \(1-\beta = 0.8 \Rightarrow Z_{1-\beta} = 0.84\)
  • +
  • \(s = 5\), \(\mu_d = 3\)
  • +
+ +

Áp dụng công thức:

+

+ $$ n = \frac{(1.96 + 0.84)^2 \cdot 5^2}{3^2} = \frac{7.84 \cdot 25}{9} = \frac{196}{9} \approx 21.78 $$ +

+

Vậy cần ít nhất 22 cặp quan sát.

+ +

6. Tính bằng R với gói pwr

+

Dùng hàm:

+
library(pwr)
+pwr.t.test(d = 3/5, sig.level = 0.05, power = 0.8, type = "paired")
+ +

Với \(d = \mu_d / s = 3 / 5 = 0.6\)

+ +

7. Kết luận

+

Kiểm định t ghép cặp hữu ích khi phân tích dữ liệu đo lặp. Tính đúng cỡ mẫu đảm bảo đủ mạnh để phát hiện khác biệt thực sự.

+ + + diff --git a/sample_size_site_full/t_two_sample.html b/sample_size_site_full/t_two_sample.html new file mode 100644 index 0000000..ef64b1e --- /dev/null +++ b/sample_size_site_full/t_two_sample.html @@ -0,0 +1 @@ + diff --git a/sample_size_site_full/t_two_sample_guide.html b/sample_size_site_full/t_two_sample_guide.html new file mode 100644 index 0000000..0751f15 --- /dev/null +++ b/sample_size_site_full/t_two_sample_guide.html @@ -0,0 +1,116 @@ + + + + + + + Hướng dẫn tính cỡ mẫu cho kiểm định t hai mẫu + + + + +

Hướng dẫn tính toán cỡ mẫu cho kiểm định t hai mẫu

+
+

1. Kiểm định t hai mẫu là gì?

+

Kiểm định t hai mẫu (Two-sample t-test) là một phương pháp thống kê được sử dụng để so sánh giá trị trung bình của hai nhóm độc lập, nhằm kiểm tra xem sự khác biệt giữa hai giá trị trung bình này có ý nghĩa thống kê hay không.

+

Ví dụ: So sánh mức độ hài lòng trung bình giữa hai nhóm bệnh nhân được điều trị bằng hai phương pháp khác nhau.

+

Công thức kiểm định t hai mẫu:

+

+ + t = (x̄₁ − x̄₂) / √[sp² (1/n₁ + 1/n₂)] + +

+

Trong đó:

+
    +
  • x̄₁, x̄₂: Trung bình nhóm 1 và nhóm 2
  • +
  • sp²: Phương sai tổng hợp, tính theo: +
    + + sp² = [(n₁−1)s₁² + (n₂−1)s₂²] / (n₁ + n₂ − 2) + +
  • +
  • s₁², s₂²: Phương sai từng nhóm
  • +
  • n₁, n₂: Kích thước mẫu nhóm 1 và 2
  • +
+
+

2. Phạm vi ứng dụng trong y tế công cộng

+
    +
  • So sánh hiệu quả hai phương pháp điều trị
  • +
  • Đánh giá sự khác biệt giữa nhóm điều trị và nhóm đối chứng
  • +
  • Phân tích chỉ số sức khỏe giữa các nhóm, ví dụ người hút thuốc và không hút thuốc
  • +
+

Ví dụ:

+
    +
  • So sánh cân nặng trung bình trẻ em có và không được bổ sung vi chất
  • +
  • So sánh thời gian sống sót trung bình giữa hai nhóm điều trị ung thư
  • +
+
+

3. Công thức tính cỡ mẫu

+

+ n = [2(Z1−α/2 + Z1−β)² * s²] / (μ₁ − μ₂)² +

+
    +
  • Z1−α/2: điểm tới hạn cho mức ý nghĩa α
  • +
  • Z1−β: điểm tới hạn cho độ mạnh kiểm định
  • +
  • s: độ lệch chuẩn tổng hợp
  • +
  • μ₁, μ₂: trung bình của hai nhóm
  • +
  • n: cỡ mẫu cho mỗi nhóm
  • +
+

Lưu ý: Tổng cỡ mẫu hai nhóm là 2n.

+
+

4. Hướng dẫn tính toán cỡ mẫu

+

Thông số cần biết:

+
    +
  1. Mức ý nghĩa (α), thường là 0.05
  2. +
  3. Độ mạnh kiểm định (1−β), thường là 0.8 hoặc 0.9
  4. +
  5. Độ lệch chuẩn s
  6. +
  7. Sự khác biệt mong muốn μ₁ − μ₂
  8. +
+

Ví dụ:

+
    +
  • α = 0.05
  • +
  • 1−β = 0.8
  • +
  • s = 3 (kg)
  • +
  • μ₁ = 20, μ₂ = 18
  • +
+

Tính toán:

+

+ + n = [2(1.96 + 0.84)² * 9] / 4 = (2 × 7.84 × 9) / 4 = 141.12 / 4 = 35.28 +
+ → Cần ít nhất 36 đối tượng mỗi nhóm +

+
+

5. Tính bằng R hoặc Shiny

+

Sử dụng hàm pwr.t.test từ package pwr trong R:

+
library(pwr)
+pwr.t.test(d = 0.5, sig.level = 0.05, power = 0.8, type = "two.sample")
+
+

Trong đó:

+
    +
  • d = (μ₁ − μ₂)/s: hiệu ứng chuẩn hóa
  • +
  • sig.level: mức ý nghĩa α
  • +
  • power: độ mạnh kiểm định (1−β)
  • +
  • type = "two.sample": kiểm định t hai mẫu
  • +
+

Kết quả trả về là cỡ mẫu cần thiết mỗi nhóm.

+ + + diff --git a/sample_size_site_full/wilcoxon_signed.html b/sample_size_site_full/wilcoxon_signed.html new file mode 100644 index 0000000..3029c96 --- /dev/null +++ b/sample_size_site_full/wilcoxon_signed.html @@ -0,0 +1 @@ + diff --git a/sample_size_site_full/wilcoxon_signed_guide.html b/sample_size_site_full/wilcoxon_signed_guide.html new file mode 100644 index 0000000..df3d2b0 --- /dev/null +++ b/sample_size_site_full/wilcoxon_signed_guide.html @@ -0,0 +1,124 @@ + + + + + + Kiểm định Wilcoxon Signed-Rank + + + + + + +

Kiểm định Dấu-Hạng Wilcoxon (Wilcoxon Signed-Rank Test)

+ +

+ Kiểm định Dấu-Hạng Wilcoxon là một kiểm định phi tham số được sử dụng để so sánh trung vị của hai mẫu có liên quan — ví dụ như dữ liệu trước và sau điều trị trên cùng một nhóm đối tượng. Nó là phương án thay thế cho kiểm định t ghép cặp khi giả định về phân phối chuẩn không được đáp ứng. +

+ +

1. Giả thuyết kiểm định

+
    +
  • \( H_0: \) Trung vị của sự khác biệt giữa các cặp bằng 0.
  • +
  • \( H_1: \) Trung vị của sự khác biệt khác 0 (hai phía), hoặc lớn hơn/nhỏ hơn 0 (một phía).
  • +
+ +

2. Cách tính thống kê kiểm định

+ +

+ Với \( n \) cặp quan sát \( (X_i, Y_i) \), ta xét hiệu số: +

+ +
+\[ +D_i = X_i - Y_i +\] +
+ +

Bỏ qua các \( D_i = 0 \), sắp xếp các giá trị còn lại theo thứ tự tăng dần của \( |D_i| \) và gán hạng \( R_i \). Khi đó, thống kê kiểm định \( W \) là tổng hạng của các hiệu số dương:

+ +
+\[ +W = \sum_{i: D_i > 0} R_i +\] +
+ +

+ Giá trị \( W \) được so sánh với bảng phân phối Wilcoxon hoặc dùng xấp xỉ phân phối chuẩn khi \( n \) đủ lớn. +

+ +

3. Công thức ước lượng cỡ mẫu

+ +

+ Cỡ mẫu cần thiết để phát hiện một hiệu ứng khác biệt với mức ý nghĩa \( \alpha \) và công suất \( 1 - \beta \) có thể được ước tính xấp xỉ bằng: +

+ +
+\[ +n \approx \left( \frac{Z_{1 - \alpha/2} + Z_{1 - \beta}}{ES \cdot \sqrt{3}/\pi} \right)^2 +\] +
+ +

Trong đó:

+
    +
  • \( ES \): effect size (hiệu ứng chuẩn hoá, ví dụ: \( \delta / \sigma_D \))
  • +
  • \( Z_{1 - \alpha/2} \): điểm tới hạn cho mức ý nghĩa (≈ 1.96 nếu \( \alpha = 0.05 \))
  • +
  • \( Z_{1 - \beta} \): điểm tới hạn cho công suất mong muốn (≈ 0.84 nếu power = 80%)
  • +
+

+ Lưu ý rằng đây là công thức xấp xỉ và việc tính toán chính xác hơn thường đòi hỏi mô phỏng. +

+ +

4. Ứng dụng trong y tế công cộng

+ +
+

Kiểm định Wilcoxon Signed-Rank được sử dụng khi:

+
    +
  • So sánh huyết áp trước và sau một can thiệp trên cùng một nhóm bệnh nhân, đặc biệt khi sự thay đổi không tuân theo phân phối chuẩn.
  • +
  • Đánh giá hiệu quả của một chương trình giáo dục sức khỏe bằng cách so sánh điểm kiến thức trước và sau chương trình.
  • +
  • So sánh mức độ lo âu của cùng một nhóm người trước và sau một chương trình tư vấn tâm lý.
  • +
+
+ +

+ Ưu điểm lớn nhất của kiểm định này là không yêu cầu giả định về phân phối chuẩn của dữ liệu, điều này rất hữu ích trong thực tế thu thập dữ liệu y tế, nơi dữ liệu thường bị lệch hoặc có các giá trị ngoại lai. +

+ + + diff --git a/sample_size_site_full/z_one_sample.html b/sample_size_site_full/z_one_sample.html new file mode 100644 index 0000000..724d0c5 --- /dev/null +++ b/sample_size_site_full/z_one_sample.html @@ -0,0 +1 @@ + diff --git a/sample_size_site_full/z_one_sample_guide.html b/sample_size_site_full/z_one_sample_guide.html new file mode 100644 index 0000000..2a5d3dd --- /dev/null +++ b/sample_size_site_full/z_one_sample_guide.html @@ -0,0 +1,101 @@ + + + + + Hướng dẫn Kiểm định z Một Mẫu + + + + + + + +

Kiểm định z Một Mẫu (One-sample z-test)

+ +

Mục đích sử dụng

+

Kiểm định z một mẫu được sử dụng để so sánh trung bình mẫu với một giá trị trung bình giả định trong tổng thể, khi **độ lệch chuẩn của tổng thể đã biết**.

+ +

Giả định

+
    +
  • Mẫu ngẫu nhiên và độc lập.
  • +
  • Tổng thể có phân phối chuẩn, hoặc cỡ mẫu đủ lớn (\( n \ge 30 \)).
  • +
  • Đã biết độ lệch chuẩn tổng thể \( \sigma \).
  • +
+ +

Công thức kiểm định

+

Với mẫu có kích thước \( n \), trung bình \( \bar{X} \), kiểm định giả thuyết:

+
    +
  • \( H_0: \mu = \mu_0 \)
  • +
  • \( H_1: \mu \ne \mu_0 \) (hoặc \( <, > \) tuỳ mục tiêu)
  • +
+ +
+ \[ + z = \frac{\bar{X} - \mu_0}{\sigma / \sqrt{n}} + \] +
+ +

Giá trị z được so sánh với z tới hạn từ bảng phân phối chuẩn.

+ +

Cách tính cỡ mẫu

+

Để xác định cỡ mẫu cần thiết nhằm phát hiện một độ chênh trung bình kỳ vọng \( \Delta = |\mu - \mu_0| \), dùng công thức:

+ +
+ \[ + n = \left( \frac{(Z_{1-\alpha/2} + Z_{1-\beta}) \cdot \sigma}{\Delta} \right)^2 + \] + Trong đó: +
    +
  • \( Z_{1-\alpha/2} \): z-score ứng với mức ý nghĩa (ví dụ: 1.96 nếu \( \alpha = 0.05 \))
  • +
  • \( Z_{1-\beta} \): z-score ứng với sức mạnh kiểm định (power), ví dụ: 0.84 nếu power = 0.80
  • +
  • \( \sigma \): độ lệch chuẩn đã biết
  • +
  • \( \Delta \): mức sai khác trung bình muốn phát hiện
  • +
+
+ +

Ví dụ

+

Giả sử chiều cao trung bình chuẩn là 165 cm. Bạn muốn kiểm định xem một nhóm sinh viên có chiều cao trung bình khác biệt không, với:

+
    +
  • \( \sigma = 6 \) cm
  • +
  • \( \Delta = 2 \) cm
  • +
  • \( \alpha = 0.05 \) → \( Z_{1-\alpha/2} = 1.96 \)
  • +
  • Power = 0.80 → \( Z_{1-\beta} = 0.84 \)
  • +
+ +
+ \[ + n = \left( \frac{(1.96 + 0.84) \cdot 6}{2} \right)^2 = (8.4)^2 = 70.56 + \] + ⇒ Cần ít nhất 71 người. +
+ +

Thực hành

+

Bạn có thể sử dụng Shiny hoặc phần mềm R để nhập giá trị và tính toán tự động.

+ + + + diff --git a/sample_size_site_full/z_paired.html b/sample_size_site_full/z_paired.html new file mode 100644 index 0000000..a85fb4b --- /dev/null +++ b/sample_size_site_full/z_paired.html @@ -0,0 +1 @@ + diff --git a/sample_size_site_full/z_paired_guide.html b/sample_size_site_full/z_paired_guide.html new file mode 100644 index 0000000..128dc2c --- /dev/null +++ b/sample_size_site_full/z_paired_guide.html @@ -0,0 +1,89 @@ + + + + + Hướng dẫn Kiểm định z Ghép Cặp (Paired z-test) + + + + + + + +

Kiểm định z Ghép Cặp (Paired z-test)

+ +

Mục đích sử dụng

+

Kiểm định z ghép cặp dùng để so sánh trung bình hiệu (chênh lệch) giữa hai phép đo lặp trên cùng một đối tượng khi biết độ lệch chuẩn của hiệu số tổng thể.

+ +

Giả định

+
    +
  • Dữ liệu ghép cặp độc lập.
  • +
  • Độ lệch chuẩn của hiệu \( \sigma_D \) đã biết.
  • +
  • Hiệu số giữa các cặp tuân theo phân phối chuẩn.
  • +
+ +

Công thức kiểm định

+

Với \( n \) cặp, hiệu số trung bình \( \bar{D} \), kiểm định giả thuyết:

+
    +
  • \( H_0: \mu_D = 0 \) (không có sự khác biệt trung bình)
  • +
  • \( H_1: \mu_D \neq 0 \)
  • +
+ +
+ \[ + z = \frac{\bar{D} - 0}{\sigma_D / \sqrt{n}} = \frac{\bar{D}}{\sigma_D / \sqrt{n}} + \] +
+ +

Cách tính cỡ mẫu

+

Để xác định cỡ mẫu cần thiết phát hiện một sai khác trung bình kỳ vọng \( \Delta \) với mức ý nghĩa \( \alpha \) và power \( 1-\beta \):

+ +
+ \[ + n = \left( \frac{(Z_{1-\alpha/2} + Z_{1-\beta}) \cdot \sigma_D}{\Delta} \right)^2 + \] +
+ +

Ví dụ

+

Giả sử bạn muốn phát hiện sự thay đổi trung bình \( \Delta = 3 \) điểm, với:

+
    +
  • Độ lệch chuẩn hiệu \( \sigma_D = 6 \)
  • +
  • \( \alpha = 0.05 \) → \( Z_{1-\alpha/2} = 1.96 \)
  • +
  • Power = 0.80 → \( Z_{1-\beta} = 0.84 \)
  • +
+ +
+ \[ + n = \left( \frac{(1.96 + 0.84) \times 6}{3} \right)^2 = (5.6)^2 = 31.36 + \] + ⇒ Cần ít nhất 32 cặp quan sát. +
+ +

Thực hành

+

Bạn có thể áp dụng kiểm định này trong các phân tích dữ liệu ghép cặp khi biết độ lệch chuẩn tổng thể của hiệu số.

+ + + diff --git a/sample_size_site_full/z_two_sample.html b/sample_size_site_full/z_two_sample.html new file mode 100644 index 0000000..cbba0b5 --- /dev/null +++ b/sample_size_site_full/z_two_sample.html @@ -0,0 +1 @@ + diff --git a/sample_size_site_full/z_two_sample_guide.html b/sample_size_site_full/z_two_sample_guide.html new file mode 100644 index 0000000..8baf2fc --- /dev/null +++ b/sample_size_site_full/z_two_sample_guide.html @@ -0,0 +1,103 @@ + + + + + Hướng dẫn Kiểm định z Hai Mẫu + + + + + + + +

Kiểm định z Hai Mẫu (Two-sample z-test)

+ +

Mục đích sử dụng

+

Dùng để so sánh trung bình của hai tổng thể độc lập khi **biết trước độ lệch chuẩn tổng thể**. Ví dụ:

+
    +
  • So sánh điểm trung bình giữa nam và nữ khi biết trước phương sai.
  • +
  • So sánh hai quy trình sản xuất khác nhau về năng suất.
  • +
+ +

Giả định

+
    +
  • Hai mẫu độc lập.
  • +
  • Phân phối tổng thể chuẩn hoặc cỡ mẫu đủ lớn (\( n_1, n_2 \ge 30 \)).
  • +
  • Đã biết trước độ lệch chuẩn của hai tổng thể: \( \sigma_1, \sigma_2 \).
  • +
+ +

Công thức kiểm định

+

Với hai mẫu có kích thước \( n_1 \), \( n_2 \), trung bình mẫu \( \bar{X}_1 \), \( \bar{X}_2 \), độ lệch chuẩn tổng thể \( \sigma_1, \sigma_2 \), kiểm định giả thuyết:

+
    +
  • \( H_0: \mu_1 = \mu_2 \)
  • +
  • \( H_1: \mu_1 \ne \mu_2 \)
  • +
+ +
+ \[ + z = \frac{\bar{X}_1 - \bar{X}_2}{\sqrt{ \frac{\sigma_1^2}{n_1} + \frac{\sigma_2^2}{n_2} }} + \] +
+ +

Cách tính cỡ mẫu

+

Giả sử \( \sigma_1 = \sigma_2 = \sigma \) (phương sai hai nhóm bằng nhau), cỡ mẫu mỗi nhóm cần để phát hiện sai khác trung bình \( \Delta = |\mu_1 - \mu_2| \) với mức ý nghĩa và power cho trước:

+ +
+ \[ + n = 2 \times \left( \frac{(Z_{1-\alpha/2} + Z_{1-\beta}) \cdot \sigma}{\Delta} \right)^2 + \] + Trong đó: +
    +
  • \( Z_{1-\alpha/2} \): điểm z cho mức ý nghĩa hai phía (ví dụ: 1.96 nếu \( \alpha = 0.05 \))
  • +
  • \( Z_{1-\beta} \): điểm z cho power mong muốn (ví dụ: 0.84 nếu power = 0.80)
  • +
  • \( \Delta \): chênh lệch trung bình mong muốn phát hiện
  • +
+
+ +

Ví dụ

+

So sánh trung bình điểm thi giữa hai nhóm:

+
    +
  • \( \sigma = 10 \)
  • +
  • \( \Delta = 5 \)
  • +
  • \( \alpha = 0.05 \) → \( Z_{1-\alpha/2} = 1.96 \)
  • +
  • Power = 0.80 → \( Z_{1-\beta} = 0.84 \)
  • +
+ +
+ \[ + n = 2 \times \left( \frac{(1.96 + 0.84) \cdot 10}{5} \right)^2 + = 2 \times \left( \frac{28}{5} \right)^2 + = 2 \times (5.6)^2 = 2 \times 31.36 = 62.72 + \] + ⇒ Mỗi nhóm cần ít nhất 63 người. +
+ +

Thực hành

+

Bạn có thể sử dụng form bên trên để nhập dữ liệu và tính toán kiểm định.

+ + + diff --git a/samplesize/1ProportionTest/app.R b/samplesize/1ProportionTest/app.R new file mode 100644 index 0000000..8604764 --- /dev/null +++ b/samplesize/1ProportionTest/app.R @@ -0,0 +1,238 @@ +# Tải các thư viện cần thiết +library(shiny) +library(bslib) +library(ggplot2) +library(shinycssloaders) +library(pwr) + +# --- Giao diện người dùng (UI) --- +ui <- fluidPage( + theme = bs_theme(version = 5, bootswatch = "cerulean"), + withMathJax(), + + titlePanel("Tính toán Cỡ mẫu cho Kiểm định Tỷ lệ một mẫu"), + + sidebarLayout( + sidebarPanel( + h4("Tham số đầu vào"), + + sliderInput("p0", + label = "Tỷ lệ giả định dưới H0 (p0):", + min = 0.01, max = 0.99, value = 0.5, step = 0.01), + + sliderInput("p1", + label = "Tỷ lệ kỳ vọng dưới Ha (p1):", + min = 0.01, max = 0.99, value = 0.6, step = 0.01), + + # Hiển thị effect size được tính toán + h5("Effect Size (Cohen's h):"), + uiOutput("effect_size_display"), + + selectInput("alternative", "Loại kiểm định:", + choices = c("Hai phía (Two-sided)" = "two.sided", + "Lớn hơn (Greater)" = "greater", + "Nhỏ hơn (Less)" = "less")), + + sliderInput("sig_level", + label = "Mức ý nghĩa (\\(\\alpha\\)):", + min = 0.01, max = 0.10, value = 0.05, step = 0.01), + + sliderInput("power", + label = "Power mong muốn (\\(1 - \\beta\\)):", + min = 0.50, max = 0.99, value = 0.80, step = 0.01), + + hr(), + helpText("Ứng dụng sử dụng hàm pwr.p.test() từ gói 'pwr'.") + ), + + mainPanel( + tabsetPanel( + id = "results_tabs", + type = "pills", + + tabPanel( + "Kết quả & Diễn giải", + h4("Kết quả tính toán"), + withSpinner(uiOutput("sample_size_output"), type = 6, color = "#007bff") + ), + + tabPanel( + "Đồ thị Power vs. Cỡ mẫu", + h4("Mối quan hệ giữa Power và Cỡ mẫu"), + withSpinner(plotOutput("power_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Phân tích Effect Size", + h4("Mối quan hệ giữa Cỡ mẫu và Effect Size"), + withSpinner(plotOutput("effect_analysis_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Giả thuyết và Công thức (Helper)", + h4("Giả thuyết của Kiểm định Tỷ lệ một mẫu"), + p("Kiểm định này so sánh một tỷ lệ quan sát được trong mẫu với một tỷ lệ giả định hoặc đã biết trong tổng thể (\\(p_0\\))."), + p("$$H_0: p = p_0$$"), + p("$$H_a: p \\neq p_0 \\quad (\\text{hoặc } > p_0 \\text{ hoặc } < p_0\\text{)}$$"), + hr(), + h4("Công thức tính toán"), + + tags$b("1. Thống kê kiểm định (Z-statistic):"), + p("Giá trị thống kê Z được tính như sau:"), + p("$$ Z = \\frac{\\hat{p} - p_0}{\\sqrt{\\frac{p_0(1-p_0)}{n}}} $$"), + p("Trong đó \\(\\hat{p}\\) là tỷ lệ trong mẫu, \\(p_0\\) là tỷ lệ giả định, và \\(n\\) là cỡ mẫu."), + + tags$b("2. Effect Size (Cohen's h):"), + p("Để tính toán power, sự khác biệt giữa hai tỷ lệ được chuyển đổi bằng phép biến đổi arcsin để ổn định phương sai. Cohen's h được định nghĩa là:"), + p("$$ h = |2 \\arcsin(\\sqrt{p_1}) - 2 \\arcsin(\\sqrt{p_0})| $$"), + p("Quy ước: 0.2 (nhỏ), 0.5 (trung bình), 0.8 (lớn)."), + + hr(), + h4("Ví dụ ứng dụng trong Y tế công cộng"), + p(tags$b("Tình huống:")), + p("Một cơ quan y tế biết rằng tỷ lệ tiêm chủng vắc-xin cúm trong cộng đồng năm ngoái là 40% (\\(p_0\\)). Năm nay, họ thực hiện một chiến dịch truyền thông mới và muốn kiểm tra xem chiến dịch có hiệu quả hay không. Họ muốn có đủ power để phát hiện nếu tỷ lệ tiêm chủng tăng lên ít nhất là 50% (\\(p_1\\))."), + p(tags$b("Thiết kế nghiên cứu:")), + tags$ul( + tags$li("Kiểm định: Tỷ lệ một mẫu."), + tags$li("Giả thuyết không (H0): Tỷ lệ tiêm chủng vẫn là 40%."), + tags$li("Giả thuyết đối (Ha): Tỷ lệ tiêm chủng lớn hơn 40%.") + ), + p(tags$b("Tính toán cỡ mẫu:")), + p("Trước khi tiến hành khảo sát, họ cần biết cần khảo sát bao nhiêu người. Họ nhập các giá trị vào ứng dụng:"), + tags$ul( + tags$li("\\(p_0 = 0.40\\)"), + tags$li("\\(p_1 = 0.50\\)"), + tags$li("Loại kiểm định: Lớn hơn (Greater)"), + tags$li("Power = 0.8, Alpha = 0.05") + ), + p("Ứng dụng sẽ tự động tính effect size `h` và cho ra cỡ mẫu cần thiết để thực hiện nghiên cứu này.") + ) + ) + ) + ) +) + +# --- Logic của máy chủ (Server) --- +server <- function(input, output, session) { + + # Tính toán effect size h + h <- reactive({ + req(input$p0, input$p1) + abs(ES.h(input$p1, input$p0)) + }) + + # Hiển thị h ra giao diện + output$effect_size_display <- renderUI({ + withMathJax(paste0("$$ h = ", round(h(), 3), " $$")) + }) + + # --- PHẦN TÍNH TOÁN CHÍNH --- + main_results <- reactive({ + req(h(), input$sig_level, input$power, input$alternative) + + pwr_result <- tryCatch({ + pwr.p.test( + h = h(), + sig.level = input$sig_level, + power = input$power, + alternative = input$alternative + ) + }, error = function(e) NULL) + + if (is.null(pwr_result)) return(NULL) + + required_n <- ceiling(pwr_result$n) + + sample_sizes <- seq(10, required_n + 100, by = 2) + + power_data <- tryCatch({ + pwr.p.test( + n = sample_sizes, + h = h(), + sig.level = input$sig_level, + alternative = input$alternative + ) + }, error = function(e) NULL) + + if(is.null(power_data)) return(list(required_n = required_n, power_plot_data = NULL)) + + list( + required_n = required_n, + power_plot_data = data.frame(SampleSize = power_data$n, Power = power_data$power) + ) + }) + + output$sample_size_output <- renderUI({ + res <- main_results() + + if (is.null(res)) { + return(tags$div(class = "alert alert-danger", "Có lỗi xảy ra. Hãy chắc chắn p0 và p1 không quá gần nhau.")) + } + + tagList( + tags$p("Để phát hiện sự khác biệt giữa tỷ lệ", tags$b(input$p0), "và", tags$b(input$p1), "(effect size h =", tags$b(round(h(), 3)), ") với power là", tags$b(input$power), ", bạn cần một cỡ mẫu ước tính là:"), + tags$h3(style = "color: #007bff; text-align: center;", res$required_n) + ) + }) + + output$power_plot <- renderPlot({ + res <- main_results() + req(res$power_plot_data) + + ggplot(res$power_plot_data, aes(x = SampleSize, y = Power)) + + geom_line(color = "#007bff", size = 1.2) + + geom_hline(yintercept = input$power, linetype = "dashed", color = "red") + + geom_vline(xintercept = res$required_n, linetype = "dashed", color = "darkgreen") + + labs(title = "Power vs. Cỡ mẫu", x = "Cỡ mẫu (n)", y = "Power (1 - β)") + + scale_y_continuous(limits = c(0, 1)) + theme_minimal(base_size = 14) + }) + + # --- PHẦN PHÂN TÍCH EFFECT SIZE --- + effect_analysis_plot_data <- reactive({ + req(input$sig_level, input$power, input$alternative) + + effect_sizes <- seq(0.05, 1, by = 0.05) + + required_n_values <- sapply(effect_sizes, function(h_val) { + res <- tryCatch({ + pwr.p.test( + h = h_val, + sig.level = input$sig_level, + power = input$power, + alternative = input$alternative + )$n + }, error = function(e) NA) + ceiling(res) + }) + + data.frame(EffectSize = effect_sizes, RequiredN = required_n_values) + }) + + output$effect_analysis_plot <- renderPlot({ + plot_data <- effect_analysis_plot_data() + res <- main_results() + + p <- ggplot(plot_data, aes(x = EffectSize, y = RequiredN)) + + geom_line(color = "#28a745", size = 1.2) + + geom_point(color = "#28a745", size = 3, na.rm = TRUE) + + labs( + title = paste("Cỡ mẫu cần thiết vs. Effect Size (Power cố định =", input$power, ")"), + x = "Effect Size (h)", + y = "Cỡ mẫu cần thiết (n)" + ) + + theme_minimal(base_size = 14) + + if (!is.null(res) && !is.na(res$required_n)) { + p <- p + + geom_vline(xintercept = h(), linetype = "dotted", color = "blue", size = 1) + + geom_point(aes(x = h(), y = res$required_n), color = "blue", size = 5, shape = 18) + + annotate("text", x = h(), y = res$required_n, + label = paste("n =", res$required_n), vjust = -1.5, color = "blue", fontface = "bold") + } + + p + }) +} + +# Chạy ứng dụng Shiny +shinyApp(ui = ui, server = server) diff --git a/samplesize/2ProportionTest/app.R b/samplesize/2ProportionTest/app.R new file mode 100644 index 0000000..e662e86 --- /dev/null +++ b/samplesize/2ProportionTest/app.R @@ -0,0 +1,234 @@ +# Tải các thư viện cần thiết +library(shiny) +library(bslib) +library(ggplot2) +library(shinycssloaders) +library(pwr) + +# --- Giao diện người dùng (UI) --- +ui <- fluidPage( + theme = bs_theme(version = 5, bootswatch = "cerulean"), + withMathJax(), + + titlePanel("Tính toán Cỡ mẫu cho Kiểm định Tỷ lệ hai mẫu"), + + sidebarLayout( + sidebarPanel( + h4("Tham số đầu vào"), + + sliderInput("p1", + label = "Tỷ lệ kỳ vọng ở Nhóm 1 (p1):", + min = 0.01, max = 0.99, value = 0.65, step = 0.01), + + sliderInput("p2", + label = "Tỷ lệ kỳ vọng ở Nhóm 2 (p2):", + min = 0.01, max = 0.99, value = 0.5, step = 0.01), + + # Hiển thị effect size được tính toán + h5("Effect Size (Cohen's h):"), + uiOutput("effect_size_display"), + + selectInput("alternative", "Loại kiểm định:", + choices = c("Hai phía (Two-sided)" = "two.sided", + "Lớn hơn (Greater)" = "greater", + "Nhỏ hơn (Less)" = "less")), + + sliderInput("sig_level", + label = "Mức ý nghĩa (\\(\\alpha\\)):", + min = 0.01, max = 0.10, value = 0.05, step = 0.01), + + sliderInput("power", + label = "Power mong muốn (\\(1 - \\beta\\)):", + min = 0.50, max = 0.99, value = 0.80, step = 0.01), + + hr(), + helpText("Ứng dụng sử dụng hàm pwr.2p.test() từ gói 'pwr'.") + ), + + mainPanel( + tabsetPanel( + id = "results_tabs", + type = "pills", + + tabPanel( + "Kết quả & Diễn giải", + h4("Kết quả tính toán"), + withSpinner(uiOutput("sample_size_output"), type = 6, color = "#007bff") + ), + + tabPanel( + "Đồ thị Power vs. Cỡ mẫu", + h4("Mối quan hệ giữa Power và Cỡ mẫu"), + withSpinner(plotOutput("power_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Phân tích Effect Size", + h4("Mối quan hệ giữa Cỡ mẫu và Effect Size"), + withSpinner(plotOutput("effect_analysis_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Giả thuyết và Công thức (Helper)", + h4("Giả thuyết của Kiểm định Tỷ lệ hai mẫu"), + p("Kiểm định này so sánh tỷ lệ của hai nhóm độc lập (\\(p_1\\) và \\(p_2\\))."), + p("$$H_0: p_1 = p_2$$"), + p("$$H_a: p_1 \\neq p_2 \\quad (\\text{hoặc } > p_2 \\text{ hoặc } < p_2\\text{)}$$"), + hr(), + h4("Công thức tính toán"), + + tags$b("1. Thống kê kiểm định (Z-statistic):"), + p("Giá trị thống kê Z được tính như sau:"), + p("$$ Z = \\frac{(\\hat{p}_1 - \\hat{p}_2) - 0}{\\sqrt{\\hat{p}(1-\\hat{p})(\\frac{1}{n_1} + \\frac{1}{n_2})}} $$"), + p("Trong đó \\(\\hat{p}_1, \\hat{p}_2\\) là tỷ lệ trong hai mẫu, và \\(\\hat{p}\\) là tỷ lệ gộp."), + + tags$b("2. Effect Size (Cohen's h):"), + p("Sự khác biệt giữa hai tỷ lệ được chuyển đổi bằng phép biến đổi arcsin. Cohen's h được định nghĩa là:"), + p("$$ h = |2 \\arcsin(\\sqrt{p_1}) - 2 \\arcsin(\\sqrt{p_2})| $$"), + p("Quy ước: 0.2 (nhỏ), 0.5 (trung bình), 0.8 (lớn)."), + + hr(), + h4("Ví dụ ứng dụng trong Y tế công cộng"), + p(tags$b("Tình huống:")), + p("Một nhà nghiên cứu muốn so sánh hiệu quả của hai chiến dịch truyền thông khác nhau (A và B) nhằm khuyến khích người dân đi xét nghiệm sàng lọc ung thư. Họ chia ngẫu nhiên các cộng đồng thành hai nhóm, mỗi nhóm tiếp xúc với một chiến dịch."), + p(tags$b("Thiết kế nghiên cứu:")), + tags$ul( + tags$li("Nhóm 1: Tiếp xúc với chiến dịch A."), + tags$li("Nhóm 2: Tiếp xúc với chiến dịch B."), + tags$li("Kiểm định: Tỷ lệ hai mẫu, để so sánh tỷ lệ người dân đi xét nghiệm giữa hai nhóm.") + ), + p(tags$b("Tính toán cỡ mẫu:")), + p("Dựa trên các nghiên cứu trước, họ kỳ vọng tỷ lệ đi xét nghiệm ở nhóm B là 50% (\\(p_2\\)). Họ hy vọng chiến dịch A hiệu quả hơn, và muốn có đủ power để phát hiện nếu tỷ lệ ở nhóm A đạt 65% (\\(p_1\\))."), + p("Họ có thể nhập các giá trị này vào ứng dụng để tìm ra số người cần khảo sát cho mỗi nhóm chiến dịch.") + ) + ) + ) + ) +) + +# --- Logic của máy chủ (Server) --- +server <- function(input, output, session) { + + # Tính toán effect size h + h <- reactive({ + req(input$p1, input$p2) + abs(ES.h(input$p1, input$p2)) + }) + + # Hiển thị h ra giao diện + output$effect_size_display <- renderUI({ + withMathJax(paste0("$$ h = ", round(h(), 3), " $$")) + }) + + # --- PHẦN TÍNH TOÁN CHÍNH --- + main_results <- reactive({ + req(h(), input$sig_level, input$power, input$alternative) + + # Sử dụng pwr.2p.test + pwr_result <- tryCatch({ + pwr.2p.test( + h = h(), + sig.level = input$sig_level, + power = input$power, + alternative = input$alternative + ) + }, error = function(e) NULL) + + if (is.null(pwr_result)) return(NULL) + + required_n <- ceiling(pwr_result$n) + + sample_sizes <- seq(10, required_n + 100, by = 2) + + power_data <- tryCatch({ + pwr.2p.test( + n = sample_sizes, + h = h(), + sig.level = input$sig_level, + alternative = input$alternative + ) + }, error = function(e) NULL) + + if(is.null(power_data)) return(list(required_n = required_n, power_plot_data = NULL)) + + list( + required_n = required_n, + power_plot_data = data.frame(SampleSize = power_data$n, Power = power_data$power) + ) + }) + + output$sample_size_output <- renderUI({ + res <- main_results() + + if (is.null(res)) { + return(tags$div(class = "alert alert-danger", "Có lỗi xảy ra. Hãy chắc chắn p1 và p2 không quá gần nhau.")) + } + + tagList( + tags$p("Để phát hiện sự khác biệt giữa tỷ lệ", tags$b(input$p1), "(Nhóm 1) và", tags$b(input$p2), "(Nhóm 2) với power là", tags$b(input$power), ", bạn cần một cỡ mẫu ước tính là:"), + tags$h3(style = "color: #007bff; text-align: center;", paste(res$required_n, "cho mỗi nhóm")), + tags$p(style = "text-align: center; font-style: italic;", "Tổng cỡ mẫu là ", res$required_n * 2, ".") + ) + }) + + output$power_plot <- renderPlot({ + res <- main_results() + req(res$power_plot_data) + + ggplot(res$power_plot_data, aes(x = SampleSize, y = Power)) + + geom_line(color = "#007bff", size = 1.2) + + geom_hline(yintercept = input$power, linetype = "dashed", color = "red") + + geom_vline(xintercept = res$required_n, linetype = "dashed", color = "darkgreen") + + labs(title = "Power vs. Cỡ mẫu (cho mỗi nhóm)", x = "Cỡ mẫu cho mỗi nhóm (n)", y = "Power (1 - β)") + + scale_y_continuous(limits = c(0, 1)) + theme_minimal(base_size = 14) + }) + + # --- PHẦN PHÂN TÍCH EFFECT SIZE --- + effect_analysis_plot_data <- reactive({ + req(input$sig_level, input$power, input$alternative) + + effect_sizes <- seq(0.05, 1.2, by = 0.05) + + required_n_values <- sapply(effect_sizes, function(h_val) { + res <- tryCatch({ + pwr.2p.test( + h = h_val, + sig.level = input$sig_level, + power = input$power, + alternative = input$alternative + )$n + }, error = function(e) NA) + ceiling(res) + }) + + data.frame(EffectSize = effect_sizes, RequiredN = required_n_values) + }) + + output$effect_analysis_plot <- renderPlot({ + plot_data <- effect_analysis_plot_data() + res <- main_results() + + p <- ggplot(plot_data, aes(x = EffectSize, y = RequiredN)) + + geom_line(color = "#28a745", size = 1.2) + + geom_point(color = "#28a745", size = 3, na.rm = TRUE) + + labs( + title = paste("Cỡ mẫu cần thiết vs. Effect Size (Power cố định =", input$power, ")"), + x = "Effect Size (h)", + y = "Cỡ mẫu cần thiết cho mỗi nhóm (n)" + ) + + theme_minimal(base_size = 14) + + if (!is.null(res) && !is.na(res$required_n)) { + p <- p + + geom_vline(xintercept = h(), linetype = "dotted", color = "blue", size = 1) + + geom_point(aes(x = h(), y = res$required_n), color = "blue", size = 5, shape = 18) + + annotate("text", x = h(), y = res$required_n, + label = paste("n =", res$required_n), vjust = -1.5, color = "blue", fontface = "bold") + } + + p + }) +} + +# Chạy ứng dụng Shiny +shinyApp(ui = ui, server = server) diff --git a/samplesize/F-test/app.R b/samplesize/F-test/app.R new file mode 100644 index 0000000..c5c45ee --- /dev/null +++ b/samplesize/F-test/app.R @@ -0,0 +1,215 @@ +# Tải các thư viện cần thiết +library(shiny) +library(bslib) +library(ggplot2) +library(shinycssloaders) + +# --- Giao diện người dùng (UI) --- +ui <- fluidPage( + theme = bs_theme(version = 5, bootswatch = "cerulean"), + withMathJax(), + + titlePanel("Tính toán Cỡ mẫu cho Kiểm định F (so sánh 2 phương sai)"), + + sidebarLayout( + sidebarPanel( + h4("Tham số đầu vào"), + + numericInput("v_ratio", + label = "Tỷ lệ phương sai (\\(\\sigma_1^2 / \\sigma_2^2\\)):", + value = 2, min = 1.1, step = 0.1), + helpText("Đây là effect size. Giá trị 2 có nghĩa là phương sai của nhóm 1 được giả định lớn gấp đôi phương sai của nhóm 2."), + + selectInput("alternative", "Loại kiểm định:", + choices = c("Hai phía (Two-sided)" = "two.sided", + "Nhỏ hơn (Less)" = "less", + "Lớn hơn (Greater)" = "greater")), + + sliderInput("sig_level", + label = "Mức ý nghĩa (\\(\\alpha\\)):", + min = 0.01, max = 0.10, value = 0.05, step = 0.01), + + sliderInput("power", + label = "Power mong muốn (\\(1 - \\beta\\)):", + min = 0.50, max = 0.99, value = 0.80, step = 0.01), + + hr(), + helpText("Kết quả và đồ thị sẽ tự động cập nhật ngay lập tức.") + ), + + mainPanel( + tabsetPanel( + id = "results_tabs", + type = "pills", + + tabPanel( + "Kết quả & Diễn giải", + h4("Kết quả tính toán"), + withSpinner(uiOutput("sample_size_output"), type = 6, color = "#007bff") + ), + + tabPanel( + "Đồ thị Power vs. Cỡ mẫu", + h4("Mối quan hệ giữa Power và Cỡ mẫu"), + withSpinner(plotOutput("power_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Phân tích Effect Size", + h4("Mối quan hệ giữa Cỡ mẫu và Effect Size"), + p("Đồ thị này cho thấy cỡ mẫu cần thiết thay đổi như thế nào khi mức độ khác biệt về phương sai thay đổi."), + withSpinner(plotOutput("effect_analysis_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Giả thuyết và Công thức (Helper)", + h4("Giả thuyết của Kiểm định F"), + p("Kiểm định F được sử dụng để so sánh phương sai của hai mẫu độc lập."), + p("$$H_0: \\sigma_1^2 = \\sigma_2^2 \\quad (\\text{tỷ lệ phương sai bằng 1})$$"), + p("$$H_a: \\sigma_1^2 \\neq \\sigma_2^2 \\quad (\\text{hoặc } > \\text{ hoặc } <\\text{)}$$"), + tags$div(class = "alert alert-warning", + tags$b("Giả định quan trọng:"), " Kiểm định F rất nhạy với giả định rằng dữ liệu trong cả hai nhóm phải tuân theo phân phối chuẩn. Nếu giả định này bị vi phạm, hãy cân nhắc sử dụng kiểm định Levene hoặc Brown-Forsythe."), + hr(), + h4("Công thức tính toán"), + + tags$b("1. Thống kê kiểm định (Test Statistic):"), + p("Giá trị thống kê F được tính bằng tỷ lệ của hai phương sai mẫu:"), + p("$$ F = \\frac{s_1^2}{s_2^2} $$"), + p("Trong đó \\(s_1^2\\) và \\(s_2^2\\) là phương sai của mẫu 1 và mẫu 2, với cỡ mẫu tương ứng là \\(n_1\\) và \\(n_2\\)."), + + tags$b("2. Phân phối của thống kê kiểm định:"), + p("Dưới giả thuyết \\(H_0\\) (khi \\(\\sigma_1^2 = \\sigma_2^2\\)), thống kê F tuân theo phân phối F với bậc tự do \\(df_1 = n_1 - 1\\) và \\(df_2 = n_2 - 1\\)."), + p("$$ F \\sim F(n_1 - 1, n_2 - 1) $$"), + p("Dưới giả thuyết \\(H_a\\) (khi \\(\\frac{\\sigma_1^2}{\\sigma_2^2} = \\lambda > 1\\)), giá trị \\(F/\\lambda\\) tuân theo phân phối F nói trên. Điều này rất quan trọng để tính power."), + + tags$b("3. Tính toán Power:"), + p("Power là xác suất bác bỏ \\(H_0\\) một cách chính xác khi \\(H_a\\) là đúng. Công thức phụ thuộc vào loại kiểm định:"), + + tags$i("a) Kiểm định hai phía (Two-sided):"), + p("$$ \\text{Power} = P(F < F_{crit, lower} | H_a) + P(F > F_{crit, upper} | H_a) $$"), + p("Trong đó \\( F_{crit, lower} = F_{\\alpha/2, n_1-1, n_2-1} \\) và \\( F_{crit, upper} = F_{1-\\alpha/2, n_1-1, n_2-1} \\)."), + + tags$i("b) Kiểm định lớn hơn (Greater):"), + p("$$ \\text{Power} = P(F > F_{crit, upper} | H_a) $$"), + p("Trong đó \\( F_{crit, upper} = F_{1-\\alpha, n_1-1, n_2-1} \\)."), + + tags$i("c) Kiểm định nhỏ hơn (Less):"), + p("$$ \\text{Power} = P(F < F_{crit, lower} | H_a) $$"), + p("Trong đó \\( F_{crit, lower} = F_{\\alpha, n_1-1, n_2-1} \\).") + ) + ) + ) + ) +) + +# --- Logic của máy chủ (Server) --- +server <- function(input, output, session) { + + # --- HÀM TÍNH POWER LÕI --- + calculate_f_power <- function(n1, n2, v_ratio, sig_level, alternative) { + if (alternative == "two.sided") { + alpha <- sig_level / 2 + crit_lower <- qf(alpha, n1 - 1, n2 - 1) + crit_upper <- qf(1 - alpha, n1 - 1, n2 - 1) + power <- pf(crit_lower / v_ratio, n1 - 1, n2 - 1) + (1 - pf(crit_upper / v_ratio, n1 - 1, n2 - 1)) + } else if (alternative == "greater") { + crit_upper <- qf(1 - sig_level, n1 - 1, n2 - 1) + power <- 1 - pf(crit_upper / v_ratio, n1 - 1, n2 - 1) + } else { # less + crit_lower <- qf(sig_level, n1 - 1, n2 - 1) + power <- pf(crit_lower / v_ratio, n1 - 1, n2 - 1) + } + return(power) + } + + # --- PHẦN TÍNH TOÁN CHÍNH --- + # Hoàn toàn reactive, không cần debounce + main_results <- reactive({ + req(input$v_ratio, input$sig_level, input$power, input$alternative) + + # Tìm cỡ mẫu cần thiết + n <- 5 # Bắt đầu từ n nhỏ + power_val <- 0 + while (power_val < input$power && n <= 2000) { + n <- n + 1 + power_val <- calculate_f_power(n, n, input$v_ratio, input$sig_level, input$alternative) + } + required_n <- ifelse(n > 2000, NA, n) + + # Tạo dữ liệu cho đồ thị Power + # Đảm bảo dãy sample_sizes hợp lệ ngay cả khi không tìm thấy required_n + upper_bound <- if (!is.na(required_n)) required_n + 50 else 200 + sample_sizes <- seq(5, upper_bound, by = 1) + + powers <- sapply(sample_sizes, function(s_n) { + calculate_f_power(s_n, s_n, input$v_ratio, input$sig_level, input$alternative) + }) + + list( + required_n = required_n, + power_plot_data = data.frame(SampleSize = sample_sizes, Power = powers) + ) + }) + + output$sample_size_output <- renderUI({ + res <- main_results() + + if (!is.na(res$required_n)) { + tagList( + tags$p("Để phát hiện sự khác biệt về phương sai (tỷ lệ =", tags$b(input$v_ratio), ") với power là", tags$b(input$power), "và mức ý nghĩa", tags$b(input$sig_level), ", bạn cần một cỡ mẫu ước tính là:"), + tags$h3(style = "color: #007bff; text-align: center;", paste(res$required_n, "cho mỗi nhóm")), + tags$p(style = "text-align: center; font-style: italic;", "Tổng cỡ mẫu là ", res$required_n * 2, ".") + ) + } else { + tags$div(class = "alert alert-warning", "Không thể đạt được power mong muốn với cỡ mẫu tối đa (2000). Vui lòng xem xét lại các tham số.") + } + }) + + output$power_plot <- renderPlot({ + res <- main_results() + ggplot(res$power_plot_data, aes(x = SampleSize, y = Power)) + + geom_line(color = "#007bff", size = 1.2) + + geom_hline(yintercept = input$power, linetype = "dashed", color = "red") + + { + if(!is.na(res$required_n)) { + geom_vline(xintercept = res$required_n, linetype = "dashed", color = "darkgreen") + } + } + + labs(title = "Power vs. Cỡ mẫu (cho mỗi nhóm)", x = "Cỡ mẫu cho mỗi nhóm (n)", y = "Power (1 - β)") + + scale_y_continuous(limits = c(0, 1)) + theme_minimal(base_size = 14) + }) + + # --- PHẦN PHÂN TÍCH EFFECT SIZE --- + effect_analysis_plot_data <- reactive({ + req(input$sig_level, input$power, input$alternative) + + effect_sizes <- seq(1.2, 3.0, by = 0.1) + required_n_values <- sapply(effect_sizes, function(ratio) { + n <- 5 + power_val <- 0 + while (power_val < input$power && n <= 2000) { + n <- n + 1 + power_val <- calculate_f_power(n, n, ratio, input$sig_level, input$alternative) + } + ifelse(n > 2000, NA, n) + }) + + data.frame(EffectSize = effect_sizes, RequiredN = required_n_values) + }) + + output$effect_analysis_plot <- renderPlot({ + plot_data <- effect_analysis_plot_data() + ggplot(plot_data, aes(x = EffectSize, y = RequiredN)) + + geom_line(color = "#28a745", size = 1.2) + + geom_point(color = "#28a745", size = 3, na.rm = TRUE) + + labs( + title = paste("Cỡ mẫu cần thiết vs. Effect Size (Power cố định =", input$power, ")"), + x = "Tỷ lệ Phương sai (Effect Size)", + y = "Cỡ mẫu cần thiết cho mỗi nhóm (n)" + ) + + theme_minimal(base_size = 14) + }) +} + +# Chạy ứng dụng Shiny +shinyApp(ui = ui, server = server) diff --git a/samplesize/Kruskal-Wallis-Test/app.R b/samplesize/Kruskal-Wallis-Test/app.R new file mode 100644 index 0000000..943917e --- /dev/null +++ b/samplesize/Kruskal-Wallis-Test/app.R @@ -0,0 +1,207 @@ +# Tải các thư viện cần thiết +library(shiny) +library(bslib) +library(ggplot2) +library(shinycssloaders) + +# --- Giao diện người dùng (UI) --- +ui <- fluidPage( + theme = bs_theme(version = 5, bootswatch = "cerulean"), + withMathJax(), + + titlePanel("Tính toán Cỡ mẫu cho Kiểm định Kruskal-Wallis"), + + sidebarLayout( + sidebarPanel( + h4("Tham số đầu vào"), + + numericInput("k", "Số lượng nhóm so sánh (k):", value = 3, min = 2), + + selectInput("dist_shape", "Hình dạng phân phối của dữ liệu:", + choices = c("Lệch phải (Log-Normal)" = "lnorm", + "Đối xứng, đuôi dày (t, df=5)" = "t_dist", + "Phân phối đều (Uniform)" = "unif")), + + numericInput("effect_size", + label = "Effect Size (Median Shift / SD):", + value = 0.5, min = 0.1, step = 0.1), + helpText("Đây là độ lớn của sự dịch chuyển của trung vị (median) của một nhóm so với các nhóm còn lại, được chuẩn hóa bằng độ lệch chuẩn."), + + sliderInput("sig_level", + label = "Mức ý nghĩa (\\(\\alpha\\)):", + min = 0.01, max = 0.10, value = 0.05, step = 0.01), + + sliderInput("power", + label = "Power mong muốn (\\(1 - \\beta\\)):", + min = 0.50, max = 0.99, value = 0.80, step = 0.01), + + numericInput("n_sims", + label = "Số lần lặp mô phỏng:", + value = 500, min = 100, max = 5000), + + hr(), + actionButton("calculate", "Bắt đầu tính toán", class = "btn-primary"), + helpText("Vì tính toán dựa trên mô phỏng, quá trình có thể mất vài giây.") + ), + + mainPanel( + tabsetPanel( + id = "results_tabs", + type = "pills", + + tabPanel( + "Kết quả & Diễn giải", + h4("Kết quả tính toán"), + withSpinner(uiOutput("sample_size_output"), type = 6, color = "#007bff") + ), + + tabPanel( + "Đồ thị Power vs. Cỡ mẫu", + h4("Mối quan hệ giữa Power và Cỡ mẫu"), + withSpinner(plotOutput("power_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Phân tích Effect Size", + h4("Mối quan hệ giữa Cỡ mẫu và Effect Size"), + withSpinner(plotOutput("effect_analysis_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Giả thuyết và Công thức (Helper)", + h4("Giả thuyết của Kiểm định Kruskal-Wallis"), + p("Đây là kiểm định phi tham số thay thế cho One-Way ANOVA. Nó kiểm tra xem liệu có sự khác biệt về phân phối giữa ba hay nhiều nhóm độc lập hay không."), + p("$$H_0: \\text{Phân phối của tất cả các nhóm là như nhau}$$"), + p("$$H_a: \\text{Phân phối của ít nhất một nhóm khác biệt so với các nhóm còn lại}$$"), + + tags$div(class = "alert alert-info", + tags$b("Khi nào sử dụng?"), + p("Sử dụng kiểm định này thay cho One-Way ANOVA khi giả định về phân phối chuẩn của dữ liệu trong các nhóm không được đáp ứng, hoặc khi dữ liệu ở dạng thứ hạng (ordinal).") + ), + hr(), + + h4("Phương pháp tính toán"), + p("Ứng dụng sử dụng phương pháp mô phỏng Monte Carlo để ước tính cỡ mẫu. Quá trình này bao gồm việc tạo ra hàng nghìn bộ dữ liệu giả định (trong đó một nhóm có phân phối bị dịch chuyển đi), áp dụng kiểm định cho từng bộ, và đếm tỷ lệ các kiểm định phát hiện ra hiệu ứng."), + + hr(), + h4("Ví dụ ứng dụng trong Y tế công cộng"), + p(tags$b("Tình huống:")), + p("Một nhà nghiên cứu muốn so sánh mức độ hài lòng của bệnh nhân (đo bằng thang điểm từ 1 đến 10) đối với ba bệnh viện khác nhau (A, B, C)."), + p(tags$b("Tại sao dùng Kruskal-Wallis?")), + p("Dữ liệu về mức độ hài lòng thường không tuân theo phân phối chuẩn (nó là dữ liệu thứ hạng và bị chặn ở hai đầu). Do đó, so sánh phân phối dựa trên hạng (rank) sẽ phù hợp hơn là so sánh trung bình."), + + p(tags$b("Tính toán cỡ mẫu:")), + p("Nhà nghiên cứu cần xác định số lượng bệnh nhân cần khảo sát ở mỗi bệnh viện. Họ kỳ vọng một sự khác biệt có effect size khoảng 0.4. Họ có thể nhập các giá trị này vào ứng dụng để tìm ra cỡ mẫu cần thiết cho nghiên cứu của mình.") + ) + ) + ) + ) +) + +# --- Logic của máy chủ (Server) --- +server <- function(input, output, session) { + + simulation_results <- eventReactive(input$calculate, { + + inputs <- list( + k = input$k, + dist_shape = input$dist_shape, + effect_size = input$effect_size, + sig_level = input$sig_level, + target_power = input$power, + n_sims = input$n_sims + ) + + sample_sizes <- seq(10, 500, by = 5) + powers <- numeric(length(sample_sizes)) + + withProgress(message = 'Đang chạy mô phỏng...', value = 0, { + + for (i in 1:length(sample_sizes)) { + n <- sample_sizes[i] + + p_values <- replicate(inputs$n_sims, { + + # Tạo dữ liệu cho k nhóm + data_list <- lapply(1:inputs$k, function(group_idx) { + # Nhóm đầu tiên có phân phối bị dịch chuyển, các nhóm còn lại thì không + shift <- if (group_idx == 1) inputs$effect_size else 0 + + if (inputs$dist_shape == "t_dist") { + return(rt(n, df = 5) + shift) + } else if (inputs$dist_shape == "lnorm") { + return(rlnorm(n, meanlog = shift, sdlog = 1)) + } else { # unif + return(runif(n, 0, 1) + shift) + } + }) + + # Chuyển thành dataframe phù hợp cho kruskal.test + df <- data.frame( + value = unlist(data_list), + group = factor(rep(1:inputs$k, each = n)) + ) + + # Thực hiện kiểm định Kruskal-Wallis + kruskal.test(value ~ group, data = df)$p.value + }) + + powers[i] <- mean(p_values < inputs$sig_level, na.rm = TRUE) + + incProgress(1/length(sample_sizes), detail = paste("Cỡ mẫu n =", n)) + + if (powers[i] >= inputs$target_power) { + sample_sizes <- sample_sizes[1:i] + powers <- powers[1:i] + break + } + } + }) + + list( + sample_sizes = sample_sizes, + powers = powers, + inputs = inputs + ) + }) + + output$sample_size_output <- renderUI({ + res <- simulation_results() + + required_n_index <- which(res$powers >= res$inputs$target_power)[1] + + if (!is.na(required_n_index)) { + required_n <- res$sample_sizes[required_n_index] + tagList( + tags$p("Để phát hiện một effect size là", tags$b(res$inputs$effect_size), "giữa", tags$b(res$inputs$k), "nhóm với power là", tags$b(res$inputs$target_power), ", bạn cần một cỡ mẫu ước tính là:"), + tags$h3(style = "color: #007bff; text-align: center;", paste(required_n, "cho mỗi nhóm")), + tags$p(style = "text-align: center; font-style: italic;", "Tổng cỡ mẫu là ", required_n * res$inputs$k, ".") + ) + } else { + tags$div(class = "alert alert-warning", "Không đạt được power mong muốn trong khoảng cỡ mẫu đã thử (tối đa 500).") + } + }) + + output$power_plot <- renderPlot({ + res <- simulation_results() + plot_data <- data.frame(SampleSize = res$sample_sizes, Power = res$powers) + + ggplot(plot_data, aes(x = SampleSize, y = Power)) + + geom_line(color = "#007bff", size = 1.2) + + geom_point(color = "#007bff", size = 3) + + geom_hline(yintercept = res$inputs$target_power, linetype = "dashed", color = "red") + + labs(title = "Power vs. Cỡ mẫu (cho mỗi nhóm)", x = "Cỡ mẫu cho mỗi nhóm (n)", y = "Power thực nghiệm (1 - β)") + + scale_y_continuous(limits = c(0, 1)) + theme_minimal(base_size = 14) + }) + + output$effect_analysis_plot <- renderPlot({ + ggplot() + + labs(title = "Phân tích này không được thực hiện tự động do thời gian tính toán lâu.", + subtitle = "Vui lòng chạy lại với các giá trị Effect Size khác nhau để so sánh.") + + theme_minimal() + }) + +} + +# Chạy ứng dụng Shiny +shinyApp(ui = ui, server = server) diff --git a/samplesize/LevenesTest/app.R b/samplesize/LevenesTest/app.R new file mode 100644 index 0000000..cd3c001 --- /dev/null +++ b/samplesize/LevenesTest/app.R @@ -0,0 +1,251 @@ +# Tải các thư viện cần thiết +library(shiny) +library(bslib) +library(ggplot2) +library(shinycssloaders) +library(car) # Gói chứa hàm leveneTest + +# --- Giao diện người dùng (UI) --- +ui <- fluidPage( + theme = bs_theme(version = 5, bootswatch = "cerulean"), + withMathJax(), + + titlePanel("Tính toán Cỡ mẫu cho Kiểm định Levene"), + + sidebarLayout( + sidebarPanel( + h4("Tham số đầu vào"), + + numericInput("k_groups", + label = "Số lượng nhóm (k):", + value = 3, min = 2, max = 10), + + numericInput("sd_ratio", + label = "Tỷ lệ độ lệch chuẩn (Effect size):", + value = 1.5, min = 1.1, step = 0.1), + helpText("Đây là tỷ lệ giữa độ lệch chuẩn của nhóm 'khác biệt' so với các nhóm còn lại (có độ lệch chuẩn là 1). Giá trị càng lớn, sự khác biệt càng rõ rệt."), + + sliderInput("sig_level", + label = "Mức ý nghĩa (\\(\\alpha\\)):", + min = 0.01, max = 0.10, value = 0.05, step = 0.01), + + sliderInput("power", + label = "Power mong muốn (\\(1 - \\beta\\)):", + min = 0.50, max = 0.99, value = 0.80, step = 0.01), + + numericInput("n_sims", + label = "Số lần lặp mô phỏng:", + value = 500, min = 100, max = 5000), + + hr(), + helpText("Kết quả chính và đồ thị Power sẽ tự động cập nhật sau 1 giây. Chuyển sang các tab khác để chạy phân tích sâu hơn.") + ), + + mainPanel( + tabsetPanel( + id = "results_tabs", + type = "pills", + + tabPanel( + "Kết quả & Diễn giải", + h4("Kết quả ước tính"), + withSpinner(uiOutput("sample_size_output"), type = 6, color = "#007bff") + ), + + tabPanel( + "Đồ thị Power vs. Cỡ mẫu", + h4("Mối quan hệ giữa Power và Cỡ mẫu"), + withSpinner(plotOutput("power_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Phân tích Effect Size", + h4("Mối quan hệ giữa Cỡ mẫu và Effect Size"), + p("Phân tích này cho thấy cỡ mẫu cần thiết thay đổi như thế nào khi mức độ khác biệt về phương sai thay đổi (với power và alpha được giữ cố định)."), + actionButton("run_effect_analysis", "Chạy phân tích Effect Size", class = "btn-success mb-3"), + withSpinner(plotOutput("effect_analysis_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Giả thuyết và Phương pháp", + h4("Giả thuyết của Kiểm định Levene"), + p("Kiểm định Levene được sử dụng để kiểm tra giả định về tính đồng nhất của phương sai (homogeneity of variance) giữa hai hay nhiều nhóm."), + p("$$H_0: \\sigma_1^2 = \\sigma_2^2 = \\dots = \\sigma_k^2$$"), + p("$$H_a: \\text{Có ít nhất một cặp phương sai không bằng nhau}$$"), + hr(), + h4("Phương pháp tính toán"), + p("Ứng dụng sử dụng phương pháp mô phỏng Monte Carlo để ước tính cỡ mẫu:"), + tags$ol( + tags$li("Với mỗi cỡ mẫu \\(n\\) (cho mỗi nhóm), chúng tôi tạo ra một số lượng lớn các bộ dữ liệu (ví dụ 500 bộ)."), + tags$li("Trong mỗi bộ dữ liệu, dữ liệu cho \\(k-1\\) nhóm được tạo từ phân phối chuẩn với phương sai bằng 1, và một nhóm được tạo với phương sai bằng (effect size)². "), + tags$li("Áp dụng kiểm định Levene cho từng bộ dữ liệu và ghi lại p-value."), + tags$li("Power thực nghiệm được tính bằng tỷ lệ các kiểm định có p-value nhỏ hơn mức ý nghĩa \\(\\alpha\\)."), + tags$li("Cỡ mẫu cần thiết là cỡ mẫu nhỏ nhất đạt được power mong muốn.") + ) + ) + ) + ) + ) +) + +# --- Logic của máy chủ (Server) --- +server <- function(input, output, session) { + + # --- HÀM MÔ PHỎNG LÕI --- + # Hàm này tìm cỡ mẫu cần thiết cho một bộ tham số cụ thể + find_required_n <- function(target_power, sig_level, k_groups, sd_ratio, n_sims, progress = NULL) { + sample_sizes <- seq(10, 500, by = 5) + required_n <- NA + + for (i in 1:length(sample_sizes)) { + n <- sample_sizes[i] + + p_values <- replicate(n_sims, { + # Tạo dữ liệu cho k nhóm + data_list <- lapply(1:k_groups, function(group_idx) { + # Nhóm đầu tiên có sd khác biệt, các nhóm còn lại có sd = 1 + current_sd <- if (group_idx == 1) sd_ratio else 1 + rnorm(n, mean = 0, sd = current_sd) + }) + + # Chuyển thành dataframe phù hợp cho leveneTest + df <- data.frame( + value = unlist(data_list), + group = factor(rep(1:k_groups, each = n)) + ) + + # Thực hiện kiểm định Levene + leveneTest(value ~ group, data = df)$`Pr(>F)`[1] + }) + + current_power <- mean(p_values < sig_level, na.rm = TRUE) + + if (!is.null(progress)) { + progress$inc(1/length(sample_sizes), detail = paste("Cỡ mẫu n =", n, "/ Power =", round(current_power, 2))) + } + + if (current_power >= target_power) { + required_n <- n + break + } + } + return(required_n) + } + + # --- PHẦN TÍNH TOÁN CHÍNH (TỰ ĐỘNG) --- + reactive_inputs <- reactive({ + list( + sig_level = input$sig_level, + power = input$power, + k_groups = input$k_groups, + sd_ratio = input$sd_ratio, + n_sims = input$n_sims + ) + }) + + debounced_inputs <- debounce(reactive_inputs, 1000) + + simulation_results <- reactive({ + inputs <- debounced_inputs() + + sample_sizes <- seq(10, 500, by = 5) + powers <- numeric(length(sample_sizes)) + + withProgress(message = 'Đang chạy mô phỏng chính...', value = 0, { + for (i in 1:length(sample_sizes)) { + n <- sample_sizes[i] + p_values <- replicate(inputs$n_sims, { + data_list <- lapply(1:inputs$k_groups, function(j) { + current_sd <- if (j == 1) inputs$sd_ratio else 1 + rnorm(n, mean = 0, sd = current_sd) + }) + df <- data.frame(value = unlist(data_list), group = factor(rep(1:inputs$k_groups, each = n))) + leveneTest(value ~ group, data = df)$`Pr(>F)`[1] + }) + powers[i] <- mean(p_values < inputs$sig_level, na.rm = TRUE) + incProgress(1/length(sample_sizes), detail = paste("Cỡ mẫu n =", n)) + if (powers[i] >= inputs$power) { + sample_sizes <- sample_sizes[1:i] + powers <- powers[1:i] + break + } + } + }) + + list( + sample_sizes = sample_sizes, + powers = powers, + inputs = inputs + ) + }) + + output$sample_size_output <- renderUI({ + res <- simulation_results() + first_index_above_power <- which(res$powers >= res$inputs$power)[1] + + if (!is.na(first_index_above_power)) { + required_n <- res$sample_sizes[first_index_above_power] + tagList( + tags$p("Để phát hiện sự khác biệt về phương sai (tỷ lệ SD =", tags$b(res$inputs$sd_ratio), ") giữa", tags$b(res$inputs$k_groups), "nhóm với power là", tags$b(res$inputs$power), "và mức ý nghĩa", tags$b(res$inputs$sig_level), ", bạn cần một cỡ mẫu ước tính là:"), + tags$h3(style = "color: #007bff; text-align: center;", paste(required_n, "cho mỗi nhóm")), + tags$p(style = "text-align: center; font-style: italic;", "Tổng cỡ mẫu là ", required_n * res$inputs$k_groups, ".") + ) + } else { + tags$div(class = "alert alert-warning", "Không đạt được power mong muốn trong khoảng cỡ mẫu đã thử (tối đa 500). Hãy thử tăng effect size hoặc giảm power mong muốn.") + } + }) + + output$power_plot <- renderPlot({ + res <- simulation_results() + plot_data <- data.frame(SampleSize = res$sample_sizes, Power = res$powers) + ggplot(plot_data, aes(x = SampleSize, y = Power)) + + geom_line(color = "#007bff", size = 1.2) + geom_point(color = "#007bff", size = 3) + + geom_hline(yintercept = res$inputs$power, linetype = "dashed", color = "red") + + annotate("text", x = max(res$sample_sizes) * 0.1, y = res$inputs$power + 0.04, label = paste("Power mục tiêu =", res$inputs$power), color = "red") + + labs(title = paste("Power vs. Cỡ mẫu (cho mỗi nhóm)"), x = "Cỡ mẫu cho mỗi nhóm (n)", y = "Power thực nghiệm (1 - β)") + + scale_y_continuous(limits = c(0, 1)) + theme_minimal(base_size = 14) + }) + + # --- PHẦN PHÂN TÍCH EFFECT SIZE --- + effect_analysis_results <- eventReactive(input$run_effect_analysis, { + inputs <- isolate(reactive_inputs()) # Lấy giá trị hiện tại, không cần reactive + + effect_sizes <- seq(1.2, 2.5, by = 0.1) + required_n_values <- numeric(length(effect_sizes)) + + withProgress(message = 'Đang chạy phân tích Effect Size...', value = 0, { + for (i in 1:length(effect_sizes)) { + current_sd_ratio <- effect_sizes[i] + # Sử dụng hàm lõi để tìm cỡ mẫu + required_n_values[i] <- find_required_n( + target_power = inputs$power, + sig_level = inputs$sig_level, + k_groups = inputs$k_groups, + sd_ratio = current_sd_ratio, + n_sims = inputs$n_sims, + progress = NULL # Không cần progress lồng nhau + ) + incProgress(1/length(effect_sizes), detail = paste("Tỷ lệ SD =", current_sd_ratio)) + } + }) + + data.frame(EffectSize = effect_sizes, RequiredN = required_n_values) + }) + + output$effect_analysis_plot <- renderPlot({ + plot_data <- effect_analysis_results() + + ggplot(plot_data, aes(x = EffectSize, y = RequiredN)) + + geom_line(color = "#28a745", size = 1.2) + + geom_point(color = "#28a745", size = 3, na.rm = TRUE) + + labs( + title = paste("Cỡ mẫu cần thiết vs. Effect Size (Power cố định =", isolate(input$power), ")"), + x = "Tỷ lệ Độ lệch chuẩn (Effect Size)", + y = "Cỡ mẫu cần thiết cho mỗi nhóm (n)" + ) + + theme_minimal(base_size = 14) + }) +} + +# Chạy ứng dụng Shiny +shinyApp(ui = ui, server = server) diff --git a/samplesize/MannWhitneyUtest/app.R b/samplesize/MannWhitneyUtest/app.R new file mode 100644 index 0000000..d26ff75 --- /dev/null +++ b/samplesize/MannWhitneyUtest/app.R @@ -0,0 +1,206 @@ +# Tải các thư viện cần thiết +library(shiny) +library(bslib) +library(ggplot2) +library(shinycssloaders) + +# --- Giao diện người dùng (UI) --- +ui <- fluidPage( + theme = bs_theme(version = 5, bootswatch = "cerulean"), + withMathJax(), + + titlePanel("Tính toán Cỡ mẫu cho Kiểm định Mann-Whitney U"), + + sidebarLayout( + sidebarPanel( + h4("Tham số đầu vào"), + + selectInput("dist_shape", "Hình dạng phân phối của dữ liệu:", + choices = c("Lệch phải (Log-Normal)" = "lnorm", + "Đối xứng, đuôi dày (t, df=5)" = "t_dist", + "Phân phối đều (Uniform)" = "unif")), + + numericInput("effect_size", + label = "Effect Size (Location Shift / SD):", + value = 0.5, min = 0.1, step = 0.1), + helpText("Đây là độ lớn của sự dịch chuyển vị trí (ví dụ: trung vị) của nhóm 1 so với nhóm 2, được chuẩn hóa bằng độ lệch chuẩn."), + + selectInput("alternative", "Loại kiểm định:", + choices = c("Hai phía (Two-sided)" = "two.sided", + "Lớn hơn (Greater)" = "greater", + "Nhỏ hơn (Less)" = "less")), + + sliderInput("sig_level", + label = "Mức ý nghĩa (\\(\\alpha\\)):", + min = 0.01, max = 0.10, value = 0.05, step = 0.01), + + sliderInput("power", + label = "Power mong muốn (\\(1 - \\beta\\)):", + min = 0.50, max = 0.99, value = 0.80, step = 0.01), + + numericInput("n_sims", + label = "Số lần lặp mô phỏng:", + value = 500, min = 100, max = 5000), + + hr(), + actionButton("calculate", "Bắt đầu tính toán", class = "btn-primary"), + helpText("Vì tính toán dựa trên mô phỏng, quá trình có thể mất vài giây.") + ), + + mainPanel( + tabsetPanel( + id = "results_tabs", + type = "pills", + + tabPanel( + "Kết quả & Diễn giải", + h4("Kết quả tính toán"), + withSpinner(uiOutput("sample_size_output"), type = 6, color = "#007bff") + ), + + tabPanel( + "Đồ thị Power vs. Cỡ mẫu", + h4("Mối quan hệ giữa Power và Cỡ mẫu"), + withSpinner(plotOutput("power_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Phân tích Effect Size", + h4("Mối quan hệ giữa Cỡ mẫu và Effect Size"), + withSpinner(plotOutput("effect_analysis_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Giả thuyết và Công thức (Helper)", + h4("Giả thuyết của Kiểm định Mann-Whitney U"), + p("Đây là kiểm định phi tham số thay thế cho kiểm định t hai mẫu độc lập. Nó kiểm tra xem liệu một quan sát được chọn ngẫu nhiên từ nhóm 1 có khả năng lớn hơn (hoặc nhỏ hơn) một quan sát được chọn ngẫu nhiên từ nhóm 2 hay không."), + p("$$H_0: P(X > Y) = 0.5$$"), + p("$$H_a: P(X > Y) \\neq 0.5 \\quad (\\text{hoặc } > 0.5 \\text{ hoặc } < 0.5\\text{)}$$"), + p("Một cách diễn giải khác là \\(H_0\\) cho rằng phân phối của hai nhóm là như nhau."), + + tags$div(class = "alert alert-info", + tags$b("Khi nào sử dụng?"), + p("Sử dụng kiểm định này thay cho kiểm định t hai mẫu khi giả định về phân phối chuẩn của dữ liệu trong các nhóm không được đáp ứng, hoặc khi dữ liệu ở dạng thứ hạng (ordinal).") + ), + hr(), + + h4("Phương pháp tính toán"), + p("Ứng dụng sử dụng phương pháp mô phỏng Monte Carlo để ước tính cỡ mẫu. Quá trình này bao gồm việc tạo ra hàng nghìn bộ dữ liệu giả định (trong đó một nhóm có phân phối bị dịch chuyển đi), áp dụng kiểm định cho từng bộ, và đếm tỷ lệ các kiểm định phát hiện ra hiệu ứng."), + + hr(), + h4("Ví dụ ứng dụng trong Y tế công cộng"), + p(tags$b("Tình huống:")), + p("Một nhà nghiên cứu muốn so sánh hiệu quả của hai phương pháp tư vấn cai thuốc lá khác nhau (A và B). Họ đo lường số điếu thuốc trung bình mà bệnh nhân hút mỗi ngày sau một tháng can thiệp."), + p(tags$b("Tại sao dùng Mann-Whitney?")), + p("Dữ liệu về số điếu thuốc hút mỗi ngày thường không tuân theo phân phối chuẩn (nó là dữ liệu đếm, không thể âm, và thường bị lệch phải). Do đó, so sánh phân phối dựa trên hạng sẽ phù hợp hơn là so sánh trung bình."), + + p(tags$b("Tính toán cỡ mẫu:")), + p("Nhà nghiên cứu cần xác định số lượng bệnh nhân cần cho mỗi phương pháp tư vấn. Họ kỳ vọng một sự khác biệt có effect size khoảng 0.5. Họ có thể nhập các giá trị này vào ứng dụng để tìm ra cỡ mẫu cần thiết cho nghiên cứu của mình.") + ) + ) + ) + ) +) + +# --- Logic của máy chủ (Server) --- +server <- function(input, output, session) { + + simulation_results <- eventReactive(input$calculate, { + + inputs <- list( + dist_shape = input$dist_shape, + effect_size = input$effect_size, + alternative = input$alternative, + sig_level = input$sig_level, + target_power = input$power, + n_sims = input$n_sims + ) + + sample_sizes <- seq(10, 500, by = 5) + powers <- numeric(length(sample_sizes)) + + withProgress(message = 'Đang chạy mô phỏng...', value = 0, { + + for (i in 1:length(sample_sizes)) { + n <- sample_sizes[i] + + p_values <- replicate(inputs$n_sims, { + + # Tạo dữ liệu cho 2 nhóm + # Nhóm 1 có phân phối bị dịch chuyển, nhóm 2 thì không + shift <- inputs$effect_size + + if (inputs$dist_shape == "t_dist") { + group1 <- rt(n, df = 5) + shift + group2 <- rt(n, df = 5) + } else if (inputs$dist_shape == "lnorm") { + group1 <- rlnorm(n, meanlog = shift, sdlog = 1) + group2 <- rlnorm(n, meanlog = 0, sdlog = 1) + } else { # unif + group1 <- runif(n, 0, 1) + shift + group2 <- runif(n, 0, 1) + } + + # Thực hiện kiểm định Mann-Whitney U (wilcox.test cho 2 mẫu) + wilcox.test(group1, group2, alternative = inputs$alternative)$p.value + }) + + powers[i] <- mean(p_values < inputs$sig_level, na.rm = TRUE) + + incProgress(1/length(sample_sizes), detail = paste("Cỡ mẫu n =", n)) + + if (powers[i] >= inputs$target_power) { + sample_sizes <- sample_sizes[1:i] + powers <- powers[1:i] + break + } + } + }) + + list( + sample_sizes = sample_sizes, + powers = powers, + inputs = inputs + ) + }) + + output$sample_size_output <- renderUI({ + res <- simulation_results() + + required_n_index <- which(res$powers >= res$inputs$target_power)[1] + + if (!is.na(required_n_index)) { + required_n <- res$sample_sizes[required_n_index] + tagList( + tags$p("Để phát hiện một effect size là", tags$b(res$inputs$effect_size), "với power là", tags$b(res$inputs$target_power), ", bạn cần một cỡ mẫu ước tính là:"), + tags$h3(style = "color: #007bff; text-align: center;", paste(required_n, "cho mỗi nhóm")), + tags$p(style = "text-align: center; font-style: italic;", "Tổng cỡ mẫu là ", required_n * 2, ".") + ) + } else { + tags$div(class = "alert alert-warning", "Không đạt được power mong muốn trong khoảng cỡ mẫu đã thử (tối đa 500).") + } + }) + + output$power_plot <- renderPlot({ + res <- simulation_results() + plot_data <- data.frame(SampleSize = res$sample_sizes, Power = res$powers) + + ggplot(plot_data, aes(x = SampleSize, y = Power)) + + geom_line(color = "#007bff", size = 1.2) + + geom_point(color = "#007bff", size = 3) + + geom_hline(yintercept = res$inputs$target_power, linetype = "dashed", color = "red") + + labs(title = "Power vs. Cỡ mẫu (cho mỗi nhóm)", x = "Cỡ mẫu cho mỗi nhóm (n)", y = "Power thực nghiệm (1 - β)") + + scale_y_continuous(limits = c(0, 1)) + theme_minimal(base_size = 14) + }) + + output$effect_analysis_plot <- renderPlot({ + ggplot() + + labs(title = "Phân tích này không được thực hiện tự động do thời gian tính toán lâu.", + subtitle = "Vui lòng chạy lại với các giá trị Effect Size khác nhau để so sánh.") + + theme_minimal() + }) + +} + +# Chạy ứng dụng Shiny +shinyApp(ui = ui, server = server) diff --git a/samplesize/Onesampleztest/app.R b/samplesize/Onesampleztest/app.R new file mode 100644 index 0000000..be1f083 --- /dev/null +++ b/samplesize/Onesampleztest/app.R @@ -0,0 +1,211 @@ +# Tải các thư viện cần thiết +library(shiny) +library(bslib) +library(ggplot2) +library(shinycssloaders) + +# --- Giao diện người dùng (UI) --- +ui <- fluidPage( + theme = bs_theme(version = 5, bootswatch = "cerulean"), + withMathJax(), + + titlePanel("Tính toán Cỡ mẫu cho Kiểm định Z một mẫu"), + + sidebarLayout( + sidebarPanel( + h4("Tham số đầu vào"), + + numericInput("d", + label = "Effect Size (Cohen's d):", + value = 0.5, min = 0.1, step = 0.1), + helpText("Cohen's d đo lường độ lớn của sự khác biệt, được chuẩn hóa bằng độ lệch chuẩn TỔNG THỂ (đã biết)."), + + selectInput("alternative", "Loại kiểm định:", + choices = c("Hai phía (Two-sided)" = "two.sided", + "Lớn hơn (Greater)" = "greater", + "Nhỏ hơn (Less)" = "less")), + + sliderInput("sig_level", + label = "Mức ý nghĩa (\\(\\alpha\\)):", + min = 0.01, max = 0.10, value = 0.05, step = 0.01), + + sliderInput("power", + label = "Power mong muốn (\\(1 - \\beta\\)):", + min = 0.50, max = 0.99, value = 0.80, step = 0.01), + + hr(), + helpText("Kết quả và đồ thị sẽ tự động cập nhật ngay lập tức.") + ), + + mainPanel( + tabsetPanel( + id = "results_tabs", + type = "pills", + + tabPanel( + "Kết quả & Diễn giải", + h4("Kết quả tính toán"), + withSpinner(uiOutput("sample_size_output"), type = 6, color = "#007bff") + ), + + tabPanel( + "Đồ thị Power vs. Cỡ mẫu", + h4("Mối quan hệ giữa Power và Cỡ mẫu"), + withSpinner(plotOutput("power_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Phân tích Effect Size", + h4("Mối quan hệ giữa Cỡ mẫu và Effect Size"), + p("Đồ thị này cho thấy cỡ mẫu cần thiết thay đổi như thế nào khi Effect Size thay đổi."), + withSpinner(plotOutput("effect_analysis_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Giả thuyết và Công thức (Helper)", + h4("Sự khác biệt chính: Z-test vs. T-test"), + tags$div(class = "alert alert-info", + tags$b("Điểm mấu chốt:"), + p("Sự lựa chọn giữa Z-test và T-test phụ thuộc vào việc bạn có biết độ lệch chuẩn của tổng thể (\\(\\sigma\\)) hay không."), + tags$ul( + tags$li(tags$b("Sử dụng Z-test khi:"), "Độ lệch chuẩn của tổng thể (\\(\\sigma\\)) đã biết. Điều này hiếm khi xảy ra trong thực tế, nhưng có thể xảy ra khi có một lượng lớn dữ liệu lịch sử (ví dụ: điểm thi chuẩn hóa toàn quốc, thông số trong một quy trình sản xuất đã được kiểm soát chặt chẽ)."), + tags$li(tags$b("Sử dụng T-test khi:"), "Độ lệch chuẩn của tổng thể (\\(\\sigma\\)) không xác định và phải được ước tính từ độ lệch chuẩn của mẫu (\\(s\\)). Đây là trường hợp phổ biến nhất trong nghiên cứu.") + ), + p("Khi cỡ mẫu rất lớn (thường > 30 hoặc > 100), phân phối t sẽ xấp xỉ phân phối chuẩn, và kết quả của hai kiểm định sẽ rất giống nhau.") + ), + hr(), + h4("Giả thuyết của Kiểm định Z một mẫu"), + p("Kiểm định này so sánh trung bình của một mẫu duy nhất (\\(\\mu\\)) với một giá trị trung bình đã biết hoặc giả định (\\(\\mu_0\\)), khi \\(\\sigma\\) đã biết."), + p("$$H_0: \\mu = \\mu_0$$"), + p("$$H_a: \\mu \\neq \\mu_0 \\quad (\\text{hoặc } > \\text{ hoặc } <\\text{)}$$"), + hr(), + h4("Công thức tính toán"), + + tags$b("1. Thống kê kiểm định (Test Statistic):"), + p("Giá trị thống kê Z được tính như sau:"), + p("$$ Z = \\frac{\\bar{x} - \\mu_0}{\\sigma / \\sqrt{n}} $$"), + p("Trong đó \\(\\bar{x}\\) là trung bình mẫu, \\(\\sigma\\) là độ lệch chuẩn TỔNG THỂ, và \\(n\\) là cỡ mẫu."), + + tags$b("2. Tính toán Power:"), + p("Power được tính toán dựa trên phân phối chuẩn. Dưới giả thuyết đối \\(H_a\\), thống kê Z tuân theo phân phối chuẩn với trung bình là \\(d\\sqrt{n}\\).") + ) + ) + ) + ) +) + +# --- Logic của máy chủ (Server) --- +server <- function(input, output, session) { + + # --- HÀM TÍNH POWER LÕI CHO Z-TEST --- + calculate_z_power <- function(n, d, sig_level, alternative) { + if (alternative == "two.sided") { + alpha <- sig_level / 2 + crit_val_upper <- qnorm(1 - alpha) + power <- pnorm(crit_val_upper - d * sqrt(n), lower.tail = FALSE) + pnorm(-crit_val_upper - d * sqrt(n), lower.tail = TRUE) + } else { # one.sided (greater or less) + crit_val <- qnorm(1 - sig_level) + power <- pnorm(crit_val - abs(d) * sqrt(n), lower.tail = FALSE) + } + return(power) + } + + # --- PHẦN TÍNH TOÁN CHÍNH --- + main_results <- reactive({ + req(input$d, input$sig_level, input$power, input$alternative) + + # Tìm cỡ mẫu cần thiết + n <- 2 # Bắt đầu từ n nhỏ + power_val <- 0 + while (power_val < input$power && n <= 5000) { + n <- n + 1 + power_val <- calculate_z_power(n, input$d, input$sig_level, input$alternative) + } + required_n <- ifelse(n > 5000, NA, n) + + # Tạo dữ liệu cho đồ thị Power + upper_bound <- if (!is.na(required_n)) required_n + 50 else 200 + sample_sizes <- seq(5, upper_bound, by = 1) + + powers <- sapply(sample_sizes, function(s_n) { + calculate_z_power(s_n, input$d, input$sig_level, input$alternative) + }) + + list( + required_n = required_n, + power_plot_data = data.frame(SampleSize = sample_sizes, Power = powers) + ) + }) + + output$sample_size_output <- renderUI({ + res <- main_results() + + if (is.null(res) || is.na(res$required_n)) { + return(tags$div(class = "alert alert-warning", "Không thể đạt được power mong muốn với cỡ mẫu tối đa (5000). Vui lòng xem xét lại các tham số.")) + } + + tagList( + tags$p("Để phát hiện một effect size (Cohen's d) là", tags$b(input$d), "với power là", tags$b(input$power), "và mức ý nghĩa", tags$b(input$sig_level), ", bạn cần một cỡ mẫu ước tính là:"), + tags$h3(style = "color: #007bff; text-align: center;", res$required_n) + ) + }) + + output$power_plot <- renderPlot({ + res <- main_results() + req(res$power_plot_data) + + ggplot(res$power_plot_data, aes(x = SampleSize, y = Power)) + + geom_line(color = "#007bff", size = 1.2) + + geom_hline(yintercept = input$power, linetype = "dashed", color = "red") + + geom_vline(xintercept = res$required_n, linetype = "dashed", color = "darkgreen") + + labs(title = "Power vs. Cỡ mẫu", x = "Cỡ mẫu (n)", y = "Power (1 - β)") + + scale_y_continuous(limits = c(0, 1)) + theme_minimal(base_size = 14) + }) + + # --- PHẦN PHÂN TÍCH EFFECT SIZE --- + effect_analysis_plot_data <- reactive({ + req(input$sig_level, input$power, input$alternative) + + effect_sizes <- seq(0.1, 1.5, by = 0.05) + + required_n_values <- sapply(effect_sizes, function(d_val) { + n <- 2 + power_val <- 0 + while (power_val < input$power && n <= 5000) { + n <- n + 1 + power_val <- calculate_z_power(n, d_val, input$sig_level, input$alternative) + } + ifelse(n > 5000, NA, n) + }) + + data.frame(EffectSize = effect_sizes, RequiredN = required_n_values) + }) + + output$effect_analysis_plot <- renderPlot({ + plot_data <- effect_analysis_plot_data() + res <- main_results() + + p <- ggplot(plot_data, aes(x = EffectSize, y = RequiredN)) + + geom_line(color = "#28a745", size = 1.2) + + geom_point(color = "#28a745", size = 3, na.rm = TRUE) + + labs( + title = paste("Cỡ mẫu cần thiết vs. Effect Size (Power cố định =", input$power, ")"), + x = "Effect Size (Cohen's d)", + y = "Cỡ mẫu cần thiết (n)" + ) + + theme_minimal(base_size = 14) + + if (!is.null(res) && !is.na(res$required_n)) { + p <- p + + geom_vline(xintercept = input$d, linetype = "dotted", color = "blue", size = 1) + + geom_point(aes(x = input$d, y = res$required_n), color = "blue", size = 5, shape = 18) + + annotate("text", x = input$d, y = res$required_n, + label = paste("n =", res$required_n), vjust = -1.5, color = "blue", fontface = "bold") + } + + p + }) +} + +# Chạy ứng dụng Shiny +shinyApp(ui = ui, server = server) diff --git a/samplesize/PearsonCorrelationTest/app.R b/samplesize/PearsonCorrelationTest/app.R new file mode 100644 index 0000000..fec6202 --- /dev/null +++ b/samplesize/PearsonCorrelationTest/app.R @@ -0,0 +1,222 @@ +# Tải các thư viện cần thiết +library(shiny) +library(bslib) +library(ggplot2) +library(shinycssloaders) +library(pwr) + +# --- Giao diện người dùng (UI) --- +ui <- fluidPage( + theme = bs_theme(version = 5, bootswatch = "cerulean"), + withMathJax(), + + titlePanel("Tính toán Cỡ mẫu cho Tương quan Pearson"), + + sidebarLayout( + sidebarPanel( + h4("Tham số đầu vào"), + + sliderInput("r", + label = "Hệ số tương quan kỳ vọng (r):", + value = 0.3, min = 0.1, max = 0.9, step = 0.05), + helpText("Đây là effect size. Quy ước: 0.1 (nhỏ), 0.3 (trung bình), 0.5 (lớn)."), + + selectInput("alternative", "Loại kiểm định:", + choices = c("Hai phía (Two-sided)" = "two.sided", + "Lớn hơn (Greater)" = "greater", + "Nhỏ hơn (Less)" = "less")), + + sliderInput("sig_level", + label = "Mức ý nghĩa (\\(\\alpha\\)):", + min = 0.01, max = 0.10, value = 0.05, step = 0.01), + + sliderInput("power", + label = "Power mong muốn (\\(1 - \\beta\\)):", + min = 0.50, max = 0.99, value = 0.80, step = 0.01), + + hr(), + # Hiển thị công thức biến đổi Fisher's Z + h5("Phép biến đổi Fisher's Z:"), + uiOutput("fisher_z_formula_display"), + helpText("Hệ số r được biến đổi thành Z để tính toán power.") + ), + + mainPanel( + tabsetPanel( + id = "results_tabs", + type = "pills", + + tabPanel( + "Kết quả & Diễn giải", + h4("Kết quả tính toán"), + withSpinner(uiOutput("sample_size_output"), type = 6, color = "#007bff") + ), + + tabPanel( + "Đồ thị Power vs. Cỡ mẫu", + h4("Mối quan hệ giữa Power và Cỡ mẫu"), + withSpinner(plotOutput("power_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Phân tích Effect Size", + h4("Mối quan hệ giữa Cỡ mẫu và Effect Size"), + withSpinner(plotOutput("effect_analysis_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Giả thuyết và Công thức (Helper)", + h4("Giả thuyết của Kiểm định Tương quan Pearson"), + p("Kiểm định này kiểm tra xem có mối tương quan tuyến tính giữa hai biến liên tục hay không. Hệ số tương quan của tổng thể được ký hiệu là \\(\\rho\\)."), + p("$$H_0: \\rho = 0$$"), + p("$$H_a: \\rho \\neq 0 \\quad (\\text{hoặc } > 0 \\text{ hoặc } < 0\\text{)}$$"), + hr(), + h4("Công thức tính toán"), + + tags$b("1. Thống kê kiểm định (t-statistic):"), + p("Để kiểm định ý nghĩa của hệ số tương quan mẫu (r), nó được chuyển đổi thành một giá trị t:"), + p("$$ t = \\frac{r \\sqrt{n-2}}{\\sqrt{1-r^2}} $$"), + p("Giá trị này tuân theo phân phối t với \\(n-2\\) bậc tự do."), + + tags$b("2. Phép biến đổi Fisher's Z (dùng cho tính power):"), + p("Để tính toán power, hệ số tương quan `r` được biến đổi sang thang đo Z, có phân phối gần chuẩn hơn:"), + p("$$ Z_r = \\frac{1}{2} \\ln{\\left(\\frac{1+r}{1-r}\\right)} = \\text{arctanh}(r) $$"), + p("Hàm `pwr.r.test` sử dụng phép biến đổi này để thực hiện các tính toán power."), + + hr(), + h4("Ví dụ ứng dụng trong Y tế công cộng"), + p(tags$b("Tình huống:")), + p("Một nhà nghiên cứu sức khỏe muốn tìm hiểu mối liên hệ giữa số giờ ngủ trung bình mỗi đêm và mức độ căng thẳng (stress) được đo bằng một thang điểm chuẩn hóa."), + p(tags$b("Thiết kế nghiên cứu:")), + tags$ul( + tags$li("Biến 1: Số giờ ngủ (biến liên tục)."), + tags$li("Biến 2: Điểm số căng thẳng (biến liên tục)."), + tags$li("Phân tích: Tương quan Pearson sẽ được sử dụng để xem liệu có mối tương quan tuyến tính (âm) giữa hai biến này hay không.") + ), + p(tags$b("Tính toán cỡ mẫu:")), + p("Trước khi thu thập dữ liệu, nhà nghiên cứu cần biết cần khảo sát bao nhiêu người. Dựa trên các tài liệu, họ kỳ vọng một mối tương quan ở mức 'trung bình', khoảng \\(r = -0.3\\)."), + p("Họ có thể nhập giá trị tuyệt đối của r (r = 0.3) vào ứng dụng, cùng với power và mức ý nghĩa mong muốn, để tìm ra cỡ mẫu cần thiết cho nghiên cứu.") + ) + ) + ) + ) +) + +# --- Logic của máy chủ (Server) --- +server <- function(input, output, session) { + + # Hiển thị công thức Fisher's Z + output$fisher_z_formula_display <- renderUI({ + withMathJax("$$ Z_r = \\frac{1}{2} \\ln{\\left(\\frac{1+r}{1-r}\\right)} $$") + }) + + # --- PHẦN TÍNH TOÁN CHÍNH --- + main_results <- reactive({ + req(input$r, input$sig_level, input$power, input$alternative) + + pwr_result <- tryCatch({ + pwr.r.test( + r = input$r, + sig.level = input$sig_level, + power = input$power, + alternative = input$alternative + ) + }, error = function(e) NULL) + + if (is.null(pwr_result)) return(NULL) + + required_n <- ceiling(pwr_result$n) + + sample_sizes <- seq(5, required_n + 100, by = 1) + + power_data <- tryCatch({ + pwr.r.test( + n = sample_sizes, + r = input$r, + sig.level = input$sig_level, + alternative = input$alternative + ) + }, error = function(e) NULL) + + if(is.null(power_data)) return(list(required_n = required_n, power_plot_data = NULL)) + + list( + required_n = required_n, + power_plot_data = data.frame(SampleSize = power_data$n, Power = power_data$power) + ) + }) + + output$sample_size_output <- renderUI({ + res <- main_results() + + if (is.null(res)) { + return(tags$div(class = "alert alert-danger", "Có lỗi xảy ra trong quá trình tính toán.")) + } + + tagList( + tags$p("Để phát hiện một mối tương quan (r) là", tags$b(input$r), "với power là", tags$b(input$power), "và mức ý nghĩa", tags$b(input$sig_level), ", bạn cần một cỡ mẫu ước tính là:"), + tags$h3(style = "color: #007bff; text-align: center;", res$required_n) + ) + }) + + output$power_plot <- renderPlot({ + res <- main_results() + req(res$power_plot_data) + + ggplot(res$power_plot_data, aes(x = SampleSize, y = Power)) + + geom_line(color = "#007bff", size = 1.2) + + geom_hline(yintercept = input$power, linetype = "dashed", color = "red") + + geom_vline(xintercept = res$required_n, linetype = "dashed", color = "darkgreen") + + labs(title = "Power vs. Cỡ mẫu", x = "Cỡ mẫu (n)", y = "Power (1 - β)") + + scale_y_continuous(limits = c(0, 1)) + theme_minimal(base_size = 14) + }) + + # --- PHẦN PHÂN TÍCH EFFECT SIZE --- + effect_analysis_plot_data <- reactive({ + req(input$sig_level, input$power, input$alternative) + + effect_sizes <- seq(0.1, 0.9, by = 0.05) + + required_n_values <- sapply(effect_sizes, function(r_val) { + res <- tryCatch({ + pwr.r.test( + r = r_val, + sig.level = input$sig_level, + power = input$power, + alternative = input$alternative + )$n + }, error = function(e) NA) + ceiling(res) + }) + + data.frame(EffectSize = effect_sizes, RequiredN = required_n_values) + }) + + output$effect_analysis_plot <- renderPlot({ + plot_data <- effect_analysis_plot_data() + res <- main_results() + + p <- ggplot(plot_data, aes(x = EffectSize, y = RequiredN)) + + geom_line(color = "#28a745", size = 1.2) + + geom_point(color = "#28a745", size = 3, na.rm = TRUE) + + labs( + title = paste("Cỡ mẫu cần thiết vs. Effect Size (Power cố định =", input$power, ")"), + x = "Hệ số tương quan (r)", + y = "Cỡ mẫu cần thiết (n)" + ) + + theme_minimal(base_size = 14) + + if (!is.null(res) && !is.na(res$required_n)) { + p <- p + + geom_vline(xintercept = input$r, linetype = "dotted", color = "blue", size = 1) + + geom_point(aes(x = input$r, y = res$required_n), color = "blue", size = 5, shape = 18) + + annotate("text", x = input$r, y = res$required_n, + label = paste("n =", res$required_n), vjust = -1.5, color = "blue", fontface = "bold") + } + + p + }) +} + +# Chạy ứng dụng Shiny +shinyApp(ui = ui, server = server) diff --git a/samplesize/RepeatAnova/app.R b/samplesize/RepeatAnova/app.R new file mode 100644 index 0000000..9b6d955 --- /dev/null +++ b/samplesize/RepeatAnova/app.R @@ -0,0 +1,244 @@ +# Tải các thư viện cần thiết +library(shiny) +library(bslib) +library(ggplot2) +library(shinycssloaders) +library(pwr) + +# --- Giao diện người dùng (UI) --- +ui <- fluidPage( + theme = bs_theme(version = 5, bootswatch = "cerulean"), + withMathJax(), + + titlePanel("Tính toán Cỡ mẫu cho Repeated Measures ANOVA"), + + sidebarLayout( + sidebarPanel( + h4("Tham số đầu vào"), + + numericInput("k", "Số lần đo lường lặp lại (k):", value = 3, min = 2), + helpText("Ví dụ: đo lường tại 3 thời điểm (Baseline, 3 tháng, 6 tháng) thì k=3."), + + numericInput("f", + label = "Effect Size (Cohen's f):", + value = 0.25, min = 0.05, step = 0.05), + helpText("Cohen's f đo lường độ lớn của sự khác biệt giữa các lần đo. Quy ước: 0.1 (nhỏ), 0.25 (trung bình), 0.4 (lớn)."), + + sliderInput("epsilon", + label = "Hệ số điều chỉnh Sphericity (ε):", + min = 0.5, max = 1, value = 1, step = 0.05), + helpText("Epsilon = 1 nếu giả định sphericity được đáp ứng. Giá trị nhỏ hơn cho thấy sự vi phạm. Sử dụng ước tính từ các nghiên cứu trước nếu có."), + + sliderInput("sig_level", + label = "Mức ý nghĩa (\\(\\alpha\\)):", + min = 0.01, max = 0.10, value = 0.05, step = 0.01), + + sliderInput("power", + label = "Power mong muốn (\\(1 - \\beta\\)):", + min = 0.50, max = 0.99, value = 0.80, step = 0.01), + + hr(), + h5("Bậc tự do của tử số (u):"), + uiOutput("df_numerator_display") + ), + + mainPanel( + tabsetPanel( + id = "results_tabs", + type = "pills", + + tabPanel( + "Kết quả & Diễn giải", + h4("Kết quả tính toán"), + withSpinner(uiOutput("sample_size_output"), type = 6, color = "#007bff") + ), + + tabPanel( + "Đồ thị Power vs. Cỡ mẫu", + h4("Mối quan hệ giữa Power và Số đối tượng"), + withSpinner(plotOutput("power_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Phân tích Effect Size", + h4("Mối quan hệ giữa Cỡ mẫu và Effect Size"), + withSpinner(plotOutput("effect_analysis_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Giả thuyết và Công thức (Helper)", + h4("Giả thuyết của Repeated Measures ANOVA"), + p("Kiểm định này so sánh trung bình của ba hay nhiều lần đo lường trên cùng một nhóm đối tượng."), + p("$$H_0: \\mu_1 = \\mu_2 = \\dots = \\mu_k$$"), + p("$$H_a: \\text{Có ít nhất một cặp trung bình không bằng nhau}$$"), + + tags$div(class = "alert alert-info", + tags$b("Giả định Sphericity:"), + p("Đây là một giả định quan trọng, cho rằng phương sai của sự khác biệt giữa các cặp đo lường là bằng nhau. Nếu giả định này bị vi phạm, kết quả có thể không chính xác. Hệ số điều chỉnh Epsilon (ε) được sử dụng để điều chỉnh bậc tự do, làm cho kiểm định trở nên chặt chẽ hơn. Epsilon = 1 có nghĩa là giả định được đáp ứng hoàn hảo.") + ), + hr(), + + h4("Công thức tính toán"), + p("Cỡ mẫu được tính toán dựa trên hàm `pwr.f2.test`, một công cụ cho các mô hình tuyến tính tổng quát."), + tags$b("1. Bậc tự do của tử số (Numerator df - u):"), + p("Bậc tự do này được điều chỉnh bởi epsilon:"), + p("$$ u = (k - 1) \\times \\epsilon $$"), + + tags$b("2. Effect Size (Cohen's f):"), + p("Tương tự như One-Way ANOVA, `f` đo lường độ lớn của sự khác biệt giữa các trung bình của các lần đo."), + + hr(), + h4("Ví dụ ứng dụng trong Y tế công cộng"), + p(tags$b("Tình huống:")), + p("Một nhóm nghiên cứu muốn đánh giá hiệu quả lâu dài của một loại vaccine mới. Họ đo nồng độ kháng thể trong máu của cùng một nhóm tình nguyện viên tại 3 thời điểm: 1 tháng, 6 tháng, và 12 tháng sau khi tiêm."), + p(tags$b("Thiết kế nghiên cứu:")), + tags$ul( + tags$li("Thiết kế: Đo lường lặp lại (within-subjects)."), + tags$li("Số lần đo (k): 3."), + tags$li("Phân tích: Repeated Measures ANOVA sẽ được sử dụng để xem liệu nồng độ kháng thể trung bình có thay đổi một cách có ý nghĩa theo thời gian hay không.") + ), + p(tags$b("Tính toán cỡ mẫu:")), + p("Trước khi bắt đầu, họ cần biết cần bao nhiêu tình nguyện viên. Họ kỳ vọng một sự thay đổi ở mức 'trung bình' theo thời gian, và chọn effect size \\(f = 0.25\\). Họ cũng không chắc về giả định sphericity, nên dựa trên các nghiên cứu tương tự, họ ước tính một cách thận trọng với \\(\\epsilon = 0.85\\)."), + p("Họ có thể nhập các giá trị này vào ứng dụng để tìm ra số lượng tình nguyện viên cần thiết cho nghiên cứu.") + ) + ) + ) + ) +) + +# --- Logic của máy chủ (Server) --- +server <- function(input, output, session) { + + # Tính toán bậc tự do tử số (u) + u <- reactive({ + req(input$k, input$epsilon) + (input$k - 1) * input$epsilon + }) + + output$df_numerator_display <- renderUI({ + withMathJax(paste0("$$ u = (k-1)\\epsilon = ", round(u(), 2), " $$")) + }) + + # --- PHẦN TÍNH TOÁN CHÍNH --- + main_results <- reactive({ + req(u(), input$f, input$sig_level, input$power, input$k, input$epsilon) + + f2 <- input$f^2 + + # pwr.f2.test giải ra v (bậc tự do mẫu số) + pwr_result <- tryCatch({ + pwr.f2.test( + u = u(), + f2 = f2, + sig.level = input$sig_level, + power = input$power + ) + }, error = function(e) NULL) + + if (is.null(pwr_result)) return(NULL) + + v <- pwr_result$v + # Từ v, tính số đối tượng n + # v = (n-1)*(k-1)*epsilon => n = v/((k-1)*epsilon) + 1 + required_n <- ceiling(v / ((input$k - 1) * input$epsilon) + 1) + + # Tạo dữ liệu cho đồ thị Power + sample_sizes <- seq(5, required_n + 50, by = 1) + + # Tính lại v cho mỗi n để tính power + v_for_plot <- (sample_sizes - 1) * (input$k - 1) * input$epsilon + + power_data <- tryCatch({ + pwr.f2.test( + u = u(), + v = v_for_plot, + f2 = f2, + sig.level = input$sig_level + ) + }, error = function(e) NULL) + + if(is.null(power_data)) return(list(required_n = required_n, power_plot_data = NULL)) + + list( + required_n = required_n, + power_plot_data = data.frame(SampleSize = sample_sizes, Power = power_data$power) + ) + }) + + output$sample_size_output <- renderUI({ + res <- main_results() + + if (is.null(res)) { + return(tags$div(class = "alert alert-danger", "Có lỗi xảy ra trong quá trình tính toán.")) + } + + tagList( + tags$p("Để phát hiện một sự khác biệt giữa các lần đo có độ lớn (f) là", tags$b(input$f), "với power là", tags$b(input$power), ", bạn cần một cỡ mẫu ước tính là:"), + tags$h3(style = "color: #007bff; text-align: center;", paste(res$required_n, "đối tượng")) + ) + }) + + output$power_plot <- renderPlot({ + res <- main_results() + req(res$power_plot_data) + + ggplot(res$power_plot_data, aes(x = SampleSize, y = Power)) + + geom_line(color = "#007bff", size = 1.2) + + geom_hline(yintercept = input$power, linetype = "dashed", color = "red") + + geom_vline(xintercept = res$required_n, linetype = "dashed", color = "darkgreen") + + labs(title = "Power vs. Số đối tượng", x = "Số đối tượng (n)", y = "Power (1 - β)") + + scale_y_continuous(limits = c(0, 1)) + theme_minimal(base_size = 14) + }) + + # --- PHẦN PHÂN TÍCH EFFECT SIZE --- + effect_analysis_plot_data <- reactive({ + req(u(), input$sig_level, input$power, input$k, input$epsilon) + + effect_sizes <- seq(0.05, 0.6, by = 0.05) + + required_n_values <- sapply(effect_sizes, function(f_val) { + res <- tryCatch({ + pwr.f2.test( + u = u(), + f2 = f_val^2, + sig.level = input$sig_level, + power = input$power + )$v + }, error = function(e) NA) + + if(is.na(res)) return(NA) + + ceiling(res / ((input$k - 1) * input$epsilon) + 1) + }) + + data.frame(EffectSize = effect_sizes, RequiredN = required_n_values) + }) + + output$effect_analysis_plot <- renderPlot({ + plot_data <- effect_analysis_plot_data() + res <- main_results() + + p <- ggplot(plot_data, aes(x = EffectSize, y = RequiredN)) + + geom_line(color = "#28a745", size = 1.2) + + geom_point(color = "#28a745", size = 3, na.rm = TRUE) + + labs( + title = paste("Cỡ mẫu cần thiết vs. Effect Size (Power cố định =", input$power, ")"), + x = "Effect Size (f)", + y = "Số đối tượng cần thiết (n)" + ) + + theme_minimal(base_size = 14) + + if (!is.null(res) && !is.na(res$required_n)) { + p <- p + + geom_vline(xintercept = input$f, linetype = "dotted", color = "blue", size = 1) + + geom_point(aes(x = input$f, y = res$required_n), color = "blue", size = 5, shape = 18) + + annotate("text", x = input$f, y = res$required_n, + label = paste("n =", res$required_n), vjust = -1.5, color = "blue", fontface = "bold") + } + + p + }) +} + +# Chạy ứng dụng Shiny +shinyApp(ui = ui, server = server) diff --git a/samplesize/Shapiro-Wilk/app.R b/samplesize/Shapiro-Wilk/app.R new file mode 100644 index 0000000..3bc6dcd --- /dev/null +++ b/samplesize/Shapiro-Wilk/app.R @@ -0,0 +1,239 @@ +# Tải các thư viện cần thiết +library(shiny) +library(bslib) # Để có giao diện hiện đại +library(ggplot2) # Để vẽ đồ thị +library(shinycssloaders) # Để thêm hiệu ứng tải + +# Định nghĩa các lựa chọn phân phối một lần để sử dụng ở cả UI và Server +dist_choices <- c("Chi-squared (df=3)" = "chisq", + "Beta (alpha=2, beta=5)" = "beta", + "Phân phối t (df=5)" = "t", + "Gamma (shape=2, rate=1.5)" = "gamma") + +# --- Giao diện người dùng (UI) --- +ui <- fluidPage( + # Sử dụng theme Bootstrap hiện đại + theme = bs_theme(version = 5, bootswatch = "cerulean"), + + # MathJax để hiển thị công thức toán học + withMathJax(), + + # Tiêu đề của ứng dụng + titlePanel("Tính toán Cỡ mẫu cho Kiểm định Chuẩn Shapiro-Wilk"), + + # Bố cục sidebar + sidebarLayout( + sidebarPanel( + h4("Tham số đầu vào"), + + # Chọn phân phối của giả thuyết đối (H1) + selectInput("alt_dist", + label = "Phân phối của giả thuyết đối (H1):", + choices = dist_choices, + selected = "chisq"), + + # Thanh trượt cho mức ý nghĩa (alpha) + sliderInput("sig_level", + label = "Mức ý nghĩa (\\(\\alpha\\)):", + min = 0.01, max = 0.10, value = 0.05, step = 0.01), + + # Thanh trượt cho power mong muốn + sliderInput("power", + label = "Power mong muốn (\\(1 - \\beta\\)):", + min = 0.50, max = 0.99, value = 0.80, step = 0.01), + + # Số lần lặp mô phỏng + numericInput("n_sims", + label = "Số lần lặp mô phỏng:", + value = 500, min = 100, max = 5000), + + hr(), + helpText("Kết quả chính và đồ thị Power sẽ tự động cập nhật. Chuyển sang tab 'Phân tích Mức ý nghĩa' để chạy các phân tích sâu hơn.") + ), + + mainPanel( + # Sử dụng tab để tổ chức kết quả + tabsetPanel( + id = "results_tabs", + type = "pills", + + # Tab 1: Kết quả tính toán + tabPanel( + "Kết quả & Diễn giải", + h4("Kết quả ước tính"), + withSpinner(uiOutput("sample_size_output"), type = 6, color = "#007bff") + ), + + # Tab 2: Đồ thị Power + tabPanel( + "Đồ thị Power vs. Cỡ mẫu", + h4("Mối quan hệ giữa Power và Cỡ mẫu"), + withSpinner(plotOutput("power_plot"), type = 6, color = "#007bff") + ), + + # Tab 3: Phân tích Mức ý nghĩa (MỚI) + tabPanel( + "Phân tích Mức ý nghĩa", + h4("Mối quan hệ giữa Cỡ mẫu và Mức ý nghĩa (\\(\\alpha\\))"), + p("Phân tích này cho thấy cỡ mẫu cần thiết thay đổi như thế nào khi bạn điều chỉnh mức ý nghĩa alpha (với power và phân phối được giữ cố định từ các tham số chính)."), + p("Vì tính toán này khá nặng, vui lòng nhấn nút bên dưới để bắt đầu."), + actionButton("run_alpha_analysis", "Chạy phân tích Alpha", class = "btn-success mb-3"), + withSpinner(plotOutput("alpha_analysis_plot"), type = 6, color = "#007bff") + ), + + # Tab 4: Công thức và phương pháp + tabPanel( + "Giả thuyết và Phương pháp", + h4("Giả thuyết của Kiểm định Shapiro-Wilk"), + p("$$H_0: \\text{Dữ liệu tuân theo phân phối chuẩn}$$"), + p("$$H_a: \\text{Dữ liệu không tuân theo phân phối chuẩn}$$"), + hr(), + h4("Phương pháp tính toán"), + p("Chúng tôi sử dụng phương pháp mô phỏng Monte Carlo... (giải thích chi tiết)") + ) + ) + ) + ) +) + +# --- Logic của máy chủ (Server) --- +server <- function(input, output, session) { + + # --- PHẦN TÍNH TOÁN CHÍNH (TỰ ĐỘNG) --- + + reactive_inputs <- reactive({ + list( + sig_level = input$sig_level, + power = input$power, + alt_dist = input$alt_dist, + n_sims = input$n_sims + ) + }) + + debounced_inputs <- debounce(reactive_inputs, 1000) + + simulation_results <- reactive({ + inputs <- debounced_inputs() + sig_level <- inputs$sig_level + target_power <- inputs$power + alt_dist <- inputs$alt_dist + n_sims <- inputs$n_sims + + sample_sizes <- seq(10, 500, by = 5) + powers <- numeric(length(sample_sizes)) + + withProgress(message = 'Đang chạy mô phỏng chính...', value = 0, { + for (i in 1:length(sample_sizes)) { + n <- sample_sizes[i] + p_values <- replicate(n_sims, { + if (alt_dist == "chisq") random_data <- rchisq(n, df = 3) + else if (alt_dist == "beta") random_data <- rbeta(n, shape1 = 2, shape2 = 5) + else if (alt_dist == "t") random_data <- rt(n, df = 5) + else if (alt_dist == "gamma") random_data <- rgamma(n, shape = 2, rate = 1.5) + shapiro.test(random_data)$p.value + }) + powers[i] <- mean(p_values < sig_level) + incProgress(1/length(sample_sizes), detail = paste("Cỡ mẫu n =", n)) + if (powers[i] >= target_power) { + sample_sizes <- sample_sizes[1:i] + powers <- powers[1:i] + break + } + } + }) + + list( + sample_sizes = sample_sizes, + powers = powers, + target_power = target_power, + sig_level = sig_level, + alt_dist_name = names(dist_choices)[dist_choices == alt_dist] + ) + }) + + output$sample_size_output <- renderUI({ + res <- simulation_results() + first_index_above_power <- which(res$powers >= res$target_power)[1] + if (!is.na(first_index_above_power)) { + required_n <- res$sample_sizes[first_index_above_power] + tagList( + tags$p("Để phát hiện phân phối không chuẩn dạng", tags$b(res$alt_dist_name), "với power là", tags$b(res$target_power), "và mức ý nghĩa", tags$b(res$sig_level), ", bạn cần một cỡ mẫu ước tính là:"), + tags$h3(style = "color: #007bff; text-align: center;", required_n) + ) + } else { + tags$div(class = "alert alert-warning", "Không đạt được power mong muốn trong khoảng cỡ mẫu đã thử (tối đa 500).") + } + }) + + output$power_plot <- renderPlot({ + res <- simulation_results() + plot_data <- data.frame(SampleSize = res$sample_sizes, Power = res$powers) + ggplot(plot_data, aes(x = SampleSize, y = Power)) + + geom_line(color = "#007bff", size = 1.2) + geom_point(color = "#007bff", size = 3) + + geom_hline(yintercept = res$target_power, linetype = "dashed", color = "red") + + labs(title = paste("Power của Kiểm định Shapiro-Wilk vs. Phân phối", res$alt_dist_name), x = "Cỡ mẫu (n)", y = "Power thực nghiệm (1 - β)") + + theme_minimal(base_size = 14) + }) + + # --- PHẦN PHÂN TÍCH ALPHA (CHẠY KHI BẤM NÚT) --- + + alpha_analysis_results <- eventReactive(input$run_alpha_analysis, { + # Lấy các tham số cố định từ input + target_power <- input$power + alt_dist <- input$alt_dist + n_sims <- input$n_sims + + # Dãy các giá trị alpha để phân tích + alpha_values <- seq(0.01, 0.15, by = 0.01) + required_n_values <- numeric(length(alpha_values)) + + withProgress(message = 'Đang chạy phân tích Alpha...', value = 0, { + # Vòng lặp qua từng giá trị alpha + for (j in 1:length(alpha_values)) { + current_alpha <- alpha_values[j] + + # Logic tìm cỡ mẫu cho alpha hiện tại + sample_sizes <- seq(10, 500, by = 10) # Dùng bước nhảy lớn hơn để nhanh hơn + required_n <- NA + for (i in 1:length(sample_sizes)) { + n <- sample_sizes[i] + p_values <- replicate(n_sims, { + if (alt_dist == "chisq") random_data <- rchisq(n, df = 3) + else if (alt_dist == "beta") random_data <- rbeta(n, shape1 = 2, shape2 = 5) + else if (alt_dist == "t") random_data <- rt(n, df = 5) + else if (alt_dist == "gamma") random_data <- rgamma(n, shape = 2, rate = 1.5) + shapiro.test(random_data)$p.value + }) + + current_power <- mean(p_values < current_alpha) + + if (current_power >= target_power) { + required_n <- n + break + } + } + required_n_values[j] <- required_n + incProgress(1/length(alpha_values), detail = paste("Alpha =", current_alpha)) + } + }) + + data.frame(Alpha = alpha_values, RequiredN = required_n_values) + }) + + output$alpha_analysis_plot <- renderPlot({ + plot_data <- alpha_analysis_results() + + ggplot(plot_data, aes(x = Alpha, y = RequiredN)) + + geom_line(color = "#28a745", size = 1.2) + + geom_point(color = "#28a745", size = 3) + + labs( + title = paste("Cỡ mẫu cần thiết vs. Mức ý nghĩa (Power cố định =", input$power, ")"), + x = "Mức ý nghĩa (α)", + y = "Cỡ mẫu cần thiết (n)" + ) + + theme_minimal(base_size = 14) + }) +} + +# Chạy ứng dụng Shiny +shinyApp(ui = ui, server = server) diff --git a/samplesize/SpearmanCorrelationTest/app.R b/samplesize/SpearmanCorrelationTest/app.R new file mode 100644 index 0000000..b3b2d9c --- /dev/null +++ b/samplesize/SpearmanCorrelationTest/app.R @@ -0,0 +1,214 @@ +# Tải các thư viện cần thiết +library(shiny) +library(bslib) +library(ggplot2) +library(shinycssloaders) +library(pwr) + +# --- Giao diện người dùng (UI) --- +ui <- fluidPage( + theme = bs_theme(version = 5, bootswatch = "cerulean"), + withMathJax(), + + titlePanel("Tính toán Cỡ mẫu cho Tương quan hạng Spearman"), + + sidebarLayout( + sidebarPanel( + h4("Tham số đầu vào"), + + sliderInput("rho", + label = "Hệ số tương quan Spearman kỳ vọng (ρ):", + value = 0.3, min = 0.1, max = 0.9, step = 0.05), + helpText("Đây là effect size. Quy ước tương tự Pearson: 0.1 (nhỏ), 0.3 (trung bình), 0.5 (lớn)."), + + selectInput("alternative", "Loại kiểm định:", + choices = c("Hai phía (Two-sided)" = "two.sided", + "Lớn hơn (Greater)" = "greater", + "Nhỏ hơn (Less)" = "less")), + + sliderInput("sig_level", + label = "Mức ý nghĩa (\\(\\alpha\\)):", + min = 0.01, max = 0.10, value = 0.05, step = 0.01), + + sliderInput("power", + label = "Power mong muốn (\\(1 - \\beta\\)):", + min = 0.50, max = 0.99, value = 0.80, step = 0.01), + + hr(), + helpText("Lưu ý: Tính toán dựa trên phép xấp xỉ bằng phân tích power của tương quan Pearson.") + ), + + mainPanel( + tabsetPanel( + id = "results_tabs", + type = "pills", + + tabPanel( + "Kết quả & Diễn giải", + h4("Kết quả tính toán"), + withSpinner(uiOutput("sample_size_output"), type = 6, color = "#007bff") + ), + + tabPanel( + "Đồ thị Power vs. Cỡ mẫu", + h4("Mối quan hệ giữa Power và Cỡ mẫu"), + withSpinner(plotOutput("power_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Phân tích Effect Size", + h4("Mối quan hệ giữa Cỡ mẫu và Effect Size"), + withSpinner(plotOutput("effect_analysis_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Giả thuyết và Công thức (Helper)", + h4("Giả thuyết của Kiểm định Tương quan Spearman"), + p("Kiểm định này kiểm tra xem có mối tương quan đơn điệu (monotonic) giữa hai biến hay không. Hệ số tương quan hạng của tổng thể được ký hiệu là \\(\\rho_s\\)."), + p("$$H_0: \\rho_s = 0$$"), + p("$$H_a: \\rho_s \\neq 0 \\quad (\\text{hoặc } > 0 \\text{ hoặc } < 0\\text{)}$$"), + + tags$div(class = "alert alert-info", + tags$b("Khi nào sử dụng?"), + p("Sử dụng kiểm định này thay cho tương quan Pearson khi:"), + tags$ul( + tags$li("Mối quan hệ giữa hai biến không phải là tuyến tính, nhưng có tính đơn điệu (cùng tăng hoặc cùng giảm)."), + tags$li("Dữ liệu không tuân theo phân phối chuẩn."), + tags$li("Dữ liệu ở dạng thứ hạng (ordinal)."), + tags$li("Có các giá trị ngoại lai (outliers) ảnh hưởng đến kết quả của Pearson.") + ) + ), + hr(), + + h4("Phương pháp tính toán"), + p("Phân tích power cho tương quan Spearman thường được xấp xỉ bằng phân tích cho tương quan Pearson. Phương pháp này hoạt động tốt vì tương quan Spearman về bản chất chính là tương quan Pearson trên dữ liệu đã được xếp hạng. Ứng dụng này sử dụng hàm `pwr.r.test` để thực hiện phép xấp xỉ này."), + + hr(), + h4("Ví dụ ứng dụng trong Y tế công cộng"), + p(tags$b("Tình huống:")), + p("Một nhà xã hội học muốn nghiên cứu mối liên hệ giữa tình trạng kinh tế-xã hội (được xếp hạng từ 1 đến 10) và mức độ tiếp cận dịch vụ y tế (cũng được xếp hạng từ 1 đến 10) của các cộng đồng dân cư."), + p(tags$b("Tại sao dùng Spearman?")), + p("Vì cả hai biến đều ở dạng thứ hạng (ordinal), tương quan Spearman là phương pháp phân tích phù hợp nhất."), + + p(tags$b("Tính toán cỡ mẫu:")), + p("Nhà nghiên cứu kỳ vọng một mối tương quan dương ở mức 'trung bình', khoảng \\(\\rho_s = 0.35\\). Họ có thể nhập giá trị này vào ứng dụng để tìm ra số lượng cộng đồng cần khảo sát để có đủ power phát hiện mối liên hệ này.") + ) + ) + ) + ) +) + +# --- Logic của máy chủ (Server) --- +server <- function(input, output, session) { + + # --- PHẦN TÍNH TOÁN CHÍNH --- + main_results <- reactive({ + req(input$rho, input$sig_level, input$power, input$alternative) + + # Sử dụng pwr.r.test như một phép xấp xỉ + pwr_result <- tryCatch({ + pwr.r.test( + r = input$rho, + sig.level = input$sig_level, + power = input$power, + alternative = input$alternative + ) + }, error = function(e) NULL) + + if (is.null(pwr_result)) return(NULL) + + required_n <- ceiling(pwr_result$n) + + sample_sizes <- seq(5, required_n + 100, by = 1) + + power_data <- tryCatch({ + pwr.r.test( + n = sample_sizes, + r = input$rho, + sig.level = input$sig_level, + alternative = input$alternative + ) + }, error = function(e) NULL) + + if(is.null(power_data)) return(list(required_n = required_n, power_plot_data = NULL)) + + list( + required_n = required_n, + power_plot_data = data.frame(SampleSize = power_data$n, Power = power_data$power) + ) + }) + + output$sample_size_output <- renderUI({ + res <- main_results() + + if (is.null(res)) { + return(tags$div(class = "alert alert-danger", "Có lỗi xảy ra trong quá trình tính toán.")) + } + + tagList( + tags$p("Để phát hiện một mối tương quan Spearman (ρ) là", tags$b(input$rho), "với power là", tags$b(input$power), "và mức ý nghĩa", tags$b(input$sig_level), ", bạn cần một cỡ mẫu ước tính là:"), + tags$h3(style = "color: #007bff; text-align: center;", res$required_n) + ) + }) + + output$power_plot <- renderPlot({ + res <- main_results() + req(res$power_plot_data) + + ggplot(res$power_plot_data, aes(x = SampleSize, y = Power)) + + geom_line(color = "#007bff", size = 1.2) + + geom_hline(yintercept = input$power, linetype = "dashed", color = "red") + + geom_vline(xintercept = res$required_n, linetype = "dashed", color = "darkgreen") + + labs(title = "Power vs. Cỡ mẫu", x = "Cỡ mẫu (n)", y = "Power (1 - β)") + + scale_y_continuous(limits = c(0, 1)) + theme_minimal(base_size = 14) + }) + + # --- PHẦN PHÂN TÍCH EFFECT SIZE --- + effect_analysis_plot_data <- reactive({ + req(input$sig_level, input$power, input$alternative) + + effect_sizes <- seq(0.1, 0.9, by = 0.05) + + required_n_values <- sapply(effect_sizes, function(r_val) { + res <- tryCatch({ + pwr.r.test( + r = r_val, + sig.level = input$sig_level, + power = input$power, + alternative = input$alternative + )$n + }, error = function(e) NA) + ceiling(res) + }) + + data.frame(EffectSize = effect_sizes, RequiredN = required_n_values) + }) + + output$effect_analysis_plot <- renderPlot({ + plot_data <- effect_analysis_plot_data() + res <- main_results() + + p <- ggplot(plot_data, aes(x = EffectSize, y = RequiredN)) + + geom_line(color = "#28a745", size = 1.2) + + geom_point(color = "#28a745", size = 3, na.rm = TRUE) + + labs( + title = paste("Cỡ mẫu cần thiết vs. Effect Size (Power cố định =", input$power, ")"), + x = "Hệ số tương quan Spearman (ρ)", + y = "Cỡ mẫu cần thiết (n)" + ) + + theme_minimal(base_size = 14) + + if (!is.null(res) && !is.na(res$required_n)) { + p <- p + + geom_vline(xintercept = input$rho, linetype = "dotted", color = "blue", size = 1) + + geom_point(aes(x = input$rho, y = res$required_n), color = "blue", size = 5, shape = 18) + + annotate("text", x = input$rho, y = res$required_n, + label = paste("n =", res$required_n), vjust = -1.5, color = "blue", fontface = "bold") + } + + p + }) +} + +# Chạy ứng dụng Shiny +shinyApp(ui = ui, server = server) diff --git a/samplesize/TwowayANOVA/app.R b/samplesize/TwowayANOVA/app.R new file mode 100644 index 0000000..eaa429a --- /dev/null +++ b/samplesize/TwowayANOVA/app.R @@ -0,0 +1,253 @@ +# Tải các thư viện cần thiết +library(shiny) +library(bslib) +library(ggplot2) +library(shinycssloaders) +library(pwr) + +# --- Giao diện người dùng (UI) --- +ui <- fluidPage( + theme = bs_theme(version = 5, bootswatch = "cerulean"), + withMathJax(), + + titlePanel("Tính toán Cỡ mẫu cho Phân tích phương sai hai yếu tố (Two-Way ANOVA)"), + + sidebarLayout( + sidebarPanel( + h4("Tham số đầu vào"), + + numericInput("a", "Số mức của Yếu tố A:", value = 2, min = 2), + numericInput("b", "Số mức của Yếu tố B:", value = 3, min = 2), + + selectInput("effect_of_interest", "Hiệu ứng cần tính cỡ mẫu:", + choices = c("Hiệu ứng tương tác (A x B)" = "interaction", + "Hiệu ứng chính của A" = "main_A", + "Hiệu ứng chính của B" = "main_B")), + + numericInput("f2", + label = "Effect Size (Cohen's f-squared):", + value = 0.15, min = 0.01, step = 0.01), + helpText("Cohen's f-squared (f²) đo lường độ lớn của hiệu ứng. Quy ước: 0.02 (nhỏ), 0.15 (trung bình), 0.35 (lớn)."), + + sliderInput("sig_level", + label = "Mức ý nghĩa (\\(\\alpha\\)):", + min = 0.01, max = 0.10, value = 0.05, step = 0.01), + + sliderInput("power", + label = "Power mong muốn (\\(1 - \\beta\\)):", + min = 0.50, max = 0.99, value = 0.80, step = 0.01), + + hr(), + h5("Bậc tự do của tử số (u):"), + uiOutput("df_numerator_display") + ), + + mainPanel( + tabsetPanel( + id = "results_tabs", + type = "pills", + + tabPanel( + "Kết quả & Diễn giải", + h4("Kết quả tính toán"), + withSpinner(uiOutput("sample_size_output"), type = 6, color = "#007bff") + ), + + tabPanel( + "Đồ thị Power vs. Cỡ mẫu", + h4("Mối quan hệ giữa Power và Cỡ mẫu"), + withSpinner(plotOutput("power_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Phân tích Effect Size", + h4("Mối quan hệ giữa Cỡ mẫu và Effect Size"), + withSpinner(plotOutput("effect_analysis_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Giả thuyết và Công thức (Helper)", + h4("Giả thuyết của Two-Way ANOVA"), + p("Two-Way ANOVA kiểm tra ba bộ giả thuyết khác nhau:"), + tags$ol( + tags$li(tags$b("Hiệu ứng chính của Yếu tố A:"), "So sánh trung bình giữa các mức của yếu tố A. \\(H_0: \\mu_{A1} = \\mu_{A2} = \\dots\\)"), + tags$li(tags$b("Hiệu ứng chính của Yếu tố B:"), "So sánh trung bình giữa các mức của yếu tố B. \\(H_0: \\mu_{B1} = \\mu_{B2} = \\dots\\)"), + tags$li(tags$b("Hiệu ứng tương tác (A x B):"), "Kiểm tra xem hiệu ứng của yếu tố A có phụ thuộc vào mức của yếu tố B hay không (và ngược lại). \\(H_0: \\text{Không có sự tương tác}\\)") + ), + hr(), + h4("Công thức tính toán"), + + tags$b("1. Bậc tự do của tử số (Numerator df - u):"), + p("Giá trị `u` phụ thuộc vào hiệu ứng bạn đang kiểm tra:"), + p("$$ u_{A} = a - 1 $$"), + p("$$ u_{B} = b - 1 $$"), + p("$$ u_{A \\times B} = (a - 1)(b - 1) $$"), + p("Trong đó `a` và `b` là số mức của yếu tố A và B."), + + tags$b("2. Effect Size (Cohen's f-squared):"), + p("Cohen's f² đo lường tỷ lệ phương sai được giải thích bởi một hiệu ứng, so với phương sai không được giải thích."), + p("$$ f^2 = \\frac{R^2_{effect}}{1 - R^2_{effect}} $$"), + p("Trong đó \\(R^2_{effect}\\) là tỷ lệ phương sai được giải thích bởi hiệu ứng đang xét."), + + hr(), + h4("Ví dụ ứng dụng trong Y tế công cộng"), + p(tags$b("Tình huống:")), + p("Các nhà nghiên cứu muốn đánh giá hiệu quả của một can thiệp mới (ví dụ: một ứng dụng di động về sức khỏe) trong việc tăng số bước đi trung bình hàng ngày. Họ cũng muốn biết liệu hiệu quả của can thiệp có khác nhau giữa nam và nữ hay không."), + p(tags$b("Thiết kế nghiên cứu:")), + tags$ul( + tags$li("Yếu tố A (Can thiệp): Nhóm dùng app, Nhóm đối chứng (không dùng app). (a=2)"), + tags$li("Yếu tố B (Giới tính): Nam, Nữ. (b=2)"), + tags$li("Phân tích: Two-Way ANOVA."), + tags$li(tags$b("Câu hỏi chính (Hiệu ứng tương tác):"), "Liệu ứng dụng có hiệu quả hơn đối với nam hay nữ không? Để trả lời câu hỏi này, họ cần tính cỡ mẫu để có đủ power phát hiện hiệu ứng tương tác.") + ), + p(tags$b("Tính toán cỡ mẫu:")), + p("Nhà nghiên cứu chọn 'Hiệu ứng tương tác' trong ứng dụng. Họ kỳ vọng một hiệu ứng ở mức 'trung bình', và chọn \\(f^2 = 0.15\\). Họ nhập các giá trị này vào ứng dụng để tìm ra số người cần thiết cho mỗi ô trong thiết kế (nam/dùng app, nữ/dùng app, nam/không dùng, nữ/không dùng).") + ) + ) + ) + ) +) + +# --- Logic của máy chủ (Server) --- +server <- function(input, output, session) { + + # Tính toán bậc tự do tử số (u) + u <- reactive({ + req(input$a, input$b, input$effect_of_interest) + if (input$effect_of_interest == "main_A") { + return(input$a - 1) + } else if (input$effect_of_interest == "main_B") { + return(input$b - 1) + } else { # interaction + return((input$a - 1) * (input$b - 1)) + } + }) + + output$df_numerator_display <- renderUI({ + withMathJax(paste0("$$ u = ", u(), " $$")) + }) + + # --- PHẦN TÍNH TOÁN CHÍNH --- + main_results <- reactive({ + req(u(), input$f2, input$sig_level, input$power, input$a, input$b) + + # pwr.f2.test giải ra v (bậc tự do mẫu số) + pwr_result <- tryCatch({ + pwr.f2.test( + u = u(), + f2 = input$f2, + sig.level = input$sig_level, + power = input$power + ) + }, error = function(e) NULL) + + if (is.null(pwr_result)) return(NULL) + + v <- pwr_result$v + # Từ v, tính tổng cỡ mẫu N và cỡ mẫu mỗi ô n + # v = N - a*b => N = v + a*b + total_N <- ceiling(v + (input$a * input$b)) + required_n_per_cell <- ceiling(total_N / (input$a * input$b)) + + # Tạo dữ liệu cho đồ thị Power + sample_sizes_per_cell <- seq(5, required_n_per_cell + 50, by = 1) + + # Tính lại v cho mỗi n để tính power + v_for_plot <- (sample_sizes_per_cell * input$a * input$b) - (input$a * input$b) + + power_data <- tryCatch({ + pwr.f2.test( + u = u(), + v = v_for_plot, + f2 = input$f2, + sig.level = input$sig_level + ) + }, error = function(e) NULL) + + if(is.null(power_data)) return(list(required_n = required_n_per_cell, power_plot_data = NULL)) + + list( + required_n = required_n_per_cell, + power_plot_data = data.frame(SampleSize = sample_sizes_per_cell, Power = power_data$power) + ) + }) + + output$sample_size_output <- renderUI({ + res <- main_results() + + if (is.null(res)) { + return(tags$div(class = "alert alert-danger", "Có lỗi xảy ra trong quá trình tính toán.")) + } + + tagList( + tags$p("Để phát hiện một", tags$b(names(which(c("interaction"="Hiệu ứng tương tác (A x B)", "main_A"="Hiệu ứng chính của A", "main_B"="Hiệu ứng chính của B") == input$effect_of_interest))), "có độ lớn (f²) là", tags$b(input$f2), "với power là", tags$b(input$power), ", bạn cần một cỡ mẫu ước tính là:"), + tags$h3(style = "color: #007bff; text-align: center;", paste(res$required_n, "cho mỗi ô (cell)")), + tags$p(style = "text-align: center; font-style: italic;", "Tổng cỡ mẫu là ", res$required_n * input$a * input$b, ".") + ) + }) + + output$power_plot <- renderPlot({ + res <- main_results() + req(res$power_plot_data) + + ggplot(res$power_plot_data, aes(x = SampleSize, y = Power)) + + geom_line(color = "#007bff", size = 1.2) + + geom_hline(yintercept = input$power, linetype = "dashed", color = "red") + + geom_vline(xintercept = res$required_n, linetype = "dashed", color = "darkgreen") + + labs(title = "Power vs. Cỡ mẫu (cho mỗi ô)", x = "Cỡ mẫu cho mỗi ô (n)", y = "Power (1 - β)") + + scale_y_continuous(limits = c(0, 1)) + theme_minimal(base_size = 14) + }) + + # --- PHẦN PHÂN TÍCH EFFECT SIZE --- + effect_analysis_plot_data <- reactive({ + req(u(), input$sig_level, input$power, input$a, input$b) + + effect_sizes <- seq(0.01, 0.5, by = 0.01) + + required_n_values <- sapply(effect_sizes, function(f2_val) { + res <- tryCatch({ + pwr.f2.test( + u = u(), + f2 = f2_val, + sig.level = input$sig_level, + power = input$power + )$v + }, error = function(e) NA) + + if(is.na(res)) return(NA) + + total_N <- res + (input$a * input$b) + ceiling(total_N / (input$a * input$b)) + }) + + data.frame(EffectSize = effect_sizes, RequiredN = required_n_values) + }) + + output$effect_analysis_plot <- renderPlot({ + plot_data <- effect_analysis_plot_data() + res <- main_results() + + p <- ggplot(plot_data, aes(x = EffectSize, y = RequiredN)) + + geom_line(color = "#28a745", size = 1.2) + + geom_point(color = "#28a745", size = 3, na.rm = TRUE) + + labs( + title = paste("Cỡ mẫu cần thiết vs. Effect Size (Power cố định =", input$power, ")"), + x = "Effect Size (f²)", + y = "Cỡ mẫu cần thiết cho mỗi ô (n)" + ) + + theme_minimal(base_size = 14) + + if (!is.null(res) && !is.na(res$required_n)) { + p <- p + + geom_vline(xintercept = input$f2, linetype = "dotted", color = "blue", size = 1) + + geom_point(aes(x = input$f2, y = res$required_n), color = "blue", size = 5, shape = 18) + + annotate("text", x = input$f2, y = res$required_n, + label = paste("n =", res$required_n), vjust = -1.5, color = "blue", fontface = "bold") + } + + p + }) +} + +# Chạy ứng dụng Shiny +shinyApp(ui = ui, server = server) diff --git a/samplesize/Wilcoxon-Signed-Rank/app.R b/samplesize/Wilcoxon-Signed-Rank/app.R new file mode 100644 index 0000000..ff4889a --- /dev/null +++ b/samplesize/Wilcoxon-Signed-Rank/app.R @@ -0,0 +1,203 @@ +# Tải các thư viện cần thiết +library(shiny) +library(bslib) +library(ggplot2) +library(shinycssloaders) +library(sn) # Gói để tạo phân phối lệch + +# --- Giao diện người dùng (UI) --- +ui <- fluidPage( + theme = bs_theme(version = 5, bootswatch = "cerulean"), + withMathJax(), + + titlePanel("Tính toán Cỡ mẫu cho Kiểm định Dấu-Hạng Wilcoxon"), + + sidebarLayout( + sidebarPanel( + h4("Tham số đầu vào"), + + selectInput("dist_shape", "Hình dạng phân phối của sự khác biệt:", + choices = c("Đối xứng, đuôi dày (t, df=5)" = "t_dist", + "Lệch phải (Skew-Normal)" = "skew_right", + "Đối xứng (Normal)" = "normal")), + + numericInput("effect_size", + label = "Effect Size (Median Shift / SD):", + value = 0.5, min = 0.1, step = 0.1), + helpText("Đây là độ lớn của sự dịch chuyển của trung vị (median), được chuẩn hóa bằng độ lệch chuẩn của sự khác biệt."), + + selectInput("alternative", "Loại kiểm định:", + choices = c("Hai phía (Two-sided)" = "two.sided", + "Lớn hơn (Greater)" = "greater", + "Nhỏ hơn (Less)" = "less")), + + sliderInput("sig_level", + label = "Mức ý nghĩa (\\(\\alpha\\)):", + min = 0.01, max = 0.10, value = 0.05, step = 0.01), + + sliderInput("power", + label = "Power mong muốn (\\(1 - \\beta\\)):", + min = 0.50, max = 0.99, value = 0.80, step = 0.01), + + numericInput("n_sims", + label = "Số lần lặp mô phỏng:", + value = 500, min = 100, max = 5000), + + hr(), + actionButton("calculate", "Bắt đầu tính toán", class = "btn-primary"), + helpText("Vì tính toán dựa trên mô phỏng, quá trình có thể mất vài giây.") + ), + + mainPanel( + tabsetPanel( + id = "results_tabs", + type = "pills", + + tabPanel( + "Kết quả & Diễn giải", + h4("Kết quả tính toán"), + withSpinner(uiOutput("sample_size_output"), type = 6, color = "#007bff") + ), + + tabPanel( + "Đồ thị Power vs. Cỡ mẫu", + h4("Mối quan hệ giữa Power và Số cặp"), + withSpinner(plotOutput("power_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Phân tích Effect Size", + h4("Mối quan hệ giữa Cỡ mẫu và Effect Size"), + withSpinner(plotOutput("effect_analysis_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Giả thuyết và Công thức (Helper)", + h4("Giả thuyết của Kiểm định Dấu-Hạng Wilcoxon"), + p("Đây là kiểm định phi tham số thay thế cho kiểm định t ghép cặp. Nó kiểm tra xem liệu trung vị (median) của sự khác biệt trong các cặp có bằng 0 hay không."), + p("$$H_0: \\text{Median của sự khác biệt} = 0$$"), + p("$$H_a: \\text{Median của sự khác biệt} \\neq 0 \\quad (\\text{hoặc } > 0 \\text{ hoặc } < 0\\text{)}$$"), + + tags$div(class = "alert alert-info", + tags$b("Khi nào sử dụng?"), + p("Sử dụng kiểm định này thay cho kiểm định t ghép cặp khi giả định về phân phối chuẩn của sự khác biệt không được đáp ứng (ví dụ: dữ liệu bị lệch nhiều hoặc có các giá trị ngoại lai).") + ), + hr(), + + h4("Phương pháp tính toán"), + p("Vì không có công thức giải tích đơn giản, ứng dụng sử dụng phương pháp mô phỏng Monte Carlo để ước tính cỡ mẫu. Quá trình này bao gồm việc tạo ra hàng nghìn bộ dữ liệu giả định, áp dụng kiểm định cho từng bộ, và đếm tỷ lệ các kiểm định phát hiện ra hiệu ứng."), + + hr(), + h4("Ví dụ ứng dụng trong Y tế công cộng"), + p(tags$b("Tình huống:")), + p("Một chương trình can thiệp nhằm giảm số lượng đồ uống có đường mà thanh thiếu niên tiêu thụ mỗi tuần. Nhà nghiên cứu đo lường số lượng đồ uống trước và sau chương trình."), + p(tags$b("Tại sao dùng Wilcoxon?")), + p("Sự thay đổi trong hành vi này có thể không tuân theo phân phối chuẩn. Nhiều người có thể không thay đổi (khác biệt = 0), một số thay đổi ít, và một số ít thay đổi rất nhiều, tạo ra một phân phối bị lệch. Do đó, kiểm định trung vị (median) sẽ phù hợp và mạnh mẽ hơn là kiểm định trung bình (mean)."), + + p(tags$b("Tính toán cỡ mẫu:")), + p("Nhà nghiên cứu cần xác định số lượng thanh thiếu niên cần tham gia. Họ kỳ vọng một sự thay đổi có effect size khoảng 0.4. Họ có thể nhập các giá trị này vào ứng dụng để tìm ra số cặp cần thiết cho nghiên cứu của mình.") + ) + ) + ) + ) +) + +# --- Logic của máy chủ (Server) --- +server <- function(input, output, session) { + + simulation_results <- eventReactive(input$calculate, { + + inputs <- list( + dist_shape = input$dist_shape, + effect_size = input$effect_size, + alternative = input$alternative, + sig_level = input$sig_level, + target_power = input$power, + n_sims = input$n_sims + ) + + sample_sizes <- seq(10, 500, by = 5) + powers <- numeric(length(sample_sizes)) + + withProgress(message = 'Đang chạy mô phỏng...', value = 0, { + + for (i in 1:length(sample_sizes)) { + n <- sample_sizes[i] + + p_values <- replicate(inputs$n_sims, { + + # Tạo dữ liệu khác biệt từ phân phối đã chọn + if (inputs$dist_shape == "t_dist") { + random_data <- rt(n, df = 5) + inputs$effect_size + } else if (inputs$dist_shape == "skew_right") { + # Sử dụng gói 'sn' để tạo dữ liệu lệch + random_data <- rsn(n, xi = inputs$effect_size, omega = 1, alpha = 4) + } else { # normal + random_data <- rnorm(n, mean = inputs$effect_size, sd = 1) + } + + # Thực hiện kiểm định Wilcoxon Signed-Rank + # mu=0 là giả thuyết không + wilcox.test(random_data, mu = 0, alternative = inputs$alternative)$p.value + }) + + powers[i] <- mean(p_values < inputs$sig_level, na.rm = TRUE) + + incProgress(1/length(sample_sizes), detail = paste("Cỡ mẫu n =", n)) + + if (powers[i] >= inputs$target_power) { + sample_sizes <- sample_sizes[1:i] + powers <- powers[1:i] + break + } + } + }) + + list( + sample_sizes = sample_sizes, + powers = powers, + inputs = inputs + ) + }) + + output$sample_size_output <- renderUI({ + res <- simulation_results() + + required_n_index <- which(res$powers >= res$inputs$target_power)[1] + + if (!is.na(required_n_index)) { + required_n <- res$sample_sizes[required_n_index] + tagList( + tags$p("Để phát hiện một effect size là", tags$b(res$inputs$effect_size), "với power là", tags$b(res$inputs$target_power), ", bạn cần một cỡ mẫu ước tính là:"), + tags$h3(style = "color: #007bff; text-align: center;", paste(required_n, "cặp quan sát")) + ) + } else { + tags$div(class = "alert alert-warning", "Không đạt được power mong muốn trong khoảng cỡ mẫu đã thử (tối đa 500).") + } + }) + + output$power_plot <- renderPlot({ + res <- simulation_results() + plot_data <- data.frame(SampleSize = res$sample_sizes, Power = res$powers) + + ggplot(plot_data, aes(x = SampleSize, y = Power)) + + geom_line(color = "#007bff", size = 1.2) + + geom_point(color = "#007bff", size = 3) + + geom_hline(yintercept = res$inputs$target_power, linetype = "dashed", color = "red") + + labs(title = "Power vs. Số cặp", x = "Số cặp (n)", y = "Power thực nghiệm (1 - β)") + + scale_y_continuous(limits = c(0, 1)) + theme_minimal(base_size = 14) + }) + + output$effect_analysis_plot <- renderPlot({ + # Phân tích này rất nặng, nên có thể để trống hoặc đơn giản hóa + # Hiện tại để trống để tránh thời gian chờ quá lâu + ggplot() + + labs(title = "Phân tích này không được thực hiện tự động do thời gian tính toán lâu.", + subtitle = "Vui lòng chạy lại với các giá trị Effect Size khác nhau để so sánh.") + + theme_minimal() + }) + +} + +# Chạy ứng dụng Shiny +shinyApp(ui = ui, server = server) diff --git a/samplesize/aa/app.R b/samplesize/aa/app.R new file mode 100644 index 0000000..aaaa21e --- /dev/null +++ b/samplesize/aa/app.R @@ -0,0 +1,66 @@ +library(shiny) +library(pwr) +library(ggplot2) +library(latex2exp) + +ui <- fluidPage( + titlePanel("Tính cỡ mẫu cho kiểm định t-test một mẫu"), + + sidebarLayout( + sidebarPanel( + numericInput("delta", "Hiệu ứng (mean difference):", value = 0.5, step = 0.1), + numericInput("sd", "Độ lệch chuẩn (standard deviation):", value = 1, step = 0.1), + numericInput("sig.level", "Mức ý nghĩa (alpha):", value = 0.05, min = 0.001, max = 0.1, step = 0.01), + numericInput("power", "Độ mạnh kiểm định mong muốn (power):", value = 0.8, min = 0.5, max = 0.99, step = 0.01) + ), + + mainPanel( + h4("Công thức tính cỡ mẫu (LaTeX):"), + uiOutput("latexFormula"), + h4("Kết quả tính cỡ mẫu:"), + verbatimTextOutput("sampleSize"), + h4("Biểu đồ: Power vs. Sample Size"), + plotOutput("powerPlot") + ) + ) +) + +server <- function(input, output) { + output$sampleSize <- renderPrint({ + result <- pwr.t.test(d = input$delta / input$sd, + sig.level = input$sig.level, + power = input$power, + type = "one.sample", + alternative = "two.sided") + result$n + }) + + output$latexFormula <- renderUI({ + withMathJax( + helpText('$$n = \\left( \\frac{(z_{1-\\alpha/2} + z_{power}) \\cdot \\sigma}{\\delta} \\right)^2$$') + ) + }) + + output$powerPlot <- renderPlot({ + sample_sizes <- seq(5, 100, by = 1) + powers <- sapply(sample_sizes, function(n) { + pwr.t.test(n = n, + d = input$delta / input$sd, + sig.level = input$sig.level, + type = "one.sample", + alternative = "two.sided")$power + }) + + df <- data.frame(SampleSize = sample_sizes, Power = powers) + + ggplot(df, aes(x = SampleSize, y = Power)) + + geom_line(color = "blue", size = 1) + + geom_hline(yintercept = input$power, linetype = "dashed", color = "red") + + labs(title = "Quan hệ giữa Power và Cỡ mẫu", + x = "Cỡ mẫu", + y = "Power") + + theme_minimal() + }) +} + +shinyApp(ui = ui, server = server) \ No newline at end of file diff --git a/samplesize/anova/app.R b/samplesize/anova/app.R new file mode 100644 index 0000000..b752753 --- /dev/null +++ b/samplesize/anova/app.R @@ -0,0 +1,218 @@ +# Tải các thư viện cần thiết +library(shiny) +library(bslib) +library(ggplot2) +library(shinycssloaders) +library(pwr) + +# --- Giao diện người dùng (UI) --- +ui <- fluidPage( + theme = bs_theme(version = 5, bootswatch = "cerulean"), + withMathJax(), + + titlePanel("Tính toán Cỡ mẫu cho Phân tích phương sai một yếu tố (One-Way ANOVA)"), + + sidebarLayout( + sidebarPanel( + h4("Tham số đầu vào"), + + numericInput("k", "Số lượng nhóm so sánh (k):", value = 3, min = 2), + + numericInput("f", + label = "Effect Size (Cohen's f):", + value = 0.25, min = 0.05, step = 0.05), + helpText("Cohen's f đo lường độ lớn của sự khác biệt giữa các trung bình nhóm. Quy ước: 0.1 (nhỏ), 0.25 (trung bình), 0.4 (lớn)."), + + sliderInput("sig_level", + label = "Mức ý nghĩa (\\(\\alpha\\)):", + min = 0.01, max = 0.10, value = 0.05, step = 0.01), + + sliderInput("power", + label = "Power mong muốn (\\(1 - \\beta\\)):", + min = 0.50, max = 0.99, value = 0.80, step = 0.01), + + hr(), + h5("Công thức Effect Size (f):"), + # Hiển thị công thức tính effect size + uiOutput("effect_size_formula_display") + ), + + mainPanel( + tabsetPanel( + id = "results_tabs", + type = "pills", + + tabPanel( + "Kết quả & Diễn giải", + h4("Kết quả tính toán"), + withSpinner(uiOutput("sample_size_output"), type = 6, color = "#007bff") + ), + + tabPanel( + "Đồ thị Power vs. Cỡ mẫu", + h4("Mối quan hệ giữa Power và Cỡ mẫu"), + withSpinner(plotOutput("power_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Phân tích Effect Size", + h4("Mối quan hệ giữa Cỡ mẫu và Effect Size"), + p("Đồ thị này cho thấy cỡ mẫu cần thiết thay đổi như thế nào khi Effect Size thay đổi."), + withSpinner(plotOutput("effect_analysis_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Giả thuyết và Công thức (Helper)", + h4("Giả thuyết của One-Way ANOVA"), + p("Phân tích phương sai một yếu tố (One-Way ANOVA) được sử dụng để so sánh trung bình của ba hay nhiều nhóm độc lập."), + p("$$H_0: \\mu_1 = \\mu_2 = \\dots = \\mu_k$$"), + p("$$H_a: \\text{Có ít nhất một cặp trung bình không bằng nhau}$$"), + hr(), + h4("Công thức tính toán"), + + tags$b("1. Thống kê kiểm định (F-statistic):"), + p("Giá trị thống kê F so sánh sự biến thiên giữa các nhóm (Mean Square Between) với sự biến thiên bên trong mỗi nhóm (Mean Square Within)."), + p("$$ F = \\frac{MS_{between}}{MS_{within}} $$"), + + tags$b("2. Effect Size (Cohen's f):"), + p("Cohen's f đo lường độ lớn của sự khác biệt giữa các trung bình nhóm, được chuẩn hóa bằng độ lệch chuẩn bên trong nhóm."), + p("$$ f = \\frac{\\sigma_m}{\\sigma} $$"), + p("Trong đó \\(\\sigma_m\\) là độ lệch chuẩn của các trung bình nhóm và \\(\\sigma\\) là độ lệch chuẩn bên trong mỗi nhóm."), + + hr(), + h4("Ví dụ ứng dụng trong Y tế công cộng"), + p(tags$b("Tình huống:")), + p("Một nhà nghiên cứu muốn so sánh hiệu quả của ba liệu pháp khác nhau (A: thuốc mới, B: thuốc tiêu chuẩn, C: giả dược) trong việc giảm mức cholesterol ở bệnh nhân."), + p(tags$b("Thiết kế nghiên cứu:")), + tags$ul( + tags$li("Số lượng nhóm (k): 3."), + tags$li("Phân tích: One-Way ANOVA sẽ được sử dụng để xem liệu có sự khác biệt đáng kể về mức giảm cholesterol trung bình giữa ba nhóm hay không.") + ), + p(tags$b("Tính toán cỡ mẫu:")), + p("Trước khi tuyển bệnh nhân, nhà nghiên cứu cần xác định số lượng cho mỗi nhóm. Dựa trên các nghiên cứu sơ bộ, họ kỳ vọng một sự khác biệt ở mức 'trung bình' giữa các liệu pháp. Họ chọn một effect size \\(f = 0.25\\)."), + p("Họ có thể nhập các giá trị này (k=3, f=0.25, power=0.8, alpha=0.05) vào ứng dụng để tìm ra số bệnh nhân cần thiết cho mỗi nhóm trị liệu.") + ) + ) + ) + ) +) + +# --- Logic của máy chủ (Server) --- +server <- function(input, output, session) { + + # Hiển thị công thức tính effect size + output$effect_size_formula_display <- renderUI({ + withMathJax("$$ f = \\sqrt{\\frac{\\sum_{i=1}^{k} p_i(\\mu_i - \\bar{\\mu})^2}{\\sigma^2}} $$") + }) + + # --- PHẦN TÍNH TOÁN CHÍNH --- + main_results <- reactive({ + req(input$k, input$f, input$sig_level, input$power) + + pwr_result <- tryCatch({ + pwr.anova.test( + k = input$k, + f = input$f, + sig.level = input$sig_level, + power = input$power + ) + }, error = function(e) NULL) + + if (is.null(pwr_result)) return(NULL) + + required_n <- ceiling(pwr_result$n) + + sample_sizes <- seq(5, required_n + 50, by = 1) + + power_data <- tryCatch({ + pwr.anova.test( + k = input$k, + n = sample_sizes, + f = input$f, + sig.level = input$sig_level + ) + }, error = function(e) NULL) + + if(is.null(power_data)) return(list(required_n = required_n, power_plot_data = NULL)) + + list( + required_n = required_n, + power_plot_data = data.frame(SampleSize = power_data$n, Power = power_data$power) + ) + }) + + output$sample_size_output <- renderUI({ + res <- main_results() + + if (is.null(res)) { + return(tags$div(class = "alert alert-danger", "Có lỗi xảy ra trong quá trình tính toán. Vui lòng kiểm tra lại các tham số.")) + } + + tagList( + tags$p("Để phát hiện một sự khác biệt giữa", tags$b(input$k), "nhóm có độ lớn (effect size f) là", tags$b(input$f), "với power là", tags$b(input$power), "và mức ý nghĩa", tags$b(input$sig_level), ", bạn cần một cỡ mẫu ước tính là:"), + tags$h3(style = "color: #007bff; text-align: center;", paste(res$required_n, "cho mỗi nhóm")), + tags$p(style = "text-align: center; font-style: italic;", "Tổng cỡ mẫu là ", res$required_n * input$k, ".") + ) + }) + + output$power_plot <- renderPlot({ + res <- main_results() + req(res$power_plot_data) + + ggplot(res$power_plot_data, aes(x = SampleSize, y = Power)) + + geom_line(color = "#007bff", size = 1.2) + + geom_hline(yintercept = input$power, linetype = "dashed", color = "red") + + geom_vline(xintercept = res$required_n, linetype = "dashed", color = "darkgreen") + + labs(title = "Power vs. Cỡ mẫu (cho mỗi nhóm)", x = "Cỡ mẫu cho mỗi nhóm (n)", y = "Power (1 - β)") + + scale_y_continuous(limits = c(0, 1)) + theme_minimal(base_size = 14) + }) + + # --- PHẦN PHÂN TÍCH EFFECT SIZE --- + effect_analysis_plot_data <- reactive({ + req(input$k, input$sig_level, input$power) + + effect_sizes <- seq(0.05, 0.6, by = 0.05) + + required_n_values <- sapply(effect_sizes, function(f_val) { + res <- tryCatch({ + pwr.anova.test( + k = input$k, + f = f_val, + sig.level = input$sig_level, + power = input$power + )$n + }, error = function(e) NA) + ceiling(res) + }) + + data.frame(EffectSize = effect_sizes, RequiredN = required_n_values) + }) + + output$effect_analysis_plot <- renderPlot({ + plot_data <- effect_analysis_plot_data() + res <- main_results() + + p <- ggplot(plot_data, aes(x = EffectSize, y = RequiredN)) + + geom_line(color = "#28a745", size = 1.2) + + geom_point(color = "#28a745", size = 3, na.rm = TRUE) + + labs( + title = paste("Cỡ mẫu cần thiết vs. Effect Size (Power cố định =", input$power, ")"), + x = "Effect Size (f)", + y = "Cỡ mẫu cần thiết cho mỗi nhóm (n)" + ) + + theme_minimal(base_size = 14) + + if (!is.null(res) && !is.na(res$required_n)) { + p <- p + + geom_vline(xintercept = input$f, linetype = "dotted", color = "blue", size = 1) + + geom_point(aes(x = input$f, y = res$required_n), color = "blue", size = 5, shape = 18) + + annotate("text", x = input$f, y = res$required_n, + label = paste("n =", res$required_n), vjust = -1.5, color = "blue", fontface = "bold") + } + + p + }) +} + +# Chạy ứng dụng Shiny +shinyApp(ui = ui, server = server) diff --git a/samplesize/chisquare/app.R b/samplesize/chisquare/app.R new file mode 100644 index 0000000..0604b87 --- /dev/null +++ b/samplesize/chisquare/app.R @@ -0,0 +1,242 @@ +# Tải các thư viện cần thiết +library(shiny) +library(bslib) +library(ggplot2) +library(shinycssloaders) +library(pwr) + +# --- Giao diện người dùng (UI) --- +ui <- fluidPage( + theme = bs_theme(version = 5, bootswatch = "cerulean"), + withMathJax(), + + titlePanel("Tính toán Cỡ mẫu cho Kiểm định Độc lập Chi bình phương"), + + sidebarLayout( + sidebarPanel( + h4("Tham số đầu vào"), + + numericInput("w", + label = "Effect Size (w):", + value = 0.3, min = 0.05, step = 0.05), + helpText("Cohen's w đo lường độ lớn của mối liên hệ. Quy ước: 0.1 (nhỏ), 0.3 (trung bình), 0.5 (lớn)."), + + numericInput("rows", "Số hàng của bảng:", value = 2, min = 2), + numericInput("cols", "Số cột của bảng:", value = 2, min = 2), + + # Hiển thị bậc tự do được tính toán + uiOutput("df_display"), + + sliderInput("sig_level", + label = "Mức ý nghĩa (\\(\\alpha\\)):", + min = 0.01, max = 0.10, value = 0.05, step = 0.01), + + sliderInput("power", + label = "Power mong muốn (\\(1 - \\beta\\)):", + min = 0.50, max = 0.99, value = 0.80, step = 0.01), + + hr(), + helpText("Ứng dụng sử dụng hàm pwr.chisq.test() từ gói 'pwr'. Kết quả và đồ thị sẽ tự động cập nhật ngay lập tức.") + ), + + mainPanel( + tabsetPanel( + id = "results_tabs", + type = "pills", + + tabPanel( + "Kết quả & Diễn giải", + h4("Kết quả tính toán"), + withSpinner(uiOutput("sample_size_output"), type = 6, color = "#007bff") + ), + + tabPanel( + "Đồ thị Power vs. Cỡ mẫu", + h4("Mối quan hệ giữa Power và Tổng Cỡ mẫu"), + withSpinner(plotOutput("power_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Phân tích Effect Size", + h4("Mối quan hệ giữa Cỡ mẫu và Effect Size"), + p("Đồ thị này cho thấy tổng cỡ mẫu cần thiết thay đổi như thế nào khi Effect Size thay đổi."), + withSpinner(plotOutput("effect_analysis_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Giả thuyết và Công thức (Helper)", + h4("Giả thuyết của Kiểm định Độc lập Chi bình phương"), + p("Kiểm định này được sử dụng để xác định xem có mối liên hệ (association) giữa hai biến phân loại hay không."), + p("$$H_0: \\text{Hai biến là độc lập (không có mối liên hệ)}$$"), + p("$$H_a: \\text{Hai biến không độc lập (có mối liên hệ)}$$"), + hr(), + h4("Công thức tính toán"), + + tags$b("1. Thống kê kiểm định (Test Statistic):"), + p("Giá trị thống kê \\(\\chi^2\\) được tính như sau:"), + p("$$ \\chi^2 = \\sum \\frac{(O_{ij} - E_{ij})^2}{E_{ij}} $$"), + p("Trong đó \\(O_{ij}\\) là tần số quan sát và \\(E_{ij}\\) là tần số kỳ vọng trong ô (hàng i, cột j) của bảng chéo."), + + tags$b("2. Bậc tự do (Degrees of Freedom - df):"), + p("$$ df = (\\text{số hàng} - 1) \\times (\\text{số cột} - 1) $$"), + + tags$b("3. Effect Size (w):"), + p("Cohen's w là một thước đo độ lớn của mối liên hệ, được tính từ giá trị Chi bình phương:"), + p("$$ w = \\sqrt{ \\frac{\\chi^2}{N} } $$"), + p("Trong đó N là tổng cỡ mẫu. Để tính toán cỡ mẫu, bạn cần ước tính giá trị `w` mà bạn kỳ vọng sẽ phát hiện được."), + + hr(), + h4("Ví dụ ứng dụng trong Y tế công cộng"), + p(tags$b("Tình huống:")), + p("Một nhà dịch tễ học muốn nghiên cứu mối liên hệ giữa tình trạng hút thuốc lá và sự xuất hiện của bệnh ung thư phổi. Họ thu thập dữ liệu và lập một bảng chéo 2x2."), + p(tags$b("Thiết kế nghiên cứu:")), + tags$ul( + tags$li("Biến 1 (Hàng): Tình trạng hút thuốc (Hút thuốc, Không hút thuốc)."), + tags$li("Biến 2 (Cột): Tình trạng bệnh (Mắc ung thư phổi, Không mắc ung thư phổi)."), + tags$li("Bảng chéo: 2 hàng và 2 cột."), + tags$li("Bậc tự do (df): (2-1) x (2-1) = 1.") + ), + p(tags$b("Tính toán cỡ mẫu:")), + p("Trước khi tiến hành nghiên cứu, nhà dịch tễ học muốn biết cần bao nhiêu người tham gia. Dựa trên các tài liệu y văn, họ kỳ vọng sẽ tìm thấy một mối liên hệ ở mức độ 'trung bình', và chọn effect size \\(w = 0.3\\)."), + p("Họ có thể nhập các giá trị này (w=0.3, df=1, power=0.8, alpha=0.05) vào ứng dụng để tìm ra tổng cỡ mẫu cần thiết cho nghiên cứu của mình.") + ) + ) + ) + ) +) + +# --- Logic của máy chủ (Server) --- +server <- function(input, output, session) { + + # Tính toán bậc tự do (df) một cách reactive + df <- reactive({ + req(input$rows, input$cols) + (input$rows - 1) * (input$cols - 1) + }) + + # Hiển thị df ra giao diện + output$df_display <- renderUI({ + tagList( + tags$p(tags$b("Bậc tự do (df) = "), df()) + ) + }) + + # --- PHẦN TÍNH TOÁN CHÍNH --- + main_results <- reactive({ + req(input$w, df(), input$sig_level, input$power) + + # Bậc tự do phải > 0 + if (df() <= 0) return(NULL) + + pwr_result <- tryCatch({ + pwr.chisq.test( + w = input$w, + df = df(), + sig.level = input$sig_level, + power = input$power + ) + }, error = function(e) NULL) + + if (is.null(pwr_result)) return(NULL) + + required_n <- ceiling(pwr_result$N) + + sample_sizes <- seq(10, required_n + 100, by = 2) + + power_data <- tryCatch({ + pwr.chisq.test( + N = sample_sizes, + w = input$w, + df = df(), + sig.level = input$sig_level + ) + }, error = function(e) NULL) + + if(is.null(power_data)) return(list(required_n = required_n, power_plot_data = NULL)) + + list( + required_n = required_n, + power_plot_data = data.frame(SampleSize = power_data$N, Power = power_data$power) + ) + }) + + output$sample_size_output <- renderUI({ + if (df() <= 0) { + return(tags$div(class = "alert alert-warning", "Bậc tự do phải lớn hơn 0. Vui lòng kiểm tra lại số hàng và số cột.")) + } + + res <- main_results() + + if (is.null(res)) { + return(tags$div(class = "alert alert-danger", "Có lỗi xảy ra trong quá trình tính toán. Vui lòng kiểm tra lại các tham số.")) + } + + tagList( + tags$p("Để phát hiện một mối liên hệ có độ lớn (effect size w) là", tags$b(input$w), "với power là", tags$b(input$power), "và mức ý nghĩa", tags$b(input$sig_level), ", bạn cần một tổng cỡ mẫu ước tính là:"), + tags$h3(style = "color: #007bff; text-align: center;", res$required_n) + ) + }) + + output$power_plot <- renderPlot({ + req(main_results(), main_results()$power_plot_data) + res <- main_results() + + ggplot(res$power_plot_data, aes(x = SampleSize, y = Power)) + + geom_line(color = "#007bff", size = 1.2) + + geom_hline(yintercept = input$power, linetype = "dashed", color = "red") + + geom_vline(xintercept = res$required_n, linetype = "dashed", color = "darkgreen") + + labs(title = "Power vs. Tổng Cỡ mẫu", x = "Tổng Cỡ mẫu (N)", y = "Power (1 - β)") + + scale_y_continuous(limits = c(0, 1)) + theme_minimal(base_size = 14) + }) + + # --- PHẦN PHÂN TÍCH EFFECT SIZE --- + effect_analysis_plot_data <- reactive({ + req(df(), input$sig_level, input$power) + if (df() <= 0) return(NULL) + + effect_sizes <- seq(0.05, 0.8, by = 0.05) + + required_n_values <- sapply(effect_sizes, function(w_val) { + res <- tryCatch({ + pwr.chisq.test( + w = w_val, + df = df(), + sig.level = input$sig_level, + power = input$power + )$N + }, error = function(e) NA) + ceiling(res) + }) + + data.frame(EffectSize = effect_sizes, RequiredN = required_n_values) + }) + + output$effect_analysis_plot <- renderPlot({ + req(effect_analysis_plot_data()) + plot_data <- effect_analysis_plot_data() + res <- main_results() + + p <- ggplot(plot_data, aes(x = EffectSize, y = RequiredN)) + + geom_line(color = "#28a745", size = 1.2) + + geom_point(color = "#28a745", size = 3, na.rm = TRUE) + + labs( + title = paste("Cỡ mẫu cần thiết vs. Effect Size (Power cố định =", input$power, ")"), + x = "Effect Size (w)", + y = "Tổng Cỡ mẫu cần thiết (N)" + ) + + theme_minimal(base_size = 14) + + if (!is.null(res) && !is.na(res$required_n)) { + p <- p + + geom_vline(xintercept = input$w, linetype = "dotted", color = "blue", size = 1) + + geom_point(aes(x = input$w, y = res$required_n), color = "blue", size = 5, shape = 18) + + annotate("text", x = input$w, y = res$required_n, + label = paste("N =", res$required_n), vjust = -1.5, color = "blue", fontface = "bold") + } + + p + }) +} + +# Chạy ứng dụng Shiny +shinyApp(ui = ui, server = server) diff --git a/samplesize/chisquaregoodfit/app.R b/samplesize/chisquaregoodfit/app.R new file mode 100644 index 0000000..abe0353 --- /dev/null +++ b/samplesize/chisquaregoodfit/app.R @@ -0,0 +1,242 @@ +# Tải các thư viện cần thiết +library(shiny) +library(bslib) +library(ggplot2) +library(shinycssloaders) +library(pwr) + +# --- Giao diện người dùng (UI) --- +ui <- fluidPage( + theme = bs_theme(version = 5, bootswatch = "cerulean"), + withMathJax(), + + titlePanel("Tính toán Cỡ mẫu cho Kiểm định Mức độ phù hợp (Goodness-of-Fit)"), + + sidebarLayout( + sidebarPanel( + h4("Tham số đầu vào"), + + numericInput("w", + label = "Effect Size (w):", + value = 0.3, min = 0.05, step = 0.05), + helpText("Cohen's w đo lường mức độ khác biệt giữa tần số quan sát và tần số kỳ vọng. Quy ước: 0.1 (nhỏ), 0.3 (trung bình), 0.5 (lớn)."), + + numericInput("k", "Số lượng nhóm/phân loại (k):", value = 4, min = 2), + + # Hiển thị bậc tự do được tính toán + uiOutput("df_display"), + + sliderInput("sig_level", + label = "Mức ý nghĩa (\\(\\alpha\\)):", + min = 0.01, max = 0.10, value = 0.05, step = 0.01), + + sliderInput("power", + label = "Power mong muốn (\\(1 - \\beta\\)):", + min = 0.50, max = 0.99, value = 0.80, step = 0.01), + + hr(), + helpText("Ứng dụng sử dụng hàm pwr.chisq.test() từ gói 'pwr'. Kết quả và đồ thị sẽ tự động cập nhật ngay lập tức.") + ), + + mainPanel( + tabsetPanel( + id = "results_tabs", + type = "pills", + + tabPanel( + "Kết quả & Diễn giải", + h4("Kết quả tính toán"), + withSpinner(uiOutput("sample_size_output"), type = 6, color = "#007bff") + ), + + tabPanel( + "Đồ thị Power vs. Cỡ mẫu", + h4("Mối quan hệ giữa Power và Tổng Cỡ mẫu"), + withSpinner(plotOutput("power_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Phân tích Effect Size", + h4("Mối quan hệ giữa Cỡ mẫu và Effect Size"), + p("Đồ thị này cho thấy tổng cỡ mẫu cần thiết thay đổi như thế nào khi Effect Size thay đổi."), + withSpinner(plotOutput("effect_analysis_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Giả thuyết và Công thức (Helper)", + h4("Giả thuyết của Kiểm định Mức độ phù hợp Chi bình phương"), + p("Kiểm định này được sử dụng để xác định xem tần số quan sát của một biến phân loại có phù hợp với một phân phối kỳ vọng (lý thuyết) hay không."), + p("$$H_0: \\text{Tần số quan sát phù hợp với tần số kỳ vọng}$$"), + p("$$H_a: \\text{Tần số quan sát không phù hợp với tần số kỳ vọng}$$"), + hr(), + h4("Công thức tính toán"), + + tags$b("1. Thống kê kiểm định (Test Statistic):"), + p("Giá trị thống kê \\(\\chi^2\\) được tính như sau:"), + p("$$ \\chi^2 = \\sum_{i=1}^{k} \\frac{(O_i - E_i)^2}{E_i} $$"), + p("Trong đó \\(O_i\\) là tần số quan sát và \\(E_i\\) là tần số kỳ vọng cho nhóm thứ \\(i\\)."), + + tags$b("2. Bậc tự do (Degrees of Freedom - df):"), + p("$$ df = k - 1 $$"), + p("Trong đó \\(k\\) là số lượng nhóm hoặc phân loại của biến."), + + tags$b("3. Effect Size (w):"), + p("Cohen's w đo lường độ lớn của sự khác biệt giữa tỷ lệ quan sát (\\(P_{1i}\\)) và tỷ lệ kỳ vọng (\\(P_{0i}\\)):"), + p("$$ w = \\sqrt{ \\sum_{i=1}^{k} \\frac{(P_{1i} - P_{0i})^2}{P_{0i}} } $$"), + p("Để tính toán cỡ mẫu, bạn cần ước tính giá trị `w` mà bạn kỳ vọng sẽ phát hiện được."), + + hr(), + h4("Ví dụ ứng dụng trong Y tế công cộng"), + p(tags$b("Tình huống:")), + p("Một cơ quan y tế công cộng muốn kiểm tra xem sự phân bố của 4 chủng cúm (A, B, C, D) trong mùa dịch năm nay có khác biệt so với dữ liệu lịch sử hay không. Dữ liệu lịch sử cho thấy tỷ lệ các chủng là: A (40%), B (30%), C (20%), D (10%)."), + p(tags$b("Thiết kế nghiên cứu:")), + tags$ul( + tags$li("Biến phân loại: Chủng cúm."), + tags$li("Số lượng nhóm (k): 4."), + tags$li("Phân phối kỳ vọng (H0): Tỷ lệ là 0.4, 0.3, 0.2, 0.1."), + tags$li("Bậc tự do (df): 4 - 1 = 3.") + ), + p(tags$b("Tính toán cỡ mẫu:")), + p("Nhà nghiên cứu nghi ngờ rằng chủng B đang trở nên phổ biến hơn. Họ giả định một phân phối mới có thể là: A (35%), B (40%), C (15%), D (10%). Họ có thể tính effect size `w` từ hai phân phối này và nhập vào ứng dụng, hoặc đơn giản là ước tính một effect size ở mức 'nhỏ' đến 'trung bình', ví dụ \\(w = 0.2\\)."), + p("Họ có thể nhập các giá trị này (w=0.2, k=4, power=0.8, alpha=0.05) vào ứng dụng để tìm ra tổng số ca bệnh cần phân tích để phát hiện sự thay đổi này.") + ) + ) + ) + ) +) + +# --- Logic của máy chủ (Server) --- +server <- function(input, output, session) { + + # Tính toán bậc tự do (df) một cách reactive + df <- reactive({ + req(input$k) + input$k - 1 + }) + + # Hiển thị df ra giao diện + output$df_display <- renderUI({ + tagList( + tags$p(tags$b("Bậc tự do (df) = "), df()) + ) + }) + + # --- PHẦN TÍNH TOÁN CHÍNH --- + main_results <- reactive({ + req(input$w, df(), input$sig_level, input$power) + + # Bậc tự do phải > 0 + if (df() <= 0) return(NULL) + + pwr_result <- tryCatch({ + pwr.chisq.test( + w = input$w, + df = df(), + sig.level = input$sig_level, + power = input$power + ) + }, error = function(e) NULL) + + if (is.null(pwr_result)) return(NULL) + + required_n <- ceiling(pwr_result$N) + + sample_sizes <- seq(10, required_n + 100, by = 2) + + power_data <- tryCatch({ + pwr.chisq.test( + N = sample_sizes, + w = input$w, + df = df(), + sig.level = input$sig_level + ) + }, error = function(e) NULL) + + if(is.null(power_data)) return(list(required_n = required_n, power_plot_data = NULL)) + + list( + required_n = required_n, + power_plot_data = data.frame(SampleSize = power_data$N, Power = power_data$power) + ) + }) + + output$sample_size_output <- renderUI({ + if (df() <= 0) { + return(tags$div(class = "alert alert-warning", "Bậc tự do phải lớn hơn 0. Vui lòng kiểm tra lại số lượng nhóm.")) + } + + res <- main_results() + + if (is.null(res)) { + return(tags$div(class = "alert alert-danger", "Có lỗi xảy ra trong quá trình tính toán. Vui lòng kiểm tra lại các tham số.")) + } + + tagList( + tags$p("Để phát hiện một sự khác biệt có độ lớn (effect size w) là", tags$b(input$w), "với power là", tags$b(input$power), "và mức ý nghĩa", tags$b(input$sig_level), ", bạn cần một tổng cỡ mẫu ước tính là:"), + tags$h3(style = "color: #007bff; text-align: center;", res$required_n) + ) + }) + + output$power_plot <- renderPlot({ + req(main_results(), main_results()$power_plot_data) + res <- main_results() + + ggplot(res$power_plot_data, aes(x = SampleSize, y = Power)) + + geom_line(color = "#007bff", size = 1.2) + + geom_hline(yintercept = input$power, linetype = "dashed", color = "red") + + geom_vline(xintercept = res$required_n, linetype = "dashed", color = "darkgreen") + + labs(title = "Power vs. Tổng Cỡ mẫu", x = "Tổng Cỡ mẫu (N)", y = "Power (1 - β)") + + scale_y_continuous(limits = c(0, 1)) + theme_minimal(base_size = 14) + }) + + # --- PHẦN PHÂN TÍCH EFFECT SIZE --- + effect_analysis_plot_data <- reactive({ + req(df(), input$sig_level, input$power) + if (df() <= 0) return(NULL) + + effect_sizes <- seq(0.05, 0.8, by = 0.05) + + required_n_values <- sapply(effect_sizes, function(w_val) { + res <- tryCatch({ + pwr.chisq.test( + w = w_val, + df = df(), + sig.level = input$sig_level, + power = input$power + )$N + }, error = function(e) NA) + ceiling(res) + }) + + data.frame(EffectSize = effect_sizes, RequiredN = required_n_values) + }) + + output$effect_analysis_plot <- renderPlot({ + req(effect_analysis_plot_data()) + plot_data <- effect_analysis_plot_data() + res <- main_results() + + p <- ggplot(plot_data, aes(x = EffectSize, y = RequiredN)) + + geom_line(color = "#28a745", size = 1.2) + + geom_point(color = "#28a745", size = 3, na.rm = TRUE) + + labs( + title = paste("Cỡ mẫu cần thiết vs. Effect Size (Power cố định =", input$power, ")"), + x = "Effect Size (w)", + y = "Tổng Cỡ mẫu cần thiết (N)" + ) + + theme_minimal(base_size = 14) + + if (!is.null(res) && !is.na(res$required_n)) { + p <- p + + geom_vline(xintercept = input$w, linetype = "dotted", color = "blue", size = 1) + + geom_point(aes(x = input$w, y = res$required_n), color = "blue", size = 5, shape = 18) + + annotate("text", x = input$w, y = res$required_n, + label = paste("N =", res$required_n), vjust = -1.5, color = "blue", fontface = "bold") + } + + p + }) +} + +# Chạy ứng dụng Shiny +shinyApp(ui = ui, server = server) diff --git a/samplesize/onesamplettest/app.R b/samplesize/onesamplettest/app.R new file mode 100644 index 0000000..6f9128f --- /dev/null +++ b/samplesize/onesamplettest/app.R @@ -0,0 +1,208 @@ +# Tải các thư viện cần thiết +library(shiny) +library(bslib) +library(ggplot2) +library(shinycssloaders) +library(pwr) + +# --- Giao diện người dùng (UI) --- +ui <- fluidPage( + theme = bs_theme(version = 5, bootswatch = "cerulean"), + withMathJax(), + + titlePanel("Tính toán Cỡ mẫu cho Kiểm định t một mẫu"), + + sidebarLayout( + sidebarPanel( + h4("Tham số đầu vào"), + + numericInput("d", + label = "Effect Size (Cohen's d):", + value = 0.5, min = 0.1, step = 0.1), + helpText("Cohen's d đo lường độ lớn của sự khác biệt. Quy ước: 0.2 (nhỏ), 0.5 (trung bình), 0.8 (lớn)."), + + selectInput("alternative", "Loại kiểm định:", + choices = c("Hai phía (Two-sided)" = "two.sided", + "Lớn hơn (Greater)" = "greater", + "Nhỏ hơn (Less)" = "less")), + + sliderInput("sig_level", + label = "Mức ý nghĩa (\\(\\alpha\\)):", + min = 0.01, max = 0.10, value = 0.05, step = 0.01), + + sliderInput("power", + label = "Power mong muốn (\\(1 - \\beta\\)):", + min = 0.50, max = 0.99, value = 0.80, step = 0.01), + + hr(), + helpText("Ứng dụng sử dụng hàm pwr.t.test() từ gói 'pwr'. Kết quả và đồ thị sẽ tự động cập nhật ngay lập tức.") + ), + + mainPanel( + tabsetPanel( + id = "results_tabs", + type = "pills", + + tabPanel( + "Kết quả & Diễn giải", + h4("Kết quả tính toán"), + withSpinner(uiOutput("sample_size_output"), type = 6, color = "#007bff") + ), + + tabPanel( + "Đồ thị Power vs. Cỡ mẫu", + h4("Mối quan hệ giữa Power và Cỡ mẫu"), + withSpinner(plotOutput("power_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Phân tích Effect Size", + h4("Mối quan hệ giữa Cỡ mẫu và Effect Size"), + p("Đồ thị này cho thấy cỡ mẫu cần thiết thay đổi như thế nào khi Effect Size thay đổi."), + withSpinner(plotOutput("effect_analysis_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Giả thuyết và Công thức (Helper)", + h4("Giả thuyết của Kiểm định t một mẫu"), + p("Kiểm định này so sánh trung bình của một mẫu duy nhất (\\(\\mu\\)) với một giá trị trung bình đã biết hoặc giả định (\\(\\mu_0\\))."), + p("$$H_0: \\mu = \\mu_0$$"), + p("$$H_a: \\mu \\neq \\mu_0 \\quad (\\text{hoặc } > \\text{ hoặc } <\\text{)}$$"), + hr(), + h4("Công thức tính toán"), + + tags$b("1. Thống kê kiểm định (Test Statistic):"), + p("Giá trị thống kê t được tính như sau:"), + p("$$ t = \\frac{\\bar{x} - \\mu_0}{s / \\sqrt{n}} $$"), + p("Trong đó \\(\\bar{x}\\) là trung bình mẫu, \\(s\\) là độ lệch chuẩn mẫu, và \\(n\\) là cỡ mẫu."), + + tags$b("2. Effect Size (Cohen's d):"), + p("Cohen's d cho trường hợp một mẫu được định nghĩa là:"), + p("$$ d = \\frac{|\\mu_{alternative} - \\mu_0|}{\\sigma} $$"), + p("Trong đó \\(\\mu_{alternative}\\) là trung bình thực sự dưới giả thuyết đối, và \\(\\sigma\\) là độ lệch chuẩn của tổng thể."), + + tags$b("3. Tính toán Power:"), + p("Ứng dụng này sử dụng hàm `pwr.t.test` với tham số `type = 'one.sample'`. Hàm này giải phương trình power dựa trên phân phối t phi trung tâm để tìm ra cỡ mẫu `n`.") + ) + ) + ) + ) +) + +# --- Logic của máy chủ (Server) --- +server <- function(input, output, session) { + + # --- PHẦN TÍNH TOÁN CHÍNH --- + main_results <- reactive({ + req(input$d, input$sig_level, input$power, input$alternative) + + pwr_result <- tryCatch({ + pwr.t.test( + d = input$d, + sig.level = input$sig_level, + power = input$power, + type = "one.sample", # THAY ĐỔI QUAN TRỌNG + alternative = input$alternative + ) + }, error = function(e) NULL) + + if (is.null(pwr_result)) return(NULL) + + required_n <- ceiling(pwr_result$n) + + sample_sizes <- seq(5, required_n + 50, by = 1) + + power_data <- tryCatch({ + pwr.t.test( + n = sample_sizes, + d = input$d, + sig.level = input$sig_level, + type = "one.sample", # THAY ĐỔI QUAN TRỌNG + alternative = input$alternative + ) + }, error = function(e) NULL) + + if(is.null(power_data)) return(list(required_n = required_n, power_plot_data = NULL)) + + list( + required_n = required_n, + power_plot_data = data.frame(SampleSize = power_data$n, Power = power_data$power) + ) + }) + + output$sample_size_output <- renderUI({ + res <- main_results() + + if (is.null(res)) { + return(tags$div(class = "alert alert-danger", "Có lỗi xảy ra trong quá trình tính toán. Vui lòng kiểm tra lại các tham số.")) + } + + tagList( + tags$p("Để phát hiện một effect size (Cohen's d) là", tags$b(input$d), "với power là", tags$b(input$power), "và mức ý nghĩa", tags$b(input$sig_level), ", bạn cần một cỡ mẫu ước tính là:"), + tags$h3(style = "color: #007bff; text-align: center;", res$required_n) + ) + }) + + output$power_plot <- renderPlot({ + res <- main_results() + req(res$power_plot_data) + + ggplot(res$power_plot_data, aes(x = SampleSize, y = Power)) + + geom_line(color = "#007bff", size = 1.2) + + geom_hline(yintercept = input$power, linetype = "dashed", color = "red") + + geom_vline(xintercept = res$required_n, linetype = "dashed", color = "darkgreen") + + labs(title = "Power vs. Cỡ mẫu", x = "Cỡ mẫu (n)", y = "Power (1 - β)") + + scale_y_continuous(limits = c(0, 1)) + theme_minimal(base_size = 14) + }) + + # --- PHẦN PHÂN TÍCH EFFECT SIZE --- + effect_analysis_plot_data <- reactive({ + req(input$sig_level, input$power, input$alternative) + + effect_sizes <- seq(0.1, 1.5, by = 0.05) + + required_n_values <- sapply(effect_sizes, function(d_val) { + res <- tryCatch({ + pwr.t.test( + d = d_val, + sig.level = input$sig_level, + power = input$power, + type = "one.sample", # THAY ĐỔI QUAN TRỌNG + alternative = input$alternative + )$n + }, error = function(e) NA) + ceiling(res) + }) + + data.frame(EffectSize = effect_sizes, RequiredN = required_n_values) + }) + + output$effect_analysis_plot <- renderPlot({ + plot_data <- effect_analysis_plot_data() + res <- main_results() # Lấy kết quả chính để đánh dấu + + p <- ggplot(plot_data, aes(x = EffectSize, y = RequiredN)) + + geom_line(color = "#28a745", size = 1.2) + + geom_point(color = "#28a745", size = 3, na.rm = TRUE) + + labs( + title = paste("Cỡ mẫu cần thiết vs. Effect Size (Power cố định =", input$power, ")"), + x = "Effect Size (Cohen's d)", + y = "Cỡ mẫu cần thiết (n)" + ) + + theme_minimal(base_size = 14) + + # THÊM ĐÁNH DẤU TRỰC QUAN (IMPROVED) + if (!is.null(res) && !is.na(res$required_n)) { + p <- p + + geom_vline(xintercept = input$d, linetype = "dotted", color = "blue", size = 1) + + geom_point(aes(x = input$d, y = res$required_n), color = "blue", size = 5, shape = 18) + + annotate("text", x = input$d, y = res$required_n, + label = paste("n =", res$required_n), vjust = -1.5, color = "blue", fontface = "bold") + } + + p # In đồ thị + }) +} + +# Chạy ứng dụng Shiny +shinyApp(ui = ui, server = server) diff --git a/samplesize/pairedztest/app.R b/samplesize/pairedztest/app.R new file mode 100644 index 0000000..25ffe8c --- /dev/null +++ b/samplesize/pairedztest/app.R @@ -0,0 +1,221 @@ +# Tải các thư viện cần thiết +library(shiny) +library(bslib) +library(ggplot2) +library(shinycssloaders) + +# --- Giao diện người dùng (UI) --- +ui <- fluidPage( + theme = bs_theme(version = 5, bootswatch = "cerulean"), + withMathJax(), + + titlePanel("Tính toán Cỡ mẫu cho Kiểm định Z ghép cặp"), + + sidebarLayout( + sidebarPanel( + h4("Tham số đầu vào"), + + numericInput("d", + label = "Effect Size (Cohen's d):", + value = 0.5, min = 0.1, step = 0.1), + helpText("Cohen's d đo lường độ lớn của sự khác biệt trung bình của các cặp, được chuẩn hóa bằng độ lệch chuẩn TỔNG THỂ của sự khác biệt (đã biết)."), + + selectInput("alternative", "Loại kiểm định:", + choices = c("Hai phía (Two-sided)" = "two.sided", + "Lớn hơn (Greater)" = "greater", + "Nhỏ hơn (Less)" = "less")), + + sliderInput("sig_level", + label = "Mức ý nghĩa (\\(\\alpha\\)):", + min = 0.01, max = 0.10, value = 0.05, step = 0.01), + + sliderInput("power", + label = "Power mong muốn (\\(1 - \\beta\\)):", + min = 0.50, max = 0.99, value = 0.80, step = 0.01), + + hr(), + h5("Công thức tính số cặp (n):"), + # Hiển thị công thức tính mẫu trực tiếp + uiOutput("sample_size_formula_display") + ), + + mainPanel( + tabsetPanel( + id = "results_tabs", + type = "pills", + + tabPanel( + "Kết quả & Diễn giải", + h4("Kết quả tính toán"), + withSpinner(uiOutput("sample_size_output"), type = 6, color = "#007bff") + ), + + tabPanel( + "Đồ thị Power vs. Cỡ mẫu", + h4("Mối quan hệ giữa Power và Số cặp"), + withSpinner(plotOutput("power_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Phân tích Effect Size", + h4("Mối quan hệ giữa Cỡ mẫu và Effect Size"), + p("Đồ thị này cho thấy số cặp cần thiết thay đổi như thế nào khi Effect Size thay đổi."), + withSpinner(plotOutput("effect_analysis_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Giả thuyết và Công thức (Helper)", + h4("Sự khác biệt chính: Paired Z-test vs. Paired T-test"), + tags$div(class = "alert alert-info", + tags$b("Điểm mấu chốt:"), + p("Sự lựa chọn giữa hai kiểm định này phụ thuộc vào việc bạn có biết độ lệch chuẩn của SỰ KHÁC BIỆT trong tổng thể (\\(\\sigma_D\\)) hay không."), + tags$ul( + tags$li(tags$b("Sử dụng Paired Z-test khi:"), "Độ lệch chuẩn của sự khác biệt trong tổng thể (\\(\\sigma_D\\)) đã biết. Giả định này cực kỳ hiếm trong thực tế."), + tags$li(tags$b("Sử dụng Paired T-test khi:"), "Độ lệch chuẩn \\(\\sigma_D\\) không xác định và phải được ước tính từ độ lệch chuẩn của sự khác biệt trong mẫu (\\(s_d\\)). Đây là trường hợp tiêu chuẩn và phổ biến nhất.") + ) + ), + hr(), + h4("Giả thuyết của Kiểm định Z ghép cặp"), + p("Kiểm định này phân tích sự khác biệt (D) trong mỗi cặp (ví dụ: D = Sau - Trước) để xem liệu trung bình của sự khác biệt (\\(\\mu_D\\)) có khác 0 hay không."), + p("$$H_0: \\mu_D = 0$$"), + p("$$H_a: \\mu_D \\neq 0 \\quad (\\text{hoặc } > 0 \\text{ hoặc } < 0\\text{)}$$"), + hr(), + h4("Công thức tính toán"), + + tags$b("1. Thống kê kiểm định (Test Statistic):"), + p("Giá trị thống kê Z được tính trên sự khác biệt của các cặp:"), + p("$$ Z = \\frac{\\bar{d} - 0}{\\sigma_D / \\sqrt{n}} $$"), + p("Trong đó \\(\\bar{d}\\) là trung bình của sự khác biệt trong mẫu, \\(\\sigma_D\\) là độ lệch chuẩn TỔNG THỂ của sự khác biệt, và \\(n\\) là số lượng cặp.") + ) + ) + ) + ) +) + +# --- Logic của máy chủ (Server) --- +server <- function(input, output, session) { + + # Hiển thị công thức tính cỡ mẫu + output$sample_size_formula_display <- renderUI({ + if (input$alternative == "two.sided") { + withMathJax(paste0("$$ n = \\frac{(Z_{1-\\alpha/2} + Z_{1-\\beta})^2}{d^2} $$")) + } else { + withMathJax(paste0("$$ n = \\frac{(Z_{1-\\alpha} + Z_{1-\\beta})^2}{d^2} $$")) + } + }) + + # --- HÀM TÍNH POWER LÕI CHO Z-TEST GHÉP CẶP (tương tự 1 mẫu) --- + calculate_z_power <- function(n, d, sig_level, alternative) { + non_centrality_param <- d * sqrt(n) + + if (alternative == "two.sided") { + alpha <- sig_level / 2 + crit_val_upper <- qnorm(1 - alpha) + power <- pnorm(crit_val_upper - non_centrality_param, lower.tail = FALSE) + + pnorm(-crit_val_upper - non_centrality_param, lower.tail = TRUE) + } else { # one.sided + crit_val <- qnorm(1 - sig_level) + power <- pnorm(crit_val - abs(non_centrality_param), lower.tail = FALSE) + } + return(power) + } + + # --- PHẦN TÍNH TOÁN CHÍNH --- + main_results <- reactive({ + req(input$d, input$sig_level, input$power, input$alternative) + + # Tìm số cặp cần thiết + n <- 2 + power_val <- 0 + while (power_val < input$power && n <= 5000) { + n <- n + 1 + power_val <- calculate_z_power(n, input$d, input$sig_level, input$alternative) + } + required_n <- ifelse(n > 5000, NA, n) + + # Tạo dữ liệu cho đồ thị Power + upper_bound <- if (!is.na(required_n)) required_n + 50 else 200 + sample_sizes <- seq(5, upper_bound, by = 1) + + powers <- sapply(sample_sizes, function(s_n) { + calculate_z_power(s_n, input$d, input$sig_level, input$alternative) + }) + + list( + required_n = required_n, + power_plot_data = data.frame(SampleSize = sample_sizes, Power = powers) + ) + }) + + output$sample_size_output <- renderUI({ + res <- main_results() + + if (is.null(res) || is.na(res$required_n)) { + return(tags$div(class = "alert alert-warning", "Không thể đạt được power mong muốn với cỡ mẫu tối đa (5000). Vui lòng xem xét lại các tham số.")) + } + + tagList( + tags$p("Để phát hiện một effect size (Cohen's d) là", tags$b(input$d), "với power là", tags$b(input$power), "và mức ý nghĩa", tags$b(input$sig_level), ", bạn cần một cỡ mẫu ước tính là:"), + tags$h3(style = "color: #007bff; text-align: center;", paste(res$required_n, "cặp quan sát")) + ) + }) + + output$power_plot <- renderPlot({ + res <- main_results() + req(res$power_plot_data) + + ggplot(res$power_plot_data, aes(x = SampleSize, y = Power)) + + geom_line(color = "#007bff", size = 1.2) + + geom_hline(yintercept = input$power, linetype = "dashed", color = "red") + + geom_vline(xintercept = res$required_n, linetype = "dashed", color = "darkgreen") + + labs(title = "Power vs. Số cặp quan sát", x = "Số cặp (n)", y = "Power (1 - β)") + + scale_y_continuous(limits = c(0, 1)) + theme_minimal(base_size = 14) + }) + + # --- PHẦN PHÂN TÍCH EFFECT SIZE --- + effect_analysis_plot_data <- reactive({ + req(input$sig_level, input$power, input$alternative) + + effect_sizes <- seq(0.1, 1.5, by = 0.05) + + required_n_values <- sapply(effect_sizes, function(d_val) { + n <- 2 + power_val <- 0 + while (power_val < input$power && n <= 5000) { + n <- n + 1 + power_val <- calculate_z_power(n, d_val, input$sig_level, input$alternative) + } + ifelse(n > 5000, NA, n) + }) + + data.frame(EffectSize = effect_sizes, RequiredN = required_n_values) + }) + + output$effect_analysis_plot <- renderPlot({ + plot_data <- effect_analysis_plot_data() + res <- main_results() + + p <- ggplot(plot_data, aes(x = EffectSize, y = RequiredN)) + + geom_line(color = "#28a745", size = 1.2) + + geom_point(color = "#28a745", size = 3, na.rm = TRUE) + + labs( + title = paste("Số cặp cần thiết vs. Effect Size (Power cố định =", input$power, ")"), + x = "Effect Size (Cohen's d)", + y = "Số cặp cần thiết (n)" + ) + + theme_minimal(base_size = 14) + + if (!is.null(res) && !is.na(res$required_n)) { + p <- p + + geom_vline(xintercept = input$d, linetype = "dotted", color = "blue", size = 1) + + geom_point(aes(x = input$d, y = res$required_n), color = "blue", size = 5, shape = 18) + + annotate("text", x = input$d, y = res$required_n, + label = paste("n =", res$required_n), vjust = -1.5, color = "blue", fontface = "bold") + } + + p + }) +} + +# Chạy ứng dụng Shiny +shinyApp(ui = ui, server = server) diff --git a/samplesize/pairsamplettest/app.R b/samplesize/pairsamplettest/app.R new file mode 100644 index 0000000..c1fd261 --- /dev/null +++ b/samplesize/pairsamplettest/app.R @@ -0,0 +1,223 @@ +# Tải các thư viện cần thiết +library(shiny) +library(bslib) +library(ggplot2) +library(shinycssloaders) +library(pwr) + +# --- Giao diện người dùng (UI) --- +ui <- fluidPage( + theme = bs_theme(version = 5, bootswatch = "cerulean"), + withMathJax(), + + titlePanel("Tính toán Cỡ mẫu cho Kiểm định t ghép cặp"), + + sidebarLayout( + sidebarPanel( + h4("Tham số đầu vào"), + + numericInput("d", + label = "Effect Size (Cohen's d):", + value = 0.5, min = 0.1, step = 0.1), + helpText("Cohen's d đo lường độ lớn của sự khác biệt trung bình của các cặp. Quy ước: 0.2 (nhỏ), 0.5 (trung bình), 0.8 (lớn)."), + + selectInput("alternative", "Loại kiểm định:", + choices = c("Hai phía (Two-sided)" = "two.sided", + "Lớn hơn (Greater)" = "greater", + "Nhỏ hơn (Less)" = "less")), + + sliderInput("sig_level", + label = "Mức ý nghĩa (\\(\\alpha\\)):", + min = 0.01, max = 0.10, value = 0.05, step = 0.01), + + sliderInput("power", + label = "Power mong muốn (\\(1 - \\beta\\)):", + min = 0.50, max = 0.99, value = 0.80, step = 0.01), + + hr(), + helpText("Ứng dụng sử dụng hàm pwr.t.test() từ gói 'pwr'. Kết quả và đồ thị sẽ tự động cập nhật ngay lập tức.") + ), + + mainPanel( + tabsetPanel( + id = "results_tabs", + type = "pills", + + tabPanel( + "Kết quả & Diễn giải", + h4("Kết quả tính toán"), + withSpinner(uiOutput("sample_size_output"), type = 6, color = "#007bff") + ), + + tabPanel( + "Đồ thị Power vs. Cỡ mẫu", + h4("Mối quan hệ giữa Power và Cỡ mẫu"), + withSpinner(plotOutput("power_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Phân tích Effect Size", + h4("Mối quan hệ giữa Cỡ mẫu và Effect Size"), + p("Đồ thị này cho thấy cỡ mẫu cần thiết thay đổi như thế nào khi Effect Size thay đổi."), + withSpinner(plotOutput("effect_analysis_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Giả thuyết và Công thức (Helper)", + h4("Giả thuyết của Kiểm định t ghép cặp"), + p("Kiểm định này được sử dụng khi các quan sát được thu thập theo cặp, ví dụ như đo lường trên cùng một đối tượng trước và sau một can thiệp. Kiểm định thực chất được thực hiện trên sự khác biệt (difference) của mỗi cặp."), + p("Đặt \\(D = X_{sau} - X_{trước}\\), và \\(\\mu_D\\) là trung bình của sự khác biệt trong tổng thể."), + p("$$H_0: \\mu_D = 0$$"), + p("$$H_a: \\mu_D \\neq 0 \\quad (\\text{hoặc } > 0 \\text{ hoặc } < 0\\text{)}$$"), + hr(), + h4("Công thức tính toán"), + + tags$b("1. Thống kê kiểm định (Test Statistic):"), + p("Giá trị thống kê t được tính trên sự khác biệt của các cặp:"), + p("$$ t = \\frac{\\bar{d} - 0}{s_d / \\sqrt{n}} $$"), + p("Trong đó \\(\\bar{d}\\) là trung bình của sự khác biệt trong mẫu, \\(s_d\\) là độ lệch chuẩn của sự khác biệt, và \\(n\\) là số lượng cặp."), + + tags$b("2. Effect Size (Cohen's d):"), + p("Cohen's d cho trường hợp ghép cặp được định nghĩa là:"), + p("$$ d = \\frac{|\\mu_D|}{\\sigma_D} $$"), + p("Trong đó \\(\\mu_D\\) và \\(\\sigma_D\\) là trung bình và độ lệch chuẩn của sự khác biệt trong tổng thể."), + + hr(), + h4("Ví dụ ứng dụng trong Y tế công cộng"), + p(tags$b("Tình huống:")), + p("Một nhà nghiên cứu muốn đánh giá hiệu quả của một chương trình giáo dục dinh dưỡng mới nhằm giảm huyết áp tâm thu ở bệnh nhân cao huyết áp. Họ đo huyết áp của một nhóm bệnh nhân trước khi tham gia chương trình và đo lại trên chính những bệnh nhân đó sau khi chương trình kết thúc 3 tháng."), + p(tags$b("Thiết kế nghiên cứu:")), + tags$ul( + tags$li("Đối tượng: Cùng một nhóm bệnh nhân."), + tags$li("Đo lường: Huyết áp tâm thu."), + tags$li("Thời điểm: Trước và Sau can thiệp."), + tags$li("Phân tích: Kiểm định t ghép cặp được sử dụng để xem liệu có sự thay đổi huyết áp trung bình có ý nghĩa thống kê hay không.") + ), + p(tags$b("Tính toán cỡ mẫu:")), + p("Trước khi bắt đầu, nhà nghiên cứu muốn biết cần tuyển bao nhiêu bệnh nhân. Dựa trên các nghiên cứu trước, họ kỳ vọng chương trình sẽ giúp giảm huyết áp trung bình khoảng 5 mmHg (đây là \\(\\mu_D\\)) với độ lệch chuẩn của sự thay đổi là 10 mmHg (đây là \\(\\sigma_D\\))."), + p("Do đó, Effect Size (Cohen's d) sẽ là: \\(d = | -5 | / 10 = 0.5\\)."), + p("Nhà nghiên cứu có thể nhập d = 0.5, power = 0.8, và alpha = 0.05 vào ứng dụng này để tìm ra số lượng bệnh nhân (số cặp) cần thiết cho nghiên cứu của mình.") + + ) + ) + ) + ) +) + +# --- Logic của máy chủ (Server) --- +server <- function(input, output, session) { + + # --- PHẦN TÍNH TOÁN CHÍNH --- + main_results <- reactive({ + req(input$d, input$sig_level, input$power, input$alternative) + + pwr_result <- tryCatch({ + pwr.t.test( + d = input$d, + sig.level = input$sig_level, + power = input$power, + type = "paired", # THAY ĐỔI QUAN TRỌNG + alternative = input$alternative + ) + }, error = function(e) NULL) + + if (is.null(pwr_result)) return(NULL) + + required_n <- ceiling(pwr_result$n) + + sample_sizes <- seq(5, required_n + 50, by = 1) + + power_data <- tryCatch({ + pwr.t.test( + n = sample_sizes, + d = input$d, + sig.level = input$sig_level, + type = "paired", # THAY ĐỔI QUAN TRỌNG + alternative = input$alternative + ) + }, error = function(e) NULL) + + if(is.null(power_data)) return(list(required_n = required_n, power_plot_data = NULL)) + + list( + required_n = required_n, + power_plot_data = data.frame(SampleSize = power_data$n, Power = power_data$power) + ) + }) + + output$sample_size_output <- renderUI({ + res <- main_results() + + if (is.null(res)) { + return(tags$div(class = "alert alert-danger", "Có lỗi xảy ra trong quá trình tính toán. Vui lòng kiểm tra lại các tham số.")) + } + + tagList( + tags$p("Để phát hiện một effect size (Cohen's d) là", tags$b(input$d), "với power là", tags$b(input$power), "và mức ý nghĩa", tags$b(input$sig_level), ", bạn cần một cỡ mẫu ước tính là:"), + tags$h3(style = "color: #007bff; text-align: center;", paste(res$required_n, "cặp quan sát")) + ) + }) + + output$power_plot <- renderPlot({ + res <- main_results() + req(res$power_plot_data) + + ggplot(res$power_plot_data, aes(x = SampleSize, y = Power)) + + geom_line(color = "#007bff", size = 1.2) + + geom_hline(yintercept = input$power, linetype = "dashed", color = "red") + + geom_vline(xintercept = res$required_n, linetype = "dashed", color = "darkgreen") + + labs(title = "Power vs. Số cặp quan sát", x = "Số cặp (n)", y = "Power (1 - β)") + + scale_y_continuous(limits = c(0, 1)) + theme_minimal(base_size = 14) + }) + + # --- PHẦN PHÂN TÍCH EFFECT SIZE --- + effect_analysis_plot_data <- reactive({ + req(input$sig_level, input$power, input$alternative) + + effect_sizes <- seq(0.1, 1.5, by = 0.05) + + required_n_values <- sapply(effect_sizes, function(d_val) { + res <- tryCatch({ + pwr.t.test( + d = d_val, + sig.level = input$sig_level, + power = input$power, + type = "paired", # THAY ĐỔI QUAN TRỌNG + alternative = input$alternative + )$n + }, error = function(e) NA) + ceiling(res) + }) + + data.frame(EffectSize = effect_sizes, RequiredN = required_n_values) + }) + + output$effect_analysis_plot <- renderPlot({ + plot_data <- effect_analysis_plot_data() + res <- main_results() # Lấy kết quả chính để đánh dấu + + p <- ggplot(plot_data, aes(x = EffectSize, y = RequiredN)) + + geom_line(color = "#28a745", size = 1.2) + + geom_point(color = "#28a745", size = 3, na.rm = TRUE) + + labs( + title = paste("Số cặp cần thiết vs. Effect Size (Power cố định =", input$power, ")"), + x = "Effect Size (Cohen's d)", + y = "Số cặp cần thiết (n)" + ) + + theme_minimal(base_size = 14) + + # THÊM ĐÁNH DẤU TRỰC QUAN (IMPROVED) + if (!is.null(res) && !is.na(res$required_n)) { + p <- p + + geom_vline(xintercept = input$d, linetype = "dotted", color = "blue", size = 1) + + geom_point(aes(x = input$d, y = res$required_n), color = "blue", size = 5, shape = 18) + + annotate("text", x = input$d, y = res$required_n, + label = paste("n =", res$required_n), vjust = -1.5, color = "blue", fontface = "bold") + } + + p # In đồ thị + }) +} + +# Chạy ứng dụng Shiny +shinyApp(ui = ui, server = server) diff --git a/samplesize/twosamplettest/app.R b/samplesize/twosamplettest/app.R new file mode 100644 index 0000000..e42d81b --- /dev/null +++ b/samplesize/twosamplettest/app.R @@ -0,0 +1,209 @@ +# Tải các thư viện cần thiết +library(shiny) +library(bslib) +library(ggplot2) +library(shinycssloaders) +library(pwr) + +# --- Giao diện người dùng (UI) --- +ui <- fluidPage( + theme = bs_theme(version = 5, bootswatch = "cerulean"), + withMathJax(), + + titlePanel("Tính toán Cỡ mẫu cho Kiểm định t hai mẫu độc lập"), + + sidebarLayout( + sidebarPanel( + h4("Tham số đầu vào"), + + numericInput("d", + label = "Effect Size (Cohen's d):", + value = 0.5, min = 0.1, step = 0.1), + helpText("Cohen's d đo lường độ lớn của sự khác biệt giữa hai trung bình. Quy ước: 0.2 (nhỏ), 0.5 (trung bình), 0.8 (lớn)."), + + selectInput("alternative", "Loại kiểm định:", + choices = c("Hai phía (Two-sided)" = "two.sided", + "Lớn hơn (Greater)" = "greater", + "Nhỏ hơn (Less)" = "less")), + + sliderInput("sig_level", + label = "Mức ý nghĩa (\\(\\alpha\\)):", + min = 0.01, max = 0.10, value = 0.05, step = 0.01), + + sliderInput("power", + label = "Power mong muốn (\\(1 - \\beta\\)):", + min = 0.50, max = 0.99, value = 0.80, step = 0.01), + + hr(), + helpText("Ứng dụng sử dụng hàm pwr.t.test() từ gói 'pwr'. Kết quả và đồ thị sẽ tự động cập nhật ngay lập tức.") + ), + + mainPanel( + tabsetPanel( + id = "results_tabs", + type = "pills", + + tabPanel( + "Kết quả & Diễn giải", + h4("Kết quả tính toán"), + withSpinner(uiOutput("sample_size_output"), type = 6, color = "#007bff") + ), + + tabPanel( + "Đồ thị Power vs. Cỡ mẫu", + h4("Mối quan hệ giữa Power và Cỡ mẫu"), + withSpinner(plotOutput("power_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Phân tích Effect Size", + h4("Mối quan hệ giữa Cỡ mẫu và Effect Size"), + p("Đồ thị này cho thấy cỡ mẫu cần thiết thay đổi như thế nào khi Effect Size thay đổi."), + withSpinner(plotOutput("effect_analysis_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Giả thuyết và Công thức (Helper)", + h4("Giả thuyết của Kiểm định t hai mẫu"), + p("Kiểm định này so sánh trung bình của hai nhóm độc lập (\\(\\mu_1\\) và \\(\\mu_2\\))."), + p("$$H_0: \\mu_1 = \\mu_2$$"), + p("$$H_a: \\mu_1 \\neq \\mu_2 \\quad (\\text{hoặc } > \\text{ hoặc } <\\text{)}$$"), + hr(), + h4("Công thức tính toán"), + + tags$b("1. Thống kê kiểm định (Test Statistic):"), + p("Giả định phương sai bằng nhau, giá trị thống kê t được tính như sau:"), + p("$$ t = \\frac{(\\bar{x}_1 - \\bar{x}_2)}{s_p \\sqrt{\\frac{1}{n_1} + \\frac{1}{n_2}}} $$"), + p("Trong đó \\(\\bar{x}_1, \\bar{x}_2\\) là trung bình mẫu; \\(n_1, n_2\\) là cỡ mẫu; và \\(s_p\\) là độ lệch chuẩn gộp (pooled standard deviation)."), + + tags$b("2. Effect Size (Cohen's d):"), + p("Cohen's d cho trường hợp hai mẫu được định nghĩa là:"), + p("$$ d = \\frac{|\\mu_1 - \\mu_2|}{\\sigma} $$"), + p("Trong đó \\(\\sigma\\) là độ lệch chuẩn của tổng thể (giả định bằng nhau cho cả hai nhóm)."), + + tags$b("3. Tính toán Power:"), + p("Ứng dụng này sử dụng hàm `pwr.t.test` với tham số `type = 'two.sample'`. Hàm này giải phương trình power dựa trên phân phối t phi trung tâm để tìm ra cỡ mẫu `n` cần thiết cho mỗi nhóm.") + ) + ) + ) + ) +) + +# --- Logic của máy chủ (Server) --- +server <- function(input, output, session) { + + # --- PHẦN TÍNH TOÁN CHÍNH --- + main_results <- reactive({ + req(input$d, input$sig_level, input$power, input$alternative) + + pwr_result <- tryCatch({ + pwr.t.test( + d = input$d, + sig.level = input$sig_level, + power = input$power, + type = "two.sample", + alternative = input$alternative + ) + }, error = function(e) NULL) + + if (is.null(pwr_result)) return(NULL) + + required_n <- ceiling(pwr_result$n) + + sample_sizes <- seq(5, required_n + 50, by = 1) + + power_data <- tryCatch({ + pwr.t.test( + n = sample_sizes, + d = input$d, + sig.level = input$sig_level, + type = "two.sample", + alternative = input$alternative + ) + }, error = function(e) NULL) + + if(is.null(power_data)) return(list(required_n = required_n, power_plot_data = NULL)) + + list( + required_n = required_n, + power_plot_data = data.frame(SampleSize = power_data$n, Power = power_data$power) + ) + }) + + output$sample_size_output <- renderUI({ + res <- main_results() + + if (is.null(res)) { + return(tags$div(class = "alert alert-danger", "Có lỗi xảy ra trong quá trình tính toán. Vui lòng kiểm tra lại các tham số.")) + } + + tagList( + tags$p("Để phát hiện một effect size (Cohen's d) là", tags$b(input$d), "với power là", tags$b(input$power), "và mức ý nghĩa", tags$b(input$sig_level), ", bạn cần một cỡ mẫu ước tính là:"), + tags$h3(style = "color: #007bff; text-align: center;", paste(res$required_n, "cho mỗi nhóm")), + tags$p(style = "text-align: center; font-style: italic;", "Tổng cỡ mẫu là ", res$required_n * 2, ".") + ) + }) + + output$power_plot <- renderPlot({ + res <- main_results() + req(res$power_plot_data) + + ggplot(res$power_plot_data, aes(x = SampleSize, y = Power)) + + geom_line(color = "#007bff", size = 1.2) + + geom_hline(yintercept = input$power, linetype = "dashed", color = "red") + + geom_vline(xintercept = res$required_n, linetype = "dashed", color = "darkgreen") + + labs(title = "Power vs. Cỡ mẫu (cho mỗi nhóm)", x = "Cỡ mẫu cho mỗi nhóm (n)", y = "Power (1 - β)") + + scale_y_continuous(limits = c(0, 1)) + theme_minimal(base_size = 14) + }) + + # --- PHẦN PHÂN TÍCH EFFECT SIZE --- + effect_analysis_plot_data <- reactive({ + req(input$sig_level, input$power, input$alternative) + + effect_sizes <- seq(0.1, 1.5, by = 0.05) + + required_n_values <- sapply(effect_sizes, function(d_val) { + res <- tryCatch({ + pwr.t.test( + d = d_val, + sig.level = input$sig_level, + power = input$power, + type = "two.sample", + alternative = input$alternative + )$n + }, error = function(e) NA) + ceiling(res) + }) + + data.frame(EffectSize = effect_sizes, RequiredN = required_n_values) + }) + + output$effect_analysis_plot <- renderPlot({ + plot_data <- effect_analysis_plot_data() + res <- main_results() # Lấy kết quả chính để đánh dấu + + p <- ggplot(plot_data, aes(x = EffectSize, y = RequiredN)) + + geom_line(color = "#28a745", size = 1.2) + + geom_point(color = "#28a745", size = 3, na.rm = TRUE) + + labs( + title = paste("Cỡ mẫu cần thiết vs. Effect Size (Power cố định =", input$power, ")"), + x = "Effect Size (Cohen's d)", + y = "Cỡ mẫu cần thiết cho mỗi nhóm (n)" + ) + + theme_minimal(base_size = 14) + + # THÊM ĐÁNH DẤU TRỰC QUAN (IMPROVED) + if (!is.null(res) && !is.na(res$required_n)) { + p <- p + + geom_vline(xintercept = input$d, linetype = "dotted", color = "blue", size = 1) + + geom_point(aes(x = input$d, y = res$required_n), color = "blue", size = 5, shape = 18) + + annotate("text", x = input$d, y = res$required_n, + label = paste("n =", res$required_n), vjust = -1.5, color = "blue", fontface = "bold") + } + + p # In đồ thị + }) +} + +# Chạy ứng dụng Shiny +shinyApp(ui = ui, server = server) diff --git a/samplesize/twosampleztest/app.R b/samplesize/twosampleztest/app.R new file mode 100644 index 0000000..8e4755b --- /dev/null +++ b/samplesize/twosampleztest/app.R @@ -0,0 +1,227 @@ +# Tải các thư viện cần thiết +library(shiny) +library(bslib) +library(ggplot2) +library(shinycssloaders) + +# --- Giao diện người dùng (UI) --- +ui <- fluidPage( + theme = bs_theme(version = 5, bootswatch = "cerulean"), + withMathJax(), + + titlePanel("Tính toán Cỡ mẫu cho Kiểm định Z hai mẫu độc lập"), + + sidebarLayout( + sidebarPanel( + h4("Tham số đầu vào"), + + numericInput("d", + label = "Effect Size (Cohen's d):", + value = 0.5, min = 0.1, step = 0.1), + helpText("Cohen's d đo lường độ lớn của sự khác biệt giữa hai trung bình, được chuẩn hóa bằng độ lệch chuẩn TỔNG THỂ (đã biết)."), + + selectInput("alternative", "Loại kiểm định:", + choices = c("Hai phía (Two-sided)" = "two.sided", + "Lớn hơn (Greater)" = "greater", + "Nhỏ hơn (Less)" = "less")), + + sliderInput("sig_level", + label = "Mức ý nghĩa (\\(\\alpha\\)):", + min = 0.01, max = 0.10, value = 0.05, step = 0.01), + + sliderInput("power", + label = "Power mong muốn (\\(1 - \\beta\\)):", + min = 0.50, max = 0.99, value = 0.80, step = 0.01), + + hr(), + h5("Công thức tính cỡ mẫu (mỗi nhóm):"), + # Hiển thị công thức tính mẫu trực tiếp + uiOutput("sample_size_formula_display") + ), + + mainPanel( + tabsetPanel( + id = "results_tabs", + type = "pills", + + tabPanel( + "Kết quả & Diễn giải", + h4("Kết quả tính toán"), + withSpinner(uiOutput("sample_size_output"), type = 6, color = "#007bff") + ), + + tabPanel( + "Đồ thị Power vs. Cỡ mẫu", + h4("Mối quan hệ giữa Power và Cỡ mẫu"), + withSpinner(plotOutput("power_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Phân tích Effect Size", + h4("Mối quan hệ giữa Cỡ mẫu và Effect Size"), + p("Đồ thị này cho thấy cỡ mẫu cần thiết thay đổi như thế nào khi Effect Size thay đổi."), + withSpinner(plotOutput("effect_analysis_plot"), type = 6, color = "#007bff") + ), + + tabPanel( + "Giả thuyết và Công thức (Helper)", + h4("Sự khác biệt chính: Z-test vs. T-test (cho hai mẫu)"), + tags$div(class = "alert alert-info", + tags$b("Điểm mấu chốt:"), + p("Sự lựa chọn giữa Z-test và T-test phụ thuộc vào việc bạn có biết phương sai của tổng thể (\\(\\sigma^2\\)) hay không."), + tags$ul( + tags$li(tags$b("Sử dụng Z-test khi:"), "Phương sai của CẢ HAI tổng thể (\\(\\sigma_1^2\\) và \\(\\sigma_2^2\\)) đều đã biết. Đây là một giả định rất chặt và hiếm khi xảy ra trong thực tế."), + tags$li(tags$b("Sử dụng T-test khi:"), "Phương sai của tổng thể không xác định và phải được ước tính từ dữ liệu mẫu. Đây là trường hợp gần như luôn luôn xảy ra trong nghiên cứu thực tế.") + ) + ), + hr(), + h4("Giả thuyết của Kiểm định Z hai mẫu"), + p("Kiểm định này so sánh trung bình của hai nhóm độc lập (\\(\\mu_1\\) và \\(\\mu_2\\)), khi phương sai của chúng đã biết."), + p("$$H_0: \\mu_1 = \\mu_2$$"), + p("$$H_a: \\mu_1 \\neq \\mu_2 \\quad (\\text{hoặc } > \\text{ hoặc } <\\text{)}$$"), + hr(), + h4("Công thức tính toán"), + + tags$b("1. Thống kê kiểm định (Test Statistic):"), + p("Giá trị thống kê Z được tính như sau:"), + p("$$ Z = \\frac{(\\bar{x}_1 - \\bar{x}_2) - 0}{\\sqrt{\\frac{\\sigma_1^2}{n_1} + \\frac{\\sigma_2^2}{n_2}}} $$"), + p("Trong đó \\(\\sigma_1^2\\) và \\(\\sigma_2^2\\) là các phương sai TỔNG THỂ đã biết.") + ) + ) + ) + ) +) + +# --- Logic của máy chủ (Server) --- +server <- function(input, output, session) { + + # Hiển thị công thức tính cỡ mẫu + output$sample_size_formula_display <- renderUI({ + alpha <- input$sig_level + beta <- 1 - input$power + d <- input$d + + if (input$alternative == "two.sided") { + withMathJax(paste0("$$ n = \\frac{2(Z_{1-\\alpha/2} + Z_{1-\\beta})^2}{d^2} $$")) + } else { + withMathJax(paste0("$$ n = \\frac{2(Z_{1-\\alpha} + Z_{1-\\beta})^2}{d^2} $$")) + } + }) + + # --- HÀM TÍNH POWER LÕI CHO Z-TEST 2 MẪU --- + calculate_z_power <- function(n, d, sig_level, alternative) { + # Đối với 2 mẫu, mean của Z-statistic dưới Ha là d * sqrt(n/2) + non_centrality_param <- d * sqrt(n / 2) + + if (alternative == "two.sided") { + alpha <- sig_level / 2 + crit_val_upper <- qnorm(1 - alpha) + power <- pnorm(crit_val_upper - non_centrality_param, lower.tail = FALSE) + + pnorm(-crit_val_upper - non_centrality_param, lower.tail = TRUE) + } else { # one.sided + crit_val <- qnorm(1 - sig_level) + power <- pnorm(crit_val - non_centrality_param, lower.tail = FALSE) + } + return(power) + } + + # --- PHẦN TÍNH TOÁN CHÍNH --- + main_results <- reactive({ + req(input$d, input$sig_level, input$power, input$alternative) + + # Tìm cỡ mẫu cần thiết + n <- 2 # Bắt đầu từ n nhỏ + power_val <- 0 + while (power_val < input$power && n <= 5000) { + n <- n + 1 + power_val <- calculate_z_power(n, input$d, input$sig_level, input$alternative) + } + required_n <- ifelse(n > 5000, NA, n) + + # Tạo dữ liệu cho đồ thị Power + upper_bound <- if (!is.na(required_n)) required_n + 50 else 200 + sample_sizes <- seq(5, upper_bound, by = 1) + + powers <- sapply(sample_sizes, function(s_n) { + calculate_z_power(s_n, input$d, input$sig_level, input$alternative) + }) + + list( + required_n = required_n, + power_plot_data = data.frame(SampleSize = sample_sizes, Power = powers) + ) + }) + + output$sample_size_output <- renderUI({ + res <- main_results() + + if (is.null(res) || is.na(res$required_n)) { + return(tags$div(class = "alert alert-warning", "Không thể đạt được power mong muốn với cỡ mẫu tối đa (5000). Vui lòng xem xét lại các tham số.")) + } + + tagList( + tags$p("Để phát hiện một effect size (Cohen's d) là", tags$b(input$d), "với power là", tags$b(input$power), "và mức ý nghĩa", tags$b(input$sig_level), ", bạn cần một cỡ mẫu ước tính là:"), + tags$h3(style = "color: #007bff; text-align: center;", paste(res$required_n, "cho mỗi nhóm")), + tags$p(style = "text-align: center; font-style: italic;", "Tổng cỡ mẫu là ", res$required_n * 2, ".") + ) + }) + + output$power_plot <- renderPlot({ + res <- main_results() + req(res$power_plot_data) + + ggplot(res$power_plot_data, aes(x = SampleSize, y = Power)) + + geom_line(color = "#007bff", size = 1.2) + + geom_hline(yintercept = input$power, linetype = "dashed", color = "red") + + geom_vline(xintercept = res$required_n, linetype = "dashed", color = "darkgreen") + + labs(title = "Power vs. Cỡ mẫu (cho mỗi nhóm)", x = "Cỡ mẫu cho mỗi nhóm (n)", y = "Power (1 - β)") + + scale_y_continuous(limits = c(0, 1)) + theme_minimal(base_size = 14) + }) + + # --- PHẦN PHÂN TÍCH EFFECT SIZE --- + effect_analysis_plot_data <- reactive({ + req(input$sig_level, input$power, input$alternative) + + effect_sizes <- seq(0.1, 1.5, by = 0.05) + + required_n_values <- sapply(effect_sizes, function(d_val) { + n <- 2 + power_val <- 0 + while (power_val < input$power && n <= 5000) { + n <- n + 1 + power_val <- calculate_z_power(n, d_val, input$sig_level, input$alternative) + } + ifelse(n > 5000, NA, n) + }) + + data.frame(EffectSize = effect_sizes, RequiredN = required_n_values) + }) + + output$effect_analysis_plot <- renderPlot({ + plot_data <- effect_analysis_plot_data() + res <- main_results() + + p <- ggplot(plot_data, aes(x = EffectSize, y = RequiredN)) + + geom_line(color = "#28a745", size = 1.2) + + geom_point(color = "#28a745", size = 3, na.rm = TRUE) + + labs( + title = paste("Cỡ mẫu cần thiết vs. Effect Size (Power cố định =", input$power, ")"), + x = "Effect Size (Cohen's d)", + y = "Cỡ mẫu cần thiết cho mỗi nhóm (n)" + ) + + theme_minimal(base_size = 14) + + if (!is.null(res) && !is.na(res$required_n)) { + p <- p + + geom_vline(xintercept = input$d, linetype = "dotted", color = "blue", size = 1) + + geom_point(aes(x = input$d, y = res$required_n), color = "blue", size = 5, shape = 18) + + annotate("text", x = input$d, y = res$required_n, + label = paste("n =", res$required_n), vjust = -1.5, color = "blue", fontface = "bold") + } + + p + }) +} + +# Chạy ứng dụng Shiny +shinyApp(ui = ui, server = server)