기존의 시퀀스 자료형처럼 인덱싱과 슬라이싱을 이용하여 어레이 항목들을 확인할 수 있다.
In [1]:
import numpy as np
In [2]:
a = np.arange(10)
a
Out[2]:
In [3]:
a[0], a[2], a[-1]
Out[3]:
In [4]:
a[::-1]
Out[4]:
In [5]:
a[2:9:3] # [start:end:step]
Out[5]:
In [6]:
a[:4]
Out[6]:
In [7]:
a[1:3]
Out[7]:
In [8]:
a[::2]
Out[8]:
In [9]:
a[3:]
Out[9]:
In [10]:
b = [[0, 1, 2], [3, 4, 5]]
len(b)
Out[10]:
a
에서 0
을 뽑아내고자 할 경우 인덱싱을 두 번 연속해서 사용해야 하는데,
이는 0
이 a
의 첫째 항목의 첫째 항목이기 때문이다. 즉,
In [11]:
b[0][0]
Out[11]:
5
의 경우 둘째 항목의 셋째 항목이다.
In [12]:
b[1][2]
Out[12]:
반면에 b
를 이차원 어레이로 다룰 경우에는 다음과 같이 하면 된다.
In [13]:
b = np.array([[0, 1, 2], [3, 4, 5]])
b
Out[13]:
In [14]:
b[0, 0]
Out[14]:
In [15]:
b[1,2]
Out[15]:
따라서, 다차원 어레이에서 사용되는 인덱싱은 인덱스 숫자들의 튜플을 사용한다. 사용된 튜플의 길이만큼 인덱싱의 대상이 되는 항목들의 차원이 증가한다.
보다 다양한 예제들을 이용하여 인덱싱을 연습해보자.
In [16]:
d = np.diag(np.arange(3))
d
Out[16]:
1
의 위치는 2행 2열이므로 인덱싱을 (1, 1)
을 이용한다.
In [17]:
d[1, 1]
Out[17]:
어레이의 특정 항목의 값을 변경할 수 있다. 즉, 어레이는 가변(mutable) 자료형이다. 예를 들어, 3행 2열 항목값인 0을 10으로 변경하고자 하면 다음과 같이 인덱싱을 이용하면 된다.
In [18]:
d[2, 1] = 10
d
Out[18]:
d[2]
는 무엇을 의미할까?
d
를 리스트라 생각하면 이해가 될 것이다.
즉, 3번째 항목인 [0, 10, 2]
를 가리킨다.
In [19]:
d[2]
Out[19]:
다차원 어레이의 슬라이싱은 좀 더 집중을 요구한다. 복잡하거나 어렵지는 않지만 항상 집중해야 한다.
먼저, 다음과 같은 모양의 2차원 어레이를 생성해보자. 단, 수동으로 항목들을 입력하면 안된다.
0 | 1 | 2 | 3 | 4 | 5 |
10 | 11 | 12 | 13 | 14 | 15 |
20 | 21 | 22 | 23 | 24 | 25 |
30 | 31 | 32 | 33 | 34 | 35 |
40 | 41 | 42 | 43 | 44 | 45 |
50 | 51 | 52 | 53 | 54 | 55 |
여러 가지 방법이 있을 수 있으나 shape을 변경하는 방법을 활용하는 것이 가장 기초적이다.
먼저, np.arange
함수를 이용하여 길이가 36인 어레이를 생성한 다음에
shape 값을 변경하여 2차원 어레이를 생성한다.
In [20]:
a = np.arange(36)
a.shape = (6,6)
a
Out[20]:
shape
변경하는 여러 가지 방법이 있다.
shape
속성을 위와 같이 변경하기.reshape
메소드 이용하기
In [21]:
a = np.arange(36).reshape((6,6))
a
Out[21]:
np.reshape
함수 사용하기
In [22]:
a = np.arange(36)
a = np.reshape(a, (6,6))
a
Out[22]:
이제 각 행별로 4의 배수를 더하면 원하는 어레이가 얻어지는 성질을 이용한다. 어레이와 숫자의 연산은 각 항목별로 실행된다는 점을 이용한다.
In [23]:
a = np.arange(36).reshape((6,6))
for i in range(len(a)):
a[i] = a[i] + 4*i
a
Out[23]:
In [24]:
a = np.arange(36).reshape((6,6))
for i in range(len(a)):
for j in range(6):
a[i, j] = a[i, j] + 4*i
a
Out[24]:
In [25]:
a = np.arange(6) + np.arange(0, 51, 10)[:, np.newaxis]
a
Out[25]:
이제 위 어레이를 이용하여 슬라이싱을 연습하도록 해보자. 먼저 아래 그림에 나와 있는대로 슬라이싱을 따라해보면서 슬라이싱이 어떻게 작동하는지 확인해보자. 코드의 색깔과 어레이 그림에서 표시된 동일한 색을 가진 상자들 사이의 관계를 주시하면 된다.
In [26]:
from IPython.display import Image
Image(filename='images/array-slicing.png', width=370)
Out[26]:
a[0, 3:5]
확인하기
먼저 a[0]
를 확인한다.
In [27]:
a[0]
Out[27]:
이제 a[0][3:5]
를 확인한다.
In [28]:
a[0][3:5]
Out[28]:
따라서 a[0, 3:5]
결과는 아래와 같다.
In [29]:
a[0, 3:5]
Out[29]:
a[4:, 4:]를 앞서의 경우처럼 확인할 수는 없다.
왜냐하면 a[4:]
가 길이가 2인 어레이이기에 a[4:][4:]
는 빈 어레이가 된다.
In [30]:
a[4:]
Out[30]:
In [31]:
a[4:][4:]
Out[31]:
따라서 1차원 값과 2차원 값을 함께 생각해야 한다.
'4:'
는 4번 인덱스 행부터 마지막 행까지 전체를 의미한다.'4:'
는 앞서 선택한 4번 인덱스 행부터 마지막 행 각각에서 4번 인덱스 열부터 전체를 나타낸다.
In [32]:
a[4:, 4:]
Out[32]:
위와 비슷한 방식으로 해석하면 아래 코드도 이해할 수 있다.
':'
은 모든 행을 대상으로 삼는 것이고'2'
는 각 행에서 2번 인덱스 값을 선택한다는 의미이다.따라서 아래 값이 나온다.
In [33]:
a[:, 2]
Out[33]:
이제 아래 코드를 이해할 수 있어야 한다.
In [34]:
a[2::2, ::2]
Out[34]:
인덱싱과 슬라이싱을 적절히 이용하면 원하는 모양의 어레이를 쉽게 구할 수 있다.
In [35]:
c = np.arange(10)
In [36]:
c[5:] = 10
c
Out[36]:
In [37]:
d = np.arange(5)
In [38]:
c[5:] = d[::-1]
c
Out[38]:
슬라이싱을 이용하여 아래 기능을 수행하는 함수 odd_numbers
와 even_numbers
를 구현하라.
odd_numbers
함수는 양의 정수 n
을 입력 받아 0부터 n까지의 정수 중에서 홀수를 역순으로
담은 1차원 어레이를 리턴한다.
In [31]: odd_numbers(10)
Out[31]: array([9, 7, 5, 3, 1])
even_numbers
함수는 양의 정수 n
을 입력 받아 0부터 n까지의 정수 중에서 짝수를
담은 1차원 어레이를 리턴한다.
In [32]: even_numbers(10)
Out[32]: array([0, 2, 4, 6, 8, 10])
힌트: linspace
함수와 슬라이싱을 적절히 활용한다.
In [39]:
a = [1, 2, 3]
In [40]:
b = a[0:2]
b
Out[40]:
b
의 값을 변경해도 a
는 영향을 받지 않는다.
In [41]:
b[0] = 4
b
Out[41]:
In [42]:
a
Out[42]:
In [43]:
c = np.array([1,2, 3])
In [44]:
d = c[0:2]
d
Out[44]:
d
의 값을 변경하면 c
의 값도 변경된다.
In [45]:
d[0] = 4
d
Out[45]:
In [46]:
c
Out[46]:
어레이를 슬라이싱 하면 어레이를 새로 생성하는 것이 아니라 기존의 어레이를 관찰하며 필요한 정보만 보여준다. 즉, 슬라이싱을 위해 메모리를 새롭게 사용하지 않는다. 복사보다 뷰 방식을 사용하는 이유는 처리속도 및 메모리 활용성을 높이기 위해서이다.
두 개의 변수가 동일한 메모리 위치를 가리키고 있는지, 즉,
특정 메모리를 공유하는가를 확인하기 위해 np.may_share_memory()
함수를
이용할 수 있다. 단, 이 함수의 리턴값을 100% 믿을 수는 없다.
In [47]:
e = np.arange(10)
e
Out[47]:
In [48]:
f = e[::2]
f
Out[48]:
변수 f
가 뷰 방식을 이용해 변수 e
와 메모리를 공유한다.
In [49]:
np.may_share_memory(e, f)
Out[49]:
따라서 f
의 값을 변경하면 e
의 값도 변경된다.
In [50]:
f[0] = 12
f
Out[50]:
In [51]:
e
Out[51]:
뷰 방식을 사용하지 않으려면 copy
메소드를 사용하여 복사해야 한다.
In [52]:
e = np.arange(10)
g = e[::2].copy()
g
Out[52]:
In [53]:
g[0] = 12
g
Out[53]:
복사를 사용하엿 e
는 변하지 않는다.
In [54]:
e
Out[54]:
e
와 g
는 메모리를 공유하지 않는다.
In [55]:
np.may_share_memory(e, g)
Out[55]:
In [56]:
np.random.seed(5)
이제 0과 20 사이에서 무작위로 15개의 정수를 생성한다.
In [57]:
a = np.random.random_integers(0, 20, 15)
a
Out[57]:
a
의 항목들이 3의 배수 여부를 알려주는 어레이는 다음처럼 생성할 수 있다.
In [58]:
mask = (a % 3 == 0)
mask
Out[58]:
mask
와 a
의 길이가 동일하며 mask
는 a
의 각 항목의 정수가 3배수 여부를 불 값으로 갖고 있다.
이제 a
의 항목들 중에서 3배수 정수들만 항목으로 갖는 어레이를 다음과 같이 구할 수 있다.
In [59]:
multiple_3 = a[mask]
multiple_3
Out[59]:
다음처럼 보다 간단한 구현이 가능하다.
In [60]:
a[a % 3 == 0]
Out[60]:
In [61]:
a[a % 3 == 0] = -1
a
Out[61]:
In [62]:
a = np.arange(0, 100, 10)
a
Out[62]:
이제 다른 정수들의 어레이를 이용한다.
이때 사용되는 정수들은 인덱싱 대상으로 삼는 어레이의 인덱스 값들로 이루어져야 한다.
그렇지 않으면 IndexError
가 발생한다.
정수들의 어레이를 이용하여 인덱싱을 하면 각 항목들에 대해 인덱싱을 실행한다. 동일한 인덱스가 반복되면 인덱싱도 반복된다. 따라서 리턴값의 모양은 인덱싱으로 사용된 어레이의 모양과 동일하다.
In [63]:
b = np.array([2, 3, 2, 4, 2])
In [64]:
a[b]
Out[64]:
인덱싱을 위해 어레이 대신에 리스트를 이용해도 동일한 결과를 얻는다.
In [65]:
c = [2, 3, 2, 4, 2]
In [66]:
a[c]
Out[66]:
마스크의 경우와 마찬가지로 정수들의 어레이를 이용하여 특정 위치의 값들을 변경할 수 있다.
In [67]:
a[[9, 7]] = -100
a
Out[67]:
주의
앞서 언급하였듯이 정수들의 어레이로 인덱싱을 한 결과는 사용된 정수들의 어레이의 모양과 동일하다.
In [68]:
a = np.arange(10)
In [69]:
idx = np.array([[3, 4], [9, 7]])
idx.shape
Out[69]:
In [70]:
a[idx]
Out[70]:
In [71]:
Image(filename='images/fancy-indexing.png', width=400)
Out[71]:
In [72]:
a = np.arange(6) + np.arange(0, 51, 10)[:, np.newaxis]
a
Out[72]:
In [73]:
a[(0, 1, 2, 3, 4), (1, 2, 3, 4, 5)]
Out[73]: