본문 바로가기
ㄴ DS

Numpy Indexing, Boolean Indexing, Fancy Indexing

by 경아ㅏ 2021. 10. 31.

Numpy Indexing

View(reference) 혹은 Copy를 리턴하는 Indexing 방법들에 대해 알아보자.
Numpy Indexing Tutorials

 

 

Indexing & Slicing : view 리턴

리스트의 인덱싱, 슬라이싱과 동일하게 [](bracket) 을 사용하여 부분 배열을 추출한다.

 

 

- 1차원 배열의 indexing & slicing

 

import numpy as np

arr = np.array([1, 2, 3, 4, 5])

print(arr[0])
print(arr[-1]) 
print(arr[-2])

 

output:

1
5
4

 

왼쪽의 인덱스는 0 부터 시작한다. 배열의 맨 뒷부분 부터 접근하기 위해서는 -1 부터 시작하는 음수의 인덱스를 사용하면 된다. Python  리스트에서와 마찬가지로, : 기호를 이용한 슬라이싱을 통해 여러개의 요소들을 추출할 수 있다.

 

import numpy as np

arr = np.array([1, 2, 3, 4, 5])

print(arr[1:3]) # 1번 인덱스 부터 2번 인덱스까지
print(arr[3:5]) # 3번 인덱스 부터 4번 인덱스까지
print(arr[2:])  # 2번 인덱스 부터 끝까지

 

output:

[2 3]
[4 5]
[3 4 5]

 

 

- 2차원 배열의 indexing & slicing

 

import numpy as np

arr = np.array([[1, 2, 3], 
                [4, 5, 6]])

print(arr[0])    # 0번째 row 추출
print(arr[1, 1]) 
print(arr[0, 2])

 

output:

[1 2 3]
5
3

 

[](bracket)의 ,(comma)를 중심으로 왼쪽에는 row 인덱스, 오른쪽에는 column 인덱스를 입력하여 특정 원소를 추출한다. 이때, 배열의 차원보다 인덱스의 개수가 작으면 subdimensional array가 추출된다. 예를 들어, arr 은 2차원의 배열이다. 이 때 arr[0] 은 1개의 인덱스만 주어졌으므로 첫번째 행을 참조한다.

 

import numpy as np

arr = np.array([[1, 2, 3], 
                [4, 5, 6], 
                [7, 8, 9]])

print(arr[0:2, 1:3], "\n")  # row는 0번에서 1번까지, column은 1번에서 2번까지
print(arr[:, 1:2], "\n")    # 모든 행에 대하여 1번 column 추출
print(arr[1:4:2, :])        # row는 1번 부터 3번행까지 2를 간격으로, 모든 column 추출

 

output:

[[2 3]
 [5 6]] 

[[2]
 [5]
 [8]] 

[[4 5 6]]

 

 

Boolean  Indexing (Mask Indexing): copy 리턴

Boolean 배열의 값이 True인 요소들만 추출해보자.

 

 

import numpy as np

boolean = np.array([True]*5+[False]*5+[True]*5+[False]*5)
boolean

 

output:

array([ True,  True,  True,  True,  True, False, False, False, False,
       False,  True,  True,  True,  True,  True, False, False, False,
       False, False])

 

ary = np.arange(0, 20) # 0에서 19까지의 수로 배열 생성
ary

 

output:

array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,
       17, 18, 19])

 

boolean 배열과 arr 이 위와 같다고 할 때, arr[boolean] 과 같이 boolean 배열을 [] 안에 넣어주면 boolean 배열의 값이 True인 인덱스의 원소만 추출할 수 있다. 

 

ary[boolean]

 

output:

array([ 0,  1,  2,  3,  4, 10, 11, 12, 13, 14])

 

이러한 Boolean Indexing 방식은 비교연산자(>, <, =)와 논리연산자(and, or) 로 이루어진 조건식과 함께 주로 사용된다. 배열의 조건식을 이용하여 boolean array를 만들고 이를 이용하여 인덱싱 함으로써 조건을 만족하는 원소만 추출한다. 

 

import numpy as np

ary = np.arange(0, 20) 
boolean = (ary < 10) & (ary%2 == 0)

ary[boolean] # ary[(ary < 10) & (ary%2 == 0)] 와 동일

 

