넘파이 어레이: 인덱싱과 슬라이싱 2

주요 내용

넘파이 어레이와 관련된 인덱싱과 슬라이싱 기능을 좀 더 알아본다.


In [1]:
import numpy as np

정수 어레이 인덱싱

바로 예제를 따라하면서 이해하는 것이 가장 좋은 방법이다.

먼저, 0, 10, ..., 90으로 구성된 어레이를 생성한다.


In [2]:
a = np.arange(0, 100, 10)
a


Out[2]:
array([ 0, 10, 20, 30, 40, 50, 60, 70, 80, 90])

이제 다른 정수들의 어레이를 이용한다. 이때 사용되는 정수들은 인덱싱 대상으로 삼는 어레이의 인덱스 값들로 이루어져야 한다. 그렇지 않으면 IndexError가 발생한다.

정수들의 어레이를 이용하여 인덱싱을 하면 각 항목들에 대해 인덱싱을 실행한다. 동일한 인덱스가 반복되면 인덱싱도 반복된다. 따라서 리턴값의 모양은 인덱싱으로 사용된 어레이의 모양과 동일하다.


In [3]:
b = np.array([2, 3, 2, 4, 2])

In [4]:
a[b]


Out[4]:
array([20, 30, 20, 40, 20])

인덱싱을 위해 어레이 대신에 리스트를 이용해도 동일한 결과를 얻는다.


In [5]:
c = [2, 3, 2, 4, 2]

In [6]:
a[c]


Out[6]:
array([20, 30, 20, 40, 20])

마스크의 경우와 마찬가지로 정수들의 어레이를 이용하여 특정 위치의 값들을 변경할 수 있다.


In [7]:
a[[9, 7]] = -100
a


Out[7]:
array([   0,   10,   20,   30,   40,   50,   60, -100,   80, -100])

주의

앞서 언급하였듯이 정수들의 어레이로 인덱싱을 한 결과는 사용된 정수들의 어레이의 모양과 동일하다.


In [8]:
a = np.arange(10)

In [9]:
idx = np.array([[3, 4], [9, 7]])
idx


Out[9]:
array([[3, 4],
       [9, 7]])

In [10]:
a[idx]


Out[10]:
array([[3, 4],
       [9, 7]])

다차원 어레이와 정수 인덱싱

다차원 어레이에 대한 정수 인덱싱도 가능하다.

아래 그림을 보면서 팬시 인덱싱을 연습해보자.


In [11]:
a = np.arange(6) + np.arange(0, 51, 10)[:, np.newaxis]
a


Out[11]:
array([[ 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]])

주황색 영역

  • 규칙: 주황색으로 표시된 부분을 추출하려면 행과 열에 대한 정보가 순서쌍으로 필요하다. 즉, 함수 그래프를 그릴 때 x축과 y축의 좌표값들을 각각 리스트로 모아서 이용했던 것처럼
    • x축 좌표값: (0, 1, 2, 3, 4)
    • y축 좌표값: (1, 2, 3, 4, 5)

In [12]:
a[(0, 1, 2, 3, 4), (1, 2, 3, 4, 5)]


Out[12]:
array([ 1, 12, 23, 34, 45])

하늘색 영역

  • 규칙: 하늘색으로 표시된 부분을 추출하려면 역시 행과 열에 대한 정보가 필요하다. 하지만 함수 그래프 형식과는 다른 점에 주의해야 한다. 왜냐하면, 행이 연속적으로 변하는 반면에 열의 값은 변하지 않기도 한다. 이럴 때는 행 또는 열에 대해 슬라이싱 기능을 함께 사용해야 한다.
    • x 축은, 3행 이후, 즉 [3:]
    • y 축은 0, 2, 5열, 즉 [0, 2, 5]

In [13]:
a[3:, [0, 2, 5]]


Out[13]:
array([[30, 32, 35],
       [40, 42, 45],
       [50, 52, 55]])

빨강색 영역

빨강색으로 표시된 부분을 추출하려면 역시 행과 열에 대한 정보가 필요하며, 하지만 주황색 부분과 동일한 방식으로 추출이 가능하다.


In [14]:
a[(0, 2, 5), (2, 2, 2)]


Out[14]:
array([ 2, 22, 52])

(2, 2, 2) 부분을 단순히 2라고 적어도 된다. 이유는 브로드캐스팅이 자동 적용되어 (0, 2, 5) 의 모양과 통일시켜 주기 때문이다.


In [15]:
a[(0, 2, 5), 2]


Out[15]:
array([ 2, 22, 52])

하지만 마스크 인덱싱을 활용할 수도 있다.

(0, 2, 5)에 대응하는 마스크는 다음과 같다.


In [16]:
mask = np.array([1,0, 1, 0, 0, 1], dtype =bool)

따라서 아래와 같이 실행하면 동일한 결과를 얻는다.


In [17]:
a[mask, 2]


Out[17]:
array([ 2, 22, 52])

팬시 인덱싱(Fancy indexing)

마스크 인덱싱과 정수 인덱싱을 팬시 인덱싱이라 부른다.

주의: 팬시 인덱싱은 뷰 방식을 따르지 않는다.

어레이의 슬라이싱과 뷰 방식

슬라이싱을 처리할 때 리스트의 경우와 어레이의 경우가 다르다.

  • 리스트 슬라이싱: 새로운 리스트를 복사(copy)해서 사용한다.
  • 어레이 슬라이싱: 어레이를 복사하지 않고 기존의 어레이를 사용하며 관찰(view)한다.

리스트 슬라이싱: 복사 사용


In [18]:
a = [1, 2, 3]

In [19]:
b = a[0:2]
b


Out[19]:
[1, 2]

b의 값을 변경해도 a는 영향을 받지 않는다.


In [20]:
b[0] = 4
b


Out[20]:
[4, 2]

In [21]:
a


Out[21]:
[1, 2, 3]

어레이 슬라이싱: 뷰 사용


In [22]:
c = np.array([1, 2, 3])

In [23]:
d = c[0:2]
d


Out[23]:
array([1, 2])

d의 값을 변경하면 c의 값도 변경된다.


In [24]:
d[0] = 4
d


Out[24]:
array([4, 2])

In [25]:
c


Out[25]:
array([4, 2, 3])

어레이를 슬라이싱 하면 어레이를 새로 생성하는 것이 아니라 기존의 어레이를 관찰하며 필요한 정보만 보여준다. 즉, 슬라이싱을 위해 메모리를 새롭게 사용하지 않는다. 복사보다 뷰 방식을 사용하는 이유는 처리속도 및 메모리 활용성을 높이기 위해서이다.

뷰 방식을 사용하지 않으려면 copy 메소드를 사용하여 복사해야 한다.


In [26]:
e = np.arange(10)
g = e[::2].copy()
g


Out[26]:
array([0, 2, 4, 6, 8])

In [27]:
g[0] = 12
g


Out[27]:
array([12,  2,  4,  6,  8])

복사를 사용하였기 때문에 e는 변하지 않는다.


In [28]:
e


Out[28]:
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

뷰 방식으로 작동하는 함수 정리

지금까지 살펴 본 어레이와 관련된 기능들 중에서 뷰 방식으로 작동하는 것들은 다음과 같다.

  • 전치행렬 구하기
  • reshape 함수
  • 슬라이싱