기술통계 개념 이해

난수
모의실험
기술통계
평균
표준편차
최소값
최대값
저자

이광춘

공개

2026-07-06

1~100 사이 난수를 뽑아 기술통계를 구하고 시각화를 통해 개념을 확실히 이해할 수 있다.

#| label: shinylive-desc-stat-concept
#| viewerHeight: 600
#| standalone: true

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

# UI 정의
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(
      sliderInput("numSelect",
                  "1~100 사이 무작위 추출할 데이터수:",
                  min = 1,
                  max = 100,
                  value = 10),
      checkboxInput("replace", "복원추출", value = FALSE)
    ),
    mainPanel(
      plotOutput("numberPlot"),
      uiOutput("stats")
    )
  )
)

# 서버 로직
server <- function(input, output) {
  selectedNumbers <- reactive({
    sample(1:100, input$numSelect, replace = input$replace)
  })

  output$numberPlot <- renderPlot({
    data <- data.frame(value = selectedNumbers())
    mean_val <- mean(data$value)
    std_dev <- sd(data$value)

    ggplot(data, aes(x = factor(1), y = value)) +
      geom_point(stat = "identity", position = position_dodge(width = 0.2), size = 4) +
      annotate("segment", x = 0.8, xend = 1.2, y = mean_val, yend = mean_val, color = "#4a4a44", linewidth = 1.5) +
      geom_errorbar(aes(ymin = mean_val - std_dev, ymax = mean_val + std_dev, x = factor(1)), width = 0.2, color = "#8a1500") +
      labs(title = paste0("추출된", length(selectedNumbers()), "개 데이터와 통계"), y = "", x = "") +
      theme_tufte_bit() +
      coord_flip() +
      theme(
        aspect.ratio = 1/5,
        axis.text.y = element_blank(),
        axis.ticks.y = element_blank()
      )
  })

  output$stats <- renderUI({
    data <- selectedNumbers()
    if (length(data) > 0) {
      mean_val <- mean(data)
      sd_val <- sd(data)
      max_val <- max(data)
      min_val <- min(data)

      tags$ul(
        tags$li(HTML(paste("<b>추출된 숫자:</b> ", paste(data, collapse = ", ")))),
        tags$li(HTML(paste("<b>평균:</b> ", round(mean_val, 2)))),
        tags$li(HTML(paste("<b>표준편차:</b> ", round(sd_val, 2)))),
        tags$li(HTML(paste("<b>최대값:</b> ", max_val))),
        tags$li(HTML(paste("<b>최소값:</b> ", min_val)))
      )
    } else {
      "숫자를 선택하세요."
    }
  })
}

# 앱 실행
shinyApp(ui, server)

라이센스