In [1]:
import numpy as np
In [2]:
a= np.array([1, 2, 3, 4])
In [3]:
a + 1
Out[3]:
덧셈은 교환법칙이 성립한다. 즉, 더하는 순서를 바꿔도 결과는 마찬가지이다.
In [4]:
1 + a
Out[4]:
어레이아 어레이의 덧셈
어레이 a에 동일한 모양의 어레이 b를 더해보자.
In [5]:
b = np.ones(4) + 1
b
Out[5]:
In [6]:
a + b
Out[6]:
In [7]:
a * 3
Out[7]:
어레이아 어레이의 곱셈
In [8]:
a * b
Out[8]:
In [9]:
a = np.ones((3,3))
a
Out[9]:
In [10]:
b = a + a
b
Out[10]:
In [11]:
a * b
Out[11]:
In [12]:
a.dot(b) # 행렬의 곱으로 연산한다.
Out[12]:
In [13]:
a/3.
Out[13]:
In [14]:
5./a
Out[14]:
In [15]:
a / b
Out[15]:
In [16]:
a ** 3
Out[16]:
In [17]:
2**a
Out[17]:
In [18]:
a ** b
Out[18]:
이제 어레이를 이용하여 방정식을 계산할 수 있다.
In [19]:
2 ** a - (b * b - 4)
Out[19]:
In [21]:
a = np.arange(4)
a + np.arange(1, 3)
In [22]:
a = np.arange(10000)
In [23]:
%timeit a + 1
In [24]:
b = range(10000)
In [25]:
%timeit [i + 1 for i in b]
In [26]:
a = np.arange(1, 5)
a
Out[26]:
In [27]:
b = np.arange(4, 0, -1)
b
Out[27]:
In [28]:
a == b
Out[28]:
In [29]:
a > b
Out[29]:
항목별 비교가 아닌 어레이 자체의 동일성 여부를 확인하려면 np.array_equal 함수를 이용해야 한다.
In [30]:
np.array_equal(a, b)
Out[30]:
In [31]:
True and False
Out[31]:
In [32]:
True or False
Out[32]:
이제 어레이를 대상으로 하여 논리연산을 실행해 보자.
True와 False 만으로 구성된 어레이를 이용한다.
아래 정의에서 0과 1을 이용하였지만 dtype을 이용하여 불값으로 변경되었음에 주의하라.
In [33]:
a = np.array([1, 1, 0, 0], dtype=bool)
b = np.array([1, 0, 1, 1], dtype=bool)
a와 b의 각 항목별로 논리연산을 다음과 같이 실행한다.
np.logical_or는 각 항목별로 or 함수를 실행한다.np.logical_and는 각 항목별로 and 함수를 실행한다.
In [34]:
np.logical_or(a, b)
Out[34]:
In [35]:
np.logical_and(a, b)
Out[35]:
In [36]:
a = np.arange(5)
a
Out[36]:
In [37]:
np.sin(a)
Out[37]:
In [38]:
np.log(a)
Out[38]:
In [39]:
np.exp(a)
Out[39]:
In [40]:
def f(x):
return x ** x
f(a)
Out[40]:
In [41]:
a = np.triu(np.ones((3, 3)), 1)
a
Out[41]:
전치행렬을 T 메소드를 활용하여 쉽게 구할 수 있다.
In [42]:
a.T
Out[42]:
전치행렬은 내부적으로 '뷰(view)' 방식을 이용하여 처리된다. 따라서 전치행렬의 값을 변경하면 원래 행력의 값도 변경됨에 주의해야 한다.
In [43]:
a.T[0, 1] = 5
a.T
Out[43]:
In [44]:
a
Out[44]:
In [45]:
def mat_add(a, b):
for i in range(a.shape[0]):
for j in range(a.shape[1]):
a[i,j] += b[i,j]
return a
In [46]:
c = np.arange(9).reshape((3,3))
c
Out[46]:
어레이 c는 다음 행렬에 대응한다.
따라서 c.T는 다음 행렬에 대응한다.
이제 mat_add(c, c.T)는 아래의 대칭행렬이 될 것으로 기대한다.
하지만 결과가 다르게 나온다.
In [47]:
mat_add(c, c.T)
Out[47]:
이유가 무엇일까? 먼저 0번 행의 경우는 예상과 일치한다.
그런데 1번 행부터 값이 달라졌다.
이는, 예를 들어 (i, j) = (1, 0) 의 경우
c.T[1, 0]의 값이 c[0, 1]인데 0번 행을 계산하면서 값이 1에서
이미 4로 변경되었기 때문이다.
따라서
c[1,0] + c.T[1,0] = c[1,0] + c[0,1] = 3 + 4 = 7
이 되는 것이다.
In [48]:
x = np.arange(1, 5)
x
Out[48]:
In [49]:
np.sum(x)
Out[49]:
In [50]:
x.sum()
Out[50]:
In [51]:
x = np.arange(6).reshape((2, 3))
x
Out[51]:
In [52]:
x.sum()
Out[52]:
축(axis)를 이용하여 행별, 열별 합을 구할 수 있다.
axis=0을 이용한다.
In [53]:
x.sum(axis=0)
Out[53]:
위의 결과는 아래의 세 과정을 거쳐서 생성된다.
먼저, 0번 열의 합은 다음과 같이 구해진다.
In [54]:
x[:, 0].sum()
Out[54]:
다음으로, 1번 열의 합은 다음과 같이 구해진다.
In [55]:
x[:, 1].sum()
Out[55]:
다음으로, 2번 열의 합은 다음과 같이 구해진다.
In [56]:
x[:, 2].sum()
Out[56]:
즉, axis=0을 사용할 경우 1차원의 경우에는 전체(':')를 사용하고,
나머지 차원에 대해서는 모든 가능성, 즉, 각 열에 대해 따로따로 계산을 하는 것과
동일하다. 위 예제의 경우는 0번 열, 1번 열, 2번 열의 3개의 가능성이 있다.
axis=1을 이용한다.
In [57]:
x.sum(axis=1)
Out[57]:
앞서의 경우와 마찬가지로 아래와 같이 axis=1을 설명할 수 있다.
In [58]:
x[0, :].sum()
Out[58]:
In [59]:
x[1, :].sum()
Out[59]:
In [60]:
x = np.arange(30).reshape((2,3,5))
x
Out[60]:
축을 이용하지 않는 합은 간단하다. 전체 항목의 합을 구한다.
In [61]:
x.sum()
Out[61]:
이제 axis=0, axis=1, axis=2의 역할을 이해하기 위해
axis=1을 이해하는 방법을 설명한다.
In [62]:
y = x.sum(axis=1)
y
Out[62]:
In [63]:
y.shape
Out[63]:
y는 2x5행렬에 대응한다. 왜 그럴까?
앞서 2차원 어레이에서 axis=1을 이해한 방식과 동일하게 이해하면 된다.
axis=1이기 때문에 2차원 값에 해당하는 부분은 전체를 대항으로 슬라이싱 하고
나머지는 각각의 경우를 모두 따져야 한다. 2x5 의 경우가 발생한다.(2,5) 모양을 갖는 어레이가 된다.2x5 에 해당하는 경우의 몇 가지 예는 아래와 같다.
In [64]:
x[0, :, 0].sum()
Out[64]:
In [65]:
x[0, :, 1].sum()
Out[65]:
In [66]:
x[0, :, 2].sum()
Out[66]:
등등.
통계 분석을 위해 어레이를 사용하는 예제이다.
먼저 아래 사이트에서 populations.txt 파일을 다운 받는다. 파일을 다운 받으려면 링크에 마우스를 가져간 다음 마우스 오른쪽 버튼을 눌러 링크에 연결된 파일을 다운받으면 된다.
https://github.com/scipy-lectures/scipy-lecture-notes/blob/master/data/populations.txt
해당 파일은 1900년부터 1920년까지 캐나다 북부지역에서 서식한 산토끼(hare)와 스라소니(lynx)의 숫자, 그리고 채소인 당근(carrot)의 재배숫자를 순수 텍스트 데이터로 담고 있다.
파일의 내용을 확인하려면 리눅스 터미널 명령어인 cat를 이용한다.
In [67]:
!cat data/populations.txt
앞서 설명한 대로 각 연도별로 산토끼, 스라소니, 당근의 개체수를 담고 있다. 첫째 행처럼 '#'로 시작하면 파이썬에 의해 주석처리 된다. 'cat' 명령어는 파이썬 명령어가 아니라서 주석처리하지 않는 것 뿐이다.
이제 해당 파일을 파이썬으로 불러 들이자.
파이썬의 open 함수 대신에 넘파이의 loadtxt 함수를 이용한다.
In [68]:
data = np.loadtxt('data/populations.txt')
data를 확인해보면 첫째 행을 제외한 행과 열이 2차원 어레이로 저장되었음을 확인할 수 있다.
In [69]:
data
Out[69]:
위 데이터를 이용하여 1900년부터 1920년 사이에 각 개체별로 어떤 변화가 발생하였는지를 분석하고자 한다. 그러기 위해서는 각 개체별로 따로 어레이로 저장하는 것이 필요하다. 즉, 전치(transposition)을 이용해야 한다.
data.T는 4개의 행으로 이루어진 2차원 어레이를 나타내며,
각각의 행은 차례대로 년도, 산토끼 수, 스라소니 수, 당근 수를 연도별로 담고 있다.
넘파이는 각각의 행에 대해 변수를 선언할 수 있는 기능을 제공한다. (이는 튜플의 경우 각각의 항목에 변수를 선언할 수 있는 것과 동일하다.)
In [70]:
year, hares, lynxes, carrots = data.T
예를 들어 year 변수에는 연도들로 이루어진 1차원 어레이가 할당된다.
In [71]:
year
Out[71]:
hares 변수에는 각 연도별 개체수로 이루어진 1차원 어레이가 할당된다.
In [72]:
hares
Out[72]:
lynxes, carrots 경우도 동일하게 작동한다.
이제 위 데이터를 그래프를 이용하여 확인해보자.
그러기 위해 먼저 pyplot 모듈을 임포트한다.
In [73]:
from matplotlib import pyplot as plt
In [74]:
%pylab inline
아래 그림은 연도별 산토끼의 개체수, 스라소니의 개체수, 당근의 재배수를 각각 파란색, 녹색, 빨간색 그래프를 이용하여 동시에 표현한 것이다.
axes, plot, legned 함수의 활용방법에 대해서는 우선 너무 많은
신경을 쓰지 않아도 된다. 여기서는 기본 사용법만 알면 된다.
In [75]:
plt.axes([0.2, 0.1, 0.5, 0.8])
plt.plot(year, hares, year, lynxes, year, carrots)
plt.legend(('Hare', 'Lynx', 'Carrot'), loc=(1.05, 0.5))
Out[75]:
위 그림을 보고 어떤 분석을 할 수 있는가?
이제, 예를 들어, 1900년부터 1920년 사이에서 산토끼의 개체수와 스라소니의 개체수 각각의 평균을 구해보자.
산토끼 개체수의 평균만을 확인하고자 한다면 다음과 같이 할 수 있다.
In [76]:
hares.mean()
Out[76]:
그리고 스라소니 개체수의 평균만을 확인하고자 한다면 다음과 같이 할 수 있다.
In [77]:
lynxes.mean()
Out[77]:
하지만 두 동물의 개체수를 한꺼번에 구할 수 있다.
앞서 sum을 다루면서 배운 axis 기능을 이용하면 된다.
In [78]:
data.T[1:3].mean(axis=1)
Out[78]:
아래와 같이 구할 수도 있다.
In [79]:
data.mean(axis=0)[1:3]
Out[79]:
직선상에서 원점에서 출발하여 임의로 오른쪽, 왼쪽으로 한 칸씩 스텝을 움직이는 게임을
1000명을 대상으로 진행한다.
게임은 1000명 모두가 각각 200 스텝을 옮길 때까지 진행되며, 각 참가자는 서로
독립적으로 임의적으로 오른쪽, 왼쪽 스텝을 정한다.
질문: 1000명의 참가자가 t 번의 스텝을 밟았을 때 원점으로부터의 거리가 평균 얼마인가? 그 평균값을 미리 예상할 수 있는가?
위 질문에 답하기 위해 (1000, 200) 모양의 2차원 어레이를 이용한다. 해당 어레이는 1, -1이 무작위로 채워져야 하며, 예를 들어 아래와 같은 모양을 갖는다.
더불어, 참가자들의 스텝이 위와 같을 때 t 스텝 이후의 각 참가자들의 원점으로부터의 거리는
아래 그림에서처럼 행별로 t 스텝까지의 합을 계산하는 것으로부터 구할 수 있다.
이제 1과 -1로 무작위적으로 채워진 (1000, 200) 모양의 2차원 어레이를 생성해보자.
먼저, 참가자들의 수를 n_stories, 최대 스텝의 수를 t_max라 하자.
In [80]:
n_stories = 1000
t_max = 200
t 는 스텝을 밟는 단위를 담고 있는 어레이이다.
In [81]:
t = np.arange(t_max)
1과 -1을 무작위적으로 채운 어레이를 구하려면 다음과 정의하면 된다.
In [82]:
steps = 2 * np.random.random_integers(0, 1, (n_stories, t_max)) - 1
실제로 steps 살펴보면 1과 -1이 임의로 채워져 있다.
In [83]:
steps
Out[83]:
위 어레이가 1과 -1만으로 채워졌다는 사실은 np.unique 함수로 확인할 수 있다.
In [84]:
np.unique(steps)
Out[84]:
t 스텝 후의 각 참가자들의 원점으로부터의 거리를 계산하기 위해서는 누적 합(cumulative sum)을 계산하는 cumsum 함수를 이용한다.
행별 누적 합을 계산하기 위해 축(axis) 키워드를 활용한다.
In [85]:
positions = np.cumsum(steps, axis=1)
positions
Out[85]:
각 참가자들의 t 스텝 후의 누적 합의 평균을 구하기 위해
각각의 제곱의 평균의 제곱근 값을 이용한다.
In [86]:
sq_distance = positions ** 2
sq_distance
Out[86]:
예를 들어 200스텝이 완료 된 후에 참가자들의 원점으로부터의 거리의 평균은 다음과 같다.
In [87]:
mean_sq_distance = np.mean(sq_distance, axis=0)
np.sqrt(mean_sq_distance[-1])
Out[87]:
t 스텝 후의 참자자들의 원점으로부터의 거리를 그래프로 그리면 다음과 같다.
In [88]:
plt.figure(figsize=(4, 3))
plt.plot(t, np.sqrt(mean_sq_distance), 'y.')
plt.xlabel(r"$t$")
plt.ylabel(r"$\sqrt{\langle (\delta x)^2 \rangle}$")
Out[88]:
그런데 위 그래프는 $f(t) = \sqrt(t)$ 함수의 그래프와 매우 비슷하다.
In [89]:
plt.figure(figsize=(4, 3))
plt.plot(t, np.sqrt(t), 'r-')
plt.xlabel(r"$t$")
plt.ylabel(r"$\sqrt{t}$")
Out[89]:
실제로 두 그래프를 합치면 거의 구분되지 않는다.
In [90]:
plt.figure(figsize=(4, 3))
plt.plot(t, np.sqrt(mean_sq_distance), 'y.', t, np.sqrt(t), 'r-')
plt.xlabel(r"$t$")
plt.ylabel(r"$\sqrt{\langle (\delta x)^2 \rangle}$")
Out[90]:
결론
참가자들이 임의로 좌, 우를 선택할 때 $t$ 스텝 후에 원점으로부터의 거리는 평균적으로 $\sqrt{t}$ 이다.