집한 관련 함수
NumPy는 1차원 ndarray를 위한 몇가지 기본적인 집합 연산을 제공한다.
아마도 가장 자주 사용하는 함수는 배열 내에서 중복된 원소를 제거하고 남은 원소를 정렬된 형태로 반환하는
np.unique일 것이다.
in : names = np.array(['Bob','Joe','Will','Bob','Will','Joe','Joe'])
np.unique(names)
out : array(['Bob', 'Joe', 'Will'], dtype='<U4')
in : ints = np.array([3,3,3,2,2,1,1,4,4])
np.unique(ints)
out : array([1, 2, 3, 4])
np.in1d 함수는 두 개읜 배열을 인자로 받아서 첫 번째 배열의 원소가 두 번째 배열의 원소를 포함하는지
나타내는 불리언 배열을 반환한다.
in : values = np.array([6,0,0,3,2,5,6])
np.in1d(values,[2,3,6])
out : array([ True, False, False, True, True, False, True])
배열 집합 연산
- unique(x): 배열 x에서 중복된 원소를 제거한 뒤 정렬하여 반환한다.
- intersect1d(x,y): 배열 x와 y에 공통적으로 존재하는 원소를 정렬하여 반환한다.
- union1d(x,y): 두 배열의 합집합을 반환한다.
- in1d(x,y): x의 원소가 y의 원소에 포함되는지 나타내는 불리언 배열을 반환한다.
- setdiff1d(x,y): x와 y의 차집합을 반환한다.
- setxor1d(x,y): 한 배열에는 포함되지만 두 배열 모두에는 포함되지 않는 원소들의 집합인 대칭 차집합을 반환한다.
배열 데이터의 파일 입출력
NumPy는 디스크에서 텍스트나 바이너리 형식의 데이터를 불러오거나 저장할 수 있다.
np.save와 np.load는 배열 데이터를 효과적으로 디스크에 저장하고 불러오기 위한 함수이다.
배열은 기본적으로 압축되지 않은 원시 바이너리 형식의 .npy파일로 저장된다.
in : arr = np.arange(10)
np.save('some_array', arr)
저장되는 파일 경로가 .npy로 끝나지 않으면 자동적으로 확장자가 추가된다.
이렇게 저장된 배열은 np.load로 불러올 수 있다.
in : np.load('some_array.npy')
out : array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
np.savez함수를 이용하면 여러 개의 배열을 압축된 형식으로 저장할 수 있는데,
저장하려는 배열을 키워드 인자 형태로 전달한다.
in : np.savez('array_archive.npz', a=arr, b=arr)
in : arch = np.load('array_archive.npz')
arch['b']
out : array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
압축이 잘되는 형식의 데이터라면 대신 numpy.savez_compressed를 사용하자.
in : np.savez_compressed('arrays_compressed.npz', a=arr, b=arr)
arch2 = np.load('arrays_compressed.npz')
arch2['b']
out : array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
선형대수
행렬의 곱셉, 분할, 행렬식 그리고 정사각 행렬 수학 같은 선형대수는 배열을 다루는 라이브러리에서 중요한 부분이다.
매트랩 같은 언어와 다르게 두 개의 2차원 배열을 * 연산자로 곱하면 행렬 곱셈이 아니라 대응하는 각각의 원소의 곱을 계산한다.
행렬 곱셈은 배열 메서드이자 numpy 네임스페이스 안에 있는 dot함수를 이용해서 계산하자.
in : x = np.array([[1,2,3],[4,5,6]])
y = np.array([[6,23],[-1,7],[8,9]])
x, y
out : (array([[1, 2, 3],
[4, 5, 6]]),
array([[ 6, 23],
[-1, 7],
[ 8, 9]]))
in : x.dot(y), np.dot(x,y) # 2개는 비슷하다.
out : (array([[ 28, 64],
[ 67, 181]]),
array([[ 28, 64],
[ 67, 181]]))
2차원 배열과 곱셈이 가능한 크기의 1차원 배열 간의 행렬 곱셈의 결과는 1차원 배열이다.
in : np.dot(x,np.ones(3))
out : array([ 6., 15.])
파이썬 3.5부터 사용할 수 있는 @ 기호는 행렬 곱셈을 수행하는 연산자이다.
in : x @ np.ones(3)
out : array([ 6., 15.])
numpy.linalg는 행렬의 분할과 역행렬, 행렬식과 같은 것들을 포함하고 있다. 이는 매트랩,R 같은 언어에서
사용하는 표준 포트란 라이브러리인 BLAS,LAPACK 또는 Intel MKL을 사용해서 구현되었다.
in : from numpy.linalg import inv, qr
X = np.random.randn(5,5)
mat = X.T.dot(X)
inv(mat)
out : array([[ 1.65791817, 0.19102552, -0.915723 , -2.14627913,
0.6529658 ],
[ 0.19102552, 3.3224154 , 6.75404589, 1.42767562,
-3.99553952],
[ -0.915723 , 6.75404589, 21.12846251, 2.26406894,
-12.95301012],
[ -2.14627913, 1.42767562, 2.26406894, 5.14040658,
-1.46406556],
[ 0.6529658 , -3.99553952, -12.95301012, -1.46406556,
8.12964249]])
in : mat.dot(inv(mat))
out : array([[ 1.00000000e+00, 9.73048263e-16, 1.21558413e-15,
4.08989193e-15, -1.71996985e-15],
[ 5.32083634e-16, 1.00000000e+00, -5.67853323e-15,
-1.88045345e-15, 5.81422861e-16],
[-6.85761722e-17, 2.23606874e-15, 1.00000000e+00,
2.11757715e-15, -1.61640160e-15],
[-9.11894568e-16, -5.09299941e-16, 1.38603777e-15,
1.00000000e+00, 2.77108106e-16],
[ 4.54152491e-17, 1.91403645e-15, 1.91132879e-14,
1.88244552e-15, 1.00000000e+00]])
in : q, r = qr(mat)
in : q
out : array([[-0.67427654, -0.58420058, -0.24317864, 0.37846503, 0.0410999 ],
[ 0.52675722, -0.40161203, -0.69816207, -0.10274056, -0.25149295],
[-0.29587335, 0.4412488 , -0.20013331, 0.11392924, -0.81530684],
[-0.36114481, -0.1784972 , -0.00260157, -0.91061198, -0.0921533 ],
[-0.22340852, 0.520438 , -0.64294149, -0.06336044, 0.51170755]])
in : r
out : array([[-8.54016321, 7.75478498, -5.97149341, -4.84500967, -5.91719138],
[ 0. , -1.25004494, 3.60995808, 0.21493492, 5.2401242 ],
[ 0. , 0. , -2.50559991, -0.0595403 , -4.08199664],
[ 0. , 0. , 0. , -0.18906522, -0.04184247],
[ 0. , 0. , 0. , 0. , 0.06294343]])
X.T.dot(X)은 X.T의 전치행렬과 X의 곱을 계산한다.
자주 사용하는 선형대수 함수 모음
- diag: 정사각 행렬의 대각/비대각 원소를 1차원 배열로 반환하거나,
1차원 배열을 대각선 원소로 하고 나머지는 0으로 채운 단위 행렬을 반환한다. - dot: 행렬 곱셈
- trace: 행렬의 대각선 원소의 합을 계산
- det: 행렬식을 계산한다.
- eig: 정사각 행렬의 고윳값과 고유벡터를 계산한다.
- inv: 정사각 행렬의 역행렬을 계산한다.
- pinv: 정사각 행렬의 무어-펜로즈 유사역원 역행렬을 구한다.
- qr: QR분해를 계산한다.
- svd: 특잇값 분해(SVD)를 계산한다.
- solve: A가 정사각 행렬일 때 Ax = B를 만족하는 x를 구한다.
- lstsq: Ax = b를 만족하는 최소제곱해를 구한다.
난수 생성
numpy.random 모듈은 파이썬 내장 random 함수를 보강하여 다양한 종류의 확률분포로부터
효과적으로 표본값을 생성하는 데 주로 사용된다.
예를 들어 normal을 사용하여 표준정규분포로 부터 4x4 크기의 표본을 생성할 수 있다.
in : samples = np.random.normal(size=(4,4))
samples
out : array([[ 0.27736344, -0.74937387, -0.83935769, 1.068788 ],
[-1.30344171, -0.44795558, 1.24656545, -1.720645 ],
[ 0.40024253, 0.75544651, 2.04030529, -0.1969776 ],
[ 0.68205537, -0.06302825, 0.15166983, -0.6813747 ]])
이에 비해 파이썬 내장 random 모듈은 한 번에 하나의 값만 생성할 수 있다.
다음 성능 비교에서 알 수 있듯이 numpy.random은 매우 큰 표본을 생성하는데 파이썬 내장 모듈보다 수십 배 이상 빠르다.
in : from random import normalvariate
n = 1000000
%timeit samples = [normalvariate(0,1) for _ in range(n)]
out : 998 ms ± 23.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
in : %timeit np.random.normal(size=n)
out : 31.4 ms ± 2.17 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
이를 엄밀하게는 유사 난수라고 부르는데, 난수 생성기의 시드값에 따라 정해진 난수를 알고리즘으로 생성하기 때문이다.
NumPy의 난수 생성기의 시드값은 np.random.seed를 이용해서 변경할 수 있다.
in : np.random.seed(1234)
numpy.random에서 제공하는 데이터를 생성할 수 있는 함수들은 전역 난수 시드값을 이용한다.
numpy.random.RandomState를 이용해서 다른 난수 생성기로부터 격리된 난수 생성기를 만들수 있다.
in : rng = np.random.RandomState(1234)
rng.randn(10)
out : array([ 0.47143516, -1.19097569, 1.43270697, -0.3126519 , -0.72058873,
0.88716294, 0.85958841, -0.6365235 , 0.01569637, -2.24268495])
numpy.random함수
- seed: 난수 생성기의 시드를 지정한다.
- permutation: 순서를 임의로 바꾸거나 임의의 순열을 반환한다.
- shuffle: 리스트나 배열의 순서를 뒤섞는다.
- rand: 균등분포에서 표본을 추출한다.
- randint: 주어진 최소/최대 범위 안에서 임의의 난수를 추출한다.
- randn: 표준편차가 1이고 평균값이 0인 정규분포(매트랩과 같은 방식)에서 표본을 추출한다.
- binomial: 이항분포에서 표본을 추출한다.
- normal: 정규분포(가우시안)에서 표본을 추출한다.
- beta: 베타분포에서 표본을 추출한다.
- chisquare: 카이제곱분포에서 표본을 추출한다.
- gamma: 감마분포에서 표본을 추출한다.
- uniform: 균등(0,1)분포에서 표본을 추출한다.
계산 오르내리기 예제
계단 오르내리기 예제는 배열 연산의 활용을 보여줄 수 있는 간단한 애플리케이션이다.
계단 중간에서 같은 확률로 한 계단 올라가거나 내려간다가 가정하자.
in : # 순수 파이썬 코드
import random
position = 0
walk = [position]
steps = 1000
for i in range(steps):
step = 1 if random.randint(0,1) else -1
position += step
walk.append(position)
plt.plot(walk[:100])
out : [<matplotlib.lines.Line2D at 0x20d6a191400>] # 계단 모양 그래프가 나온다.
walk는 계단을 오르거나 내려간 값의 누적합이라는 사실을 알 수 있으면 배열식으로 나타낼 수 있다.
그래서 np.random 모듈을 사용해서 1000번 수행한 결과(1, -1)를 한번에 저장하고 누적합을 계산한다.
수치는 다 다르기 때문에 다르다고 틀린거 아니냐고 오해하지 말기!
in : nsteps = 1000
draws = np.random.randint(0, 2, size=nsteps)
steps = np.where(draws > 0, 1, -1)
walk = steps.cumsum()
in : walk.min()
out : -9
in : walk.max()
out : 60
in : walk
out : array([ 1, 2, 1, 2, 1, 0, -1, 0, 1, 2, 3, 4, 3, 2, 3, 2, 1,
0, -1, -2, -3, -4, -5, -6, -5, -6, -5, -4, -5, -6, -5, -6, -7, -6,
-7, -6, -7, -8, -9, -8, -7, -6, -7, -6, -5, -6, -5, -6, -5, -6, -5,
-4, -3, -2, -3, -2, -3, -2, -1, -2, -3, -2, -3, -4, -3, -2, -1, -2,
-3, -4, -3, -2, -1, 0, 1, 2, 3, 2, 3, 2, 3, 2, 3, 2, 1,
0, -1, -2, -3, -4, -5, -4, -3, -2, -3, -2, -3, -4, -3, -2, -3, -2,
-3, -2, -1, -2, -1, -2, -3, -4, -3, -2, -3, -4, -5, -6, -7, -6, -5,
-4, -3, -4, -3, -4, -3, -4, -3, -4, -3, -4, -3, -4, -3, -4, -3, -4,
-3, -2, -1, 0, -1, 0, 1, 2, 1, 0, -1, 0, -1, 0, -1, 0, 1,
0, 1, 0, -1, 0, 1, 2, 3, 2, 1, 0, 1, 0, -1, -2, -3, -4,
-5, -6, -5, -6, -7, -6, -5, -6, -7, -8, -7, -8, -9, -8, -7, -8, -9,
-8, -7, -8, -9, -8, -7, -8, -9, -8, -7, -8, -7, -8, -9, -8, -7, -6,
-7, -8, -7, -6, -5, -6, -7, -8, -7, -6, -7, -8, -7, -6, -5, -6, -5,
-4, -3, -2, -1, 0, -1, -2, -1, -2, -1, 0, -1, 0, 1, 0, -1, -2,
-1, -2, -1, -2, -3, -4, -5, -6, -5, -4, -3, -2, -1, -2, -1, 0, 1,
2, 1, 2, 3, 4, 5, 6, 5, 6, 7, 6, 7, 6, 5, 4, 3, 4,
5, 6, 7, 8, 7, 8, 7, 6, 5, 6, 7, 8, 7, 6, 7, 8, 9,
8, 7, 8, 7, 6, 7, 8, 9, 10, 11, 10, 11, 12, 11, 10, 11, 10,
9, 10, 11, 10, 11, 12, 11, 12, 13, 12, 11, 12, 13, 12, 13, 14, 13,
14, 15, 16, 15, 14, 13, 14, 15, 16, 15, 16, 17, 16, 15, 14, 15, 14,
13, 14, 15, 14, 13, 12, 13, 14, 15, 14, 15, 16, 15, 16, 15, 16, 17,
16, 17, 18, 19, 18, 17, 18, 17, 18, 19, 20, 21, 22, 23, 24, 23, 22,
21, 20, 21, 22, 21, 22, 23, 22, 21, 22, 21, 22, 23, 22, 21, 20, 21,
22, 23, 24, 25, 24, 23, 22, 21, 20, 21, 20, 21, 20, 19, 20, 19, 20,
19, 18, 19, 18, 17, 16, 15, 14, 13, 14, 15, 14, 13, 12, 13, 14, 13,
14, 13, 12, 11, 10, 11, 12, 11, 10, 9, 10, 9, 10, 9, 10, 11, 12,
13, 12, 13, 14, 15, 14, 13, 14, 13, 14, 13, 14, 15, 14, 15, 16, 15,
14, 13, 12, 13, 14, 15, 16, 15, 16, 15, 16, 15, 14, 15, 14, 15, 14,
15, 16, 17, 16, 15, 16, 17, 18, 17, 18, 19, 20, 19, 20, 19, 20, 21,
22, 21, 20, 19, 18, 17, 18, 17, 16, 15, 14, 15, 14, 15, 14, 13, 14,
15, 14, 13, 14, 13, 14, 13, 12, 11, 12, 11, 10, 11, 10, 11, 10, 9,
10, 11, 12, 11, 10, 9, 8, 9, 8, 7, 8, 9, 8, 9, 10, 9, 10,
11, 10, 9, 10, 9, 10, 11, 12, 13, 12, 11, 10, 9, 10, 11, 10, 11,
12, 13, 12, 11, 12, 11, 12, 13, 12, 11, 10, 11, 10, 11, 10, 9, 8,
9, 10, 9, 8, 9, 8, 9, 8, 9, 10, 9, 10, 9, 8, 7, 6, 5,
4, 5, 6, 5, 6, 7, 8, 7, 8, 9, 8, 7, 8, 7, 8, 9, 10,
9, 8, 9, 10, 11, 12, 11, 10, 11, 10, 11, 12, 13, 12, 11, 12, 13,
12, 11, 12, 13, 14, 15, 16, 17, 18, 17, 18, 19, 18, 17, 16, 17, 18,
19, 20, 21, 20, 19, 20, 21, 22, 23, 22, 23, 24, 25, 24, 23, 24, 23,
22, 21, 20, 21, 22, 21, 22, 23, 22, 21, 20, 19, 18, 19, 20, 21, 22,
21, 22, 21, 22, 23, 24, 25, 24, 25, 24, 25, 24, 23, 24, 25, 26, 25,
26, 27, 26, 27, 28, 29, 28, 27, 28, 29, 30, 29, 30, 29, 28, 29, 30,
31, 32, 31, 30, 29, 30, 29, 30, 29, 30, 31, 32, 33, 34, 33, 34, 35,
34, 33, 32, 31, 30, 31, 32, 33, 34, 35, 36, 35, 36, 37, 38, 39, 38,
37, 38, 39, 38, 39, 38, 37, 36, 37, 38, 39, 40, 39, 38, 37, 36, 35,
36, 35, 36, 35, 36, 37, 36, 37, 38, 37, 36, 37, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 47, 48, 47, 48, 49, 50, 49, 48, 49,
48, 47, 46, 45, 46, 45, 46, 45, 46, 45, 44, 43, 42, 43, 42, 43, 42,
43, 44, 45, 44, 43, 42, 43, 42, 41, 40, 41, 42, 41, 42, 41, 42, 43,
44, 43, 44, 45, 46, 47, 46, 47, 48, 49, 50, 51, 50, 51, 52, 51, 52,
53, 52, 53, 52, 51, 52, 51, 52, 53, 54, 55, 56, 55, 54, 53, 54, 55,
54, 55, 54, 55, 54, 55, 56, 57, 56, 57, 58, 57, 56, 57, 56, 57, 56,
57, 58, 59, 58, 57, 58, 59, 58, 59, 58, 59, 58, 59, 58, 59, 60, 59,
58, 57, 56, 57, 56, 55, 56, 55, 54, 53, 54, 55, 56, 57, 58, 57, 58,
57, 56, 55, 54, 53, 54, 55, 56, 57, 56, 55, 56, 55, 56, 55, 54, 55,
54, 53, 52, 51, 52, 53, 52, 51, 52, 51, 52, 51, 50, 49, 48, 49, 48,
49, 48, 49, 50, 51, 52, 53, 54, 55, 54, 55, 56, 55, 54, 55, 54, 53,
52, 51, 52, 51, 52, 53, 54, 55, 56, 55, 54, 55, 54, 53, 52, 53, 52,
51, 52, 51, 52, 53, 54, 55, 54, 53, 52, 53, 52, 51, 50],
dtype=int32)
in : plt.plot(walk[:100])
out : [<matplotlib.lines.Line2D at 0x20d69fe1370>] # 산 같은 그림이 나온다.
계단에서 특정 위치에 도달하기까지의 시간 같은 좀 더 복잡한 통계를 구할 수 있는데,
계단의 처음 위치에서 최초로 10칸 떨어지기까지 얼마나 걸렸는지 확인해보자. np.abs(walk) >= 10으로 처음 위치에서
10칸 이상 떨어진 시점을 알려주는 불리언 배열을 얻을 수 있다.
우리는 최초의 10 혹은 -10인 시점을 구해야 하므로 불리언 배열에서 최댓값의 처음
index를 반환하는 argmax를 사용하자(True가 최댓값이다)
in : (np.abs(walk) >= 10).argmax()
out : 297
여기서 argmax를 사용했지만 argmax는 배열 전체를 모두 확인하기 때문에 효과적인 방법은 아니다.
또한 이 예제에서는 True가 최댓값임을 이미 알고 있었다.
5000회로 다시 한번 해보자.
in : nwalks = 5000
nsteps = 1000
draws = np.random.randint(0, 2, size=(nwalks, nsteps)) # 0 또는 1
steps = np.where(draws > 0 ,1, -1)
walks = steps.cumsum(1)
walks
out : array([[ 1, 2, 3, ..., 46, 47, 46],
[ 1, 0, 1, ..., 40, 41, 42],
[ 1, 2, 3, ..., -26, -27, -28],
...,
[ 1, 0, 1, ..., 64, 65, 66],
[ 1, 2, 1, ..., 2, 1, 0],
[ -1, -2, -3, ..., 32, 33, 34]], dtype=int32)
in : walks.max()
out : 122
in : walks.min()
out : -128
이 데이터에서 누적합이 30혹은 -30이 되는 최소 시점을 계산해보자.
5000회의 시뮬레이션중 모든 경우가 30에 도달하지 않기 때문에 약간 까다로운데, any 메서드를 이용해서 해결 가능하다.
in : hits30 = (np.abs(walks) >= 30).any(1)
hits30
out : array([ True, True, True, ..., True, False, True])
in : hits30.sum()
out : 3368
이 불리언 배열을 사용해서 walks에서 컬럼을 선택하고 절댓값이 30을 넘는 경우에 대해서 축 1의 argmax()값을 구하면
처음 위치에서 30칸 이상 멀어지는 최소 횟수를 구할 수 있다.
in : crossing_times = (np.abs(walks[hits30]) >= 30).argmax(1)
crossing_times.mean()
out : 509.99762470308787
in : crossing_times
out : array([133, 395, 343, ..., 409, 297, 747], dtype=int64)
다른 분포를 사용해서 여러 가지 시도를 해보자. normal 함수에 표준편차와 평균값을 넣어서
정규분포에서 표본을 추출하는 것처럼 그냥 다른 난수 발생 함수를 사용하기만 하면 된다.
in : steps = np.random.normal(loc=0, scale=0.25, size=(nwalks,nsteps))
steps
out : array([[ 0.34363475, -0.05522411, 0.05083405, ..., -0.33842559,
-0.0230145 , 0.23113296],
[ 0.16099452, -0.50311406, 0.18528403, ..., 0.2945566 ,
-0.12584062, -0.19622136],
[ 0.26578933, -0.09885966, 0.23270679, ..., 0.16738906,
0.060458 , 0.28324365],
...,
[ 0.24418693, 0.58143407, 0.01644158, ..., 0.11702547,
-0.02086471, 0.00513563],
[-0.15765375, -0.19420976, 0.0542288 , ..., 0.15976314,
0.22189473, 0.07530368],
[ 0.37258899, -0.0645595 , -0.42768902, ..., 0.28853102,
-0.25261274, -0.32302591]])
'머신러닝 > numpy' 카테고리의 다른 글
NumPy의 기초(4) (0) | 2021.10.06 |
---|---|
NumPy의 기초(3) (0) | 2021.09.09 |
NumPy의 기초(2) (0) | 2021.09.09 |
NumPy의 기초(1) (0) | 2021.09.09 |