3.5 난수 발생과 카운팅

파이썬을 이용하여 데이터를 무작위로 섞거나 임의의 수 즉, 난수(random number)를 발생시키는 방법에 대해 알아본다. 이 기능은 주로 NumPy의 random 서브패키지에서 제공한다.

시드 설정하기

컴퓨터 프로그램에서 발생하는 무작위 수는 사실 엄격한 의미의 무작위 수가 아니다. 어떤 특정한 시작 숫자를 정해 주면 컴퓨터가 정해진 알고리즘에 의해 마치 난수처럼 보이는 수열을 생성한다. 이런 시작 숫자를 시드(seed)라고 한다. 일단 생성된 난수는 다음번 난수 생성을 위한 시드값이 된다. 따라서 시드값은 한 번만 정해주면 된다. 시드는 보통 현재 시각등을 이용하여 자동으로 정해지지만 사람이 수동으로 설정할 수도 있다. 특정한 시드값이 사용되면 그 다음에 만들어지는 난수들은 모두 예측할 수 있다. 이 책에서는 코드의 결과를 재현하기 위해 항상 시드를 설정한다.

파이썬에서 시드를 설정하는 함수는 seed이다. 인수로는 0과 같거나 큰 정수를 넣어준다.

np.random.seed(0)

이렇게 시드를 설정한 후 넘파이 random 서브패키지에 있는 rand 함수로 5개의 난수를 생성해 보자. rand 함수는 0과 1사이의 난수를 발생시키는 함수로 인수로 받은 숫자 횟수만큼 난수를 발생시킨다.

np.random.rand(5)
array([0.5488135 , 0.71518937, 0.60276338, 0.54488318, 0.4236548 ])

몇번 더 난수를 생성해보자. 사람이 예측할 수 없는 무작위 숫자가 나오는 것을 볼 수 있다.

np.random.rand(10)
array([0.64589411, 0.43758721, 0.891773  , 0.96366276, 0.38344152,
       0.79172504, 0.52889492, 0.56804456, 0.92559664, 0.07103606])
np.random.rand(10)
array([0.0871293 , 0.0202184 , 0.83261985, 0.77815675, 0.87001215,
       0.97861834, 0.79915856, 0.46147936, 0.78052918, 0.11827443])

이제 시드를 0으로 재설정하고 다시 난수를 발생시켜 본다.

np.random.seed(0)
np.random.rand(5)
array([0.5488135 , 0.71518937, 0.60276338, 0.54488318, 0.4236548 ])
np.random.rand(10)
array([0.64589411, 0.43758721, 0.891773  , 0.96366276, 0.38344152,
       0.79172504, 0.52889492, 0.56804456, 0.92559664, 0.07103606])
np.random.rand(10)
array([0.0871293 , 0.0202184 , 0.83261985, 0.77815675, 0.87001215,
       0.97861834, 0.79915856, 0.46147936, 0.78052918, 0.11827443])

아까와 같은 숫자가 나오는 것을 확인할 수 있다.

데이터의 순서 바꾸기

데이터의 순서를 바꾸려면 shuffle 함수를 사용한다. shuffle 함수도 자체 변환(in-place) 함수로 한 번 사용하면 변수의 값이 바뀌므로 사용에 주의해야 한다.

x = np.arange(10)
x
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
np.random.shuffle(x)
x
array([3, 6, 4, 5, 2, 9, 7, 8, 1, 0])

데이터 샘플링

이미 있는 데이터 집합에서 일부를 무작위로 선택하는 것을 표본선택 혹은 샘플링(sampling)이라고 한다. 샘플링에는 choice 함수를 사용한다. choice 함수는 다음과 같은 인수를 가질 수 있다.

numpy.random.choice(a, size=None, replace=True, p=None)

  • a : 배열이면 원래의 데이터, 정수이면 arange(a) 명령으로 데이터 생성

  • size : 정수. 샘플 숫자

  • replace : 불리언. True이면 한번 선택한 데이터를 다시 선택 가능

  • p : 배열. 각 데이터가 선택될 수 있는 확률

np.random.choice(5, 5, replace=False)  # shuffle 명령과 같다.
array([1, 4, 0, 3, 2])
np.random.choice(5, 3, replace=False)  # 3개만 선택
array([2, 1, 3])
np.random.choice(5, 10)  # 반복해서 10개 선택
array([0, 4, 1, 4, 1, 2, 2, 0, 1, 1])
np.random.choice(5, 10, p=[0.1, 0, 0.3, 0.6, 0])  # 선택 확률을 다르게 해서 10개 선택
array([0, 3, 3, 2, 2, 3, 3, 2, 0, 3])

난수 생성

