[파이썬 프로그래밍 23] 넘파이(numpy)기초 1

Home / 파이썬 프로그래밍 / [파이썬 프로그래밍 23] 넘파이(numpy)기초 1

넘파이(numpy)사용법 1

대용량 숫자 데이터를 처리하는 경우에는 넘파이(numoy)라는 모듈을 사용해야할 경우가 많습니다. numpy는 목록(list), 2차원 목록과 밀접하게 관련되어 있습니다. 하지만 처리하는 속도가 훨씬 빠르고 더 간결하게 프로그래밍을 할 수 있습니다. 본격적으로 넘파이를 다루기 전에 목록(list)를 짧게 복습해 보겠습니다.

목록(list)복습

목록은 [1,2,3,4,5]와 같이 숫자의 목록도 있고, [‘apple’,’banana’,’orange’]와 같이 텍스트(또는 string:문자열)의 목록도 있고,다른 종류의 것들이 섞여있는 목록도 있습니다. 한편 목록한에 목록이 들어있는 경우도 있습니다.
[[‘a’,’b’,’c’], [1,2,3], [4,5,6], [7,8,9]]와 같이 목록의 목록도 있습니다. 이를 표의 형식으로 나타내면,
[[‘a’,’b’,’c’],
[1,2,3],
[4,5,6],
[7,8,9]]
처럼도 나타낼 수 있습니다.

위의 2차원 목록을 a라고 할때, 두번쨰줄의 세번째값은 a[1][2]로 알아낼 수 있습니다. 파이썬 목록에서 첫번째는 0으로 시작됨을 알아둘 필요가 있습니다. (반면, FORTRAN이나 R과 같은 언어는 첫번째를 나타내는 숫자가 1입니다)

그중에서 숫자의 목록은 여러모로 중요하고 많이 쓰는 목록입니다. 머신러닝같이 데이터를 분석하고 결과를 적용할때는 주로 숫자로 데이터를 다루기 때문에 숫자의 목록이 중요합니다. 자주 쓰는 숫자의 목록의 예를 들자면, 1부터 10까지 10개의 숫자가 들어있는 목록을 들 수 있겠습니다. 복습겸, 이를 파이썬 코드를 만들면 다음과 같습니다.

a = []
for n in range(1,11):
    a.append(n)
print(a)

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

또는 대괄호안에 for를 쓰는 방법으로 한 줄만에 만들 수도 있습니다.

a = []
for n in range(1,11):
    a.append(n)
print(a)

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

때로는 좀 더 촘촘한 숫자의 목록이 필요하기도 합니다. 특히 그래프를 그릴때 그런 경우가 많습니다. 예를 들어 [0.0, 0.01, 0.02, …. , 9.99, 9.99, 10.0] 과 같은 목록을 변수 x에 저장하려면 다음과 같은 코드를 만드는 것도 비슷한 방법으로 만들 수 있습니다.

x = [0.01*n for n in range(1001)]

x의 목록안에 저장된 숫자로 아래의 수식으로 나타내는 목록을 만들어 y에 저장하려고 합니다.

$$ y = (x-5)^2+1 = x^2-10x+26$$

이를 파이썬 코드로 만들면 다음과 갔습니다.

y = [ (x[n]-5.0)**2+1.0 for n in range(1001)]

변수 x와 y로 그래프를 그려보겠습니다. matplotlib의 pyplot을 이용합니다. 그럴러면 matplotlib.pyplot이라는 모듈을 import를 써서 불러와야합니다. jupyter notebook에서 그래프를 보려면 %matplotlib inline이라는 줄을 추가로 써야합니다. 그래프를 크게하기위헤 plt.rcParams[‘figure.dpi’] = 150도 코드에 추가 했습니다.

import matplotlib.pyplot as plt
%matplotlib inline
plt.rcParams['figure.dpi'] = 150

plt.plot(x,y)

math라는 모듈을 불러와서 수학함수를 사용할 수도 있습니다. sin(x)를 계산해서 y2라는 목록에 저장한 다음에 그래프로 그려 보려면 다음과 같이 코딩을 하면 됩니다.

import math

y2 = [math.sin(x[n]) for n in range(1001)]
plt.plot(x, y2)

