Numpy 배열(ndarray)을 사용하면 반복문을 작성하지 않고, 간결한 배열 연산을 사용해 다양한 데이터 처리 작업이 가능하다는 것을 배웠다.
이와 같이 배열 연산에서 반복문을 명시적으로 제거하는 기법을 벡터화라고 부른다.
numpy의 where 함수는 x if 조건 else y 같은 삼항식의 벡터화된 버전이다.
다음과 같은 불리언 배열 하나와 스칼라 값이 들어있는 두 개의 배열이 있다.
xarr=np.array(1.1,1.2,1.3,1.4,1.5)
yarr=np.array(2.1,2.2,2.3,2.4,2.5)
cond=np.array(True,False,True,True,False)
cond 배열의 값이 true일 때는 xarr값을 취하고 아니면 yarr의 값을 취하고 싶다면 리스트 컴프리헨션을 이용해 다음처럼 작성할 수 있다.
result=[(x if c else y) for x,y,c in zip(xarr,yarr,cond)]
result
#out:[1.1, 2.2, 1.3, 1.4, 2.5]
이 코드는 순수 파이썬으로 수행되기 때문에 큰 배열을 빠르게 처리하지 못한다. 또한 다차원 배열에서는 사용할 수 없는 문제가 있다. np.where 함수을 사용하면 이를 아주 간결하게 작성할 수 있다.
result=np.where(cond,xarr,yarr)
result
#out:array[1.1, 2.2, 1.3, 1.4, 2.5]
데이터 분석에서 일반적인 where의 사용은 다른 배열에 기반한 새로운 배열을 생성한다. 예로, 임의로 생성된 데이터가 들어 있는 행렬이 있고 양수는 모두 2로, 음수는 모두 -2로 쉽게 바꿀 수 있다.
arr=np.random.randn(4,4)
arr
#out:array([[-1.39741853, -1.91114205, -0.85085232, -1.60730242],
# [ 0.23930779, 0.34628558, -1.40719031, 0.88285417],
# [ 0.00597419, -1.89260066, 0.77111677, -0.35423173],
# [ 0.2200386 , -0.01310659, -0.16438436, 1.81450233]])
arr>0
#out:array([[False, False, False, False],
# [ True, True, False, True],
# [ True, False, True, False],
# [ True, False, False, True]])
np.where(arr>0,2,-2)
#out:array([[-2, -2, -2, -2],
# [ 2, 2, -2, 2],
# [ 2, -2, 2, -2],
# [ 2, -2, -2, 2]])
배열 전체 혹은 배열에서 한 축을 따르는 자료에 대한 통계를 계산(평균, 중앙값)등 하는 수학 함수는 ndarray에 포함된 메서드나 numpy의 함수로 사용할 수 있다.
임의의 정규 분포 데이터를 생성하고 집계해보자.
arr=np.random.randn(5,4)
arr
#out:array([[ 0.74518331, -0.20450409, 1.05944236, 0.81362944],
# [ 1.40170423, -0.34929491, -1.34879215, 1.87738087],
# [ 0.7375819 , -1.1300144 , 0.59544129, -2.70714279],
# [-1.04883382, 1.3705129 , -1.08954062, 1.08250636],
# [-0.06125845, 0.2868562 , -0.55248946, 1.22842506]])
arr.mean() #평균
#out:0.1353396609908732
arr.sum() #합
#out:2.706793219817464
mean이나 sum 같은 함수는 선택적으로 axis 인자를 받아서, 해당 axis에 대한 통계를 계산할 수 있다.
axis 0과 1은 각각 로우와 칼럼을 의미한다.
arr.mean(axis=1)
#out:array([ 0.60343776, 0.39524951, -0.6260335 , 0.0786612 , 0.22538334])
arr.sum(axis=0)
#out:array([ 1.77437717, -0.02644431, -1.33593858, 2.29479894])
cumsum(각 원소의 누적합)과 cumprod(각 원소의 누적곱) 메서드는 중간 계산값을 담고있는 배열을 반환한다. 해당 메소드를 보면 axis 값이 어떤 역할을 하는지 이해할 수 있을 것이다.
arr=np.array([[0,1,2],[3,4,5],[6,7,8]])
arr.cumsum(axis=0)
#out:array([[ 0, 1, 2],[ 3, 5, 7],[ 9, 12, 15]])
arr.cumsum(axis=1)
#out:array([[ 0, 1, 3],[ 3, 7, 12],[ 6, 13, 21]])
arr.cumprod(axis=1)
#out:array([[ 0, 0, 0],[ 3, 12, 60,],[ 6, 42, 336]])
이전 메서드의 불리언 값을 1(True) 또는 0(False)으로 강제할 수 있다. 따라서 sum 메서드를 실행하면 불리언 배열에서 True인 원소의 개수를 셀 수 있다.
arr=np.random.randn(100)
(arr>0).sum()
#out:59
any와 all 메서드는 불리언 배열에 특히 유용하다. any 메서드는 하나 이상의 값이 True인 지 검사하고, all 메서드는 모든 원소가 True인 지 검사한다.
bools=np.array([False,False,False,False,True])
bools.any()
#out:True
bools.all()
#out:False
파이썬의 내장 리스트형처럼 Numpy 배열 역시 sort 메서드를 이용해서 정렬할 수 있다.
arr=np.random.randn(6)
arr
#out:array([ 0.54935939, 1.42511203, -0.41238308, -1.52645615, 0.63195534,0.66198492])
arr.sort()
arr
#out:array([-1.52645615, -0.41238308, 0.54935939, 0.63195534, 0.66198492,1.42511203])
다차원 배열의 정렬은 sort 메서드에 넘긴 축의 값에 따라 1차원 부분을 정렬한다.
arr=np.random.randn(5,3)
arr
#out:array([[-0.11364361, 0.42016397, -0.14391891],
# [ 1.32399269, 1.01233206, -0.79045693],
# [ 0.73251752, 1.41409707, 0.17513282],
# [ 0.88788272, 0.74891664, -0.81092572],
# [-0.87910857, 0.24997558, -1.21822771]])
arr.sort(1)
#array([[ 0.26606675, 0.98965997, 2.48435754],
# [-0.44128734, -0.15115387, -0.02947167],
# [-1.04362265, -0.90307533, 2.43275463],
# [-1.21798766, -0.67430899, 0.38918481],
# [-0.44374623, -0.06460356, 0.32293438]])
arr.sort(0)
#array([[-1.21798766, -0.90307533, -0.02947167],
# [-1.04362265, -0.67430899, 0.32293438],
# [-0.44374623, -0.15115387, 0.38918481],
# [-0.44128734, -0.06460356, 2.43275463],
# [ 0.26606675, 0.98965997, 2.48435754]])
numpy는 1차원 ndarray를 위한 몇 가지 기본적인 집합 연산을 제공한다. 그중 중복된 원소를 제거하고, 남은 원소를 정렬된 형태로 반환하는 np.unique가 있다.
names=np.array(['Bob','Joe','Will','Bob','Will','Joe','Joe'])
np.unique(names)
#out:array(['Bob', 'Joe', 'Will'], dtype='<U4')
np.intersect1d(x,y)는 배열 x와 y에 공통적으로 존재하는 원소를 정렬하여 반환하고,
np.union1d(x,y)는 배열 x와 y의 합집합을 반환한다.
x=np.array([1,2,3,4,5])
y=np.array([3,4,5,6,7])
np.intersect1d(x,y)
#out:array([3, 4, 5])
np.union1d(x,y)
#out:array([1, 2, 3, 4, 5, 6, 7])
또한 x와 y의 차집합을 반환하는 setdiff1d(x,y)도 있다.
np.setdiff1d(x,y)
#out:array([1, 2])
numpy는 디스크에서 텍스트나 바이너리 형식의 데이터를 불러오거나 저장할 수 있다. 여기서는 numpy의 내장 이진 형식만 살펴본다.
np.save와 np.load는 배열 데이터를 효과적으로 디스크에 저장하고 불러오기 위한 함수이다. 배열은 기본적으로 압축되지 않은 원시 바이너리 형식의.npy파일로 저장된다.
arr-np.arange(10)
np.save('some_array',arr)
np.load("some_array.npy")
#out:array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
저장되는 파일 경로가 .npy로 끝나지 않으면 자동적으로 확장자가 추가된다.
np.savez함수를 이용하여 여러 개의 배열을 압축된 형식으로 저장할 수 있는데, 저장하려는 배열을 키워드 인자 형태로 전달된다.
arr1=np.arange(10)
arr2=np.arange(10,20,1)
np.savez('array_archive.npz',a=arr1,b=arr2)
arch=np.load('array_archive.npz')
arch['b']
#out:array([10, 11, 12, 13, 14, 15, 16, 17, 18, 19])
numpy.random 모듈은 파이썬 내장 random 함수를 보강하여 다양한 종류의 확률분포로부터 효과적으로 표본값을 생성하는 데 주로 사용된다. 예를들어 normal을 사용하여 표준정규분포로부터 4x4 크기의 표본을 생성할 수 있다.
samples=np.random.normal(size=(4,4))
samples
#out:array([[ 1.42546724, 0.90779245, 0.72968029, 1.34391412],
# [ 0.70876463, -0.03724109, -0.69615482, -0.72063911],
# [ 2.30319705, 1.55769042, -0.50521155, -0.34818464],
# [ 0.08937644, 0.13339254, -0.51255827, -0.62657158]])
이에 비해 파이썬 내장 random 모듈은 한 번에 하나의 값만 생성할 수 있다. 심지어 Numpy.random은 매우 큰 표본을 생성하는데 파이썬 내장 모듈보다 수십배 빠르다.
난수를 생성할 때 주로 random.randn을 사용하지만, random.randint를 사용하면 주어진 최소/최대 범위 안에서 임의의 정수형의 난수를 생성할 수 있다.
(randnd은 표준편차가 1이고 평균값이 0인 정규분포에서 표본을 추출)
samples=np.random.randn(10)
samples
#out:array([ 0.50458802, 1.93833195, 0.40505077, -0.71364687, -2.1226559 ,
# -0.58639938, 0.5137612 , -0.76697239, 0.74231086, 0.40758345])
samples=np.random.randint(1,10,9)#인자는 순서대로 최소값, 최대값, 크기이다.
samples
#out:array([1, 6, 1, 6, 7, 7, 4, 3, 3])
예제 하나로 numpy의 배열 연산을 복습하는 시간을 가지겠다.
계단 중간에서 같은 확률로 한 계단 올라가거나 내려간다고 가정하자. 순수 파이썬으로 내장 random 모듈을 사용하여 계단 오르내리기를 1000번 수행하는 코드를 작성해보자.
import random
impot matplotlib.pyplot as plt
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)
walk는 계단을 오르거나(+1) 내려간(-1) 값의 누적합이라는 사실을 알 수 있으며 배열식으로 나타낼 수 있다.
그래서 np.random 모듈을 사용해서 1000번 수행한 결과(1,-1)을 한 번에 저장하고 누적합을 계산한다.
nsteps=1000
draw=np.random.randint(0,2,size=nsteps)
steps=np.where(draw>0,1,-1)
walk=steps.cumsum()
plt.plot(walk)
위 두 코드는 같은 출력값을 가지지만 numpy를 사용하면 훨씬 짧은시간, 그리고 간결한 코드로 수행할 수 있는 것을 알 수 있었다.
이번 로그를 끝으로 기본적인 numpy 학습을 끝마쳤다. 당연하지만, 해당 시리즈에서 Numpy를 다룬 것은 겉햝기에 불과하다. 하지만, 이 책에서 알아야할 numpy의 개념은 여기까지이고, 이후의 Numpy 학습은 따로 진행해야 한다.
다음 로그부턴 pandas에 대한 학습을 시작해보자!
파이썬만의 강점이군요. 잘 읽고 갑니다 💡