209 lines
7.8 KiB
R
209 lines
7.8 KiB
R
# 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)
|