(파이썬으로 10,000번 시뮬레이션 돌려보기)
안녕하세요! '유쾌한 통계 산책'의 두 번째 시간입니다.
혹시 이런 얘기 들어보셨나요? "사람이 23명만 모여도, 그중에 생일이 같은 사람이 있을 확률이 50%가 넘는다!"
"에이, 말도 안 돼!" 1년은 365일이나 되는데, 고작 23명 중에 생일이 겹친다고요? 강의실에 30명 정도만 있어도 생일이 같은 사람이 꼭 있다는 건데... 우리의 직관은 이 사실을 쉽게 받아들이지 못합니다.
왜 그럴까요?
우리는 무의식 중에 이 문제를 '내' 생일과 같은 사람이 있을 확률'로 잘못 계산합니다.
(내가 아닌) 다른 22명이 나와 생일이 같을 확률은 매우 낮죠.
하지만 '생일 역설'의 핵심은 '그 방에 있는 어떤 두 사람'의 생일이 같을 확률입니다.
즉, 1번 사람과 2번 사람, 1번과 3번,..., 1번과 23번,... 22번과 23번... 이 모든 쌍(Pair)을 비교해야 하는 거죠. 23명이 모이면 비교해야 할 쌍은 무려 253개나 됩니다! ($23 \times 22 / 2 = 253$)
이 식이 이해가 되나요? 왜 2로 나누는지?
모든 경우의 수는 다음과 같습니다.
1번 사람의 경우 (1,2), (1,3)..........................(1,23)의 22개의 조합이 만들어집니다.
2번 사람의 경우 (2,1), (2,3)..........................(2,23)의 22개의 조합이 만들어집니다.
3번 사람의 경우 (3,1), (3,2), (3,4).................(3,23)의 22개의 조합이 만들어집니다.
(중략)
23번 사람의 경우 (13,1), (23,2), (23,4)...............(23,22)의 22개의 조합이 만들어집니다.
그래서 모든 경우의 수는 23*22= 506가지입니다.
그런데 자세히 보면 중복이 있습니다. 전체 경우의 수를 보면 (1,2)이나 (2,1)는 같은 조합입니다.
(1,3)이나 (3,1), (1,4)이나 (4,1), (1,5)이나 (5,1)......(22,23)이나 (23,22) 그러니까 이들 중복을 제외해 주어야 합니다.
전체를 다하려니 일이 많은데, 예시를 들어볼게요. 만약 사람이 4명(1, 2, 3, 4)이 있다면?
1번 사람이 비교: (1, 2), (1, 3), (1, 4) -> 3가지
2번 사람이 비교: (2, 1), (2, 3), (2, 4) -> 3가지
3번 사람이 비교: (3, 1), (3, 2), (3, 4) -> 3가지
4번 사람이 비교: (4, 1), (4, 2), (4, 3) -> 3가지
총합 = 12가지
오류: (1, 2)와 (2, 1)이 둘 다 세어졌습니다. (중복!)
중복을 제외하기
1번 사람: (1, 2), (1, 3), (1, 4) -> 3가지
2번 사람: (2, 3), (2, 4) -> 2가지, (1,2)와 (2,1)는 중복이므로 제외
3번 사람: (3, 4) -> 1가지, (1,3)와 (2,3)는 중복이므로 제외
4번 사람: 0, (4, 1), (4, 2), (4, 3) 이미 중복이므로 제외
총합 = 3 + 2 + 1 = 6가지
이것이 바로 4*3 / 2 = 6과 같습니다.
실제 23명의 경우도
1번 사람: 22가지
2번 사람: 21가지, (1,2)는 중복이므로 제외
3번 사람: 20가지, (1,3)와 (2,3)는 중복이므로 제외
4번 사람: 19가지, (4, 1), (4, 2), (4, 3) 중복이므로 제외
5번 사람: 18가지,
6번 사람: 17가지,
7번 사람: 16가지,
8번 사람: 15가지,
9번 사람: 14가지,
10번 사람: 13가지,
11번 사람: 12가지,
12번 사람: 11가지,
13번 사람: 10가지,
14번 사람: 9가지,
15번 사람: 8가지,
16번 사람: 7가지,
17 사람: 6가지,
18번 사람: 5가지,
19번 사람: 4가지,
20번 사람: 3가지,
21번 사람: 2가지,
22번 사람: 1가지,
23번 사람: 0가지
총합은 253가지 (덧셈은 Excel로 계산한 값)
이해가 되셨죠?
확률은 절대로 눈으로 계산하시면 안 됩니다. 손발을 다 써서라도 계산을 해야 해요.
너무 무식하게 했으니 이제는 유식한 방법으로 돌아가자고요
그래도 여전히 미심쩍다면, 확률의 단짝 친구 '여사건(반대 사건)'을 이용해 볼까요?
"적어도 한 쌍의 생일이 같을 확률"의 반대는 "모든 사람의 생일이 다 다를 확률"입니다.
이 '모두 다를 확률'을 계산해서 1에서 빼주면, 우리가 원하는 값을 얻을 수 있습니다.
1번째 사람: 아무 날이나 OK (365/365)
2번째 사람: 1번째 사람을 피해야 함 (364/365)
3번째 사람: 앞의 2명을 피해야 함 (363/365)
...
23번째 사람: 앞의 22명을 피해야 함 (343/365)
이걸 다 곱하면? 즉, 23명 모두 생일이 다를 확률은 약 49.3%입니다.
그렇다면 반대로, 적어도 한 쌍이 생일이 같을 확률은?
1 - 0.4927 = 0.5073
네, 무려 50.7%가 나옵니다! (이해가 안 되도 일단 진도를 나갈게요)
"수학 계산은 여전히 머리 아파요!"
괜찮습니다. 우리에겐 파이썬이 있으니까요.
복잡한 계산 대신, 아예 '23명을 랜덤으로 뽑는 방'을 10,000개 만들어서, 실제로 생일이 겹치는 방이 몇 개나 되는지 직접 세어봅시다!
import random
def check_birthday_match(num_people):
"""
설명: num_people 명의 사람 중 생일이 겹치는 사람이 있는지 확인하는 함수
"""
birthdays = [] # 사람들의 생일을 저장할 빈 리스트
for _ in range(num_people):
# 1월 1일(1)부터 12월 31일(365)까지 중 랜덤 한 날짜를 뽑습니다.
birthday = random.randint(1, 365)
birthdays.append(birthday)
# -----------------------------------------------------
# [핵심 로직]
# 'set'은 리스트에서 중복된 값을 자동으로 제거해 줍니다.
# 만약 생일 리스트(birthdays)의 길이와,
# 중복을 제거한 set(birthdays)의 길이가 다르다면...
# 그건 바로 "중복된 생일이 있다"는 뜻입니다!
# -----------------------------------------------------
if len(set(birthdays)) < len(birthdays):
return True # 생일 겹치는 사람 있음!
return False # 생일 겹치는 사람 없음!
# --- 이제 10,000번 시뮬레이션을 돌려봅시다 ---
num_simulations = 10000 # 만 번 시뮬레이션
num_people = 23 # 우리가 궁금한 23명
match_count = 0 # 생일이 겹친 횟수를 0으로 초기화
for _ in range(num_simulations):
# 23명이 있는 방을 계속 시뮬레이션합니다.
if check_birthday_match(num_people):
# 만약 생일이 겹쳤다면(True), 횟수를 1 증가시킵니다.
match_count += 1
# 최종 확률 계산
probability = (match_count / num_simulations) * 100
# 결과 출력
print(f"{num_people} 명이 모였을 때 {num_simulations}번 시뮬레이션한 결과, ")
print(f"적어도 한 쌍의 생일이 같을 확률은 약 {probability:. 2f}%입니다.")
만일 파이썬을 모르시분 분들은 이 코드를 돌리면 결과가
23명이 모였을 때 10000번 시뮬레이션한 결과, 적어도 한 쌍의 생일이 같을 확률은 약 50.36%입니다.
됩니다. 과반이 넘어요...
이 코드는 생일 문제(birthday problem)**라는 유명한 확률 이론을 증명하려는 거예요. 생일 문제는 "사람들이 많이 모이면, 그중에서 생일이 같은 사람이 있을 확률이 이외로 높다"는 걸 보여주는 거죠. 구체적으로, 23명만 모여도 생일이 같은 사람이 적어도 한 쌍 있을 확률이 50%가 넘는다는 사실을 시뮬레이션으로 확인하는 코드예요.
왜 이해가 안 갈 수 있나요?
코드에 "누구의 생일이 언제다" 같은 구체적인 날짜(예: 5월 10일)가 없어서 추상적으로 느껴질 수 있어요. 대신, 생일을 단순히 "1부터 365까지의 숫자"로 표현했어요. (윤년은 무시하고, 1년을 365일로 가정하는 거예요.)
초보자 입장에서는 "집합(set)" 같은 Python 기능이 생소할 수 있어요. 하지만 이건 중복을 쉽게 확인하는 도구일 뿐이에요.
코드의 의도와 동작을 단계적으로 쉽게 설명
이 코드의 목표: "23명이 모인 방에서, 생일이 같은 사람이 있을 확률을 컴퓨터로 여러 번 시뮬레이션해서 계산해 보자!"입니다.
기본 아이디어: 사람들의 생일을 랜덤으로 뽑아서, "중복(같은 날짜)"이 있는지 확인해요. 이걸 10,000번 반복해서, "중복이 일어난 횟수"를 세고, 그걸 확률로 바꾸는 거예요.
왜 23명일까? 확률 이론에 따르면, 23명쯤 되면 "우연히" 생일이 겹칠 확률이 50%를 넘어요. (직관적으로는 "365일인데 23명밖에 안 되니 겹칠 리 없지?"라고 생각하기 쉽지만, 실제로는 꽤 높아요. 이게 생일 문제의 재미있는 점!)
단계별 동작 설명 (코드의 핵심 부분만 쉽게 풀어서):
생일 뽑기: check_birthday_match 함수에서, num_people(예: 23)만큼 랜덤으로 1~365 사이 숫자를 뽑아요. 이게 사람들의 생일이에요. (예: [15, 200, 15,...]처럼 리스트로 저장.)
중복 확인: 리스트를 set으로 바꿔봐요. set은 자동으로 중복을 제거해요. 원래 리스트 길이: 23 set 길이: 만약 중복이 있으면 22나 그 이하 (중복된 게 사라지니까). 그래서 "set 길이 < 원래 길이"면 중복(생일 겹침)이 있어요! 이게 True/False로 반환돼요.
시뮬레이션 반복: 이 과정을 10,000번 반복해요. 중복이 일어나면 match_count를 +1 해요.
확률 계산: (중복 횟수 / 10,000) * 100 = 확률(%). 예를 들어, 5,000번 중복됐다면 50%예요.
출력: "23명이 모였을 때... 확률은 약 XX%입니다."처럼 결과가 나와요.
왜 이게 증명인가? 실제 확률 이론으로는 정확히 계산할 수 있지만(공식: 1 - (365! / (365-num_people)! / 365^num_people)), 초보자에겐 복잡하죠. 그래서 컴퓨터로 "랜덤 시뮬레이션"해서 근삿값을 구하는 거예요. 실행하면 정말 50% 정도 나와요!
오늘 제가 의도적으로 어려운 예제로 시작을 했는데, 저의 시리즈의 목표는 제 연재가 끝나는 마지막날에 여러분은 이야기를 strory 수순의 확률이론에 자신감을 가지게 만들겠다입니다.
통계와 확률의 세계는 이처럼 우리의 '직관'과 다른 경우가 많습니다. 365라는 숫자가 너무 커 보여서 확률이 낮을 거라 생각했지만, 실제로는 '비교해야 할 쌍(Pair)'이 훨씬 더 빠르게 늘어났기 때문이죠.
이 '생일 역설'은 왜 우리가 데이터를 직접 확인하고 '시뮬레이션'을 해봐야 하는지 알려주는 아주 재미있는 사례입니다.
다음 산책은 동전을 던지면 정말로 앞면과 뒷면이 반반 나오나요?부터 시작을 할게요. 확포자님들 다음을 기대하세요.