iBetter Books
수정

Ch 03. 분포 시각화 (히스토그램, 박스플롯)

평균만 보면 놓치는 것이 있습니다. 두 그룹의 평균이 같아도 하나는 값이 촘촘히 모여 있고, 다른 하나는 넓게 퍼져 있을 수 있습니다. 이상값 하나가 평균을 끌어올려 실제와 다른 인상을 줄 수도 있습니다.

분포를 보는 차트는 이런 평균의 함정을 드러냅니다. 데이터가 어떻게 퍼져 있는지, 어디에 몰려 있는지, 극단값은 없는지를 한눈에 보여줍니다.

히스토그램 — geom_histogram

히스토그램은 연속형 변수의 분포를 구간(bin)으로 나누어 빈도를 막대로 표현합니다.

library(tidyverse)

ggplot(diamonds, aes(x = price)) +
  geom_histogram() +
  labs(
    title = "다이아몬드 가격 분포",
    x     = "가격 (달러)",
    y     = "빈도"
  ) +
  theme_minimal()

diamonds는 5만여 개의 다이아몬드 정보가 담긴 ggplot2 내장 데이터셋입니다. 가격 분포가 오른쪽으로 크게 치우쳐 있습니다. 저가 다이아몬드가 많고 고가는 드뭅니다.

콘솔에 경고가 뜹니다.

