이산형 분포

확률분포
이산형 분포
베르누이 분포
이항 분포
포아송 분포
저자

이광춘

공개

2026-07-06

이산확률분포를 시각화하고 상호작용하는 Shiny 애플리케이션과 관련 코드를 설명합니다.

  1. Shiny 앱: 사용자가 대표적인 이산확률분포(베르누이, 이항, 포아송, 기하 분포) 중 하나를 선택하고 해당 분포의 매개변수를 조정할 수 있는 웹 기반 인터페이스를 제공합니다. 사용자는 선택한 분포에 따라 확률질량함수(PMF)와 누적질량함수(CMF)를 그래프로 시각화할 수 있습니다. 추가적으로, 선택한 매개변수에 따른 분포의 수치를 테이블 형태로 확인할 수 있습니다.

  2. 코딩: 분포의 PMF와 CMF를 계산하고 시각화하는 R 코드가 제공됩니다. 여기서 discrete_dist_summary 함수는 주어진 분포와 매개변수에 따라 PMF와 CMF를 계산하고, 이 결과를 테이블과 그래프로 출력합니다. 이 부분은 Shiny 앱보다는 코드 수준에서 분포를 다루고 싶은 사용자에게 유용합니다.

#| label: shinylive-discrete-distribution
#| viewerHeight: 700
#| standalone: true

library(shiny)
library(ggplot2)

library(bslib)
library(showtext)
tryCatch(sysfonts::font_add_google("Nanum Myeongjo", "kr"), error = function(e) NULL)
showtext_auto()

# ── Tufte 팔레트 · 테마 (자동 주입) ──────────────────────────
bit_cream <- "#fffff8"; bit_ink <- "#111111"; bit_rust <- "#8a1500"
bit_muted <- "#4a4a44"; bit_grid <- "#e8e6dc"; bit_axis <- "#8a8578"
theme_tufte_bit <- function(base_size = 13) {
  ggplot2::theme_minimal(base_size = base_size) +
    ggplot2::theme(
      text = ggplot2::element_text(family = "kr", colour = bit_ink),
      plot.title = ggplot2::element_text(family = "kr", size = base_size, hjust = 0),
      plot.background = ggplot2::element_rect(fill = bit_cream, colour = NA),
      panel.background = ggplot2::element_rect(fill = bit_cream, colour = NA),
      panel.grid.major = ggplot2::element_line(colour = bit_grid, linewidth = 0.3),
      panel.grid.minor = ggplot2::element_blank(),
      axis.line = ggplot2::element_line(colour = bit_axis, linewidth = 0.3),
      axis.ticks = ggplot2::element_line(colour = bit_axis, linewidth = 0.3)
    )
}
if (requireNamespace("ggplot2", quietly = TRUE)) ggplot2::theme_set(theme_tufte_bit())
bit_theme <- bs_theme(
  version = 5, bg = bit_cream, fg = bit_ink, primary = bit_rust,
  base_font = font_collection("Palatino Linotype", "Georgia", "Times New Roman", "serif"),
  "card-box-shadow" = "none"
)
# ─────────────────────────────────────────────────────────────

ui <- fluidPage(
  theme = bit_theme,
  titlePanel("이산형 분포"),
  sidebarLayout(
    sidebarPanel(
      radioButtons("dist", "분포 선택:",
                   choices = c("베르누이" = "Bernoulli", "이항" = "Binomial",
                               "포아송" = "Poisson", "기하" = "Geometric"),
                   inline = TRUE),
      conditionalPanel(
        condition = "input.dist == 'Bernoulli'",
        sliderInput("bern_p", "성공 확률 (p):", min = 0, max = 1, value = 0.5, step = 0.01)
      ),
      conditionalPanel(
        condition = "input.dist == 'Binomial'",
        sliderInput("bin_n", "시행 횟수 (n):", min = 1, max = 50, value = 10),
        sliderInput("bin_p", "성공 확률 (p):", min = 0, max = 1, value = 0.5, step = 0.01)
      ),
      conditionalPanel(
        condition = "input.dist == 'Poisson'",
        sliderInput("pois_lambda", "람다 (λ):", min = 0, max = 10, value = 2, step = 0.1)
      ),
      conditionalPanel(
        condition = "input.dist == 'Geometric'",
        sliderInput("geom_p", "성공 확률 (p):", min = 0, max = 1, value = 0.2, step = 0.01)
      ),
      uiOutput("x_range_slider")
    ),
    mainPanel(
      tabsetPanel(
        tabPanel("PMF 그래프", plotOutput("pmf_plot")),
        tabPanel("CMF 그래프", plotOutput("cmf_plot")),
        tabPanel("표", tableOutput("table"))
      ),
      h4("사용 방법:"),
      tags$ol(
        tags$li("라디오 버튼으로 분포 종류를 선택합니다."),
        tags$li("슬라이더로 선택한 분포의 모수를 조정합니다."),
        tags$li("'x 범위' 슬라이더로 x축의 범위를 조절합니다."),
        tags$li("각 탭에서 PMF와 CMF 그래프를 살펴봅니다."),
        tags$li("'표' 탭에서 PMF와 CMF의 수치를 확인합니다.")
      )
    )
  )
)

server <- function(input, output) {
  x_range_values <- reactive({
    if (input$dist == "Bernoulli") {
      list(min = 0, max = 1, value = 1)
    } else {
      list(min = 0, max = 50, value = 10)
    }
  })

  output$x_range_slider <- renderUI({
    sliderInput("x_range", "x 범위:", min = x_range_values()$min, max = x_range_values()$max, value = x_range_values()$value)
  })

  dist_data <- reactive({
    x <- 0:input$x_range
    if (input$dist == "Bernoulli") {
      data.frame(x = x,
                 pmf = dbinom(x, 1, input$bern_p),
                 cmf = pbinom(x, 1, input$bern_p))
    } else if (input$dist == "Binomial") {
      data.frame(x = x,
                 pmf = dbinom(x, input$bin_n, input$bin_p),
                 cmf = pbinom(x, input$bin_n, input$bin_p))
    } else if (input$dist == "Poisson") {
      data.frame(x = x,
                 pmf = dpois(x, input$pois_lambda),
                 cmf = ppois(x, input$pois_lambda))
    } else if (input$dist == "Geometric") {
      data.frame(x = x,
                 pmf = dgeom(x, input$geom_p),
                 cmf = pgeom(x, input$geom_p))
    }
  })

  output$pmf_plot <- renderPlot({
    ggplot(dist_data(), aes(x = x, y = pmf)) +
      geom_bar(stat = "identity", fill = "#4a4a44") +
      labs(title = "확률질량함수 (PMF)",
           x = "x", y = "확률")
  })

  output$cmf_plot <- renderPlot({
    ggplot(dist_data(), aes(x = x, y = cmf)) +
      geom_step(color = "#4a4a44") +
      labs(title = "누적질량함수 (CMF)",
           x = "x", y = "누적확률")
  })

  output$table <- renderTable({
    dist_data()
  })
}

shinyApp(ui, server)

라이센스