본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 11. 28. · 17 Views
확률 분포 완벽 가이드
데이터 과학과 머신러닝의 기초가 되는 확률 분포를 초급 개발자도 쉽게 이해할 수 있도록 설명합니다. 베르누이 분포부터 정규 분포, 그리고 SciPy를 활용한 실무 코드까지 단계별로 학습합니다.
목차
1. 이산 확률 분포
어느 날 김개발 씨가 추천 시스템을 개발하다가 고민에 빠졌습니다. "사용자가 이 상품을 클릭할 확률이 30%라고 하는데, 이걸 코드로 어떻게 표현하지?" 선배 박시니어 씨가 다가와 말합니다.
"이산 확률 분포부터 알아야 해요."
이산 확률 분포란 결과가 셀 수 있는 값으로 떨어지는 확률 분포입니다. 마치 주사위를 던지면 1, 2, 3, 4, 5, 6 중 하나만 나오는 것처럼, 동전 던지기나 클릭 여부 같은 상황을 수학적으로 모델링할 수 있습니다.
가장 기본적인 베르누이 분포와 이항 분포를 이해하면 A/B 테스트 분석이나 전환율 예측에 바로 활용할 수 있습니다.
다음 코드를 살펴봅시다.
import numpy as np
from scipy import stats
# 베르누이 분포: 성공 확률 0.3인 단일 시행
bernoulli = stats.bernoulli(p=0.3)
single_result = bernoulli.rvs(size=10) # 10번 시행
print(f"베르누이 시행 결과: {single_result}")
# 이항 분포: 성공 확률 0.3으로 20번 시행 중 성공 횟수
binomial = stats.binom(n=20, p=0.3)
success_count = binomial.rvs(size=5) # 5번의 실험
print(f"이항 분포 성공 횟수: {success_count}")
# 특정 성공 횟수의 확률 계산
prob_exactly_6 = binomial.pmf(6) # 정확히 6번 성공할 확률
print(f"20번 중 정확히 6번 성공할 확률: {prob_exactly_6:.4f}")
김개발 씨는 입사 6개월 차 데이터 엔지니어입니다. 오늘 그에게 새로운 과제가 주어졌습니다.
쇼핑몰의 광고 클릭률을 분석하고, 향후 클릭 수를 예측하는 모델을 만들라는 것이었습니다. "클릭률이 30%라는 건 알겠는데, 내일 1000명이 방문하면 몇 명이 클릭할지 어떻게 계산하죠?" 김개발 씨가 박시니어 씨에게 물었습니다.
박시니어 씨가 웃으며 답합니다. "그걸 알려면 먼저 이산 확률 분포를 이해해야 해요.
특히 베르누이 분포와 이항 분포요." 그렇다면 이산 확률 분포란 정확히 무엇일까요? 쉽게 비유하자면, 이산 확률 분포는 마치 자판기에서 음료를 고르는 것과 같습니다.
콜라, 사이다, 주스 중 하나를 선택하지, 콜라 반 개를 고를 수는 없습니다. 이처럼 결과가 명확하게 구분되고 셀 수 있는 경우를 이산적이라고 합니다.
가장 간단한 이산 확률 분포가 바로 베르누이 분포입니다. 동전 던지기를 생각해 보세요.
결과는 앞면 아니면 뒷면, 딱 두 가지입니다. 성공 아니면 실패, 클릭 아니면 비클릭.
이런 상황을 수학적으로 표현한 것이 베르누이 분포입니다. 하지만 현실에서는 동전을 한 번만 던지는 경우가 드뭅니다.
광고를 1000명에게 보여주면 각 사람이 클릭할지 안 할지가 결정됩니다. 이렇게 베르누이 시행을 여러 번 반복했을 때 성공 횟수가 어떻게 분포하는지를 나타낸 것이 이항 분포입니다.
위의 코드를 살펴보겠습니다. 먼저 stats.bernoulli(p=0.3)으로 성공 확률이 30%인 베르누이 분포를 만듭니다.
rvs 메서드는 random variates의 약자로, 이 분포에서 무작위 샘플을 추출합니다. 이항 분포는 stats.binom(n=20, p=0.3)으로 생성합니다.
여기서 n은 시행 횟수, p는 각 시행의 성공 확률입니다. 20번 시행 중 몇 번 성공하는지를 시뮬레이션할 수 있습니다.
pmf 메서드는 probability mass function, 즉 확률 질량 함수입니다. 이산 분포에서 특정 값이 나올 확률을 계산합니다.
binomial.pmf(6)은 20번 중 정확히 6번 성공할 확률을 알려줍니다. 실제 현업에서는 어떻게 활용할까요?
A/B 테스트를 생각해 봅시다. 새 버튼 디자인의 클릭률이 기존보다 높은지 확인하려면 이항 분포를 사용합니다.
100명 중 35명이 클릭했을 때, 이것이 우연인지 실제로 개선된 것인지 판단할 수 있습니다. 주의할 점도 있습니다.
이항 분포는 각 시행이 독립적이라고 가정합니다. 한 사용자의 클릭이 다른 사용자에게 영향을 주면 이 가정이 깨집니다.
또한 성공 확률 p가 모든 시행에서 동일해야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
"아, 그러니까 1000명이 방문하고 클릭률이 30%면 이항 분포를 쓰면 되는 거군요!" 박시니어 씨가 고개를 끄덕입니다. "맞아요.
평균적으로 300명 정도 클릭하겠지만, 정확히 몇 명일지는 분포를 통해 확률적으로 예측할 수 있어요."
실전 팁
💡 - 베르누이는 한 번, 이항은 여러 번이라고 기억하세요
- pmf는 이산 분포에서만 사용하고, 연속 분포에서는 pdf를 사용합니다
- 시행 횟수 n이 크고 p가 작으면 포아송 분포로 근사할 수 있습니다
2. 연속 확률 분포
김개발 씨가 이산 확률 분포를 이해하고 나니 새로운 의문이 생겼습니다. "사용자의 체류 시간은 30초, 45.7초, 127.3초처럼 연속적인 값인데, 이건 어떻게 다루죠?" 박시니어 씨가 말합니다.
"연속 확률 분포의 세계로 들어가 볼까요?"
연속 확률 분포는 키, 몸무게, 시간처럼 연속적인 값을 가지는 변수의 확률을 다룹니다. 이산 분포와 달리 특정 값이 나올 확률은 사실상 0이고, 구간에 속할 확률을 계산합니다.
마치 자를 사용해 길이를 재면 무한히 정밀하게 측정할 수 있는 것처럼, 연속 변수는 무한히 많은 값을 가질 수 있습니다.
다음 코드를 살펴봅시다.
import numpy as np
from scipy import stats
import matplotlib.pyplot as plt
# 균등 분포: 0과 10 사이 모든 값이 동일한 확률
uniform = stats.uniform(loc=0, scale=10)
samples = uniform.rvs(size=1000)
print(f"균등 분포 평균: {samples.mean():.2f}")
# 지수 분포: 이벤트 사이 대기 시간 모델링
exponential = stats.expon(scale=5) # 평균 대기 시간 5
wait_times = exponential.rvs(size=1000)
print(f"지수 분포 평균 대기 시간: {wait_times.mean():.2f}")
# 연속 분포에서 구간 확률 계산 (cdf 사용)
prob_under_3 = exponential.cdf(3) # 대기 시간이 3 이하일 확률
prob_between = exponential.cdf(7) - exponential.cdf(3) # 3~7 사이
print(f"대기 시간 3 이하 확률: {prob_under_3:.4f}")
김개발 씨는 이제 사용자 행동 분석 대시보드를 만들고 있습니다. 페이지 체류 시간, 스크롤 깊이, 응답 시간 같은 데이터를 다뤄야 합니다.
이 값들은 30초, 30.1초, 30.11초처럼 소수점 아래로 무한히 정밀해질 수 있습니다. "체류 시간이 정확히 45.000초일 확률은 뭐죠?" 김개발 씨가 묻습니다.
박시니어 씨가 답합니다. "사실상 0이에요.
연속 변수에서 정확히 한 점의 확률은 의미가 없어요. 대신 45초에서 50초 사이에 있을 확률을 물어봐야 해요." 이것이 바로 연속 확률 분포와 이산 확률 분포의 핵심 차이입니다.
쉽게 비유하자면, 이산 분포는 계단과 같고 연속 분포는 경사로와 같습니다. 계단에서는 1층, 2층, 3층처럼 명확한 단계가 있지만, 경사로에서는 어느 높이든 가능합니다.
연속 분포에서는 값들이 끊김 없이 이어져 있습니다. 연속 분포에서 확률을 계산하려면 **확률 밀도 함수(PDF)**를 사용합니다.
PDF는 특정 점에서의 밀도를 나타내고, 이를 적분해서 구간 확률을 구합니다. 실제로는 **누적 분포 함수(CDF)**를 더 많이 사용합니다.
위 코드에서 균등 분포를 먼저 살펴봅시다. stats.uniform(loc=0, scale=10)은 0부터 10 사이에서 모든 값이 동일한 확률을 가지는 분포입니다.
주사위의 연속 버전이라고 생각하면 됩니다. 지수 분포는 이벤트 사이의 대기 시간을 모델링합니다.
고객 문의가 들어오는 간격, 서버 요청 사이의 시간 등을 표현할 때 사용합니다. scale 파라미터는 평균 대기 시간을 나타냅니다.
가장 중요한 부분은 cdf 메서드입니다. exponential.cdf(3)은 대기 시간이 3 이하일 확률을 반환합니다.
3에서 7 사이 확률을 구하려면 cdf(7) - cdf(3)을 계산합니다. 이것이 연속 분포에서 확률을 계산하는 표준 방법입니다.
실제 서비스에서 API 응답 시간을 분석한다고 가정해 봅시다. "응답 시간이 200ms를 초과할 확률이 5% 미만이어야 한다"는 SLA가 있다면, 지수 분포나 다른 적합한 분포를 피팅하고 cdf를 사용해 이 조건을 만족하는지 확인할 수 있습니다.
주의할 점은 실제 데이터가 특정 분포를 정확히 따르는 경우가 드물다는 것입니다. 분포를 선택하기 전에 히스토그램을 그려보고 데이터의 모양을 확인해야 합니다.
또한 극단값(outlier)이 결과를 크게 왜곡할 수 있으니 주의가 필요합니다. 김개발 씨가 고개를 끄덕입니다.
"그러니까 연속 분포에서는 점이 아니라 구간으로 생각해야 하는 거군요. cdf로 누적 확률을 구하고, 구간 확률은 차이로 계산하고요."
실전 팁
💡 - pdf는 밀도이고 cdf는 누적 확률입니다. 확률 계산에는 cdf를 사용하세요
- 역함수 ppf를 사용하면 "상위 5%에 해당하는 값"을 구할 수 있습니다
- 실제 데이터 분석에서는 여러 분포를 피팅해보고 가장 적합한 것을 선택합니다
3. 정규 분포
김개발 씨가 사용자 데이터를 분석하다가 신기한 패턴을 발견했습니다. 키, 몸무게, 시험 점수, 심지어 제조 공정의 오차까지 모두 비슷한 종 모양의 분포를 보입니다.
"왜 자연계의 많은 현상이 이런 모양일까요?" 박시니어 씨가 미소 짓습니다. "정규 분포의 마법에 온 걸 환영해요."
**정규 분포(가우시안 분포)**는 통계학에서 가장 중요한 분포입니다. 평균을 중심으로 좌우 대칭인 종 모양을 가지며, 자연 현상의 많은 데이터가 이 분포를 따릅니다.
중심극한정리에 의해 표본 평균은 원래 분포와 관계없이 정규 분포에 가까워지므로, 통계적 추론의 기초가 됩니다.
다음 코드를 살펴봅시다.
import numpy as np
from scipy import stats
# 정규 분포 생성: 평균 170, 표준편차 10
height_dist = stats.norm(loc=170, scale=10)
# 무작위 샘플 생성
heights = height_dist.rvs(size=1000)
print(f"샘플 평균: {heights.mean():.2f}, 샘플 표준편차: {heights.std():.2f}")
# 특정 값의 확률 밀도
pdf_at_180 = height_dist.pdf(180)
print(f"키 180에서 확률 밀도: {pdf_at_180:.4f}")
# 구간 확률 계산
prob_under_160 = height_dist.cdf(160) # 160 이하
prob_over_180 = 1 - height_dist.cdf(180) # 180 초과
prob_between = height_dist.cdf(180) - height_dist.cdf(160) # 160~180
print(f"160~180 사이 확률: {prob_between:.4f}")
# 68-95-99.7 규칙 확인
within_1std = height_dist.cdf(180) - height_dist.cdf(160)
print(f"평균 +/- 1 표준편차 내 확률: {within_1std:.4f}")
김개발 씨는 건강 관리 앱의 데이터 분석을 맡게 되었습니다. 사용자 10만 명의 키 데이터를 히스토그램으로 그려보니 익숙한 종 모양이 나타났습니다.
"이게 바로 그 유명한 정규 분포인가요?" 김개발 씨가 묻습니다. 박시니어 씨가 답합니다.
"맞아요. 정규 분포는 통계학의 꽃이라고 할 수 있어요.
카를 프리드리히 가우스의 이름을 따서 가우시안 분포라고도 불러요." 왜 정규 분포가 이렇게 중요할까요? 놀라운 사실이 있습니다.
동전을 1000번 던져서 앞면 나온 횟수를 세어보세요. 이 실험을 여러 번 반복하면 그 분포가 정규 분포에 가까워집니다.
주사위도, 카드도 마찬가지입니다. 이것이 바로 중심극한정리입니다.
쉽게 비유하자면, 정규 분포는 마치 민주주의와 같습니다. 수많은 작은 요인들이 각자 영향을 미치고, 그 총합이 결과를 결정합니다.
키는 수천 개의 유전자, 영양 상태, 환경 요인 등이 조금씩 영향을 미친 결과입니다. 이런 요인들이 서로 독립적으로 작용하면 결과는 정규 분포를 따릅니다.
정규 분포는 두 개의 파라미터로 완전히 정의됩니다. **평균(mu)**은 분포의 중심 위치를, **표준편차(sigma)**는 데이터가 얼마나 퍼져 있는지를 나타냅니다.
표준편차가 크면 종이 납작하고 넓게 퍼지고, 작으면 뾰족하고 좁습니다. 유명한 68-95-99.7 규칙을 기억하세요.
평균에서 표준편차 1개 범위 안에 전체 데이터의 약 68%가 있습니다. 2개 범위 안에는 95%, 3개 범위 안에는 99.7%가 있습니다.
이 규칙만 알아도 데이터를 빠르게 파악할 수 있습니다. 위 코드에서 stats.norm(loc=170, scale=10)은 평균 170, 표준편차 10인 정규 분포를 만듭니다.
loc은 위치(평균), scale은 척도(표준편차)입니다. 이것은 한국 성인 남성의 키 분포와 비슷합니다.
cdf를 사용해 구간 확률을 계산합니다. 키가 160cm 이하인 사람의 비율, 180cm 이상인 비율, 160에서 180 사이인 비율을 모두 구할 수 있습니다.
실무에서 정규 분포는 품질 관리, 금융 모델링, 머신러닝 등 거의 모든 분야에서 사용됩니다. 제조 공정에서 불량률을 계산하거나, A/B 테스트의 결과가 통계적으로 유의미한지 판단할 때 정규 분포가 기초가 됩니다.
주의할 점은 모든 데이터가 정규 분포를 따르지는 않는다는 것입니다. 소득 분포는 오른쪽으로 긴 꼬리를 가지고, 웹사이트 트래픽은 멱법칙을 따르는 경우가 많습니다.
데이터를 분석하기 전에 히스토그램이나 Q-Q plot으로 분포 형태를 확인해야 합니다. 김개발 씨가 말합니다.
"68-95-99.7 규칙이 정말 유용하네요. 표준편차만 알면 대략적인 분포를 바로 그릴 수 있겠어요."
실전 팁
💡 - 68-95-99.7 규칙을 암기하면 데이터 분포를 직관적으로 파악할 수 있습니다
- 데이터가 정규 분포를 따르는지 확인하려면 stats.normaltest()를 사용하세요
- 표본 크기가 충분히 크면 중심극한정리에 의해 표본 평균은 정규 분포에 가까워집니다
4. 기댓값과 분산
김개발 씨가 로또 시뮬레이션을 만들다가 궁금해졌습니다. "로또를 매주 사면 평균적으로 얼마를 벌 수 있을까요?" 박시니어 씨가 답합니다.
"그걸 알려면 기댓값을 계산해야 해요. 그리고 얼마나 들쭉날쭉한지는 분산으로 알 수 있고요."
기댓값은 확률 변수의 평균적인 결과를 나타내고, 분산은 결과가 평균에서 얼마나 흩어져 있는지를 측정합니다. 기댓값은 장기적인 평균 수익을 예측할 때, 분산은 리스크를 평가할 때 핵심적인 역할을 합니다.
이 두 개념은 투자, 보험, 게임 이론 등 의사결정이 필요한 모든 분야에서 필수적입니다.
다음 코드를 살펴봅시다.
import numpy as np
from scipy import stats
# 주사위 던지기의 기댓값과 분산
dice_outcomes = np.array([1, 2, 3, 4, 5, 6])
dice_probs = np.array([1/6] * 6)
# 수동 계산
expected_value = np.sum(dice_outcomes * dice_probs)
variance = np.sum(dice_probs * (dice_outcomes - expected_value)**2)
std_dev = np.sqrt(variance)
print(f"주사위 기댓값: {expected_value:.2f}")
print(f"분산: {variance:.2f}, 표준편차: {std_dev:.2f}")
# SciPy 분포 객체로 계산
normal_dist = stats.norm(loc=100, scale=15) # IQ 분포
print(f"정규분포 평균: {normal_dist.mean()}")
print(f"정규분포 분산: {normal_dist.var()}")
print(f"정규분포 표준편차: {normal_dist.std()}")
# 이항 분포의 기댓값과 분산
binom_dist = stats.binom(n=100, p=0.3)
print(f"이항분포 기댓값: {binom_dist.mean()}") # n * p
print(f"이항분포 분산: {binom_dist.var()}") # n * p * (1-p)
김개발 씨는 회사에서 새로운 과금 모델을 분석하는 업무를 맡았습니다. "무료 사용자 중 5%가 유료로 전환하고, 유료 사용자당 월 10,000원 수익이 발생해요.
사용자 1만 명이면 월 수익이 얼마일까요?" 박시니어 씨가 화이트보드에 수식을 씁니다. "기댓값으로 계산해 봅시다.
10,000명 곱하기 0.05 곱하기 10,000원은 500만 원이에요. 이게 기대 수익입니다." 기댓값이란 무엇일까요?
쉽게 비유하자면, 기댓값은 마치 무게중심과 같습니다. 시소 위에 여러 아이들이 앉아 있을 때, 시소가 균형을 이루는 지점이 있습니다.
기댓값은 확률 분포의 균형점입니다. 모든 가능한 결과를 그 확률로 가중 평균한 값입니다.
주사위를 던지면 1부터 6까지 각각 1/6 확률로 나옵니다. 기댓값은 (1+2+3+4+5+6)/6 = 3.5입니다.
물론 3.5가 실제로 나오지는 않지만, 주사위를 수천 번 던지면 평균이 3.5에 가까워집니다. 그런데 기댓값만으로는 부족합니다.
두 투자 상품이 같은 기대 수익을 가져도 리스크가 다를 수 있습니다. 여기서 분산이 등장합니다.
분산은 각 결과가 평균에서 얼마나 떨어져 있는지를 측정합니다. 수학적으로는 편차의 제곱을 확률로 가중 평균한 값입니다.
예를 들어 봅시다. 투자 A는 확실하게 5% 수익을 줍니다.
투자 B는 50% 확률로 15% 수익, 50% 확률로 -5% 손실을 줍니다. 둘 다 기댓값은 5%입니다.
하지만 투자 B의 분산이 훨씬 큽니다. 리스크를 싫어하는 사람은 A를, 리스크를 감수할 수 있는 사람은 B를 선택할 것입니다.
위 코드에서 주사위의 기댓값과 분산을 수동으로 계산합니다. np.sum(dice_outcomes * dice_probs)는 각 결과에 확률을 곱해서 더합니다.
분산은 (결과 - 기댓값)^2에 확률을 곱해서 더합니다. SciPy 분포 객체는 mean(), var(), std() 메서드를 제공합니다.
정규 분포 stats.norm(loc=100, scale=15)의 평균은 100, 분산은 225(15^2), 표준편차는 15입니다. 이항 분포의 기댓값은 np, 분산은 np*(1-p)라는 공식이 있습니다.
100번 시행에서 성공 확률이 0.3이면 기댓값은 30, 분산은 21입니다. 실무에서 기댓값과 분산은 리스크 관리의 핵심입니다.
포트폴리오 이론에서는 기대 수익을 최대화하면서 분산(리스크)을 최소화하는 조합을 찾습니다. 보험회사는 기댓값으로 보험료를 책정하고, 분산으로 준비금을 산정합니다.
주의할 점은 기댓값이 항상 현실적인 결과를 나타내지는 않는다는 것입니다. 로또의 기댓값은 마이너스이지만 사람들은 계속 삽니다.
또한 분포가 비대칭이면 평균보다 중앙값이 더 적절한 대표값일 수 있습니다. 김개발 씨가 말합니다.
"기댓값은 평균 결과, 분산은 불확실성이군요. 투자나 비즈니스 의사결정에서 둘 다 봐야 하는 이유를 알겠어요."
실전 팁
💡 - 기댓값이 같아도 분산이 다르면 완전히 다른 투자입니다
- 표준편차는 분산의 제곱근으로, 원래 단위로 해석할 수 있어 더 직관적입니다
- 이항 분포의 기댓값 np, 분산 np*(1-p)는 암기해 두면 유용합니다
5. 표준화와 Z score
김개발 씨가 두 시험 성적을 비교하려고 합니다. 수학은 100점 만점에 85점, 영어는 50점 만점에 45점입니다.
"어느 과목을 더 잘 본 거죠?" 박시니어 씨가 답합니다. "원점수로는 비교할 수 없어요.
Z-score로 표준화해야 합니다."
표준화는 서로 다른 척도의 데이터를 비교 가능하게 만드는 변환입니다. Z-score는 어떤 값이 평균에서 표준편차 몇 개만큼 떨어져 있는지를 나타냅니다.
표준화된 데이터는 평균 0, 표준편차 1을 가지며, 이를 통해 서로 다른 분포의 데이터도 동일 선상에서 비교할 수 있습니다.
다음 코드를 살펴봅시다.
import numpy as np
from scipy import stats
# 원시 데이터
math_scores = np.array([70, 75, 80, 85, 90, 95, 100])
math_score = 85
math_mean = math_scores.mean()
math_std = math_scores.std()
# Z-score 계산
z_score = (math_score - math_mean) / math_std
print(f"수학 85점의 Z-score: {z_score:.2f}")
# SciPy로 표준화
z_scores_all = stats.zscore(math_scores)
print(f"전체 Z-scores: {z_scores_all}")
# Z-score에서 백분위수 계산
percentile = stats.norm.cdf(z_score) * 100
print(f"수학 85점은 상위 {100 - percentile:.1f}%")
# 표준정규분포에서 특정 백분위수에 해당하는 Z-score
z_for_top_10 = stats.norm.ppf(0.90) # 상위 10%
print(f"상위 10%에 해당하는 Z-score: {z_for_top_10:.2f}")
# 역변환: Z-score에서 원점수로
original_score = z_for_top_10 * math_std + math_mean
print(f"상위 10%에 해당하는 수학 점수: {original_score:.1f}")
김개발 씨의 회사에서 개발자 성과 평가를 진행합니다. 프론트엔드팀은 코드 리뷰 점수 100점 만점, 백엔드팀은 버그 수정 건수로 평가합니다.
"다른 지표로 평가받는 팀원들을 어떻게 공정하게 비교하죠?" 박시니어 씨가 설명합니다. "바로 표준화예요.
각 지표를 Z-score로 변환하면 같은 기준에서 비교할 수 있어요." Z-score란 정확히 무엇일까요? 쉽게 비유하자면, Z-score는 마치 번역기와 같습니다.
한국어와 영어는 직접 비교할 수 없지만, 둘 다 세계 공통어로 번역하면 비교가 가능해집니다. Z-score는 모든 데이터를 "표준정규분포"라는 공통어로 번역합니다.
Z-score 공식은 간단합니다. (값 - 평균) / 표준편차.
이렇게 변환하면 평균은 0이 되고 표준편차는 1이 됩니다. Z-score = 0이면 정확히 평균입니다.
Z-score = 1이면 평균보다 표준편차 1개만큼 위에 있습니다. Z-score = -2면 평균보다 표준편차 2개만큼 아래에 있습니다.
이것이 왜 강력할까요? 68-95-99.7 규칙을 기억하세요.
Z-score가 -1과 1 사이면 전체의 68% 안에 있는 것입니다. Z-score가 2를 넘으면 상위 약 2.5%에 속합니다.
점수만 봐도 상대적 위치를 바로 알 수 있습니다. 위 코드에서 수학 85점의 Z-score를 계산합니다.
평균이 85, 표준편차가 약 10이라면 Z-score는 0에 가깝습니다. 정확히 평균이라는 뜻입니다.
stats.zscore() 함수는 배열 전체를 한 번에 표준화합니다. stats.norm.cdf(z_score)는 Z-score를 백분위수로 변환합니다.
Z-score가 1.5라면 cdf(1.5) = 0.93, 즉 하위 93%(상위 7%)입니다. 역으로 ppf를 사용하면 특정 백분위수의 Z-score를 구할 수 있습니다.
"상위 10%가 되려면 Z-score가 얼마여야 하지?"라는 질문에 stats.norm.ppf(0.90)이 답합니다. 약 1.28입니다.
실무에서 Z-score는 이상치 탐지에 많이 사용됩니다. Z-score의 절댓값이 3을 넘으면 극단적인 값으로 간주합니다.
99.7%의 데이터가 Z-score -3과 3 사이에 있기 때문입니다. 머신러닝에서도 표준화는 필수입니다.
특히 거리 기반 알고리즘(KNN, SVM)이나 경사하강법을 사용하는 모델에서는 피처 스케일링이 성능에 큰 영향을 미칩니다. sklearn의 StandardScaler가 바로 Z-score 변환을 수행합니다.
주의할 점은 표준화가 분포의 모양을 바꾸지는 않는다는 것입니다. 비정규 분포 데이터를 표준화해도 정규 분포가 되지 않습니다.
또한 이상치에 민감하므로, 극단값이 있으면 로버스트 스케일러나 min-max 스케일링을 고려하세요. 김개발 씨가 깨달음을 얻습니다.
"아, 그래서 머신러닝에서 StandardScaler를 쓰는 거군요. 다른 스케일의 피처들을 같은 기준으로 맞추는 거였어요."
실전 팁
💡 - Z-score 절댓값이 3을 넘으면 이상치로 의심하세요
- sklearn.preprocessing.StandardScaler는 Z-score 변환을 수행합니다
- 이상치가 많으면 RobustScaler(중앙값과 IQR 사용)를 고려하세요
6. SciPy로 분포 다루기
김개발 씨가 지금까지 배운 확률 분포 개념을 실제 프로젝트에 적용하려고 합니다. "이론은 알겠는데, 실무에서 어떤 함수를 써야 하죠?" 박시니어 씨가 SciPy 문서를 열며 말합니다.
"SciPy의 stats 모듈만 제대로 알면 대부분의 확률 문제를 해결할 수 있어요."
SciPy의 stats 모듈은 80개 이상의 확률 분포와 통계 함수를 제공합니다. 모든 분포 객체는 pdf/pmf, cdf, ppf, rvs 등 일관된 메서드를 가지고 있어 한 분포를 익히면 다른 분포도 쉽게 사용할 수 있습니다.
분포 피팅, 가설 검정, 신뢰 구간 계산까지 데이터 과학에 필요한 거의 모든 기능을 제공합니다.
다음 코드를 살펴봅시다.
import numpy as np
from scipy import stats
# 주요 메서드 패턴 (모든 분포에서 동일)
dist = stats.norm(loc=100, scale=15)
# pdf/pmf: 확률 밀도/질량 함수
print(f"pdf at 100: {dist.pdf(100):.4f}")
# cdf: 누적 분포 함수 (P(X <= x))
print(f"P(X <= 115): {dist.cdf(115):.4f}")
# ppf: 분위수 함수 (cdf의 역함수)
print(f"90번째 백분위수: {dist.ppf(0.90):.2f}")
# rvs: 랜덤 샘플 생성
samples = dist.rvs(size=1000, random_state=42)
# interval: 신뢰 구간
lower, upper = dist.interval(0.95) # 95% 구간
print(f"95% 신뢰 구간: [{lower:.2f}, {upper:.2f}]")
# 실제 데이터에 분포 피팅
data = np.random.normal(50, 10, 1000)
fitted_mean, fitted_std = stats.norm.fit(data)
print(f"피팅된 파라미터: 평균={fitted_mean:.2f}, 표준편차={fitted_std:.2f}")
# 정규성 검정
stat, p_value = stats.shapiro(data[:50])
print(f"Shapiro-Wilk 검정 p-value: {p_value:.4f}")
김개발 씨는 이제 실제 데이터 분석 프로젝트를 시작합니다. 사용자 행동 데이터가 어떤 분포를 따르는지 확인하고, 이상치를 탐지하고, 통계적 유의성을 검증해야 합니다.
"SciPy가 좋다는 건 알겠는데, 함수가 너무 많아서 어디서부터 시작해야 할지 모르겠어요." 김개발 씨가 고민합니다. 박시니어 씨가 핵심을 짚어줍니다.
"걱정 마세요. SciPy의 모든 분포는 같은 패턴을 따라요.
네 가지 메서드만 기억하면 됩니다." 첫째, **rvs(random variates)**는 무작위 샘플을 생성합니다. 시뮬레이션이나 부트스트래핑에 사용합니다.
size 파라미터로 샘플 수를, random_state로 재현성을 확보합니다. 둘째, **pdf(probability density function)**는 연속 분포에서 특정 값의 확률 밀도를 반환합니다.
이산 분포에서는 pmf(probability mass function)를 사용합니다. 그래프를 그릴 때 주로 사용합니다.
셋째, **cdf(cumulative distribution function)**는 누적 확률을 계산합니다. P(X <= x), 즉 x 이하일 확률입니다.
구간 확률을 계산할 때 cdf(b) - cdf(a)를 사용합니다. 넷째, **ppf(percent point function)**는 cdf의 역함수입니다.
"상위 10%에 해당하는 값은?"이라는 질문에 ppf(0.90)이 답합니다. 백분위수나 임계값을 구할 때 사용합니다.
이 네 가지가 핵심입니다. 정규 분포, 이항 분포, 포아송 분포, 지수 분포 등 어떤 분포든 같은 메서드 이름을 사용합니다.
위 코드에서 interval 메서드도 유용합니다. dist.interval(0.95)는 전체 확률의 95%가 포함되는 구간을 반환합니다.
신뢰 구간 계산에 직접 사용할 수 있습니다. 실제 데이터에 분포를 피팅하려면 fit 메서드를 사용합니다.
stats.norm.fit(data)는 데이터에 가장 적합한 평균과 표준편차를 찾습니다. 최대우도추정(MLE) 방법을 사용합니다.
데이터가 정규 분포를 따르는지 검정하려면 shapiro 함수를 사용합니다. p-value가 0.05보다 크면 정규성 가정을 기각하지 못합니다.
주의할 점은 샘플 크기가 크면 작은 차이도 유의하게 나온다는 것입니다. 실무에서 자주 사용하는 분포를 정리하면 다음과 같습니다.
클릭/전환 같은 이진 결과는 베르누이와 이항 분포, 드문 이벤트 횟수는 포아송 분포, 대기 시간은 지수 분포, 자연 현상과 오차는 정규 분포입니다. 주의할 점은 분포 파라미터의 의미가 분포마다 다르다는 것입니다.
정규 분포의 scale은 표준편차이지만, 지수 분포의 scale은 평균입니다. 문서를 확인하는 습관을 들이세요.
또한 실제 데이터가 이론적 분포를 정확히 따르는 경우는 드뭅니다. 분포 피팅 결과를 맹신하지 말고, 시각화로 적합도를 확인하세요.
Q-Q plot이 이 목적에 유용합니다. 김개발 씨가 정리합니다.
"rvs로 샘플링, pdf로 밀도, cdf로 누적 확률, ppf로 역변환. 이 네 가지면 어떤 분포든 다룰 수 있군요.
fit으로 피팅하고 shapiro로 검정하고요." 박시니어 씨가 고개를 끄덕입니다. "맞아요.
이제 확률 분포를 활용해서 데이터 기반 의사결정을 할 준비가 된 거예요."
실전 팁
💡 - rvs, pdf/pmf, cdf, ppf 네 가지 메서드 패턴을 기억하세요
- stats.describe()로 데이터의 기본 통계량을 한 번에 확인할 수 있습니다
- Q-Q plot(stats.probplot)으로 분포 적합도를 시각적으로 확인하세요
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
Helm 마이크로서비스 패키징 완벽 가이드
Kubernetes 환경에서 마이크로서비스를 효율적으로 패키징하고 배포하는 Helm의 핵심 기능을 실무 중심으로 학습합니다. Chart 생성부터 릴리스 관리까지 체계적으로 다룹니다.
보안 아키텍처 구성 완벽 가이드
프로젝트의 보안을 처음부터 설계하는 방법을 배웁니다. AWS 환경에서 VPC부터 WAF, 암호화, 접근 제어까지 실무에서 바로 적용할 수 있는 보안 아키텍처를 단계별로 구성해봅니다.
AWS Organizations 완벽 가이드
여러 AWS 계정을 체계적으로 관리하고 통합 결제와 보안 정책을 적용하는 방법을 실무 스토리로 쉽게 배워봅니다. 초보 개발자도 바로 이해할 수 있는 친절한 설명과 실전 예제를 제공합니다.
AWS KMS 암호화 완벽 가이드
AWS KMS(Key Management Service)를 활용한 클라우드 데이터 암호화 방법을 초급 개발자를 위해 쉽게 설명합니다. CMK 생성부터 S3, EBS 암호화, 봉투 암호화까지 실무에 필요한 모든 내용을 담았습니다.
AWS Secrets Manager 완벽 가이드
AWS에서 데이터베이스 비밀번호, API 키 등 민감한 정보를 안전하게 관리하는 Secrets Manager의 핵심 개념과 실무 활용법을 배워봅니다. 초급 개발자도 쉽게 따라할 수 있도록 실전 예제와 함께 설명합니다.