이렇게 만든 x목록과 y목록의 각 값을 쌍으로 묶어 [[x0,y0],[x1,y1],⋯,[x1000,y1000]][[x0,y0],[x1,y1],⋯,[x1000,y1000]] 와 같은 2차원 목록(목록의 목록)을 만들어 xy라는 변수에 저장하려면 다음과 같은 코드를 씁니다.

xy = [[x[n],y[n]] for n in range(1001)]

넘파이(numpy) 사용하기
지금까지 살펴본 목록과 관련된 작업을 좀 더 간단하게 그리고 빠르게 할 수 있는 방법을 제공하는 모듈이 numpy(넘파이)입니다. numpy를 쓰려면 먼자 numpy 모듈을 불러와야 합니다. 이때 numpy라는 이름을 짧게 np로 쓸 수 있도록 보통은 아래와 같은 코드를 씁니다.

import numpy as np

파이썬에서 유사한 작업을 반복할떄 for나 while을 이용하는데 적은 횟수를 반복할때는 별 문제가 없으나 많은 횟수를 반복할때는 엄청나게 느려진다는 문제점이 있습니다.가장 빠르게 돌아가는 코드를 만들때 C언어를 쓰는 경우가 많습니다. 파이썬에서 for나 while을 쓸때 경우에 따라서는 C언어로 만든 코드에 비해 수십배 느려지는 경우도 있습니다.

예를 들어 C언어로 프로그래밍을 하면 1분만에 끝날 수 있는 계산을, 파이썬으로 프로그래밍을 1시간정도 걸릴 수도 있습니다. numpy는 이런 계산 속도문제를 어느정도 해결해줍니다. 반복되는 계산작업을 내부에서 C언어로 만든 코드로 돌리기 때문입니다.

numpy는 여러 차원의 목록을 쉽게 다룰 수 있습니다. 이미 2차원 목록(목록의 목록)까지 다뤄봤기때문에 numpy 사용법은 의외로 쉽게 배울 수 있습니다. 넘파이에서는 목록 대신 배열(array)이라는 명칭을 주로 사용하기 때문에 넘파이를 사용한 경우는 배열이라고 부르겠습니다. 이제 본격적으로 numpy를 사용하는 방법을 알아보겠습니다.

먼저 0 채워진 숫자 10개로 만든 1차원 배열은 만들어서 프린트하려면 다음과 같은 코드를 쓰면됩니다.

a = np.zeros(10)
print(a)

[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]

목록을 만드는 코드와 비교해 보겠습니다.

b = [0.0 for n in range(10)]
print(b)

[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]

넘파이로 만든 배열에는 숫자사이에 쉼표가 없음을 알 수 있습니다. 배열과 목록은 다른 종류라는 것을 의미합니다. 하지만 많은 경우에서는 배열을 쓸 수 있으면 목록을 쓸 수 있고, 반대로 목록을 쓸 수 있으면 배열을 쓸 수 있는 경우가 꽤 됩니다. 그래도 다른 종류이기 때문에 넘파이로 만든 배열을 목록(list)로 만들려면 list라는 함수를 쓰면됩니다.

a2 =list(a)
print(a2)

[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]

반대로 목록(list)를 넘파이 배열(array)로 만들려면 np.array함수를 쓰면됩니다.

b2 = np.array(b)
print(b2)

[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]

1로 채워진 배열과 목록을 만들고 이를 목록과 배열로 변환해보겠습니다.

c = np.ones(10)
print(c)
c2 = list(c)
print(c2)
d = [ 1.0 for n in range(10)]
print(d)
d2 = np.array(d)
print(d2)

[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]
[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]

1로 채워진 2차원 배열을 만들어 보겠습니다. 10개의 1로 채워진 1차원 배열 5 개를 배열로 몪는 2차원 배열입니다. 이걸 쉽게 이해하려면 먼저 2차원 목록을 만들어 보는 것이 좀 더 쉽게 이해하는데 도움이 됩니다.

e = [[1.0 for n in range(10)] for m in range(5)]
print(e)

[[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]]

이 2차원 목록에서 5번째 목록의 10번째 숫자는 e[4][9]으로 표시해야합니다. 안쪽 목록안의 숫자 위치가 더 뒤에 붙고, 바깥쪽 목록안의 목록위치는 더 앞쪽에 붙습니다.

넘파이로 만드는 배열도 마찬가지입니다. 10개의 1이 들어있는 1차원배열 5개를 묶는 2차원배열을 만들려면, 5가 먼저 오고 10워 뒤에 오는 튜플(tuple)을 만들어 np.ones함수에 넣어야 합니다.

