팬시 인덱싱(Fancy indexing)

넘파이 어레이의 경우 불리언(Boolean) 값 또는 정수들의 어레이를 이용하여 특별한 인덱싱을 실행할 수 있다. 이러한 인덱싱을 팬시 인덱싱이라 부른다.

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

마스크(mask) 인덱싱

True, False의 불리언 값들로만 이루어진 어레이를 마스크(mask)라 부른다. 마스크를 이용하여 인덱싱을 실행할 수 있다.

예제: 무작위로 생성된 정수들 중에서 3의 배수만 추출하기

무작위로 선택된 정수들의 어레이를 생성하기 위해서는 np.random.randint 함수를 이용한다.

주의: 무작위성을 높이기 위해 앞서 np.random.rand의 경우처럼 seed 값을 이용하자.


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]:
array([[ 3, 14, 15,  6, 16],
       [ 9,  8,  4,  7, 16],
       [16,  7, 12, 15, 17]])

a의 항목들이 3의 배수 여부를 알려주는 어레이는 다음처럼 생성할 수 있다.


In [57]:
mask = (a % 3 == 0)
mask


Out[57]:
array([[ True, False,  True,  True, False],
       [ True, False, False, False, False],
       [False, False,  True,  True, False]], dtype=bool)

주의: 어레이의 연산은 항목별로 실행된다.


In [58]:
a % 3


Out[58]:
array([[0, 2, 0, 0, 1],
       [0, 2, 1, 1, 1],
       [1, 1, 0, 0, 2]])

또한 두 어레이의 모양이 다르면 브로드캐스팅 기능을 이용하여 두 어레이의 모양을 가능하면 통일시킨다. 물론, 모양의 통일이 불가능하면 오류가 발생한다. 브로드캐스팅에 대해서는 다음 시간에 자세히 배운다.

아래 코드에서 a % 33 x 5 모양의 2차원 어레이 인 반면에 숫자 0은 어레이가 전혀 아니다. 하지만 숫자 0으로 이루어진 3 x 5 모양의 어레이로 모양을 만들면 두 어레이가 동일한 모양을 갖게 되고, 따라서 항목별로 비교가 가능해진다.

사실은 a % 3 도 이와 같은 방식으로 이해할 수 있다. 즉, 먼저 3으로 이루어진 3 x 5 모양의 어레이로 만든 다음에 두 어레이 간의 나머지 연산을 실행한다.


In [59]:
a % 3 == 0


Out[59]:
array([[ True, False,  True,  True, False],
       [ True, False, False, False, False],
       [False, False,  True,  True, False]], dtype=bool)

이제 a의 항목들 중에서 mask의 항목들 중 True가 위치한 곳과 동일한 곳에 위치한 항목들만 뽑아서 1차원 어레이를 만들어 준다. 즉, a의 항목들 중에서 3의 배수인 숫자들만 뽑아서 어레이로 만들어서 리턴한다.


In [60]:
multiple_3 = a[mask]
multiple_3


Out[60]:
array([ 3, 15,  6,  9, 12, 15])

다음처럼 보다 간단한 구현이 가능하다.


In [61]:
a[a % 3 == 0]


Out[61]:
array([ 3, 15,  6,  9, 12, 15])

마스크 인덱싱 활용

마스크 인덱싱을 이용하여 특정 항목들의 값을 동시에 변경할 수 있다. 예를 들어, 생성된 정수들의 어레이어서 3배수값들만을 -1로 변경하고 할 때 마스크 인덱싱 기능을 활용할 수 있다.


In [62]:
a[a % 3 == 0] = -1
a


Out[62]:
array([[-1, 14, -1, -1, 16],
       [-1,  8,  4,  7, 16],
       [16,  7, -1, -1, 17]])

정수 어레이 인덱싱

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

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


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


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

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

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


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

In [65]:
a[b]


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

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


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

In [67]:
a[c]


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

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


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


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

주의

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


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

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


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

In [71]:
a[idx]


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

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

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

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


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


Out[72]:
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축의 좌표값들을 각각 리스트로 모아서 이용했던 것처럼 행과 열에 대한 좌표값들을 각각의 항목으로 갖는 두 개의 정수 어레이를 이용하여 인덱싱을 실행한다.


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


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

예제: 하늘색 부분

하늘색으로 표시된 부분을 추출하려면 역시 행과 열에 대한 정보가 필요하다. 하지만 함수 그래프 형식과는 다른 점에 주의해야 한다.

왜냐하면, 행이 연속적으로 변하는 반면에 열의 값은 변하지 않기도 한다. 이럴 때는 행과 열에 대해 슬라이싱 기능을 함께 사용해야 한다.


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


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

예제: 빨강색 부분

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


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


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

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


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


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

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

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


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

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


In [78]:
a[mask, 2]


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