최대우도추정

이론
최대우도추정
MLE
정규분포
포아송분포
이항분포
공개

2026-07-06

최대우도추정법(Maximum Likelihood Estimation, MLE)은 주어진 데이터로부터 모수(parameter)를 추정하는 통계적 방법입니다. MLE는 모수를 추정하기 위해, 관측된 데이터가 가장 일어날 가능성이 높도록 만드는 모수 값을 찾습니다. 우도함수(likelihood function)를 정의하고, 이 함수를 최대화하는 모수 값을 추정합니다. MLE는 대수우도(log-likelihood)를 사용하여 최적화 문제를 풀기도 합니다. MLE 추정량은 큰 샘플 크기에서 일치성(consistency)과 효율성(efficiency)을 가지며, 이는 대수의 법칙과 중심극한정리에 의해 뒷받침됩니다. 예를 들어, 정규분포의 평균과 표준편차, 이항분포의 성공 확률, 포아송분포의 평균 발생률 등을 추정할 때 MLE를 사용할 수 있습니다.

#| label: shinylive-lln-coin
#| viewerHeight: 700
#| standalone: true

library(shiny)
library(ggplot2)
library(showtext)
showtext_auto()    

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("최대우도추정 (MLE)"),
  sidebarLayout(
    sidebarPanel(
      h3("모수 추정"),
      p("주어진 데이터에 대해 최대우도추정(MLE)을 수행하여 모수를 추정합니다."),
      p("표본 크기와 실제 모수 값을 조절해보세요."),
      p("히스토그램은 생성된 데이터(표본 추출 데이터)를 나타냅니다."),
      p("파란색 점선은 최대우도추정을 통해 추정된 모수 값을, 오차 막대는 추정된 표준오차를 나타냅니다."),
      br(),
      h4("표본 추출"),
      sliderInput("sample_size", "표본 크기:", min = 10, max = 1000, value = 100),
      br()
    ),
    mainPanel(
      tabsetPanel(
        tabPanel("정규분포",
                 h4("모집단(정규분포)"),
                 sliderInput("norm_mean", "실제 모평균:", min = 0, max = 10, value = 5, step = 0.1),
                 sliderInput("norm_sd", "실제 모표준편차:", min = 0.1, max = 5, value = 1, step = 0.1),
                 plotOutput("norm_plot"),
                 verbatimTextOutput("norm_result")
        ),
        tabPanel("포아송분포",
                 h4("모집단(포아송분포)"),
                 sliderInput("pois_lambda", "실제 모수 (λ):", min = 0.1, max = 10, value = 2, step = 0.1),
                 plotOutput("pois_plot"),
                 verbatimTextOutput("pois_result")
        ),
        tabPanel("이항분포",
                 h4("모집단(이항분포)"),
                 sliderInput("binom_size", "실제 모수 (n):", min = 1, max = 100, value = 10, step = 1),
                 sliderInput("binom_prob", "실제 모수 (p):", min = 0, max = 1, value = 0.5, step = 0.01),
                 plotOutput("binom_plot"),
                 verbatimTextOutput("binom_result")
        )
      )
    )
  )
)

