2-비율 가설 검정

비율 검정
2-비율 z-검정
저자

이광춘

공개

2024-05-17

2-비율 가설 검정은 두 독립적인 표본 집단에서 관찰된 비율의 차이가 통계적으로 유의미한지를 검증하는 통계적 방법입니다.

1 Shiny 앱

#| label: shinylive-two-prop-testing
#| viewerWidth: 800
#| viewerHeight: 700
#| standalone: true


library(shiny)
library(showtext)
# font_add_google(name = "Nanum Gothic", regular.wt = 400)
showtext_auto()
library(gt)

# 1. 입력 데이터 변환---------------
extract <- function(text) {
  text <- gsub(" ", "", text)
  split <- strsplit(text, ",", fixed = FALSE)[[1]]
  as.numeric(split)
}

# 2. 1-표본 t-검정---------------
t.test2 <- function(x, V, m0 = 0, alpha = 0.05, alternative = "two.sided") {
  M <- mean(x)
  n <- length(x)
  sigma <- sqrt(V)
  S <- sqrt(V / n)
  statistic <- (M - m0) / S
  p <- if (alternative == "two.sided") {
    2 * pnorm(abs(statistic), lower.tail = FALSE)
  } else if (alternative == "less") {
    pnorm(statistic, lower.tail = TRUE)
  } else {
    pnorm(statistic, lower.tail = FALSE)
  }
  # p <- (1 - pnorm((M-m0)/S))
  LCL <- (M - S * qnorm(1 - alpha / 2))
  UCL <- (M + S * qnorm(1 - alpha / 2))
  value <- list(mean = M, m0 = m0, sigma = sigma, statistic = statistic, p.value = p, LCL = LCL, UCL = UCL, alternative = alternative)
  # print(sprintf("P-value = %g",p))
  # print(sprintf("Lower %.2f%% Confidence Limit = %g",
  #               alpha, LCL))
  # print(sprintf("Upper %.2f%% Confidence Limit = %g",
  #               alpha, UCL))
  return(value)
}

# 3. 2-표본 t-검정---------------
t.test3 <- function(x, y, V1, V2, m0 = 0, alpha = 0.05, alternative = "two.sided") {
  M1 <- mean(x)
  M2 <- mean(y)
  n1 <- length(x)
  n2 <- length(y)
  sigma1 <- sqrt(V1)
  sigma2 <- sqrt(V2)
  S <- sqrt((V1 / n1) + (V2 / n2))
  statistic <- (M1 - M2 - m0) / S
  p <- if (alternative == "two.sided") {
    2 * pnorm(abs(statistic), lower.tail = FALSE)
  } else if (alternative == "less") {
    pnorm(statistic, lower.tail = TRUE)
  } else {
    pnorm(statistic, lower.tail = FALSE)
  }
  # p <- (1 - pnorm((M-m0)/S))
  LCL <- (M1 - M2 - S * qnorm(1 - alpha / 2))
  UCL <- (M1 - M2 + S * qnorm(1 - alpha / 2))
  value <- list(mean1 = M1, mean2 = M2, m0 = m0, sigma1 = sigma1, sigma2 = sigma2, S = S, statistic = statistic, p.value = p, LCL = LCL, UCL = UCL, alternative = alternative)
  # print(sprintf("P-value = %g",p))
  # print(sprintf("Lower %.2f%% Confidence Limit = %g",
  #               alpha, LCL))
  # print(sprintf("Upper %.2f%% Confidence Limit = %g",
  #               alpha, UCL))
  return(value)
}

# 3. 1-표본 z-비율검정---------------
prop.z.test <- function(x, n, p0 = 0.5, conf.level = 0.95, alternative = "two.sided") {
  ts.z <- NULL
  cint <- NULL
  p.val <- NULL
  phat <- x / n
  qhat <- 1 - phat
  SE.phat <- sqrt((phat * qhat) / n)
  ts.z <- (phat - p0) / SE.phat
  p.val <- if (alternative == "two.sided") {
    2 * pnorm(abs(ts.z), lower.tail = FALSE)
  } else if (alternative == "less") {
    pnorm(ts.z, lower.tail = TRUE)
  } else {
    pnorm(ts.z, lower.tail = FALSE)
  }
  cint <- phat + c(
    -1 * ((qnorm(((1 - conf.level) / 2) + conf.level)) * SE.phat),
    ((qnorm(((1 - conf.level) / 2) + conf.level)) * SE.phat)
  )
  return(list(x = x, n = n, estimate = phat, null.value = p0, stderr = SE.phat, statistic = ts.z, p.value = p.val, conf.int = cint))
}
# 4. 2-표본 z-비율검정---------------

prop.z.test2 <- function(x1, x2, n1, n2, p0 = 0, pooled.stderr = TRUE, conf.level = 0.95, alternative = "two.sided") {
  ts.z <- NULL
  cint <- NULL
  p.val <- NULL
  phat1 <- x1 / n1
  qhat1 <- 1 - phat1
  phat2 <- x2 / n2
  qhat2 <- 1 - phat2
  pooled.phat <- ((n1 * phat1) + (n2 * phat2)) / (n1 + n2)
  pooled.qhat <- 1 - pooled.phat
  if (pooled.stderr == FALSE) {
    SE.phat <- sqrt((phat1 * qhat1) / n1 + (phat2 * qhat2) / n2)
  } else {
    SE.phat <- sqrt(pooled.phat * pooled.qhat * (1 / n1 + 1 / n2))
  }
  ts.z <- (phat1 - phat2 - p0) / SE.phat
  p.val <- if (alternative == "two.sided") {
    2 * pnorm(abs(ts.z), lower.tail = FALSE)
  } else if (alternative == "less") {
    pnorm(ts.z, lower.tail = TRUE)
  } else {
    pnorm(ts.z, lower.tail = FALSE)
  }
  cint <- (phat1 - phat2) + c(
    -1 * ((qnorm(((1 - conf.level) / 2) + conf.level)) * SE.phat),
    ((qnorm(((1 - conf.level) / 2) + conf.level)) * SE.phat)
  )
  return(list(x1 = x1, x2 = x2, n1 = n1, n2 = n2, estimate1 = phat1, estimate2 = phat2, null.value = p0, stderr = SE.phat, pooled.phat = pooled.phat, statistic = ts.z, p.value = p.val, conf.int = cint))
}

