10  도수분포표와 히스토그램

10.1 도수분포표

도수분포표는 데이터의 분포를 요약하고 이해하는 데 사용되는 기본적인 도구이다. 도수분포표는 데이터를 범주 또는 구간으로 나누고 각 범주나 구간에 속하는 데이터의 빈도(도수)를 나타낸 표다.

도수분포표는 데이터의 유형에 따라 범주형 데이터와 연속형 데이터로 나누어 작성할 수 있다. 범주형 데이터의 경우, 각 범주별 빈도와 상대 빈도(비율)을 나타내는 도수분포표를 만들 수 있다. 예를 들어, 설문 조사에서 응답자의 성별, 학력, 직업 등의 데이터를 수집했다면, 각 범주(남성/여성, 고졸/대졸/대학원졸, 직종별)에 해당하는 응답자의 수와 비율을 도수분포표로 나타낼 수 있다.

연속형 데이터의 경우, 데이터의 특성상 데이터를 일정한 구간으로 나누어 각 구간에 속하는 데이터의 빈도를 나타내는 도수분포표를 작성한다. 예를 들어, 학생들의 시험 점수 데이터가 있다면, 50-70점, 71-90점, 91-100점 등의 구간으로 나누어 각 구간에 해당하는 학생 수를 도수분포표에 나타낼 수 있다.

도수분포표는 데이터의 분포를 요약적으로 보여줌으로써 데이터의 특징을 이해하는 데 도움을 준다. 도수분포표를 통해 데이터의 중심 위치, 분산 정도, 대칭성, 극단값의 존재 등을 파악할 수 있다. 또한, 도수분포표는 막대그래프와 히스토그램 같은 그래프를 그리는 데 기초 자료로 사용된다.

10.2 히스토그램과 막대그래프

히스토그램은 연속형 데이터의 분포를 시각적으로 표현하는 그래프다. 히스토그램은 연속형 데이터를 일정한 구간으로 나누고, 각 구간에 속하는 데이터의 빈도수를 막대의 높이로 나타낸다. 데이터의 전반적인 분포 형태, 중심 위치, 분산 정도 등을 한눈에 파악할 수 있다.

히스토그램은 \(x\)축에 데이터의 값 또는 구간을, \(y\)축에는 빈도수 또는 상대 빈도(밀도)를 나타낸다. \(x\)축 각 막대의 너비는 구간의 크기를 나타내며, 막대의 높이는 해당 구간에 속하는 데이터의 빈도수에 비례한다. 히스토그램에서 막대는 서로 인접하게 그려지는데 연속형 데이터의 연속성을 나타낸다.

히스토그램은 막대그래프와 유사해 보이지만, 몇 가지 중요한 차이점이 있다. 막대그래프는 범주형 데이터의 빈도수를 나타내는 데 사용되며, 각 막대는 서로 독립적인 범주를 나타낸다. 반면, 히스토그램은 연속형 데이터를 다루며, 막대는 연속적인 구간을 나타내고, 막대그래프에서는 막대 사이에 간격이 있는 반면, 히스토그램에서는 막대가 서로 인접해 있는 차이가 있다.

히스토그램과 막대그래프는 데이터의 분포를 시각화하여 데이터의 특징을 파악하는 데 유용하다. 히스토그램을 통해 데이터의 대칭성, 봉우리의 개수, 꼬리의 길이 등을 확인할 수 있다. 예를 들어, 히스토그램이 종 모양을 나타내면 데이터가 대칭적으로 분포하고 있음을 알 수 있고, 여러 개의 봉우리가 있으면 데이터에 하위 그룹이 존재할 가능성이 있음을 강력히 시사하고 있다.

히스토그램은 데이터의 요약 통계량(평균, 중앙값, 분산 등)과 함께 사용되어 데이터의 특성을 종합적으로 파악하는 데 도움을 준다. 시각적으로 히스토그램 위에 데이터의 중심 위치를 나타내는 평균이나 중앙값을 표시하거나 데이터의 퍼짐 정도를 나타내는 분산이나 표준편차를 함께 표시하여 데이터의 변동성을 수치적으로도 확인할 수 있다.

