iBetter Books
수정

정규분포와 표준화

통계학에서 정규분포만큼 자주 등장하는 것도 없습니다. 사람의 키, 시험 점수, 측정 오차는 대부분 정규분포를 따릅니다. 정규분포가 무엇인지, R에서 어떻게 다루는지, 그리고 표준화가 왜 필요한지 알아봅니다.

정규분포란

정규분포는 평균을 중심으로 좌우 대칭인 종 모양(bell curve) 분포입니다. 두 개의 매개변수로 완전히 정의됩니다.

  • 평균 (μ): 분포의 중심 위치
  • 표준편차 (σ): 분포의 폭 (넓을수록 퍼진 모양)
library(ggplot2)

# 정규분포 곡선 그리기
x <- seq(-4, 4, by = 0.01)
y <- dnorm(x, mean = 0, sd = 1)   # 표준정규분포 (μ=0, σ=1)

ggplot(data.frame(x, y), aes(x, y)) +
  geom_line(linewidth = 1.2, color = "steelblue") +
  labs(
    title = "표준정규분포 (μ=0, σ=1)",
    x     = "z",
    y     = "밀도"
  ) +
  theme_minimal()
# 평균이 다른 정규분포 비교
x <- seq(-10, 20, by = 0.1)

df <- data.frame(
  x    = rep(x, 3),
  y    = c(dnorm(x, 0, 1), dnorm(x, 5, 1), dnorm(x, 5, 3)),
  type = rep(c("μ=0, σ=1", "μ=5, σ=1", "μ=5, σ=3"), each = length(x))
)

ggplot(df, aes(x, y, color = type)) +
  geom_line(linewidth = 1.2) +
  labs(
    title  = "평균과 표준편차에 따른 정규분포 모양",
    x      = "x",
    y      = "밀도",
    color  = NULL
  ) +
  theme_minimal()

평균이 변하면 곡선이 좌우로 이동합니다. 표준편차가 커지면 곡선이 납작하게 퍼지고, 작아지면 뾰족하게 좁아집니다.

R의 정규분포 함수 4가지

R에서 정규분포를 다루는 함수는 접두사로 구분합니다.

함수 역할 비유
dnorm() 밀도값 (높이) 특정 x에서 곡선의 높이
pnorm() 누적확률 (면적) x 이하일 확률
qnorm() 분위수 누적확률 p에 해당하는 x값
rnorm() 난수 생성 정규분포에서 표본 추출
# dnorm — 특정 값의 밀도 (곡선의 높이)
dnorm(0, mean = 0, sd = 1)   # 표준정규분포 중심의 높이: 0.3989

# pnorm — 누적확률 (x 이하일 확률)
pnorm(0, mean = 0, sd = 1)   # P(Z ≤ 0) = 0.5 (대칭이므로 정확히 절반)
pnorm(1.96, mean = 0, sd = 1) # P(Z ≤ 1.96) ≈ 0.975

# P(a ≤ X ≤ b) — 구간 확률
pnorm(1, 0, 1) - pnorm(-1, 0, 1)   # P(-1 ≤ Z ≤ 1) ≈ 0.683
pnorm(2, 0, 1) - pnorm(-2, 0, 1)   # P(-2 ≤ Z ≤ 2) ≈ 0.954
pnorm(3, 0, 1) - pnorm(-3, 0, 1)   # P(-3 ≤ Z ≤ 3) ≈ 0.997

# qnorm — 확률에 해당하는 값 (pnorm의 역함수)
qnorm(0.975, 0, 1)   # 1.96 — 상위 2.5%의 경계
qnorm(0.025, 0, 1)   # -1.96
qnorm(0.95, 0, 1)    # 1.645 — 상위 5%의 경계

# rnorm — 정규분포에서 난수 생성
set.seed(42)
samples <- rnorm(n = 1000, mean = 70, sd = 10)  # 평균 70, 표준편차 10인 정규분포
mean(samples)   # 약 70
sd(samples)     # 약 10

68-95-99.7 법칙

정규분포에서 가장 중요한 경험 법칙입니다.

# 68-95-99.7 법칙 수치 확인
cat("μ ± 1σ 범위:", pnorm(1) - pnorm(-1), "\n")   # 0.683 ≈ 68.3%
cat("μ ± 2σ 범위:", pnorm(2) - pnorm(-2), "\n")   # 0.954 ≈ 95.4%
cat("μ ± 3σ 범위:", pnorm(3) - pnorm(-3), "\n")   # 0.997 ≈ 99.7%
# 시각화 — 영역 강조
library(tidyverse)

x <- seq(-4, 4, by = 0.01)
df <- data.frame(x = x, y = dnorm(x))

