6.12. 선택과 재배열

리덕션은 배열을 스칼라나 더 낮은 랭크의 결과로 축소했습니다. 선택은 어떤 요소가 살아남고 어디에 위치하는지를 정하는 연산들을 다룹니다: 조건부 선택, 클리핑, 정렬, 인덱스 조회, 축을 따른 재정렬 등이 있습니다.

6.12.1. 조건부 선택

where() 는 조건이 참인 곳에서는 x 의 요소를, 그렇지 않은 곳에서는 y 의 요소를 취하는 배열을 반환합니다. 세 개의 피연산자는 함께 브로드캐스트됩니다:

a = np.array([1, 2, 3, 4, 5], dtype=np.float)
np.where(a < 3, a, 0.0)
# array([1.0, 2.0, 0.0, 0.0, 0.0])

이것은 Python 루프를 작성하지 않고 “요소별 if/else”를 처리하기에 적합한 도구입니다.

clip()maximum(lo, minimum(a, hi)) 의 축약형으로, 값을 특정 범위로 포화시킵니다:

np.clip(a, 2.0, 4.0)
# array([2.0, 2.0, 3.0, 4.0, 4.0])

maximum()minimum() 은 두 개의 피연산자를 받아 요소별로 더 크거나 더 작은 값을 반환합니다:

np.maximum(a, 3.0)
np.minimum(a, np.array([5, 4, 3, 2, 1]))

6.12.2. 인덱스 찾기

nonzero() 는 0이 아닌 모든 요소의 좌표를 차원당 하나의 인덱스 배열로 분할하여 반환합니다. 2차원 입력의 경우 결과는 두 배열의 튜플입니다: 첫 번째는 행 인덱스를, 두 번째는 열 인덱스를 담습니다. 이들을 열 단위로 짝지으면 각 0이 아닌 위치의 (row, col) 을 얻을 수 있습니다:

m = np.array([[0, 2, 0],
              [3, 0, 0]], dtype=np.float)
np.nonzero(m)
# (array([0, 1], dtype=uint16), array([1, 0], dtype=uint16))

m 에서 0이 아닌 항목은 m[0, 1] = 2m[1, 0] = 3 입니다. 반환된 첫 번째 배열 [0, 1] 은 이들의 행 인덱스를, 두 번째 배열 [1, 0] 은 이들의 열 인덱스를 제공합니다. 두 배열을 나란히 읽으면 (0, 1)(1, 0) 위치를 복원할 수 있습니다.

두 가지 리덕션도 인덱스를 생성합니다:

  • argmin() / argmax() – 가장 작은 / 가장 큰 요소의 인덱스.

  • argsort() – 주어진 축(기본값은 마지막 축)을 따라 입력을 정렬할 정수 배열:

    a = np.array([40, 10, 30, 20], dtype=np.uint8)
    idx = np.argsort(a)             # array([1, 3, 2, 0], dtype=uint16)
    a[idx]                          # array([10, 20, 30, 40])
    

    argsort 는 항상 uint16 을 반환합니다. 따라서 정렬되는 배열은 정렬 축에서 65,535개를 초과하는 요소를 가질 수 없습니다.

bincount() 는 1차원 uint8 / uint16 입력에서 각 음이 아닌 정수의 출현 횟수를 셉니다:

histogram = np.bincount(np.array([0, 1, 1, 2, 2, 2], dtype=np.uint8))
# array([1, 2, 3], dtype=uint16)

Python 루프를 작성하지 않고 작은 정수 픽셀 값의 히스토그램을 구축하는 데 유용합니다.

6.12.3. 정렬과 재정렬

sort() 는 주어진 축(기본값은 마지막 축)을 따라 정렬된 배열의 복사본을 반환합니다. 제자리 정렬 버전을 원하면 배열에 직접 sort() 를 사용하십시오:

np.sort(np.array([3, 1, 2], dtype=np.float))
# array([1.0, 2.0, 3.0])

flip() 은 주어진 축을 따라 순서를 뒤집습니다(axis 를 전달하지 않으면 모든 축):

np.flip(np.array([1, 2, 3, 4]))
# array([4, 3, 2, 1])

roll() 은 주어진 개수만큼 요소를 순환 이동시킵니다. 링 버퍼 방식의 시프트 레지스터를 구현하는 데 유용합니다:

np.roll(np.array([1, 2, 3, 4]), 1)
# array([4, 1, 2, 3])

take() 는 팬시 인덱싱의 명시적 형태로, 임의의 인덱스에 있는 요소를 선택합니다:

a = np.array([10, 20, 30, 40, 50], dtype=np.uint8)
np.take(a, [0, 2, 4])
# array([10, 30, 50], dtype=uint8)

6.12.4. 필터링과 구조적 편집

compress() 는 불리언 인덱싱의 명시적 형태로, 불리언 조건으로 선택된 a 의 슬라이스를 반환합니다:

a = np.array([10, 20, 30, 40], dtype=np.uint8)
np.compress(a > 15, a)
# array([20, 30, 40], dtype=uint8)

delete() 는 주어진 인덱스의 항목을 제거한 복사본을 반환합니다:

a = np.array([10, 20, 30, 40, 50], dtype=np.uint8)
np.delete(a, [1, 3])
# array([10, 30, 50], dtype=uint8)

diff() 는 한 축을 따라 배열의 n차 이산 전진 차분을 반환합니다. 인접한 샘플 간의 1차 변화량을 계산하는 데 사용됩니다:

samples = np.array([1, 3, 6, 10, 15], dtype=np.float)
np.diff(samples)
# array([2.0, 3.0, 4.0, 5.0])

6.12.5. 각 연산의 비용

이 페이지의 거의 모든 함수는 새로 할당된 배열을 반환합니다. 두 가지 예외가 있습니다:

  • sort() 는 제자리에서 정렬하지만, 자유 함수 sort() 는 정렬된 복사본을 반환합니다.

  • take() 는 이미 존재하는 버퍼에 결과를 쓰기 위한 out= 키워드를 받아들입니다.

초당 여러 번 실행되는 루프에서는 제자리 정렬인 sort() 를 선호하고, 다른 모든 곳에서는 미리 할당된 버퍼를 재사용하십시오. 불리언 마스크 자체는 비교가 실행될 때마다 할당됩니다. 매 반복마다 마스크를 다시 만들기보다는 마스크를 한 번 만들어 여러 연산에 걸쳐 재사용하십시오.