Python. NumPy 인덱싱,필터링 체계
1. 기본 인덱싱: 정수 인덱싱
특정 위치 하나를 직접 지정하는 방식입니다.
a = np.array([10, 20, 30, 40])
a[2]
# 30
2차원 배열에서는 보통 [행, 열] 구조입니다.
A = np.array([
[10, 11, 12],
[20, 21, 22],
[30, 31, 32],
])
A[1, 2]
# 22
해석:
A[1, 2]
1 -> 1번 행
2 -> 2번 열
즉 좌표로 보면 (1, 2) 위치의 값을 가져오는 것입니다.
2. 슬라이스 인덱싱
:를 이용해서 범위를 선택하는 방식입니다.
a[1:3]
은 내부적으로 대략:
a[slice(1, 3, None)]
과 비슷합니다.
예:
a = np.array([10, 20, 30, 40])
a[1:3]
# array([20, 30])
stop은 포함되지 않으므로 1:3은 인덱스 1, 2를 선택합니다.
2차원에서는 축마다 슬라이스를 줄 수 있습니다.
A[:, 1]
해석:
: -> 모든 행
1 -> 1번 열
결과:
array([11, 21, 31])
다른 예:
A[1, :] # 1번 행의 모든 열
A[:2, :] # 0~1번 행, 모든 열
A[:, 1:3] # 모든 행, 1~2번 열
A[:2, 1:3] # 0~1번 행, 1~2번 열
정리하면:
: -> 해당 축 전체
:3 -> 처음부터 3 전까지
1:3 -> 1부터 3 전까지
::2 -> 처음부터 끝까지 2칸씩
[::-1] -> 역순
3. :는 와일드카드가 아니라 slice 표기
예를 들어:
A[:, 1]
에서 :는 와일드카드처럼 보이지만, 정확히는 해당 축 전체를 선택하는 슬라이스입니다.
:
는 내부적으로:
slice(None, None, None)
에 해당합니다.
그래서:
A[:, 1]
은 개념적으로:
A.__getitem__((slice(None, None, None), 1))
처럼 이해할 수 있습니다.
A[::, 1]도 됩니다.
A[::, 1]
은 A[:, 1]과 같습니다. ::는 start, stop, step을 모두 생략한 슬라이스입니다.
4. 불리언 인덱싱 / 불리언 마스크
조건에 맞는 원소만 골라내는 방식입니다.
a = np.array([10, 20, 30, 40])
a[a > 20]
# array([30, 40])
여기서:
a > 20
의 결과는:
array([False, False, True, True])
입니다. 이 배열이 불리언 마스크입니다.
즉:
a[[False, False, True, True]]
는:
False -> 제외
False -> 제외
True -> 선택
True -> 선택
이라는 뜻입니다.
따라서:
a[[True, False, True, False]]
# array([10, 30])
여기서 [True, False, True, False]는 슬라이스가 아닙니다. 파이썬 리스트이거나 NumPy bool 배열이고, NumPy 인덱싱 문맥에서는 불리언 마스크로 해석됩니다.
조건을 조합할 수도 있습니다.
a[(a > 10) & (a < 40)]
# array([20, 30])
주의:
(a > 10) & (a < 40) # 맞음
(a > 10) and (a < 40) # NumPy에서는 보통 에러
NumPy 배열 조건 조합에서는 and, or, not 대신 &, |, ~를 씁니다.
a[(a < 20) | (a > 30)]
a[~(a > 20)]
5. 정수 배열 인덱싱 / fancy indexing
원하는 위치 번호를 직접 나열해서 선택하는 방식입니다.
a = np.array([10, 20, 30, 40])
a[[0, 2]]
# array([10, 30])
여기서 [0, 2]는 슬라이스가 아닙니다.
“0번 원소와 2번 원소를 가져와라”라는 정수 인덱스 목록입니다.
2차원에서도 쓸 수 있습니다.
A[[0, 2], :]
해석:
0번 행과 2번 행, 모든 열
A[:, [0, 2]]
해석:
모든 행, 0번 열과 2번 열
주의할 점:
A[[0, 2], [1, 2]]
이건 행과 열의 조합 전체를 가져오는 게 아니라, 좌표쌍으로 선택합니다.
(0, 1)
(2, 2)
즉 두 원소만 가져옵니다.
부분 행렬을 원하면 np.ix_를 씁니다.
A[np.ix_([0, 2], [1, 2])]
이건:
0번, 2번 행
1번, 2번 열
의 모든 조합을 가져옵니다.
6. 좌표 기반 선택
좌표 기반 선택은 값을 바로 고르는 것이 아니라, 먼저 조건을 만족하는 위치를 좌표로 찾고, 그 좌표를 이용해서 값을 선택하는 방식입니다.
예를 들어:
A = np.array([
[0, 10, 0],
[20, 0, 30],
])
이 배열의 좌표는 다음과 같습니다.
값: 0 10 0
좌표: (0,0) (0,1) (0,2)
값: 20 0 30
좌표: (1,0) (1,1) (1,2)
0이 아닌 값은:
10 -> (0, 1)
20 -> (1, 0)
30 -> (1, 2)
에 있습니다.
np.nonzero
rows, cols = np.nonzero(A)
rows
# array([0, 1, 1])
cols
# array([1, 0, 2])
이 둘을 짝지으면:
(0, 1)
(1, 0)
(1, 2)
입니다.
이 좌표를 다시 배열에 넣으면 값을 가져올 수 있습니다.
A[rows, cols]
# array([10, 20, 30])
즉:
rows, cols = np.nonzero(A)
A[rows, cols]
는 “0이 아닌 값들의 좌표를 찾아서, 그 위치의 값을 가져와라”입니다.
np.argwhere
np.argwhere는 좌표를 더 직관적으로 보여줍니다.
np.argwhere(A)
결과:
array([
[0, 1],
[1, 0],
[1, 2]
])
즉:
0이 아닌 값은 (0,1), (1,0), (1,2)에 있다
라는 뜻입니다.
비교하면:
A[A != 0]
는 조건에 맞는 값 자체를 가져옵니다.
array([10, 20, 30])
반면:
np.argwhere(A != 0)
는 조건에 맞는 값의 위치를 가져옵니다.
array([
[0, 1],
[1, 0],
[1, 2]
])
따라서 이렇게 구분하면 됩니다.
불리언 필터링:
조건에 맞는 값을 바로 가져옴
좌표 기반 선택:
조건에 맞는 위치를 먼저 찾음
그 위치를 이용해서 값을 가져올 수 있음
7. np.where
np.where는 두 가지 방식으로 자주 쓰입니다.
조건을 만족하는 위치 찾기
a = np.array([10, 20, 30, 40])
np.where(a > 20)
# (array([2, 3]),)
조건을 만족하는 위치가 2, 3이라는 뜻입니다.
그 위치를 이용해서 값을 가져올 수 있습니다.
a[np.where(a > 20)]
# array([30, 40])
조건에 따라 값 바꾸기
np.where(a > 20, "big", "small")
# array(['small', 'small', 'big', 'big'], dtype='<U5')
해석:
a > 20이면 "big"
아니면 "small"
이 경우는 필터링보다는 조건부 변환에 가깝습니다.
8. ... / Ellipsis 인덱싱
차원이 많은 배열에서 중간의 여러 :를 생략할 때 씁니다.
x[..., 0]
예를 들어 x가 3차원 배열이면:
x[:, :, 0]
과 비슷합니다.
x가 4차원 배열이면:
x[:, :, :, 0]
과 비슷합니다.
즉:
... -> 필요한 만큼 :를 채워 넣는 표현
예:
x[0, ...] # 첫 번째 축은 0번, 나머지는 전부
x[..., 0] # 마지막 축은 0번, 앞의 축들은 전부
9. None / np.newaxis
새 축을 추가하는 인덱싱입니다. 값을 필터링한다기보다는 배열의 모양을 바꿉니다.
a = np.array([10, 20, 30])
a.shape
# (3,)
a[:, None]
결과 모양:
(3, 1)
즉 세로 벡터처럼 됩니다.
array([
[10],
[20],
[30]
])
반대로:
a[None, :]
결과 모양:
(1, 3)
즉 가로 벡터처럼 됩니다.
array([[10, 20, 30]])
np.newaxis는 None과 같습니다.
a[:, np.newaxis]
10. 멤버십 필터링: np.isin
여러 값 중 특정 값들만 고르고 싶을 때 씁니다.
a = np.array([10, 20, 30, 40, 50])
np.isin(a, [20, 40])
# array([False, True, False, True, False])
이 결과는 불리언 마스크입니다.
a[np.isin(a, [20, 40])]
# array([20, 40])
반대로 제외하려면 ~를 씁니다.
a[~np.isin(a, [20, 40])]
# array([10, 30, 50])
11. NaN / inf 필터링
결측값이나 무한대를 제외할 때 자주 씁니다.
a = np.array([1.0, np.nan, 3.0, np.inf])
a[~np.isnan(a)]
# array([ 1., 3., inf])
NaN만 제외합니다.
유한한 값만 남기려면:
a[np.isfinite(a)]
# array([1., 3.])
이건 NaN, inf, -inf를 제외합니다.
12. 축 기준 선택 함수: take, compress
np.take
정수 인덱스 목록으로 가져오는 함수형 방식입니다.
a = np.array([10, 20, 30, 40])
np.take(a, [0, 2])
# array([10, 30])
2차원에서는 축을 지정할 수 있습니다.
np.take(A, [0, 2], axis=0)
0번 행과 2번 행을 가져옵니다.
np.take(A, [0, 2], axis=1)
0번 열과 2번 열을 가져옵니다.
np.compress
불리언 마스크로 특정 축을 선택합니다.
np.compress([True, False, True], A, axis=0)
행 기준으로 0번 행과 2번 행을 선택합니다.
np.compress([True, False, True], A, axis=1)
열 기준으로 0번 열과 2번 열을 선택합니다.
13. 전체 분류 요약
NumPy 인덱싱/필터링은 크게 이렇게 나눌 수 있습니다.
1. 정수 인덱싱
a[2], A[1, 2]
2. 슬라이스 인덱싱
a[1:3], A[:, 1]
3. 불리언 인덱싱 / 불리언 마스크
a[a > 20], A[A != 0]
4. 정수 배열 인덱싱 / fancy indexing
a[[0, 2]], A[:, [0, 2]]
5. 좌표 기반 선택
np.nonzero, np.argwhere, np.where
6. Ellipsis
x[..., 0]
7. 새 축 추가
a[:, None], a[np.newaxis, :]
8. 멤버십 필터링
np.isin
9. 결측값 / 무한대 필터링
np.isnan, np.isfinite
10. 축 기준 선택 함수
np.take, np.compress
11. 부분 행렬 조합
np.ix_