Data Science!/이론

[수학] 파이썬으로 배워보는 확률 - 2편 비등가 확률 (난이도 : 하)

양국남자 2021. 9. 12. 01:57

모든 수업은 파이썬 (Python 3 이상) 으로 진행됩니다. Pycharm 등 IDE 사용도 좋지만, Jupyter Notebook 이나 Google Colab 사용을 더욱 권장드립니다. Google Colab 에 있는 전체 컴필레이션은 확률편 마지막 강의에 올릴 예정입니다.

 

이번에는 비등가 확률에 대해서 알아보겠습니다. 비등가 확률이란, 모든 사건의 발생 확률이 균등하지 않은 확률입니다. 주사위를 던지면 모든 면의 확률이 1/6이라고 생각하지만, 만일 누군가가 사기를 친다면 어떨까요? 그렇다면 그사람의 주사위는 유리한 눈의 확률이 좀 더 높게 나올 겁니다. 주사위 도박을 하는데, 도박판에 있는 모든 사람이 만화 <카케구루이>나 <도박묵시록 카이지>처럼 도박에 미쳐서 목숨걸고 도박을 한다고 해봐요. 그렇다면 반칙과 트릭이 난무하는 비등가 확률의 싸움이 될 겁니다.

 

테슬라의 CEO 일론 머스크(좌)가 셔츠에 입고 나와 유명해진 도박 만화 <카케구루이>(우) 에 보면 오만가지 도박에 미친 사람들이 나옵니다. "선택하세요. 무능한 채 평온을 유지할 것인지, 파멸을 걸고 정상을 노릴 것인지." 라는 주인공 '쟈바미 유메코'의 대사는 머스크에게 정말 잘 어울리는 거 같네요...

 

뭐 현실에서 ML을 하든, 도박을 하든, 확률은 항상 공평하지 않습니다. 공은 둥그니까 토트넘이 크리스탈 팔레스에게 이길 확률이 50% 이라고 항상 장담할 수 있나요? (2021-09-12 기준 3:0 으로 깨졌답니다) 그렇다면 이 비등가 확률에 대해서 알아보기 위해서 새로운 단어 세 개만 배워볼게요.

 

빈도 (Frequency) : 특정 결과가 얼마나 나타나는지에 대한 양수입니다. 정수로 나타낼 수 도 있고, 분수로 나타낼 수 도 있습니다. 

 

분포 (Distribution) : 확률 변수가 흩어져 있는 상태입니다. 멀쩡한 주사위를 굴리면 1,2,3,4,5,6 이 다 비슷한 수대로 나올겁니다.

 

확률 분포 (Probability Distribution) : 확률 변수가 특정한 값을 가질 확률을 나타내는 함수입니다. 멀쩡한 주사위를 굴리면 모든 값이 1/6 으로 나오고, 다 더하면 1이 됩니다. (모든 확률분포에 대해서 통용되는 중요한 성질입니다!)  

from collections import Counter
#collections 모듈의 counter 클래스입니다. Counter 클래스는 데이터의 개수를 샐 때 매우 유용한 클래스입니다.

class Dist(Counter): 
#믿거나 말거나, Counter 를 사용한 함수를 초기화 해 줬습니다.

 

주사위 시뮬레이션을 한번 해 봅시다. 주사위는 1,2,3,4,5,6 이 다 같은 확률로 나오니까, 다 나왔다고 말할게요.

 

# 주사위 시뮬레이션:
Dist({1, 2, 3, 4, 5, 6})

#실행하시면 Dist({1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1}) 가 나올겁니다

분포함수 성능 테스트를 했으면, 한번 코드를 약간 손봐서 우리의 프로그램이 분포를 처리할 수 있도록 개조해 줍시다.

 

#favorable = set.intersection  기존의 favorable 은 집합이었습니다.
#cases     = len               여기서는 단지 모든 결과의 집합의 길이였습니다.


def cases(outcomes): 
    #cases 를 모든 결과값에 대한 빈도의 총합을 구하는 function 으로 바꿔줍시다.
    return sum(Dist(outcomes).values())

def favorable(event, space):
    #favorable 을 이제는 분포를 구하는 function 으로 바꿔줍시다.
    space = Dist(space)
    return Dist({x: space[x] 
                 for x in space if x in event})

def Fraction(n, d): return n / d

이제는 조작된 주사위를 넣은 뒤에, 짝수가 나올 확률을 구해봅시다.

관심 있으신 분은 눈을 빼고 칠하는 등 마개조로 한번 조작된 주사위를 만들어 봅시다.

#조작된 주사위
Rigged = Dist({1: 0.05, 2: 0.1, 3: 0.1, 4: 0.1, 5: 0.1, 6: 0.55})


P(even, Rigged)
#0.75가 나올겁니다.

 

이상 파이썬으로 비등가 확률에 대해서 알아보는 시간이었습니다.

 

비등가 확률과 분포도는 추후 업데이트 될 EPL 시즌 데이터를 바탕으로 한 분포 프로젝트로 다시 뵙겠습니다. (업데이트시 여기 링크가 걸릴겁니다.) EPL 시즌 데이터 프로젝트는 확률 4강 이후에 2020-2021 시즌 데이터를 기반으로 업데이트 되어 나올 예정입니다.

 

 