10.3 히스토그램 해석

히스토그램은 데이터의 분포를 시각적으로 나타내기 때문에, 히스토그램의 모양을 관찰하고 해석하는 것이 중요하다. 대략 히스토그램을 대칭형, 기울어진 왜도가 있는 긴 꼬리형, 봉우리가 여러개 있는 다봉형, 균일 분포형으로 분류할 수 있다.

  1. 대칭형 (종 모양) 히스토그램: 데이터가 중심을 기준으로 좌우 대칭으로 분포하는 경우, 히스토그램은 종 모양을 나타내고 데이터가 정규분포에 가까움을 시사한다.

  2. 왜도가 있는 히스토그램: 왜도가 있는 히스토그램은 오른쪽 또는 왼쪽으로 긴 꼬리를 가진 모양을 나타낸다. 오른쪽으로 긴 꼬리가 나타나는 경우 양의 왜도라고 하며, 데이터의 대다수가 왼쪽에 몰려 있고, 소수의 큰 값으로 인해 오른쪽으로 긴 꼬리를 보이는 분포 형태를 의미한다. 반대로 왼쪽으로 긴 꼬리가 나타나는 경우 음의 왜도라고 하며, 데이터의 대다수가 오른쪽에 몰려 있고, 소수의 작은 값으로 인해 왼쪽쪽으로 긴 꼬리를 보이는 분포 형태를 의미한다.

  3. 다봉 히스토그램: 히스토그램에 여러 개의 봉우리가 나타나는 경우, 데이터에 하위 그룹이 존재하거나 데이터가 혼합 분포를 따를 가능성을 시사한다.

  4. 균일 분포 히스토그램: 히스토그램의 막대 높이가 모든 구간에서 비슷한 경우, 데이터가 균일하게 분포하고 있음을 나타낸다.

히스토그램을 통해 데이터에 내재된 극단값과 이상치를 파악하는 데 도움이 된다. 극단값은 데이터의 대부분과 크게 떨어진 값으로, 히스토그램에서 다른 막대와 동떨어진 막대로 나타난다. 이상치는 데이터의 일반적인 패턴에서 벗어나는 값으로, 히스토그램에서 다른 데이터와 크게 차이 나는 막대로 식별할 수 있다.

히스토그램에서 극단값이나 이상치를 발견했을 때는 해당 값들이 실제 데이터의 일부인지, 아니면 측정이나 기록 오류로 인한 것인지 확인해야 한다. 이상치가 실제 데이터의 일부라면, 그 원인을 파악하고 필요에 따라 추가 분석을 수행하는 반면, 오류로 인한 이상치는 데이터 정제 과정에서 제거하거나 수정해야 한다.

그림 10.1: 다양한 히스토그램 사례

막대그래프는 주로 범주형 데이터의 빈도나 비율을 비교할 때 사용되어 히스토그램과 유사한 해석도 가능하지만 범주형 데이터의 특성을 반영하여 막대그래프를 해석하면 도움이 된다.

  1. 범주 간 빈도 또는 비율: 막대그래프에서 각 범주의 막대 높이를 비교하여 범주 간 빈도나 비율의 차이를 파악할 수 있다. 어떤 범주가 더 우세한지 또는 범주 간의 상대적인 크기를 이해할 수 있다.

  2. 전체 분포 파악: 막대그래프를 통해 범주형 변수의 전체적인 분포를 파악할 수 있다. 예를 들어, 특정 범주의 막대가 다른 범주에 비해 월등히 높다면 해당 범주가 전체 데이터에서 지배적임을 알 수 있다.

  3. 범주 간 패턴 파악: 막대그래프에서 범주 간 막대 높이의 패턴을 관찰하여 범주 간의 관계나 특징을 파악할 수 있다. 예를 들어, 막대그래프를 사용하면, ‘카페 A’, ‘카페 B’, ‘카페 C’ 등 각각의 카페를 하나의 범주로 나타내고, 커피 판매량을 막대의 높이로 표시할 수 있다. 만약 ’카페 A’의 막대가 다른 카페들에 비해 현저히 높으면, ’카페 A’의 커피 판매량이 더 많음을 의미하며 이러한 시각적 표현을 통해, 어떤 카페가 가장 인기 있는지와 같은 판매 추세를 쉽게 파악할 수 있다.