output:

array([0, 2, 4, 6, 8])

 

 

Fancy Indexing: copy 리턴

또 다른 배열(혹은 리스트)를 인덱스로 사용하여 특정 요소나 부분 배열을 추출해보자.

 

import numpy as pd

arr = np.arange(0, 35).reshape(5, 7)
arr

 

output:

array([[ 0,  1,  2,  3,  4,  5,  6],
       [ 7,  8,  9, 10, 11, 12, 13],
       [14, 15, 16, 17, 18, 19, 20],
       [21, 22, 23, 24, 25, 26, 27],
       [28, 29, 30, 31, 32, 33, 34]])

 

[ , ] 의 왼쪽과 오른쪽에 단일의 숫자 인덱스나 슬라이싱이 아닌 배열(혹은 리스트)를 넣어보자. 

 

fancy = arr[np.array([0,2,4]), np.array([0,1,2])]
fancy

 

output:

array([ 0, 15, 30])

 

[ , ] 의 comma를 기준으로 왼쪽 배열과 오른쪽 배열 인덱스 들이  1대 1로 매칭되어 [0, 0], [2, 1], [4, 2] 의 원소들을 추출한다. 이 때,  행 배열의 원소 개수와 column 배열의 원소 개수가 맞지 않으면, 자연스럽게 Broadcasting 되어  매칭된다.

 

fancy = arr[np.array([0,2,4]), 1]
fancy

 

output:

array([ 1, 15, 29])

 

 

View와 Copy의 차이

인덱싱의 리턴 값이 view(reference)인 경우와 copy(assignment) 인 경우의 차이

 

 

- view

 

기본적인 Indexing과 Slicing의 경우, 리턴 값은 view가 된다. 즉, 인덱싱의 결과로 원래의 데이터가 복사되어 할당되지 않고, 기존의 배열에 대한 참조가 주어진다. 따라서 새로운 변수를 이용하여 element를 변경시킬 경우, 원래의 데이터도 변경된다. 새로운 변수와 기존의 데이터는 같은 메모리를 공유하고 있기 때문이다.

 

import numpy as np

arr = np.arange(0, 5)
print("arr:\n", arr, "\n")

new_arr = arr[2:]
new_arr[0] = 100

print("arr:\n", arr, "\n")

 

output:

arr:
 [0 1 2 3 4] 

arr:
 [  0   1 100   3   4]

 

위의 코드에서, new_arr은 arr[2:]와 같은 메모리를 공유하고 있는 창(view)이다. 따라서, new_arr의 원소를 변경하였을 때, arr 원소도 함께 변경된다. 실제로 프로젝트를 진행할 때, 이러한 경우가 발생하게 되면 의도치 않게 원래의 데이터를 수정하게 되는 실수를 범하게 된다. 이러한 상황을 방지하기 위하여, .copy() 함수를 이용해 추출한 데이터를 복사 후 할당하자.

 

import numpy as np

arr = np.arange(0, 5)
print("arr:\n", arr, "\n")

new_arr = arr[2:].copy()
new_arr[0] = 100

print("arr:\n", arr, "\n")

 

output:

arr:
 [0 1 2 3 4] 

arr:
 [0 1 2 3 4]

 

 

- copy

 

기본적인 인덱스와 달리, Boolean indexing 과 Fancy indexing은 copy를 리턴한다. 새로운 변수에 인덱싱 결과를 할당하였을 때, 원래 변수의 데이터를 복사하여 새로운 메모리에 저장하게 되므로 새로운 변수와 기존의 데이터는 같은 메모리를 공유하지 않는다. 따라서, 새로운 변수의 요소들을 변경하더라도, 원래의 데이터는 변경되지 않는다.

 

import numpy as np

arr = np.arange(0, 5)
print("arr:\n", arr, "\n")

new_arr = arr[arr>2] # boolean indexing
new_arr[0] = 100

print("arr:\n", arr, "\n")

 

output:

arr:
 [0 1 2 3 4] 

arr:
 [0 1 2 3 4]

 

 

관련 포스트

[Numpy: 1.20.1] Numpy 기초(배열을 생성하고 다루는 법)

 

참고

numpy 공식 document

댓글