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}$ 이다.