`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

기본값 30개 구간을 사용했다는 안내입니다. 구간 수나 구간 너비를 직접 지정하는 것이 좋습니다.

bins와 binwidth

# 구간 수 지정
ggplot(diamonds, aes(x = price)) +
  geom_histogram(bins = 50, fill = "steelblue", color = "white") +
  theme_minimal() +
  labs(title = "가격 분포 (50 bins)")
# 구간 너비 지정
ggplot(diamonds, aes(x = price)) +
  geom_histogram(binwidth = 500, fill = "steelblue", color = "white") +
  theme_minimal() +
  labs(title = "가격 분포 (binwidth = 500달러)")

bins는 총 구간 개수를 지정합니다. binwidth는 각 구간의 너비를 지정합니다. 어느 쪽을 쓰든 되지만, 데이터의 단위가 있을 때는 binwidth가 더 직관적입니다.

구간이 너무 좁으면 들쭉날쭉해서 패턴이 안 보이고, 너무 넓으면 정보가 뭉개집니다. 몇 가지 값을 시험해보며 적당한 값을 찾습니다.

그룹별 히스토그램

# 다이아몬드 컷 품질별 가격 분포
ggplot(diamonds, aes(x = price, fill = cut)) +
  geom_histogram(binwidth = 500, alpha = 0.6, position = "identity") +
  theme_minimal() +
  labs(
    title = "컷 품질별 가격 분포",
    x     = "가격 (달러)",
    y     = "빈도",
    fill  = "컷 품질"
  )

position = "identity"는 막대를 겹쳐서 그립니다. alpha로 투명도를 주어야 아래 막대도 보입니다. 겹침이 심하면 facet_wrap(~ cut)으로 나누는 것이 더 나을 수 있습니다.

밀도 그래프 — geom_density

히스토그램의 단점 중 하나는 구간 수에 따라 모양이 달라진다는 점입니다. 밀도 그래프는 구간 없이 연속적인 곡선으로 분포를 표현합니다.

ggplot(diamonds, aes(x = price)) +
  geom_density(fill = "steelblue", alpha = 0.4) +
  theme_minimal() +
  labs(
    title = "다이아몬드 가격 밀도 분포",
    x     = "가격 (달러)",
    y     = "밀도"
  )

Y축이 빈도 대신 밀도(density)로 바뀝니다. 전체 면적의 합이 1이 됩니다.

여러 그룹을 비교할 때 히스토그램보다 밀도 그래프가 훨씬 깔끔합니다.

ggplot(diamonds, aes(x = price, fill = cut, color = cut)) +
  geom_density(alpha = 0.3) +
  theme_minimal() +
  labs(
    title = "컷 품질별 가격 밀도 분포",
    x     = "가격 (달러)",
    y     = "밀도",
    fill  = "컷 품질",
    color = "컷 품질"
  )

다섯 가지 컷 품질의 분포가 서로 겹쳐 그려져 비교가 편합니다.

히스토그램과 밀도 곡선을 함께 그리기

ggplot(diamonds, aes(x = price)) +
  geom_histogram(aes(y = after_stat(density)),
                 bins  = 50,
                 fill  = "steelblue",
                 alpha = 0.5) +
  geom_density(color = "tomato", linewidth = 1) +
  theme_minimal() +
  labs(
    title = "가격 분포 (히스토그램 + 밀도 곡선)",
    x     = "가격 (달러)",
    y     = "밀도"
  )

히스토그램과 밀도 곡선의 Y축 단위가 다릅니다. 히스토그램은 기본적으로 빈도를 Y축으로 씁니다. 둘을 함께 그리려면 aes(y = after_stat(density))로 히스토그램의 Y축을 밀도로 바꿉니다.

박스플롯 — geom_boxplot

박스플롯은 5개의 요약 통계(최솟값, 1사분위수, 중앙값, 3사분위수, 최댓값)를 한 그림에 담습니다. 그룹 간 분포를 비교하는 데 탁월합니다.

ggplot(mpg, aes(x = class, y = hwy)) +
  geom_boxplot(fill = "steelblue", alpha = 0.7) +
  theme_minimal() +
  labs(
    title = "차량 클래스별 고속도로 연비 분포",
    x     = "클래스",
    y     = "고속도로 연비 (mpg)"
  )

박스의 각 부분이 의미하는 것을 정리합니다.

요소 의미
박스 아래 경계선 1사분위수 (Q1, 하위 25%)
박스 중간 굵은 선 중앙값 (Q2, 50%)
박스 위 경계선 3사분위수 (Q3, 상위 25%)
박스 높이 (IQR) Q3 - Q1 (사분위 범위)
수염(whisker) Q1 - 1.5×IQR ~ Q3 + 1.5×IQR 범위
수염 바깥의 이상값

pickup 클래스는 연비 범위가 넓고, compact는 비교적 촘촘합니다. 수염 밖의 점은 이상값입니다.

박스플롯에 원본 데이터 점 추가

박스플롯만으로는 데이터가 몇 개인지, 어떻게 분포하는지 알기 어렵습니다. geom_jitter()로 원본 데이터를 함께 표시합니다.

ggplot(mpg, aes(x = class, y = hwy)) +
  geom_boxplot(fill = "steelblue", alpha = 0.5, outlier.shape = NA) +
  geom_jitter(width = 0.2, alpha = 0.4, color = "tomato") +
  theme_minimal() +
  labs(
    title = "클래스별 고속도로 연비 (박스플롯 + 원본 데이터)",
    x     = "클래스",
    y     = "고속도로 연비 (mpg)"
  )

outlier.shape = NA는 박스플롯의 이상값 점을 숨깁니다. geom_jitter()가 원본 데이터를 대신 표시하므로 중복을 막습니다. width = 0.2는 점이 좌우로 흩어지는 정도를 조절합니다.

바이올린 플롯 — geom_violin

바이올린 플롯은 박스플롯과 밀도 그래프를 합친 형태입니다. 분포의 형태를 더 직관적으로 보여줍니다.

ggplot(mpg, aes(x = class, y = hwy, fill = class)) +
  geom_violin(alpha = 0.7) +
  theme_minimal() +
  theme(legend.position = "none") +
  labs(
    title = "차량 클래스별 연비 분포 (바이올린 플롯)",
    x     = "클래스",
    y     = "고속도로 연비 (mpg)"
  )

바이올린의 폭이 넓을수록 그 구간에 데이터가 많습니다. compact 클래스는 중간 연비 구간에 데이터가 집중되어 있고, pickup은 넓게 퍼져 있습니다.

바이올린 + 박스플롯 조합

ggplot(mpg, aes(x = class, y = hwy, fill = class)) +
  geom_violin(alpha = 0.6) +
  geom_boxplot(width = 0.15, fill = "white", alpha = 0.8) +
  theme_minimal() +
  theme(legend.position = "none") +
  labs(
    title = "클래스별 연비 분포 (바이올린 + 박스플롯)",
    x     = "클래스",
    y     = "고속도로 연비 (mpg)"
  )

바이올린으로 전체 분포 모양을 보고, 박스플롯으로 중앙값과 사분위수를 함께 확인합니다. width = 0.15로 박스를 좁게 만들면 바이올린 안에 잘 들어옵니다.

분포 시각화 차트 선택 가이드

상황 추천 차트
단일 연속형 변수의 분포 확인 geom_histogram()
여러 그룹의 분포 곡선 비교 geom_density()
여러 그룹의 분포를 요약 통계로 비교 geom_boxplot()
분포의 형태(단봉/쌍봉 등)까지 비교 geom_violin()
가장 풍부한 정보 제공 바이올린 + 박스플롯 + jitter 조합

분포 시각화는 데이터 탐색(EDA)의 첫 걸음입니다. 모델링이나 가설 검정에 들어가기 전에 반드시 분포를 눈으로 확인하는 습관을 들이면 좋습니다.

다음 챕터에서는 완성된 차트에 색상과 테마를 입혀 발표용 수준으로 다듬는 방법을 살펴봅니다.