# 5. z-비율검정---------------
prop.z.test3 <- function(x, n, p0 = 0.5, conf.level = 0.95, alternative = "two.sided") {
  ts.z <- NULL
  cint <- NULL
  p.val <- NULL
  phat <- x / n
  qhat <- 1 - phat
  SE.phat <- sqrt((p0 * (1-p0)) / n)
  ts.z <- (phat - p0) / SE.phat
  p.val <- if (alternative == "two.sided") {
    2 * pnorm(abs(ts.z), lower.tail = FALSE)
  } else if (alternative == "less") {
    pnorm(ts.z, lower.tail = TRUE)
  } else {
    pnorm(ts.z, lower.tail = FALSE)
  }
  return(list(x = x, n = n, estimate = phat, null.value = p0, stderr = SE.phat, statistic = ts.z, p.value = p.val))
}


two_proportion_UI <- function(id) {

  ns <- NS(id)

  # 1. 메인 패널 ------------------------------------
  mainPanel <- mainPanel(
    width = 9,
    tabsetPanel(
      tabPanel("검정결과",
               uiOutput(ns("results_two_proportionn"))
      ),
      tabPanel("그래프",
               plotOutput(ns("plot_two_proportion"))
      ),
      tabPanel("표본 데이터",
               gt_output(ns("table_two_proportion"))
      )
    )
  )

  # 2. 옆 패널 --------------------------------------
  sidebarPanel <- sidebarPanel(width = 3,

     tags$br(),
     tags$h4("* 데이터"),
     tags$br(),

     tags$b("표본 크기 1"),
     numericInput( ns("n1_twoprop"), "\\(n_1 = \\)",
                   value = 30, min = 0, step = 1),
     tags$b("표본 크기 2"),
     numericInput( ns("n2_twoprop"), "\\(n_2 = \\)",
                   value = 30, min = 0, step = 1),
     tags$hr(),

     radioButtons(
       inputId = ns("propx_twoprop"),
       label = NULL,
       choices = c(
         "성공 비율 \\(\\hat{p}\\)" = "prop_true",
         "성공 횟수 \\(x\\)" = "prop_false"
       )
     ),

     conditionalPanel(
       # condition = "input.propx_twoprop == 'prop_true'",
       condition = paste0("input['", ns("propx_twoprop"), "'] == 'prop_true'"),
       tags$b("성공 비율"),
       numericInput( ns("p1_twoprop"), "\\(\\hat{p}_1 = \\)",
                     value = 0.2, min = 0, max = 1, step = 0.01
       ),
       numericInput( ns("p2_twoprop"), "\\(\\hat{p}_2 = \\)",
                     value = 0.3, min = 0, max = 1, step = 0.01
       )
     ),
     conditionalPanel(
       # condition = "input.propx_twoprop == 'prop_false'",
       condition = paste0("input['", ns("propx_twoprop"), "'] == 'prop_false'"),
       tags$b("성공 횟수"),
       numericInput( ns("x1_twoprop"), "\\(x_1 = \\)",
                     value = 10, min = 0, step = 1
       ),
       numericInput( ns("x2_twoprop"), "\\(x_2 = \\)",
                     value = 12, min = 0, step = 1
       )
     ),


     tags$h4("* 가설검정"),

     tags$p("1. 귀무가설"),
     tags$p("\\( H_0 : p_1 - p_2 = \\)"),

     numericInput( ns("twoprop_h0"),
                   label = NULL,
                   value = 0.1, step = 0.1, width="100px"),

     checkboxInput( ns("pooledstderr_twoprop"), "합동(pooled) 표준오차 사용:", FALSE),

     tags$p("2. 검정방향"),
     radioButtons(
       inputId = ns("twoprop_alternative"),
       label = "대립가설",
       inline = TRUE,
       choices = c(
         "\\( \\neq \\)" = "two.sided",
         "\\( > \\)" = "greater",
         "\\( < \\)" = "less"
       )
     ),


     tags$p("3. 유의수준"),
     sliderInput( ns("twoprop_alpha"),
                  "유의수준 \\(\\alpha = \\)",
                  min = 0.01,
                  max = 0.10,
                  step = 0.01,
                  value = 0.05
     )
  )

  # 레이아웃 -----------------------------------------------------------
  tagList(
    withMathJax(),
    tags$div(

      fluidPage(
        theme = shinythemes::shinytheme("flatly"),

        sidebarPanel,
        mainPanel
      )
    )
  )
}