server <- function(input, output) {
  norm_data <- reactive({
    req(input$sample_size, input$norm_mean, input$norm_sd)
    rnorm(input$sample_size, mean = input$norm_mean, sd = input$norm_sd)
  })

  norm_estimates <- reactive({
    list(
      mean = mean(norm_data()),
      sd = sd(norm_data()),
      se_mean = sd(norm_data()) / sqrt(input$sample_size),
      se_sd = sd(norm_data()) / sqrt(2 * input$sample_size)
    )
  })

  output$norm_plot <- renderPlot({
    ggplot(data.frame(x = norm_data()), aes(x)) +
      geom_histogram(aes(y = ..density..), bins = 30, fill = "#c9c6b8", color = "white") +
      stat_function(fun = dnorm, args = list(mean = norm_estimates()$mean, sd = norm_estimates()$sd),
                    color = "#8a1500", size = 1.5) +
      geom_vline(xintercept = norm_estimates()$mean, linetype = "dashed", color = "#4a4a44", size = 1) +
      geom_errorbarh(aes(xmin = norm_estimates()$mean - norm_estimates()$se_mean,
                         xmax = norm_estimates()$mean + norm_estimates()$se_mean,
                         y = 0), color = "#4a4a44", height = 0.01, size = 1) +
      labs(title = "정규분포 MLE", x = "값", y = "밀도") +
      theme_tufte_bit() +
      theme(plot.title = element_text(hjust = 0.5, size = 20, face = "bold"),
            axis.title = element_text(size = 14),
            axis.text = element_text(size = 12))
  })

  output$norm_result <- renderText({
    paste0("MLE 추정결과:\n",
           "추정된 평균 = ", round(norm_estimates()$mean, 3), "\n",
           "추정된 표준편차 = ", round(norm_estimates()$sd, 3), "\n",
           "평균의 표준오차 = ", round(norm_estimates()$se_mean, 3), "\n",
           "표준편차의 표준오차 = ", round(norm_estimates()$se_sd, 3))
  })

  pois_data <- reactive({
    req(input$sample_size, input$pois_lambda)
    rpois(input$sample_size, lambda = input$pois_lambda)
  })

  pois_estimates <- reactive({
    list(
      lambda = mean(pois_data()),
      se_lambda = sqrt(mean(pois_data()) / input$sample_size)
    )
  })

  output$pois_plot <- renderPlot({
    ggplot(data.frame(x = pois_data()), aes(x, fill = "#c9c6b8")) +
      geom_histogram(aes(y = ..count.. / sum(..count..)), bins = 30, color = "white") +
      geom_vline(xintercept = pois_estimates()$lambda, linetype = "dashed", color = "#4a4a44", size = 1) +
      geom_errorbarh(aes(xmin = pois_estimates()$lambda - pois_estimates()$se_lambda,
                         xmax = pois_estimates()$lambda + pois_estimates()$se_lambda,
                         y = 0), color = "#4a4a44", height = 0.01, size = 1) +
      labs(title = "포아송분포 MLE", x = "값", y = "확률") +
      theme_tufte_bit() +
      theme(plot.title = element_text(hjust = 0.5, size = 20, face = "bold"),
            axis.title = element_text(size = 14),
            axis.text = element_text(size = 12))
  })

  output$pois_result <- renderText({
    paste0("MLE 추정결과:\n",
           "추정된 λ = ", round(pois_estimates()$lambda, 3), "\n",
           "λ의 표준오차 = ", round(pois_estimates()$se_lambda, 3))
  })

  binom_data <- reactive({
    req(input$sample_size, input$binom_size, input$binom_prob)
    rbinom(input$sample_size, size = input$binom_size, prob = input$binom_prob)
  })

  binom_estimates <- reactive({
    list(
      prob = mean(binom_data()) / input$binom_size,
      se_prob = sqrt(mean(binom_data()) / input$binom_size * (1 - mean(binom_data()) / input$binom_size) / input$sample_size)
    )
  })

  output$binom_plot <- renderPlot({
    ggplot(data.frame(x = binom_data()), aes(x)) +
      geom_histogram(aes(y = ..count.. / sum(..count..)), bins = min(30, max(binom_data())), fill = "#c9c6b8", color = "white") +
      geom_vline(xintercept = binom_estimates()$prob * input$binom_size, linetype = "dashed", color = "#4a4a44", size = 1) +
      geom_errorbarh(aes(xmin = (binom_estimates()$prob - binom_estimates()$se_prob) * input$binom_size,
                         xmax = (binom_estimates()$prob + binom_estimates()$se_prob) * input$binom_size,
                         y = 0), color = "#4a4a44", height = 0.01, size = 1) +
      labs(title = "이항분포 MLE", x = "값", y = "확률") +
      theme_tufte_bit() +
      theme(plot.title = element_text(hjust = 0.5, size = 20, face = "bold"),
            axis.title = element_text(size = 14),
            axis.text = element_text(size = 12))
  })

  output$binom_result <- renderText({
    paste0("MLE 추정결과:\n",
           "추정된 p = ", round(binom_estimates()$prob, 3), "\n",
           "p의 표준오차 = ", round(binom_estimates()$se_prob, 3))
  })
}

shinyApp(ui, server)

라이센스