그림 10.2: 카페별 커피 판매 막대그래프 시각화

10.4 히스토그램 작성하기

히스토그램을 작성할 때는 데이터를 적절한 구간으로 나누는 것이 중요하다. 구간의 개수와 너비는 히스토그램의 모양과 해석에 영향을 미칠 수 있다. 일반적으로 구간의 개수가 너무 적으면 데이터의 분포를 충분히 표현하기 어렵고, 구간의 개수가 너무 많으면 히스토그램이 지나치게 불규칙해질 수 있다. 적절한 구간의 개수는 데이터의 크기와 분포 특성에 따라 달라질 수 있으며, 경험적으로 구간의 개수를 결정하는 방법도 있고 의사소통을 위해 특정 구간의 개수를 사용하는 방법도 있다.

예시로 30명의 고등학생 신장 데이터(단위: cm)를 사용해서 학생 신장 데이터를 구간을 나누고 빈도수를 계산하여 히스토그램을 그리는 과정을 포함하여 살펴보자.

165, 172, 168, 177, 160, 180, 169, 165, 174, 172, 166, 173, 178, 175, 171,
169, 164, 176, 167, 162, 181, 170, 168, 173, 179, 163, 175, 177, 171, 166
  1. 데이터 구간 설정: 먼저 데이터를 적절한 구간으로 나누어야 한다. 구간 너비를 5cm로 설정하고, 160cm부터 185cm까지 5개의 구간으로 나눈다.
    • 구간 1: 160cm 이상 165cm 미만
    • 구간 2: 165cm 이상 170cm 미만
    • 구간 3: 170cm 이상 175cm 미만
    • 구간 4: 175cm 이상 180cm 미만
    • 구간 5: 180cm 이상 185cm 미만
  2. 빈도수 계산: 각 구간에 해당하는 학생 수(빈도수)를 계산한다.
    • 구간 1: 4명
    • 구간 2: 9명
    • 구간 3: 8명
    • 구간 4: 7명
    • 구간 5: 2명
신장 빈도수
[160,165) 4
[165,170) 9
[170,175) 8
[175,180) 7
[180,185) 2
표 10.1: 학생 신장 5cm 단위 도수빈도표
줄기와 잎 그래프

줄기와 잎 그래프는 데이터 분포와 개별 값을 함께 보여준다. 줄기는 데이터의 큰 자릿수를 나타내며, 잎은 더 작은 자릿수를 표현한다. 데이터의 중앙값, 분위수, 이상치를 시각적으로 드러내고, 개별 데이터 값을 정확히 보존한다는 면에서 히스토그램보다 상세한 정보를 제공한다. 줄기와 잎 그래프를 통해 사용자는 데이터 집합의 특성을 빠르게 파악하고, 간단히 비교 분석을 수행할 수 있다.

#> 
#>   The decimal point is at the |
#> 
#>   16 | 0234
#>   16 | 556678899
#>   17 | 01122334
#>   17 | 5567789
#>   18 | 01
  1. 히스토그램 작성: 계산된 빈도수를 바탕으로 히스토그램을 제작한다. 각 구간을 \(x\)축으로 하고, 해당 구간의 빈도수를 \(y\)축으로 하여 막대그래프를 제작한다.
그림 10.3: 학생 신장 도수빈도표와 히스토그램

10.5 코드 구현