f = np.ones((5,10))
print(f)

[[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]]

튜플대신 목록으로 만들어서 np.ones안에 넣어줘도 됩니다.

f = np.ones([5,10])
print(f)

[[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]]

2차원 목록을 2차원 배열로 변환할때는 np.array를 쓰면 됩니다.

e2 = np.array(e)
print(e2)

[[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]]

2차원 배열을 2차원 목록으로 변환할때는 list함수를 써야하는데 이때는 약간의 문제가 있습니다. 일단 코드를 돌려보고 설명하겠습니다.

f2 = list(f)
print(f2)

[array([1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]), array([1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]), array([1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]), array([1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]), array([1., 1., 1., 1., 1., 1., 1., 1., 1., 1.])]

1차원 배열의 목록으로 만들었기때문에 목록안에 array란 표시가 계속 나옵니다. 반은 목록이고 반은 배열인 셈입니다.

이를 해결하려면 넘파이가 만든 배열에서만 쓸 수 있는 메쏘드(일종의 함수)인 tolist를 씁니다. 그런데 이건 사용방법이 달라서, 배열뒤에 바로 점(.)과 함께 붙여서 써야합니다. 사용 방법은 이렇습니다.

f2 = f.tolist()
print(f2)

[[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]]

f에 저장된 배열이 객체이고 이 객체에서 쓸 수 있는 메쏘드(method)중의 하나가 tolist입니다. 함수하고 거의 같은 방법으로 쓸 수 있지만 객체가 만들어진 다음에만 객체를 통해서만 쓸 수 있다는 점이 다릅니다.

배열을 목록으로 만들려면 list라는 함수를 쓰는 것보다는 배열의 메소드인 tolist를 쓰는 것이 더 적절한 방법이 됨을 알 수 있습니다.

0이나 1외의 다른 숫자로 배열(array)을 채우려면 full이라는 함수를 사용합니다.

g = np.full([5,20], 10)
print(g)

[[10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10]
[10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10]
[10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10]
[10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10]
[10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10]]

[0.0, 0.01, 0.02, …. , 9.99, 9.99, 10.0] 과 같은 배열을 만들려면 linspace함수를 사용합니다.

j = np.linspace(0.0, 10.0, 1001)
print(j)

[ 0. 0.01 0.02 ... 9.98 9.99 10. ]

이렇게 만든 배열안의 각각의 값에 sin함수를 적용하려면 다음과 같이 하면 됩니다.

k = np.sin(j)
print(k)

[ 0. 0.00999983 0.01999867 ... -0.527132 -0.53560333
-0.54402111]

sin을 적용한 값들로 바로 배열이 만들어집니다. matplotlib.pyplot의 plot으로 바로 그래프를 그릴 수 있습니다.

import matplotlib.pyplot as plt
%matplotlib inline
plt.rcParams['figure.dpi'] = 150

plt.plot(j,k)

time 모듈의 clock이라는 함수를 쓰면 어떤 작업을 할때 시간이 얼마나 흘렀는지를 잴 수 있습니다. 작업전에 clock함수로 시간을 재서 저장하고 작업을 한 후에도 시간을 재서 그 차이를 계산하면 흘러간 시간을 잴 수 있습니다. 천만개 가량의 값을 가지는 목록과 배열을 만들고 이를 가지고 계산하면 시간이 얼마 걸리나 계산해 보겠습니다.

import time
import math

t0 = time.clock()
x = [0.000001*n for n in range(10000001)]
t1 = time.clock()
print(t1-t0)
y = [2.0*x[n] for n in range(10000001)]
t2 = time.clock()
print(t2-t1)
z = [math.sin(x[n]) for n in range(10000001)]
t3 = time.clock()
print(t3-t2)

1.696742999999998
2.165867999999989
4.765906000000001

import numpy as np

t0 = time.clock()
x = np.linspace(0.0, 10.0, 10000001)
t1 = time.clock()
print(t1-t0)
y = 2.0*x
t2 = time.clock()
print(t2-t1)
z = np.sin(x)
t3 = time.clock()
print(t3-t2)

0.3868310000000008
0.28257499999999425
0.8583580000000097

넘파이를 썼을때 적어도 5배에서 10배까지 계산속도가 빨라졌음을 확인알 수 있습니다.