ggplot(df, aes(x, y)) +
  # ±3σ 영역
  geom_ribbon(data = df %>% filter(x >= -3 & x <= 3),
              aes(ymin = 0, ymax = y), fill = "#d4e6f1", alpha = 0.8) +
  # ±2σ 영역
  geom_ribbon(data = df %>% filter(x >= -2 & x <= 2),
              aes(ymin = 0, ymax = y), fill = "#85c1e9", alpha = 0.8) +
  # ±1σ 영역
  geom_ribbon(data = df %>% filter(x >= -1 & x <= 1),
              aes(ymin = 0, ymax = y), fill = "#2e86c1", alpha = 0.8) +
  geom_line(linewidth = 1.2) +
  annotate("text", x = 0,   y = 0.2, label = "68%",  color = "white", size = 4) +
  annotate("text", x = 1.5, y = 0.05, label = "95%", color = "#1a5276", size = 3.5) +
  annotate("text", x = 2.5, y = 0.01, label = "99.7%", color = "#1a5276", size = 3) +
  labs(
    title = "정규분포의 68-95-99.7 법칙",
    x     = "표준편차 (σ)",
    y     = "밀도"
  ) +
  scale_x_continuous(breaks = -3:3) +
  theme_minimal()

평균이 170cm, 표준편차가 6cm인 키 분포를 예로 들면, 전체의 68%는 164~176cm 사이에 있고, 95%는 158~182cm 사이에 있습니다.

z-점수와 표준화

서로 다른 분포의 값을 비교하려면 같은 기준으로 변환해야 합니다. z-점수는 "평균에서 표준편차 몇 개만큼 떨어져 있는가"를 나타냅니다.

z = (x - μ) / σ

# 두 과목 점수 비교
# 수학: 평균 70, 표준편차 10에서 85점
# 영어: 평균 60, 표준편차 8에서 76점
# 어느 과목 성적이 더 우수한가?

math_score  <- 85
math_mean   <- 70
math_sd     <- 10

english_score <- 76
english_mean  <- 60
english_sd    <- 8

z_math    <- (math_score - math_mean) / math_sd
z_english <- (english_score - english_mean) / english_sd

cat("수학 z-점수:", z_math, "\n")     # 1.5
cat("영어 z-점수:", z_english, "\n")  # 2.0

# z=2.0인 영어 성적이 더 우수합니다
# 상위 몇 퍼센트인지 확인
cat("수학 상위:", (1 - pnorm(z_math)) * 100, "%\n")     # 6.7%
cat("영어 상위:", (1 - pnorm(z_english)) * 100, "%\n")  # 2.3%
# R에서 데이터 표준화 — scale() 함수
scores <- c(72, 85, 90, 78, 95, 88, 62, 91, 84, 77)

z_scores <- scale(scores)         # 표준화 (평균 0, 표준편차 1)
round(z_scores, 2)

# 확인
mean(z_scores)   # ≈ 0
sd(z_scores)     # = 1

정규분포 확인 — Q-Q 플롯

데이터가 정규분포를 따르는지 시각적으로 확인합니다.

set.seed(42)

# 정규분포 데이터
normal_data <- rnorm(200, mean = 0, sd = 1)

# Q-Q 플롯 — 점이 직선 위에 놓이면 정규분포에 가깝습니다
ggplot(data.frame(x = normal_data), aes(sample = x)) +
  stat_qq() +
  stat_qq_line(color = "red", linewidth = 1) +
  labs(
    title = "정규 Q-Q 플롯 (정규분포 데이터)",
    x     = "이론적 분위수",
    y     = "표본 분위수"
  ) +
  theme_minimal()
# 비정규분포 데이터와 비교
skewed_data <- rexp(200, rate = 1)   # 지수분포 (오른쪽 꼬리가 긴 분포)

p1 <- ggplot(data.frame(x = normal_data), aes(sample = x)) +
  stat_qq() + stat_qq_line(color = "red") +
  labs(title = "정규분포 Q-Q 플롯") + theme_minimal()

p2 <- ggplot(data.frame(x = skewed_data), aes(sample = x)) +
  stat_qq() + stat_qq_line(color = "red") +
  labs(title = "비정규분포 Q-Q 플롯") + theme_minimal()

library(patchwork)
p1 + p2

점이 직선에서 크게 벗어날수록 정규분포에서 멀어집니다.

실제 예시 — 키 분포

set.seed(42)

# 한국 성인 남성 키 데이터 시뮬레이션 (평균 174cm, 표준편차 6cm)
heights <- rnorm(1000, mean = 174, sd = 6)

# 기술통계
cat("평균:", mean(heights), "\n")
cat("표준편차:", sd(heights), "\n")

# 180cm 이상일 확률
p_tall <- 1 - pnorm(180, mean = 174, sd = 6)
cat("180cm 이상 확률:", round(p_tall, 3), "\n")   # 약 0.159 (15.9%)

# 상위 10%의 경계 키
q_top10 <- qnorm(0.9, mean = 174, sd = 6)
cat("상위 10% 기준:", round(q_top10, 1), "cm\n")  # 약 181.7cm

# 시각화
ggplot(data.frame(h = heights), aes(x = h)) +
  geom_histogram(aes(y = after_stat(density)),
                 binwidth = 2, fill = "steelblue", color = "white", alpha = 0.7) +
  stat_function(fun = dnorm, args = list(mean = 174, sd = 6),
                color = "red", linewidth = 1.2) +
  geom_vline(xintercept = 174, color = "navy", linetype = "dashed") +
  labs(
    title = "한국 성인 남성 키 분포 (시뮬레이션)",
    x     = "키 (cm)",
    y     = "밀도"
  ) +
  theme_minimal()

정규분포를 이해하면 추론통계의 기반이 됩니다. 다음 챕터에서 배울 t분포, 카이제곱분포, F분포는 모두 정규분포에서 파생됩니다.