two_proportion_server <- function(id) {
  moduleServer(id, function(input, output, session) {

    # 1. 보고서 ----------------------------------------------------------------
    output$results_two_proportionn <- renderUI({
      if (input$propx_twoprop == "prop_true" & input$pooledstderr_twoprop == FALSE) {
        test <- prop.z.test2(x1 = input$n1_twoprop * input$p1_twoprop, x2 = input$n2_twoprop * input$p2_twoprop, n1 = input$n1_twoprop, n2 = input$n2_twoprop, p0 = input$twoprop_h0, conf.level = 1 - input$twoprop_alpha, alternative = input$twoprop_alternative, pooled.stderr = FALSE)
        test_confint <- prop.z.test2(x1 = input$n1_twoprop * input$p1_twoprop, x2 = input$n2_twoprop * input$p2_twoprop, n1 = input$n1_twoprop, n2 = input$n2_twoprop, p0 = input$twoprop_h0, conf.level = 1 - input$twoprop_alpha, alternative = "two.sided", pooled.stderr = FALSE)
        withMathJax(
          tags$h3("데이터"),
          br(),
          paste0("\\(n_1 =\\) ", round(test$n1, 3)),
          br(),
          paste0("\\(n_2 =\\) ", round(test$n2, 3)),
          br(),
          paste0("\\(\\hat{p}_1 =\\) ", round(test$estimate1, 3)),
          br(),
          paste0("\\(\\hat{p}_2 =\\) ", round(test$estimate2, 3)),
          br(),
          paste0("\\(\\hat{q}_1 = 1 - \\hat{p}_1 =\\) ", round(1 - test$estimate1, 3)),
          br(),
          paste0("\\(\\hat{q}_2 = 1 - \\hat{p}_2 =\\) ", round(1 - test$estimate2, 3)),
          br(),
          helpText(paste0("\\( n_1\\hat{p}_1 = \\) ", round(test$n1 * test$estimate1, 3),
                          " 와 \\( n_1(1-\\hat{p}_1) = \\) ", round(test$n1 * (1 - test$estimate1), 3))),
          helpText(paste0("\\( n_2\\hat{p}_2 = \\) ", round(test$n2 * test$estimate2, 3),
                          " 와 \\( n_2(1-\\hat{p}_2) = \\) ", round(test$n2 * (1 - test$estimate2), 3))),
          helpText(paste0("\\( n_1\\hat{p}_1 \\geq 5\\), \\( n_1(1-\\hat{p}_1) \\geq 5\\), \\( n_2\\hat{p}_2 \\geq 5\\) 와 \\( n_2(1-\\hat{p}_2) \\geq 5\\) 가정이 ",
                          ifelse(test$n1 * test$estimate1 >= 5 & test$n1 * (1 - test$estimate1) >= 5 & test$n2 * test$estimate2 >= 5 & test$n2 * (1 - test$estimate2) >= 5, " 충족됨.", " 총족되지 않음."))),
          br(),
          tags$h3("신뢰구간 - 양측"),
          br(),
          paste0(
            (1 - input$twoprop_alpha) * 100, "% 신뢰구간: \\(p_1 - p_2 = \\hat{p}_1 - \\hat{p}_2 \\pm z_{\\alpha/2} \\sqrt{\\dfrac{\\hat{p}_1(1-\\hat{p}_1)}{n_1} + \\dfrac{\\hat{p}_2(1-\\hat{p}_2)}{n_2}} = \\) ",
            round(test_confint$estimate1, 3), ifelse(test_confint$estimate2 >= 0, paste0(" - ", round(test_confint$estimate2, 3)), paste0(" + ", round(abs(test_confint$estimate2), 3))), "  \\( \\pm \\) ", "\\( ( \\)", round(qnorm(input$twoprop_alpha / 2, lower.tail = FALSE), 3), " * ", round(test_confint$stderr, 3), "\\( ) \\) ", "\\( = \\) ",
            "[", round(test_confint$conf.int[1], 3), "; ", round(test_confint$conf.int[2], 3), "]"
          ),
          br(),
          br(),
          tags$h3("가설 검정"),
          br(),
          paste0("1. \\(H_0 : p_1 - p_2 = \\) ", test$null.value, " vs. \\(H_1 : p_1 - p_2 \\) ", ifelse(input$twoprop_alternative == "two.sided", "\\( \\neq \\) ", ifelse(input$twoprop_alternative == "greater", "\\( > \\) ", "\\( < \\) ")), test$null.value),
          br(),
          paste0(
            "2. 검정 통계량 : \\(z_{obs} = \\dfrac{(\\hat{p}_1 - \\hat{p}_2) - (p_1 - p_2)}{\\sqrt{\\dfrac{\\hat{p}_1(1-\\hat{p}_1)}{n_1} + \\dfrac{\\hat{p}_2(1-\\hat{p}_2)}{n_2}}} = \\) ",
            "(", round(test$estimate1, 3), ifelse(test$estimate2 >= 0, paste0(" - ", round(test$estimate2, 3)), paste0(" + ", round(abs(test$estimate2), 3))), ifelse(test$null.value >= 0, paste0(" - ", test$null.value), paste0(" + ", abs(test$null.value))), ") / ", round(test$stderr, 3), " \\( = \\) ",
            ifelse(test$null.value >= -1 & test$null.value <= 1, round(test$statistic, 3), "Error: \\( p_1 - p_2 \\) must be \\( -1 \\leq p_1 - p_2 \\leq 1\\)")
          ),
          br(),
          paste0(
            "3. 임계값 :", ifelse(input$twoprop_alternative == "two.sided", " \\( \\pm z_{\\alpha/2} = \\pm z(\\)", ifelse(input$twoprop_alternative == "greater", " \\( z_{\\alpha} = z(\\)", " \\( -z_{\\alpha} = -z(\\)")),
            ifelse(input$twoprop_alternative == "two.sided", input$twoprop_alpha / 2, input$twoprop_alpha), "\\()\\)", " \\( = \\) ",
            ifelse(input$twoprop_alternative == "two.sided", "\\( \\pm \\)", ifelse(input$twoprop_alternative == "greater", "", " -")),
            ifelse(input$twoprop_alternative == "two.sided", round(qnorm(input$twoprop_alpha / 2, lower.tail = FALSE), 3), round(qnorm(input$twoprop_alpha, lower.tail = FALSE), 3))
          ),
          br(),
          paste0("4. 결론 : ", ifelse(test$p.value < input$twoprop_alpha, "\\(H_0\\) 기각.", "\\(H_0\\) 기각 못함.")),
          br(),
          br(),
          tags$h3("해석"),
          br(),

          paste0( input$twoprop_alpha * 100, "% 유의수준에서, ",
                  "비율에서 차이가 ", test$null.value, " 이라는 귀무가설을 ",
                  ifelse(test$p.value < input$twoprop_alpha, "기각한다", "기각하지 않는다"),
                  " \\((p\\)-값 ", ifelse(test$p.value < 0.001, "< 0.001", paste0("\\(=\\) ", round(test$p.value, 3))), ")", ".")

        )
      } else if ( input$propx_twoprop == "prop_false" & input$pooledstderr_twoprop == FALSE) {
        test <- prop.z.test2(x1 = input$x1_twoprop, x2 = input$x2_twoprop, n1 = input$n1_twoprop, n2 = input$n2_twoprop, p0 = input$twoprop_h0, conf.level = 1 - input$twoprop_alpha, alternative = input$twoprop_alternative, pooled.stderr = FALSE)
        test_confint <- prop.z.test2(x1 = input$x1_twoprop, x2 = input$x2_twoprop, n1 = input$n1_twoprop, n2 = input$n2_twoprop, p0 = input$twoprop_h0, conf.level = 1 - input$twoprop_alpha, alternative = "two.sided", pooled.stderr = FALSE)
        withMathJax(
          tags$h2("데이터"),
          br(),
          paste0("\\(n_1 =\\) ", round(test$n1, 3)),
          br(),
          paste0("\\(n_2 =\\) ", round(test$n2, 3)),
          br(),
          paste0("\\(\\hat{p}_1 = \\dfrac{x_1}{n_1} = \\) ", test$x1, " \\( / \\) ", test$n1, " \\( = \\) ", round(test$estimate1, 3)),
          br(),
          paste0("\\(\\hat{p}_2 = \\dfrac{x_2}{n_2} = \\) ", test$x2, " \\( / \\) ", test$n2, " \\( = \\) ", round(test$estimate2, 3)),
          br(),
          paste0("\\(\\hat{q}_1 = 1 - \\hat{p}_1 =\\) ", round(1 - test$estimate1, 3)),
          br(),
          paste0("\\(\\hat{q}_2 = 1 - \\hat{p}_2 =\\) ", round(1 - test$estimate2, 3)),
          br(),
          helpText(paste0("\\( n_1\\hat{p}_1 = \\) ", round(test$n1 * test$estimate1, 3), " 와 \\( n_1(1-\\hat{p}_1) = \\) ", round(test$n1 * (1 - test$estimate1), 3))),
          helpText(paste0("\\( n_2\\hat{p}_2 = \\) ", round(test$n2 * test$estimate2, 3), " 와 \\( n_2(1-\\hat{p}_2) = \\) ", round(test$n2 * (1 - test$estimate2), 3))),
          helpText(paste0("\\( n_1\\hat{p}_1 \\geq 5\\), \\( n_1(1-\\hat{p}_1) \\geq 5\\), \\( n_2\\hat{p}_2 \\geq 5\\) 와 \\( n_2(1-\\hat{p}_2) \\geq 5\\) 가정이 ", ifelse(test$n1 * test$estimate1 >= 5 & test$n1 * (1 - test$estimate1) >= 5 & test$n2 * test$estimate2 >= 5 & test$n2 * (1 - test$estimate2) >= 5, " 충족됨.", " 충족되지 않음."))),
          br(),
          tags$h2("신뢰구간 - 양측"),
          br(),
          paste0(
            (1 - input$twoprop_alpha) * 100, "% CI for \\(p_1 - p_2 = \\hat{p}_1 - \\hat{p}_2 \\pm z_{\\alpha/2} \\sqrt{\\dfrac{\\hat{p}_1(1-\\hat{p}_1)}{n_1} + \\dfrac{\\hat{p}_2(1-\\hat{p}_2)}{n_2}} = \\) ",
            round(test_confint$estimate1, 3), ifelse(test_confint$estimate2 >= 0, paste0(" - ", round(test_confint$estimate2, 3)), paste0(" + ", round(abs(test_confint$estimate2), 3))), "  \\( \\pm \\) ", "\\( ( \\)", round(qnorm(input$twoprop_alpha / 2, lower.tail = FALSE), 3), " * ", round(test_confint$stderr, 3), "\\( ) \\) ", "\\( = \\) ",
            "[", round(test_confint$conf.int[1], 3), "; ", round(test_confint$conf.int[2], 3), "]"
          ),
          br(),
          br(),
          tags$h2("가설 검정"),
          br(),
          paste0("1. \\(H_0 : p_1 - p_2 = \\) ", test$null.value, " vs. \\(H_1 : p_1 - p_2 \\) ", ifelse(input$twoprop_alternative == "two.sided", "\\( \\neq \\) ", ifelse(input$twoprop_alternative == "greater", "\\( > \\) ", "\\( < \\) ")), test$null.value),
          br(),
          paste0(
            "2. 검정 통계량 : \\(z_{obs} = \\dfrac{(\\hat{p}_1 - \\hat{p}_2) - (p_1 - p_2)}{\\sqrt{\\dfrac{\\hat{p}_1(1-\\hat{p}_1)}{n_1} + \\dfrac{\\hat{p}_2(1-\\hat{p}_2)}{n_2}}} = \\) ",
            "(", round(test$estimate1, 3), ifelse(test$estimate2 >= 0, paste0(" - ", round(test$estimate2, 3)), paste0(" + ", round(abs(test$estimate2), 3))), ifelse(test$null.value >= 0, paste0(" - ", test$null.value), paste0(" + ", abs(test$null.value))), ") / ", round(test$stderr, 3), " \\( = \\) ",
            ifelse(test$null.value >= -1 & test$null.value <= 1, round(test$statistic, 3), "Error: \\( p_1 - p_2 \\) must be \\( -1 \\leq p_1 - p_2 \\leq 1\\)")
          ),
          br(),
          paste0(
            "3. 임계값 :", ifelse(input$twoprop_alternative == "two.sided", " \\( \\pm z_{\\alpha/2} = \\pm z(\\)", ifelse(input$twoprop_alternative == "greater", " \\( z_{\\alpha} = z(\\)", " \\( -z_{\\alpha} = -z(\\)")),
            ifelse(input$twoprop_alternative == "two.sided", input$twoprop_alpha / 2, input$twoprop_alpha), "\\()\\)", " \\( = \\) ",
            ifelse(input$twoprop_alternative == "two.sided", "\\( \\pm \\)", ifelse(input$twoprop_alternative == "greater", "", " -")),
            ifelse(input$twoprop_alternative == "two.sided", round(qnorm(input$twoprop_alpha / 2, lower.tail = FALSE), 3), round(qnorm(input$twoprop_alpha, lower.tail = FALSE), 3))
          ),
          br(),
          paste0("4. 결론 : ", ifelse(test$p.value < input$twoprop_alpha, "\\(H_0\\) 기각.", "\\(H_0\\) 기각 못함.")),
          br(),
          br(),
          tags$h2("해석"),
          br(),
          paste0( input$twoprop_alpha * 100, "% 유의수준에서, ",
                  "비율에서 차이가 ", test$null.value, " 이라는 귀무가설을 ",
                  ifelse(test$p.value < input$twoprop_alpha, "기각한다", "기각하지 않는다"),
                  " \\((p\\)-값 ", ifelse(test$p.value < 0.001, "< 0.001", paste0("\\(=\\) ", round(test$p.value, 3))), ")", ".")
        )
      } else if ( input$propx_twoprop == "prop_true" & input$pooledstderr_twoprop == TRUE) {
        test <- prop.z.test2(x1 = input$n1_twoprop * input$p1_twoprop, x2 = input$n2_twoprop * input$p2_twoprop, n1 = input$n1_twoprop, n2 = input$n2_twoprop, p0 = input$twoprop_h0, conf.level = 1 - input$twoprop_alpha, alternative = input$twoprop_alternative, pooled.stderr = TRUE)
        test_confint <- prop.z.test2(x1 = input$n1_twoprop * input$p1_twoprop, x2 = input$n2_twoprop * input$p2_twoprop, n1 = input$n1_twoprop, n2 = input$n2_twoprop, p0 = input$twoprop_h0, conf.level = 1 - input$twoprop_alpha, alternative = "two.sided", pooled.stderr = FALSE)
        withMathJax(
          tags$h2("데이터"),
          br(),
          paste0("\\(n_1 =\\) ", round(test$n1, 3)),
          br(),
          paste0("\\(n_2 =\\) ", round(test$n2, 3)),
          br(),
          paste0("\\(\\hat{p}_1 =\\) ", round(test$estimate1, 3)),
          br(),
          paste0("\\(\\hat{p}_2 =\\) ", round(test$estimate2, 3)),
          br(),
          paste0("\\(\\hat{q}_1 = 1 - \\hat{p}_1 =\\) ", round(1 - test$estimate1, 3)),
          br(),
          paste0("\\(\\hat{q}_2 = 1 - \\hat{p}_2 =\\) ", round(1 - test$estimate2, 3)),
          br(),
          helpText(paste0("\\( n_1\\hat{p}_1 = \\) ", round(test$n1 * test$estimate1, 3), " 와 \\( n_1(1-\\hat{p}_1) = \\) ", round(test$n1 * (1 - test$estimate1), 3))),
          helpText(paste0("\\( n_2\\hat{p}_2 = \\) ", round(test$n2 * test$estimate2, 3), " 와 \\( n_2(1-\\hat{p}_2) = \\) ", round(test$n2 * (1 - test$estimate2), 3))),
          helpText(paste0("\\( n_1\\hat{p}_1 \\geq 5\\), \\( n_1(1-\\hat{p}_1) \\geq 5\\), \\( n_2\\hat{p}_2 \\geq 5\\) 와 \\( n_2(1-\\hat{p}_2) \\geq 5\\) 가정이", ifelse(test$n1 * test$estimate1 >= 5 & test$n1 * (1 - test$estimate1) >= 5 & test$n2 * test$estimate2 >= 5 & test$n2 * (1 - test$estimate2) >= 5, " 충족됨.", " 충족되지 않음."))),
          br(),
          tags$h2("신뢰구간 - 양측"),
          br(),
          paste0(
            (1 - input$twoprop_alpha) * 100, "% CI for \\(p_1 - p_2 = \\hat{p}_1 - \\hat{p}_2 \\pm z_{\\alpha/2} \\sqrt{\\dfrac{\\hat{p}_1(1-\\hat{p}_1)}{n_1} + \\dfrac{\\hat{p}_2(1-\\hat{p}_2)}{n_2}} = \\) ",
            round(test_confint$estimate1, 3), ifelse(test_confint$estimate2 >= 0, paste0(" - ", round(test_confint$estimate2, 3)), paste0(" + ", round(abs(test_confint$estimate2), 3))), "  \\( \\pm \\) ", "\\( ( \\)", round(qnorm(input$twoprop_alpha / 2, lower.tail = FALSE), 3), " * ", round(test_confint$stderr, 3), "\\( ) \\) ", "\\( = \\) ",
            "[", round(test_confint$conf.int[1], 3), "; ", round(test_confint$conf.int[2], 3), "]"
          ),
          br(),
          br(),
          tags$h2("가설 검정"),
          br(),
          paste0("1. \\(H_0 : p_1 - p_2 = \\) ", test$null.value, " vs. \\(H_1 : p_1 - p_2 \\) ", ifelse(input$twoprop_alternative == "two.sided", "\\( \\neq \\) ", ifelse(input$twoprop_alternative == "greater", "\\( > \\) ", "\\( < \\) ")), test$null.value),
          br(),
          paste0("2. 검정 통계량 : \\(z_{obs} = \\dfrac{(\\hat{p}_1 - \\hat{p}_2) - (p_1 - p_2)}{\\sqrt{\\hat{p}(1-\\hat{p})\\Big(\\dfrac{1}{n_1} + \\dfrac{1}{n_2}\\Big)}} \\) "),
          br(),
          paste0("여기서 ", "\\( \\hat{p} = \\dfrac{n_1 \\hat{p}_1 + n_2 \\hat{p}_2}{n_1 + n_2} = \\) ", "(", test$n1, " * ", round(test$estimate1, 3), " + ", test$n2, " * ", round(test$estimate2, 3), ") / (", test$n1, " + ", test$n2, ") = ", round(test$pooled.phat, 3)),
          br(),
          paste0(
            "\\( \\Rightarrow z_{obs} = \\dfrac{(\\hat{p}_1 - \\hat{p}_2) - (p_1 - p_2)}{\\sqrt{\\hat{p}(1-\\hat{p})\\Big(\\dfrac{1}{n_1} + \\dfrac{1}{n_2}\\Big)}} = \\) ",
            "(", round(test$estimate1, 3), ifelse(test$estimate2 >= 0, paste0(" - ", round(test$estimate2, 3)), paste0(" + ", round(abs(test$estimate2), 3))), ifelse(test$null.value >= 0, paste0(" - ", test$null.value), paste0(" + ", abs(test$null.value))), ") / ", round(test$stderr, 3), " \\( = \\) ",
            ifelse(test$null.value >= -1 & test$null.value <= 1, round(test$statistic, 3), "Error: \\( p_1 - p_2 \\) must be \\( -1 \\leq p_1 - p_2 \\leq 1\\)")
          ),
          br(),
          paste0(
            "3. 임계값 :", ifelse(input$twoprop_alternative == "two.sided", " \\( \\pm z_{\\alpha/2} = \\pm z(\\)", ifelse(input$twoprop_alternative == "greater", " \\( z_{\\alpha} = z(\\)", " \\( -z_{\\alpha} = -z(\\)")),
            ifelse(input$twoprop_alternative == "two.sided", input$twoprop_alpha / 2, input$twoprop_alpha), "\\()\\)", " \\( = \\) ",
            ifelse(input$twoprop_alternative == "two.sided", "\\( \\pm \\)", ifelse(input$twoprop_alternative == "greater", "", " -")),
            ifelse(input$twoprop_alternative == "two.sided", round(qnorm(input$twoprop_alpha / 2, lower.tail = FALSE), 3), round(qnorm(input$twoprop_alpha, lower.tail = FALSE), 3))
          ),
          br(),
          paste0("4. 결론 : ", ifelse(test$p.value < input$twoprop_alpha, "\\(H_0\\) 기각.", "\\(H_0\\) 기각 못함.")),
          br(),
          br(),
          tags$h2("해석"),
          br(),

          paste0( input$twoprop_alpha * 100, "% 유의수준에서, ",
                  "비율에서 차이가 ", test$null.value, " 이라는 귀무가설을 ",
                  ifelse(test$p.value < input$twoprop_alpha, "기각한다", "기각하지 않는다"),
                  " \\((p\\)-값 ", ifelse(test$p.value < 0.001, "< 0.001", paste0("\\(=\\) ", round(test$p.value, 3))), ")", ".")
        )
      } else if ( input$propx_twoprop == "prop_false" & input$pooledstderr_twoprop == TRUE) {
        test <- prop.z.test2(x1 = input$x1_twoprop, x2 = input$x2_twoprop, n1 = input$n1_twoprop, n2 = input$n2_twoprop, p0 = input$twoprop_h0, conf.level = 1 - input$twoprop_alpha, alternative = input$twoprop_alternative, pooled.stderr = TRUE)
        test_confint <- prop.z.test2(x1 = input$x1_twoprop, x2 = input$x2_twoprop, n1 = input$n1_twoprop, n2 = input$n2_twoprop, p0 = input$twoprop_h0, conf.level = 1 - input$twoprop_alpha, alternative = "two.sided", pooled.stderr = FALSE)
        withMathJax(
          tags$h2("데이터"),
          br(),
          paste0("\\(n_1 =\\) ", round(test$n1, 3)),
          br(),
          paste0("\\(n_2 =\\) ", round(test$n2, 3)),
          br(),
          paste0("\\(\\hat{p}_1 = \\dfrac{x_1}{n_1} = \\) ", test$x1, " \\( / \\) ", test$n1, " \\( = \\) ", round(test$estimate1, 3)),
          br(),
          paste0("\\(\\hat{p}_2 = \\dfrac{x_2}{n_2} = \\) ", test$x2, " \\( / \\) ", test$n2, " \\( = \\) ", round(test$estimate2, 3)),
          br(),
          paste0("\\(\\hat{q}_1 = 1 - \\hat{p}_1 =\\) ", round(1 - test$estimate1, 3)),
          br(),
          paste0("\\(\\hat{q}_2 = 1 - \\hat{p}_2 =\\) ", round(1 - test$estimate2, 3)),
          br(),
          helpText(paste0("\\( n_1\\hat{p}_1 = \\) ", round(test$n1 * test$estimate1, 3), " 와 \\( n_1(1-\\hat{p}_1) = \\) ", round(test$n1 * (1 - test$estimate1), 3))),
          helpText(paste0("\\( n_2\\hat{p}_2 = \\) ", round(test$n2 * test$estimate2, 3), " 와 \\( n_2(1-\\hat{p}_2) = \\) ", round(test$n2 * (1 - test$estimate2), 3))),
          helpText(paste0("\\( n_1\\hat{p}_1 \\geq 5\\), \\( n_1(1-\\hat{p}_1) \\geq 5\\), \\( n_2\\hat{p}_2 \\geq 5\\) 와 \\( n_2(1-\\hat{p}_2) \\geq 5\\) 가정이", ifelse(test$n1 * test$estimate1 >= 5 & test$n1 * (1 - test$estimate1) >= 5 & test$n2 * test$estimate2 >= 5 & test$n2 * (1 - test$estimate2) >= 5, " 충족됨.", " 충족되지 않음."))),
          br(),
          tags$h2("신뢰구간 - 양측"),
          br(),
          paste0(
            (1 - input$twoprop_alpha) * 100, "% CI for \\(p_1 - p_2 = \\hat{p}_1 - \\hat{p}_2 \\pm z_{\\alpha/2} \\sqrt{\\dfrac{\\hat{p}_1(1-\\hat{p}_1)}{n_1} + \\dfrac{\\hat{p}_2(1-\\hat{p}_2)}{n_2}} = \\) ",
            round(test_confint$estimate1, 3), ifelse(test_confint$estimate2 >= 0, paste0(" - ", round(test_confint$estimate2, 3)), paste0(" + ", round(abs(test_confint$estimate2), 3))), "  \\( \\pm \\) ", "\\( ( \\)", round(qnorm(input$twoprop_alpha / 2, lower.tail = FALSE), 3), " * ", round(test_confint$stderr, 3), "\\( ) \\) ", "\\( = \\) ",
            "[", round(test_confint$conf.int[1], 3), "; ", round(test_confint$conf.int[2], 3), "]"
          ),
          br(),
          br(),
          tags$h2("가설 검정"),
          br(),
          paste0("1. \\(H_0 : p_1 - p_2 = \\) ", test$null.value, " vs. \\(H_1 : p_1 - p_2 \\) ", ifelse(input$twoprop_alternative == "two.sided", "\\( \\neq \\) ", ifelse(input$twoprop_alternative == "greater", "\\( > \\) ", "\\( < \\) ")), test$null.value),
          br(),
          paste0("2. 검정 통계량 : \\(z_{obs} = \\dfrac{(\\hat{p}_1 - \\hat{p}_2) - (p_1 - p_2)}{\\sqrt{\\hat{p}(1-\\hat{p})\\Big(\\dfrac{1}{n_1} + \\dfrac{1}{n_2}\\Big)}} \\) "),
          br(),
          paste0("여기서 ", "\\( \\hat{p} = \\dfrac{n_1 \\hat{p}_1 + n_2 \\hat{p}_2}{n_1 + n_2} = \\) ", "(", test$n1, " * ", round(test$estimate1, 3), " + ", test$n2, " * ", round(test$estimate2, 3), ") / (", test$n1, " + ", test$n2, ") = ", round(test$pooled.phat, 3)),
          br(),
          paste0(
            "\\( \\Rightarrow z_{obs} = \\dfrac{(\\hat{p}_1 - \\hat{p}_2) - (p_1 - p_2)}{\\sqrt{\\hat{p}(1-\\hat{p})\\Big(\\dfrac{1}{n_1} + \\dfrac{1}{n_2}\\Big)}} = \\) ",
            "(", round(test$estimate1, 3), ifelse(test$estimate2 >= 0, paste0(" - ", round(test$estimate2, 3)), paste0(" + ", round(abs(test$estimate2), 3))), ifelse(test$null.value >= 0, paste0(" - ", test$null.value), paste0(" + ", abs(test$null.value))), ") / ", round(test$stderr, 3), " \\( = \\) ",
            ifelse(test$null.value >= -1 & test$null.value <= 1, round(test$statistic, 3), "Error: \\( p_1 - p_2 \\) must be \\( -1 \\leq p_1 - p_2 \\leq 1\\)")
          ),
          br(),
          paste0(
            "3. 임계값 :", ifelse(input$twoprop_alternative == "two.sided", " \\( \\pm z_{\\alpha/2} = \\pm z(\\)", ifelse(input$twoprop_alternative == "greater", " \\( z_{\\alpha} = z(\\)", " \\( -z_{\\alpha} = -z(\\)")),
            ifelse(input$twoprop_alternative == "two.sided", input$twoprop_alpha / 2, input$twoprop_alpha), "\\()\\)", " \\( = \\) ",
            ifelse(input$twoprop_alternative == "two.sided", "\\( \\pm \\)", ifelse(input$twoprop_alternative == "greater", "", " -")),
            ifelse(input$twoprop_alternative == "two.sided", round(qnorm(input$twoprop_alpha / 2, lower.tail = FALSE), 3), round(qnorm(input$twoprop_alpha, lower.tail = FALSE), 3))
          ),
          br(),
          paste0("4. 결론 : ", ifelse(test$p.value < input$twoprop_alpha, "\\(H_0\\) 기각.", "\\(H_0\\) 기각 못함.")),
          br(),
          br(),
          tags$h2("해석"),
          br(),

          paste0( input$twoprop_alpha * 100, "% 유의수준에서, ",
                  "비율에서 차이가 ", test$null.value, " 이라는 귀무가설을 ",
                  ifelse(test$p.value < input$twoprop_alpha, "기각한다", "기각하지 않는다"),
                  " \\((p\\)-값 ", ifelse(test$p.value < 0.001, "< 0.001", paste0("\\(=\\) ", round(test$p.value, 3))), ")", ".")


        )
      }
    })

    # 2. 시각화 ----------------------------------------------------------------
    output$plot_two_proportion <- renderPlot({
      if (input$propx_twoprop == "prop_true" & input$pooledstderr_twoprop == FALSE) {
        test <- prop.z.test2(x1 = input$n1_twoprop * input$p1_twoprop, x2 = input$n2_twoprop * input$p2_twoprop, n1 = input$n1_twoprop, n2 = input$n2_twoprop, p0 = input$twoprop_h0, conf.level = 1 - input$twoprop_alpha, alternative = input$twoprop_alternative, pooled.stderr = FALSE)
      } else if (input$propx_twoprop == "prop_false" & input$pooledstderr_twoprop == FALSE) {
        test <- prop.z.test2(x1 = input$x1_twoprop, x2 = input$x2_twoprop, n1 = input$n1_twoprop, n2 = input$n2_twoprop, p0 = input$twoprop_h0, conf.level = 1 - input$twoprop_alpha, alternative = input$twoprop_alternative, pooled.stderr = FALSE)
      } else if (input$propx_twoprop == "prop_true" & input$pooledstderr_twoprop == TRUE) {
        test <- prop.z.test2(x1 = input$n1_twoprop * input$p1_twoprop, x2 = input$n2_twoprop * input$p2_twoprop, n1 = input$n1_twoprop, n2 = input$n2_twoprop, p0 = input$twoprop_h0, conf.level = 1 - input$twoprop_alpha, alternative = input$twoprop_alternative, pooled.stderr = TRUE)
      } else if (input$propx_twoprop == "prop_false" & input$pooledstderr_twoprop == TRUE) {
        test <- prop.z.test2(x1 = input$x1_twoprop, x2 = input$x2_twoprop, n1 = input$n1_twoprop, n2 = input$n2_twoprop, p0 = input$twoprop_h0, conf.level = 1 - input$twoprop_alpha, alternative = input$twoprop_alternative, pooled.stderr = TRUE)
      }
      if (input$twoprop_alternative == "two.sided") {
        funcShaded <- function(x) {
          y <- dnorm(x, mean = 0, sd = 1)
          y[x < qnorm(input$twoprop_alpha / 2, mean = 0, sd = 1, lower.tail = FALSE) & x > qnorm(input$twoprop_alpha / 2, mean = 0, sd = 1) ] <- NA
          return(y)
        }
      } else if (input$twoprop_alternative == "greater") {
        funcShaded <- function(x) {
          y <- dnorm(x, mean = 0, sd = 1)
          y[x < qnorm(input$twoprop_alpha, mean = 0, sd = 1, lower.tail = FALSE) ] <- NA
          return(y)
        }
      } else if (input$twoprop_alternative == "less") {
        funcShaded <- function(x) {
          y <- dnorm(x, mean = 0, sd = 1)
          y[x > qnorm(input$twoprop_alpha, mean = 0, sd = 1, lower.tail = TRUE) ] <- NA
          return(y)
        }
      }
      p <- ggplot(data.frame(x = c(qnorm(0.999, mean = 0, sd = 1, lower.tail = FALSE), qnorm(0.999, mean = 0, sd = 1, lower.tail = TRUE))), aes(x = x)) +

        stat_function( fun = dnorm, fill="gray90", args = list(mean = 0, sd = 1)) +
        stat_function( fun = funcShaded, geom = "area", fill = "sky blue", alpha = 0.8) +

        theme_minimal() +
        geom_vline(xintercept = test$statistic, color = "steelblue") +
        geom_text(aes(x = test$statistic, label = paste0("검정 통계량 = ", round(test$statistic, 3)), y = 0.2), colour = "steelblue", angle = 90, vjust = 1.3, text = element_text(size = 11)) +
        ggtitle(paste0("정규분포 N(0,1)")) +
        theme(plot.title = element_text(face = "bold", hjust = 0.5)) +
        ylab("밀도") +
        xlab("x")
      p
    })

    # 3. 표 ----------------------------------------------------------------
    output$table_two_proportion <- render_gt({
      prop_dat1 <- rbinom(input$n1_twoprop, 1, input$p1_twoprop)
      prop_dat2 <- rbinom(input$n2_twoprop, 1, input$p2_twoprop)

      tibble(변수1 = prop_dat1,
             변수2 = prop_dat2 ) %>%
        mutate(순번 = row_number()) |>
        relocate(순번, .before = 변수1) |>
        gt()
    })

  })
}


ui <- fluidPage(
  titlePanel("2-비율 가설 검정"),
  two_proportion_UI("two_prop")
)

server <- function(input, output, session) {
  two_proportion_server("two_prop")
}

shinyApp(ui, server)

2 코딩

라이센스

CC BY-SA-NC & GPL-3