정규분포와 표준화
통계학에서 정규분포만큼 자주 등장하는 것도 없습니다. 사람의 키, 시험 점수, 측정 오차는 대부분 정규분포를 따릅니다. 정규분포가 무엇인지, 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분포는 모두 정규분포에서 파생됩니다.