리스트의 경우처럼 인덱싱과 슬라이싱을 이용하여 어레이 항목들을 확인, 수정할 수 있으며, 새로운 어레이를 생성할 수 있다. 여기서는 어레이의 인덱싱과 슬라이싱의 다양한 활용을 배운다.
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] # [시작:끝:계단]
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]]
기본적으로 b
는 길이가 2인 리스트이다.
In [11]:
len(b)
Out[11]:
b
에서 0
을 뽑아내고자 할 경우 인덱싱을 두 번 연속해서 사용해야 하는데,
이는 0
이 b
의 첫째 항목의 첫째 항목이기 때문이다. 즉,
In [12]:
b[0][0]
Out[12]:
5
의 경우 둘째 항목의 셋째 항목이다.
In [13]:
b[1][2]
Out[13]:
반면에 b
를 이차원 어레이로 다룰 경우에는 다음과 같이 하면 된다.
In [14]:
b = np.array([[0, 1, 2], [3, 4, 5]])
b
Out[14]:
이제 0을 뽑아내고자 할 때 2차원 어레이에 대한 인덱스를 사용하면 된다.
즉, 0번 인덱스 값의 0번 인덱스 값이란 의미로 [0, 0]
을 사용한다.
In [15]:
b[0, 0]
Out[15]:
5의 경우는 1번 인덱스 값의 2번 인덱스 값이므로, [1, 2]
를 사용한다.
In [16]:
b[1,2]
Out[16]:
그런데 이렇게 몇 번 인덱스 값의 몇 번 인덱스 값 이란 표현은 사용하기가 힘들다. 대신에 행과 열의 개념을 사용하는 것이 편하다.
예제: 어레이 b의 경우 0은 0행 0열의 값이며, 5는 1행 2열의 값이다. 따라서 아래 식을 사용한다.
주의: 인덱스는 0부터 시작한다.
In [17]:
b[0,0]
Out[17]:
In [18]:
b[1,2]
Out[18]:
보다 다양한 예제들을 이용하여 어레이 인덱싱을 연습해보자.
아래 코드에서 정의된 d는 2차원 어레이이며, 대각선에 0, 1, 2를 담고 있다.
In [19]:
d = np.diag(np.arange(3))
d
Out[19]:
1
의 위치는 1행 1열이므로 인덱싱을 (1, 1)
을 이용한다.
In [20]:
d[1, 1]
Out[20]:
In [21]:
e = range(3)
e
Out[21]:
리스트 e의 2번 항목인 3을 5로 변경하려면 아래와 같이 인덱싱을 사용하면 된다.
In [22]:
e[2] = 5
e
Out[22]:
동일한 방식으로 어레이의 특정 항목을 변경할 수 있다.
In [23]:
d[2, 1] = 10
d
Out[23]:
주의:
그렇다면 d[2]
는 무엇을 의미할까?
d
를 리스트라 생각하면 된다. 즉, d의 2번 항목인 [0, 10, 2]
어레이를 가리킨다.
In [24]:
d[2]
Out[24]:
np.arange
함수를 이용하여 길이가 36인 어레이를 생성한 다음에
shape 값을 변경하여 2차원 어레이를 생성한다.
In [25]:
a = np.arange(36)
a.shape = (6,6)
a
Out[25]:
In [26]:
a = np.arange(36).reshape((6,6))
a
Out[26]:
이제 각 행별로 4의 배수를 더하면 원하는 어레이가 얻어지는 성질을 이용한다. 어레이와 숫자의 연산은 각 항목별로 실행된다는 점을 이용한다.
In [27]:
a = np.arange(36).reshape((6,6))
for i in range(len(a)):
a[i] = a[i] + 4*i
a
Out[27]:
위 코드를 보다 친숙한 방식으로 구현한다면 다음과 같다.
In [28]:
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[28]:
In [29]:
a = np.arange(6) + np.arange(0, 51, 10)[:, np.newaxis]
a
Out[29]:
In [30]:
a[0]
Out[30]:
이제 a[0][3:5]
를 확인한다.
In [31]:
a[0][3:5]
Out[31]:
따라서 a[0, 3:5]
결과는 아래와 같다.
In [32]:
a[0, 3:5]
Out[32]:
In [33]:
a[4:]
Out[33]:
In [34]:
a[4:][4:]
Out[34]:
따라서 1차원 값과 2차원 값을 함께 생각해야 한다.
'4:'
는 4번 인덱스 행부터 마지막 행까지 전체를 의미한다.'4:'
는 앞서 선택한 4번 인덱스 행부터 마지막 행 각각에서 4번 인덱스 열부터 전체를 나타낸다.
In [35]:
a[4:, 4:]
Out[35]:
위와 비슷한 방식으로 해석하면 아래 코드도 이해할 수 있다.
':'
은 모든 행을 대상으로 삼는 것이고'2'
는 각 행에서 2번 인덱스 값을 선택한다는 의미이다.따라서 아래 값이 나온다.
In [36]:
a[:, 2]
Out[36]:
이제 아래 코드를 이해할 수 있어야 한다.
In [37]:
a[2::2, ::2]
Out[37]:
In [38]:
c = np.arange(10)
아래 코드는 5번 인덱스 값부터 모두 10으로 일괄적으로 변경한다.
In [39]:
c[5:] = 10
c
Out[39]:
In [40]:
d = np.arange(5)
아래 코드는 5번 이상의 인덱스 값을 특정 어레이의 항목값들로 대체한다.
In [41]:
c[5:] = d[::-1]
c
Out[41]:
슬라이싱을 이용하여 아래 기능을 수행하는 함수 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])
힌트: arange()
, 함수와 슬라이싱을 적절히 활용한다.
In [42]:
def odd_numbers(n):
odds = np.arange(1, n, 2)
return odds[::-1]
odd_numbers(10)
Out[42]:
In [43]:
def even_numbers(n):
evens = np.arange(0, n+1, 2)
return evens
even_numbers(10)
Out[43]:
In [44]:
a = [1, 2, 3]
In [45]:
b = a[0:2]
b
Out[45]:
b
의 값을 변경해도 a
는 영향을 받지 않는다.
In [46]:
b[0] = 4
b
Out[46]:
In [47]:
a
Out[47]:
In [48]:
c = np.array([1,2, 3])
In [49]:
d = c[0:2]
d
Out[49]:
d
의 값을 변경하면 c
의 값도 변경된다.
In [50]:
d[0] = 4
d
Out[50]:
In [51]:
c
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]:
In [55]:
np.random.seed(5)
이제 0과 20 사이에서 무작위로 15개의 정수를 3 x 5
행렬의 모양으로 생성하자.
In [56]:
a = np.random.randint(0, 20, size=(3, 5))
a
Out[56]:
a
의 항목들이 3의 배수 여부를 알려주는 어레이는 다음처럼 생성할 수 있다.
In [57]:
mask = (a % 3 == 0)
mask
Out[57]:
주의: 어레이의 연산은 항목별로 실행된다.
In [58]:
a % 3
Out[58]:
또한 두 어레이의 모양이 다르면 브로드캐스팅 기능을 이용하여 두 어레이의 모양을 가능하면 통일시킨다. 물론, 모양의 통일이 불가능하면 오류가 발생한다. 브로드캐스팅에 대해서는 다음 시간에 자세히 배운다.
아래 코드에서 a % 3
은 3 x 5
모양의 2차원 어레이 인 반면에 숫자 0은 어레이가 전혀 아니다.
하지만 숫자 0으로 이루어진 3 x 5
모양의 어레이로 모양을 만들면 두 어레이가 동일한 모양을 갖게 되고,
따라서 항목별로 비교가 가능해진다.
사실은 a % 3
도 이와 같은 방식으로 이해할 수 있다.
즉, 먼저 3으로 이루어진 3 x 5
모양의 어레이로 만든 다음에 두 어레이 간의 나머지 연산을 실행한다.
In [59]:
a % 3 == 0
Out[59]:
이제 a
의 항목들 중에서 mask
의 항목들 중 True
가 위치한 곳과 동일한 곳에 위치한 항목들만 뽑아서 1차원 어레이를 만들어 준다.
즉, a
의 항목들 중에서 3의 배수인 숫자들만 뽑아서 어레이로 만들어서 리턴한다.
In [60]:
multiple_3 = a[mask]
multiple_3
Out[60]:
다음처럼 보다 간단한 구현이 가능하다.
In [61]:
a[a % 3 == 0]
Out[61]:
In [62]:
a[a % 3 == 0] = -1
a
Out[62]: