Numpy 배열 지향 프로그래밍

전창우·2023년 6월 24일
0

python for data analysis

목록 보기
5/23

Numpy 배열(ndarray)을 사용하면 반복문을 작성하지 않고, 간결한 배열 연산을 사용해 다양한 데이터 처리 작업이 가능하다는 것을 배웠다.
이와 같이 배열 연산에서 반복문을 명시적으로 제거하는 기법벡터화라고 부른다.

numpy func 및 ndarray method

where 함수를 사용하여 조건절 연산 표현

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에 대한 학습을 시작해보자!

1개의 댓글

comment-user-thumbnail
2023년 6월 27일

파이썬만의 강점이군요. 잘 읽고 갑니다 💡

답글 달기