넘파이의 random 서브패키지는 이외에도 난수를 생성하는 다양한 함수를 제공한다. 그 중 가장 간단하고 많이 사용되는 것은 다음 3가지 함수다.

  • rand: 0부터 1사이의 균일 분포

  • randn: 표준 정규 분포

  • randint: 균일 분포의 정수 난수

rand 함수는 0부터 1사이에서 균일한 확률 분포로 실수 난수를 생성한다. 숫자 인수는 생성할 난수의 크기이다. 여러개의 인수를 넣으면 해당 크기를 가진 행렬을 생성한다.

np.random.rand(10)
array([0.95894927, 0.65279032, 0.63505887, 0.99529957, 0.58185033,
       0.41436859, 0.4746975 , 0.6235101 , 0.33800761, 0.67475232])
np.random.rand(3, 5)
array([[0.31720174, 0.77834548, 0.94957105, 0.66252687, 0.01357164],
       [0.6228461 , 0.67365963, 0.971945  , 0.87819347, 0.50962438],
       [0.05571469, 0.45115921, 0.01998767, 0.44171092, 0.97958673]])

randn 함수는 기댓값이 0이고 표준편차가 1인 표준 정규 분포(standard normal distribution)를 따르는 난수를 생성한다. 인수 사용법은 rand 명령과 같다.

np.random.randn(10)
array([-0.30237513, -2.2244036 ,  0.72400636,  0.35900276,  1.07612104,
        0.19214083,  0.85292596,  0.01835718,  0.42830357,  0.99627783])
np.random.randn(3, 5)
array([[-0.49114966,  0.71267817,  1.11334035, -2.15367459, -0.41611148],
       [-1.07089699,  0.22113881, -1.12305712, -1.05075796,  1.01207905],
       [ 1.54371643, -0.40211489,  0.8647491 , -0.22686923,  0.81116027]])

randint 함수는 다음과 같은 인수를 가진다.

numpy.random.randint(low, high=None, size=None)

만약 high를 입력하지 않으면 0과 low사이의 숫자를, high를 입력하면 lowhigh는 사이의 숫자를 출력한다. size는 난수의 숫자이다.

np.random.randint(10, size=10)
array([0, 4, 3, 6, 9, 8, 0, 8, 5, 9])
np.random.randint(10, 20, size=10)
array([10, 19, 16, 15, 13, 11, 18, 10, 14, 19])
np.random.randint(10, 20, size=(3, 5))
array([[16, 15, 17, 18, 18],
       [19, 12, 18, 16, 16],
       [19, 11, 16, 18, 18]])

연습 문제 3.5.1

  1. 동전을 10번 던져 앞면(숫자 1)과 뒷면(숫자 0)이 나오는 가상 실험을 파이썬으로 작성한다.

  2. 주사위를 100번 던져서 나오는 숫자의 평균을 구하라.

연습 문제 3.5.2

가격이 10,000원인 주식이 있다. 이 주식의 일간 수익률(%)은 기댓값이 0%이고 표준편차가 1%인 표준 정규 분포를 따른다고 하자. 250일 동안의 주가를 무작위로 생성하라

정수 데이터 카운팅

이렇게 발생시킨 난수가 실수값이면 히스토그램 등을 사용하여 분석하면 된다. 히스토그램을 시각화 부분에서 나중에 자세히 설명한다.

만약 난수가 정수값이면 unique 명령이나 bincount 명령으로 데이터 값을 분석할 수 있다.

unique 함수는 데이터에서 중복된 값을 제거하고 중복되지 않는 값의 리스트를 출력한다. return_counts 인수를 True 로 설정하면 각 값을 가진 데이터 갯수도 출력한다.

np.unique([11, 11, 2, 2, 34, 34])
array([ 2, 11, 34])
a = np.array(['a', 'b', 'b', 'c', 'a'])
index, count = np.unique(a, return_counts=True)
index
array(['a', 'b', 'c'], dtype='<U1')
count
array([2, 2, 1])

그러나 unique 함수는 데이터에 존재하는 값에 대해서만 갯수를 세므로 데이터 값이 나올 수 있음에도 불구하고 데이터가 하나도 없는 경우에는 정보를 주지 않는다. 예를 들어 주사위를 10번 던졌는데 6이 한 번도 나오지 않으면 이 값을 0으로 세어주지 않는다.

따라서 데이터가 주사위를 던졌을 때 나오는 수처럼 특정 범위안의 수인 경우에는 bincount 함수에 minlength 인수를 설정하여 쓰는 것이 더 편리하다. bincount 함수는 0 부터 minlength - 1 까지의 숫자에 대해 각각 카운트를 한다. 데이터가 없을 경우에는 카운트 값이 0이 된다.

np.bincount([1, 1, 2, 2, 2, 3], minlength=6)
array([0, 2, 3, 1, 0, 0])