프로그래밍 팁 : 선조건 짜넣기

 

프로그래밍은 논리를 전달하는 과정입니다. 컴퓨터에게 A는 B이니 B로 처리해라 하는 걸 짜 넣는게 프로그래밍입니다. 저번 과목에서는, 주사위의 짝수 눈을 의미하는 even 을 우리는 {2,4,6} 이라고 직접 입력했습니다. 하지만 주사위 눈이 아니라 더 큰 정수를 다루는 프로그램이라면요? 그때도 2에서 수백만까지의 모든 짝수를 직접 입력할 건 아니잖아요! 

 

그래서 인간이 생각하듯이 선조건을 짜넣는게 매우 편합니다. 

 

우리가 홀수, 짝수 나누는 기준이 뭔가요? 2로 나눴을 때 나머지가 0 이면 짝수, 1이면 홀수 아닌가요? 그러면 % 기호를 사용해서 나머지를 구하고 그걸 판별하면 되겠네요

 

def even(n): return n % 2 == 0
def odd(n): return n % 2 == 1

#여기서는 굳이 even, odd 해서 짝수함수 홀수함수 해서 따로 만들었습니다. 하지만 '짝수가 아니면 홀수' 라는 논리를 활용하면 다른 함수 없이 T/F 냐 를 활용해서 짝/홀 을 판가름 할 수 있을겁니다.

앞서 말했던 원하는 결과를 구하는 함수인 favorable 을 개조해서 사건이 아닌 집합을 출력하도록 해줍시다.

 

def favorable(event, space):
#callable 은 event 의 집합들입니다. 프로그래밍적으로는 호출 가능한 함수인지 점검합니다.
#조건처리함수로 넣는 even 이나 odd 의 출력값들을 하나의 집합으로(분포도) 묶어줍니다.
    if callable(event):
        event = {x for x in space if event(x)}
    space = Dist(space)
    return Dist({x: space[x] 
                 for x in space if x in event})

프로그래밍 주석으로 단 내용이 와닿지 않는 분들을 위한 결과물 설명입니다. 

 

#정상적인 주사위를 굴렸을때 짝수가 나오는 분포
favorable(even, D)
#Dist({2: 1, 4: 1, 6: 1}) 라고 분포를 출력해 줍니다. 다시 말해서 출력값을 분포도로 바꿔줬습니다.

실생활에서는 잘 안쓰이지만, 던전 앤 드래곤 게임에는 여러가지 주사위가 사용됩니다. 만일 게임을 짤 경우, 정 4, 12, 20면체 등 다양한 주사위를 활용할 때 쓸 수 있을겁니다.

 

#논리를 활용합시다.정n면체 주사위는 1에서 n까지의 숫자를 가지고 있습니다.
#파이썬의 range(A,B) 함수는 A에서 B-1 까지의 정수 범위를 반환합니다. 어?

def die(n): return set(range(1, n + 1))
#짠. 주사위 완성.

die(12)
#{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, 1에서 12까지 모든 정수가 출력됩니다.

P(even, die(12))
#12면체 주사위를 굴리면 짝수가 나올 확률
#0.5 출력됩니다.

 

짝수, 홀수는 너무 쉬우니까 좀 더 재밌는거 해봅시다. 12면체 주사위를 4번 던져서 합을 구하면 소수가 나올 확률이라던가요. 

 

#주사위 D를 12면체 주사위로 다시 설정해줍시다.
D= {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}

#저번 시간에 반복을 위해 import 한 itertools 를 활용하여 반복을 해줍시다.
def sum_dice(d): return Dist(sum(dice) for dice in itertools.product(D, repeat=d))
#D 주사위를 d번 던진 값에 대한 합을 구하는 함수입니다.

#소수를 구하는 함수입니다. 1보다 크고, 본인 외에는 나눠지는 수가 없는 수가 소수죠.(not any 조건 참조)
def is_prime(n): return (n > 1 and not any(n % i == 0 for i in range(2, n)))

4번정도 12면체주사위를 던져주면 과연 그 합이 소수가 될 확률이 얼마인가 찾아봅시다
for d in range(1, 4):
    p = P(is_prime, sum_dice(d))
    print("P(is_prime, sum_dice({})) = {}".format(d, round(p, 3)))
    
#P(is_prime, sum_dice(1)) = 0.417 2,3,5,7,11 로 5/12 입니다. 추후에는 여러분이~
#P(is_prime, sum_dice(2)) = 0.354 
#P(is_prime, sum_dice(3)) = 0.287
#P(is_prime, sum_dice(4)) = 0.252

 

오늘은 이만 하겠습니다. 코딩 난이도가 저번보다는 약간 빡세졌네요 다음에는 우리가 배운 확률 지식과 파이썬의 힘을 빌려서 파스칼과 페르마에게 던져졌던 질문들을 해결해 봅시다. 앞서 공언했던 EPL 관련 컨텐츠는 물론, 부루마블 시뮬레이션도 나오니까 많은 기대 부탁드립니다.