실제 현장에서 고등학생 신장 데이터를 히스토그램으로 그리는 과정을 함수를 사용해서 구현한다. 먼저, 고등학생의 신장 데이터를 저장하고, seq() 함수로 5cm 간격의 신장 구간을 생성한다. ggplot()에 입력데이터는 데이터프레임이라 data.frame() 함수로 전달한 뒤, geom_histogram() 함수에서 breaks 매개변수를 통해 신장 구간을 지정하고, closed = "left" 옵션으로 구간의 왼쪽 경계를 포함만 포함한다. 막대의 색상과 히스토그램의 제목 및 레이블을 꾸미는 시각적 조정을 추가하여 마무리한다.

10.6 shiny 앱

#| label: fig-shiny-height
#| viewerHeight: 600
#| standalone: true
library(shiny)
library(ggplot2)
library(gt)

# UI 정의
ui <- fluidPage(
  titlePanel("도수분포표와 히스토그램"),

  sidebarLayout(
    sidebarPanel(
      textAreaInput("heights", "신장 데이터 입력:", value = ""),
      actionButton("generate", "새 데이터 생성"),
      actionButton("submit", "표와 그래프 생성"),
      # 사용 방법 설명 HTML 추가
      HTML('
        <h3>Shiny 앱 사용 방법</h3>
        <p>이 앱은 고등학생 신장 데이터를 바탕으로 도수분포표와 히스토그램을 생성합니다.</p>
        <ol>
          <li><strong>신장 데이터 입력:</strong> 신장 데이터를 쉼표(,)로 구분하여 입력하세요.</li>
          <li><strong>새 데이터 생성:</strong> 새로운 무작위 신장 데이터를 생성하려면 \'새 데이터 생성\' 버튼을 클릭하세요. 이렇게 생성된 데이터는 자동으로 입력됩니다.</li>
          <li><strong>표와 그래프 생성:</strong> \'표와 그래프 생성\' 버튼을 클릭하여 입력한 데이터를 바탕으로 도수분포표와 히스토그램을 생성하세요.</li>
          <li>도수분포표와 히스토그램은 오른쪽 패널에서 확인할 수 있습니다.</li>
        </ol>
        <p>도수분포표는 각 신장 구간별 학생 수를 나타내고, 히스토그램은 시각적으로 표현합니다.</p>
      ')
    ),

    mainPanel(
      fluidRow(
        column(6, htmlOutput("table")),    # 표를 왼쪽에 배치
        column(6, plotOutput("histogram")) # 그래프를 오른쪽에 배치
      )
    )
  )
)

# Server 로직 정의
server <- function(input, output, session) {
  observeEvent(input$generate, {
    # 새 신장 데이터 생성
    heights <- round(rnorm(30, 170, 5), 0)
    updateTextAreaInput(session, "heights", value = paste(heights, collapse = ", "))
  })

  observeEvent(input$submit, {
    # textAreaInput에서 데이터 읽기
    heights_input <- unlist(strsplit(input$heights, ", "))
    heights <- as.numeric(heights_input)

    # 구간 설정 및 빈도수 계산
    breaks <- seq(min(heights), max(heights), by = 5)
    height_data <- cut(heights, breaks = breaks, right = FALSE, include.lowest = TRUE)
    freq <- table(height_data)

    # 히스토그램 그리기
    output$histogram <- renderPlot({
      height_tbl <- data.frame(height = names(freq), Freq = as.numeric(freq))
      ggplot(height_tbl, aes(x = height, y = Freq)) +
        geom_bar(stat = "identity", fill = "skyblue", color = "black", width = 1) +
        geom_text(aes(label = Freq), vjust = -0.3, size = 5) +
        labs(title = "고등학생 신장 분포", x = "신장 구간 (cm)", y = "학생 수") +
        theme_minimal()
    })

    # 도수분포표 생성 및 출력
    output$table <- renderUI({
      freq_table <- as.data.frame(freq)
      names(freq_table) <- c("신장 구간", "빈도수")

      gt_table <- gt(freq_table) %>%
        tab_header(title = "도수분포표") %>%
        cols_label("신장 구간" = "신장 구간 (cm)", 빈도수 = "학생 수")

      gt_table
    })
  })
}

# Shiny 앱 실행
shinyApp(ui = ui, server = server)