지난 포스팅에 이어 이번에는 정수/불리언 배열을 활용한 인덱싱과 슬라이싱에 대해 알아보도록 하겠습니다.
import numpy as np
1. 정수 인덱싱 배열
💡 인덱싱을 위해 사용하는 정수 인덱싱 배열의 shape을 주목해야 합니다.
1-1. 1차원 인덱싱 배열
정수 인덱싱 배열을 사용하면 특정 인덱스에 위치한 값들을 가져올 수 있습니다.
중복 인덱스는 물론 불규칙적으로 값들을 가져올 수 있다는 특징이 있습니다.
arr = np.arange(10)
print(arr)
# [0 1 2 3 4 5 6 7 8 9]
indices1 = np.array([0, 3, 4])
indices2 = np.array([0, 0, 5, 5])
print(arr[indices1])
# [0 3 4]
print(arr[indices2]
# [0 0 5 5]
정수 배열을 활용한 인덱싱은 정수 배열의 shape이 핵심입니다.
정수 배열의 shape을 통해 인덱싱 결과로 리턴되는 배열의 shape을 예측할 수 있기 때문입니다.
arr = np.arange(12).reshape((3, 4))
print(arr)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
indices = np.array([0, 2])
print(arr[indices])
# [[ 0 1 2 3]
# [ 8 9 10 11]]
다음은 인덱싱 결과 리턴받을 배열의 형태를 예측하는 예시입니다.
실생활 이미지 데이터로 예시를 들어보겠습니다.
(32, 100, 200) 형태의 이미지 데이터셋으로부터 0, 10, -1번째 이미지를 가져와야 하는 상황입니다.
이 경우 사용하는 인덱싱 배열은 [0, 10, -1]입니다.
우선 인덱싱 배열의 길이가 3이기 때문에 리턴 받을 배열의 shape이 (3, ...) 꼴인 것을 알 수 있습니다.
선택한 각 이미지의 shape은 (100, 200)이고 이는 (100, 200) 이미지 3장을 가져오는 것을 의미합니다.
따라서 인덱싱 결과로 반환되는 데이터셋의 형태는 (3, 100, 200)이 됩니다.
1-2. 중첩 인덱싱 배열
지금까지는 1차원 배열을 활용한 인덱싱에 대해 알아봤습니다.
하지만 중첩 배열, 즉 2차원 이상 배열을 활용한 인덱싱 또한 가능합니다.
아래 예제 코드를 살펴보겠습니다.
우선 2차원 인덱싱 배열을 사용하기 때문에 인덱싱의 결과로 리턴 받을 배열의 shape은 (2, 3, ...) 꼴인 것을 알 수 있습니다.
이때 인덱싱을 통해 가져오는 각 배열은 shape이 (4, )인 1차원 배열입니다.
이를 종합해보면 리턴 받을 배열의 shape이 (2, 3, 4)인 것을 알 수 있습니다.
arr = np.arange(12).reshape((3, 4))
print(arr)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
indices = np.array([[0, 1, 2], [-3, -2, -1]])
print(arr[indices]) # shape=(2, 3, 4)
# [[[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]]
중첩된 배열의 형태는 아니지만 2개의 배열을 사용해서 인덱싱하는 방법도 있습니다.
아래 예제코드에서는 [0, 1], [1, 2], [2, 3] 인덱싱 쌍(paired indices)이 만들어집니다.
참고로 인덱싱 쌍의 shape은 모두 동일해야 합니다.
arr = np.arange(12).reshape((3, 4))
print(arr)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
indices1, indices2 = np.array([0, 1, 2]), np.array([1, 2, 3])
print(arr[indices1, indices2])
# [ 1 6 11]
아래 예제코드 또한 2개의 인덱싱 배열을 사용하는데 각 배열은 2차원입니다.
이 경우에도 마찬가지로 [[0, 0], [1, 1], [2, 2]], [[0, 1], [1, 2], [2, 3]] 형태의 인덱싱 쌍이 만들어집니다.
arr = np.arange(12).reshape((3, 4))
print(arr)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
indices1 = np.array([[0, 1, 2], [0, 1, 2]])
indices2 = np.array([[0, 1, 2], [1, 2, 3]])
print(arr[indices1, indices2]) # shape=(2, 3)
# [[ 0 5 10]
# [ 1 6 11]]
2. 불리언 인덱싱 배열
💡 인덱싱 결과로 1차원 배열을 리턴합니다.
인덱싱에 사용되는 불리언 배열의 역할은 해당 인덱스의 값을 가져올지 여부를 결정하는 것입니다.
따라서 불리언 인덱싱 배열과 인덱싱 대상이 되는 배열의 shape은 동일해야 합니다.
arr = np.arange(5)
print(arr) # [0 1 2 3 4]
b_indices = np.array([True, True, False, False, True])
print(arr[b_indices]) # [0 1 4]
다음 코드는 같은 크기의 불리언 배열을 활용해 짝수 원소들만 가져옵니다.
arr = np.random.randint(1, 20, (10, ))
print(arr)
# [ 4 12 14 5 1 9 4 3 5 15]
b_indices = (arr % 2 == 0)
print(b_indices)
# [ True True True False False False True False False False]
print(arr[b_indices])
# [ 4 12 14 0 4]
2차원 배열에 대해서 불리언 배열을 사용해 인덱싱하는 경우를 살펴보겠습니다.
이 경우에도 마찬가지로 두 배열의 shape은 동일해야 합니다.
arr = np.random.randint(0, 20, (2, 2))
print(arr)
# [[19 8]
# [16 6]]
b_indices = np.array([[True, False], [False, True]])
print(b_indices)
# [[ True False]
# [False True]]
print(arr[b_indices])
# [19 6]
다음 예제코드는 비교연산자를 사용해 불리언 배열을 만든 뒤 2차원 배열에 대한 인덱싱에 활용합니다.
arr = np.random.randint(0, 20, (3, 4))
print(arr)
# [[ 6 3 18 16]
# [10 5 17 11]
# [ 8 17 15 5]]
b_indices = (arr > 10)
print(b_indices)
# [[False False True True]
# [False False True True]
# [False True True False]]
print(arr[b_indices])
# [18 16 17 11 17 15]
3. np.nonzero, np.where
np.nonzero, np.where 두 메서드는 정수 인덱싱 배열을 리턴합니다.
구체적으로 얘기하자면 np.nonzero 메서드는 0이 아닌 값들의 인덱스 정보를, np.where 메서드는 전달 받은 조건을 충족하는 값들의 인덱스 정보를 리턴합니다.
이렇게 리턴 받은 정수 배열은 인덱싱에 활용될 수 있습니다.
# 1차원
arr = np.array([True, False, True, False])
nonzero = np.nonzero(arr)
print(nonzero)
# (array([0, 2]),)
where = np.where(arr)
print(where)
# (array([0, 2]),)
# 2차원
arr2 = np.array([[True, False], [True, False]])
nonzero2 = np.nonzero(arr2)
print(nonzero2)
# (array([0, 1]), array([0, 0]))
# 인덱스 쌍 [0, 0], [1, 0]을 의미합니다.
where2 = np.where(arr2 != 0)
print(where2)
# (array([0, 1]), array([0, 0]))
# 인덱스 쌍 [0, 0], [1, 0]을 의미합니다.
구체적인 예시를 통해 두 메서드의 동작 방식을 살펴보겠습니다.
앞서 언급했듯이 두 메서드는 조건을 만족하는 값들의 인덱스 정보를 리턴합니다.
아래 예제코드는 리턴 받은 인덱싱 배열을 활용합니다.
arr = np.random.randint(-2, 3, (3, 3))
print(arr)
# [[-2 -2 -1]
# [ 1 2 0]
# [ 1 0 -2]]
# 정수 인덱싱 배열
nonzero_result = arr[np.nonzero(arr)]
where_result = arr[np.where(arr)]
# 불리언 인덱싱 배열
bool = arr[arr != 0]
# 세 코드의 결과는 동일합니다.
# [-2 -2 -1 1 2 1 -2]
print(nonzero_result)
print(where_result)
print(bool)
마치며
이상으로 정수/불리언 배열을 활용한 넘파이 배열의 인덱싱과 슬라이싱에 대한 정리를 마치겠습니다.
머신러닝과 딥러닝 공부를 위해 확실하게 알아야 하는 개념인 만큼 복습을 철저히 해야겠습니다.
'머신러닝・딥러닝 > numpy' 카테고리의 다른 글
넘파이 배열의 인덱싱과 슬라이싱 이해하기(1) (0) | 2023.07.15 |
---|