Python Numpy 강좌 : 제 19강 - 고유값 및 집합 연산
고유값(Unique)
import numpy as np
array = np.array([3, 1, 2, 3, 1, 3, 5])
print(np.unique(array))
values, counts = np.unique(array, return_counts=True)
print(values)
print(counts)- 결과
- [1 2 3 5]
[1 2 3 5]
[2 1 3 1]
고유값 함수(np.unique)는 배열에서 중복을 제거한 원소를 오름차순으로 정렬해 반환합니다.
np.unique(배열, return_index=False, return_inverse=False, return_counts=False, axis=None)으로 고유값을 계산합니다.
return_counts=True로 지정하면 각 고유값이 몇 번 등장했는지를 담은 배열을 함께 반환합니다.
예제에서 1은 두 번, 2는 한 번, 3은 세 번, 5는 한 번 등장하므로 [2 1 3 1]을 반환합니다.
반환값을 추가로 요청하면 결과는 튜플로 반환되므로, values, counts = ...처럼 나누어 할당합니다.
고유값 색인
import numpy as np
array = np.array([3, 1, 2, 3, 1, 3, 5])
values, index, inverse = np.unique(array, return_index=True, return_inverse=True)
print(index)
print(inverse)
print(values[inverse])- 결과
- [1 2 0 6]
[2 0 1 2 0 2 3]
[3 1 2 3 1 3 5]
return_index=True로 지정하면 각 고유값이 원본 배열에서 처음 나타나는 색인을 반환합니다.
고유값 [1 2 3 5]는 원본 배열의 1, 2, 0, 6번 색인에서 처음 등장합니다.
return_inverse=True로 지정하면 원본 배열의 각 원소가 고유값 배열의 몇 번째 값인지를 반환합니다.
원본의 첫 번째 원소 3은 고유값 배열의 2번 색인에 있으므로 inverse의 첫 번째 값은 2입니다.
그러므로 values[inverse]는 원본 배열을 그대로 복원합니다.
- Tip :
inverse는 문자열이나 범주형 데이터를 정수 번호로 변환할 때 유용합니다.["사과", "배", "사과"]의inverse는[1, 0, 1]처럼 각 범주에 번호를 매긴 결과가 됩니다.
최빈값과 빈도
import numpy as np
array = np.array([3, 1, 2, 3, 1, 3, 5])
values, counts = np.unique(array, return_counts=True)
print(values[np.argmax(counts)])
print(dict(zip(values.tolist(), counts.tolist())))- 결과
- 3
{1: 2, 2: 1, 3: 3, 5: 1}
고유값과 빈도를 함께 사용하면 가장 많이 등장한 값(최빈값)을 계산할 수 있습니다.
np.argmax(counts)는 빈도가 가장 큰 위치를 반환하므로, 이 색인으로 values를 조회하면 최빈값인 3을 얻습니다.
빈도가 같은 값이 여러 개라면 np.argmax는 처음 나타나는 위치를 반환하므로, 가장 작은 값이 최빈값으로 선택됩니다.
zip 함수로 두 배열을 묶으면 값: 빈도 형태의 사전(dict)으로 변환할 수 있습니다. tolist()는 numpy 자료형을 파이썬 기본 자료형으로 변환합니다.
- Tip : 배열이
0이상의 정수로만 구성되어 있다면np.bincount(배열)로 각 정수의 빈도를 더 빠르게 계산할 수 있습니다.
다차원 고유값
import numpy as np
array = np.array([
[1, 2],
[3, 4],
[1, 2],
[5, 6]
])
print(np.unique(array))
print(np.unique(array, axis=0))- 결과
- [1 2 3 4 5 6]
[[1 2]
[3 4]
[5 6]]
축(axis)을 지정하지 않으면 다차원 배열을 1차원으로 펼친 다음 고유값을 계산합니다.
axis=0으로 지정하면 행 전체를 하나의 단위로 비교해, 중복된 행을 제거합니다.
예제에서 [1, 2] 행이 두 번 등장하므로, 한 번만 남기고 제거됩니다.
고유값 전용 함수
import numpy as np
array = np.array([3, 1, 2, 3, 1, 3, 5])
result = np.unique_counts(array)
print(result.values, result.counts)
print(np.unique_values(np.array([3, 1, 2, 3])))- 결과
- [1 2 3 5] [2 1 3 1]
[3 1 2]
NumPy 2.0부터 반환값의 종류에 따라 나뉜 고유값 전용 함수가 추가되었습니다.
| 함수 | 반환 |
|---|---|
| np.unique_values | 고유값 |
| np.unique_counts | 고유값과 빈도 (values, counts) |
| np.unique_inverse | 고유값과 역색인 (values, inverse_indices) |
| np.unique_all | 고유값, 첫 색인, 역색인, 빈도 |
전용 함수는 이름이 있는 튜플을 반환하므로 result.values, result.counts처럼 속성으로 접근할 수 있습니다.
단, np.unique_values는 NumPy 2.3부터 정렬을 사용하지 않는 더 빠른 알고리즘으로 바뀌어, 예제처럼 결과가 정렬되지 않을 수 있습니다. 정렬된 결과가 필요하다면 np.unique를 사용합니다.
- Tip :
np.unique에도sorted매개변수가 추가되었습니다. 순서가 상관없다면np.unique(배열, sorted=False)로 정렬을 생략해 계산 속도를 높일 수 있습니다.
집합 연산(Set Operations)
import numpy as np
a = np.array([1, 2, 3, 4, 5])
b = np.array([4, 5, 6, 7])
print(np.intersect1d(a, b))
print(np.union1d(a, b))
print(np.setdiff1d(a, b))
print(np.setdiff1d(b, a))
print(np.setxor1d(a, b))- 결과
- [4 5]
[1 2 3 4 5 6 7]
[1 2 3]
[6 7]
[1 2 3 6 7]
numpy는 두 배열을 집합으로 간주해 계산하는 집합 연산 함수를 제공합니다.
| 함수 | 연산 | 의미 |
|---|---|---|
| np.intersect1d(a, b) | 교집합 | a와 b에 모두 있는 값 |
| np.union1d(a, b) | 합집합 | a나 b 중 하나라도 있는 값 |
| np.setdiff1d(a, b) | 차집합 | a에는 있지만 b에는 없는 값 |
| np.setxor1d(a, b) | 대칭 차집합 | a와 b 중 한쪽에만 있는 값 |
모든 집합 연산 함수는 결과에서 중복을 제거하고 오름차순으로 정렬한 1차원 배열을 반환합니다.
차집합은 순서에 따라 결과가 달라집니다. np.setdiff1d(a, b)는 [1 2 3]을, np.setdiff1d(b, a)는 [6 7]을 반환합니다.
함수 이름의 1d는 입력 배열이 다차원이라도 1차원으로 펼친 다음 계산한다는 의미입니다.
- Tip : 두 입력 배열에 중복이 없다는 것을 이미 알고 있다면
assume_unique=True를 지정해 중복 제거 과정을 생략하고 계산 속도를 높일 수 있습니다. 중복이 있는 배열에 사용하면 잘못된 결과가 반환될 수 있습니다.
교집합 색인
import numpy as np
a = np.array([3, 1, 2, 3])
b = np.array([2, 3, 3, 4])
values, indexA, indexB = np.intersect1d(a, b, return_indices=True)
print(values)
print(indexA)
print(indexB)- 결과
- [2 3]
[2 0]
[0 1]
np.intersect1d에 return_indices=True를 지정하면 교집합의 값이 각 배열의 어느 색인에 있는지를 함께 반환합니다.
교집합 [2 3]에서 2는 a의 2번 색인과 b의 0번 색인에, 3은 a의 0번 색인과 b의 1번 색인에 있습니다.
값이 여러 번 등장한다면 처음 나타나는 색인을 반환합니다.
두 데이터에서 공통된 아이디를 찾고, 각 데이터의 해당 행을 가져오는 것처럼 두 배열을 연결할 때 사용합니다.
포함 여부(Isin)
import numpy as np
array = np.array([
[1, 2, 3],
[4, 5, 6]
])
target = [2, 4, 6]
mask = np.isin(array, target)
print(mask)
print(array[mask])
print(np.isin(array, target, invert=True))- 결과
- [[False True False]
[ True False True]]
[2 4 6]
[[ True False True]
[False True False]]
포함 여부 함수(np.isin)는 배열의 각 원소가 목록에 포함되어 있는지를 불(bool) 배열로 반환합니다.
np.isin(배열, 목록, invert=False)로 포함 여부를 계산합니다.
집합 연산 함수와 달리, 반환되는 배열은 원본 배열과 같은 형태를 유지합니다.
그러므로 반환된 불 배열을 그대로 불리언 인덱싱에 사용하거나, np.where의 조건식으로 사용할 수 있습니다.
invert=True로 지정하면 목록에 포함되지 않은 원소가 True가 됩니다. 이는 ~np.isin(배열, 목록)과 같은 결과이지만, 더 효율적으로 계산됩니다.
- Tip : 파이썬의
in연산자는 배열 전체에 대해 하나의 참/거짓만 반환합니다. 원소마다 포함 여부를 확인하려면np.isin을 사용합니다. 과거에 사용하던np.in1d는NumPy 2.0에서 사용 중단이 예고되었으며, 최신 버전에서는 제거되어AttributeError가 발생합니다.
결측값의 고유값
import numpy as np
array = np.array([1.0, np.nan, 2.0, np.nan])
print(np.unique(array))
print(np.unique(array, equal_nan=False))- 결과
- [ 1. 2. nan]
[ 1. 2. nan nan]
NaN은 자기 자신과도 같지 않은 특수한 값이므로, 고유값을 계산할 때 별도의 규칙이 적용됩니다.
np.unique의 equal_nan 매개변수의 기본값은 True이며, 여러 개의 NaN을 하나의 고유값으로 취급합니다.
equal_nan=False로 지정하면 모든 NaN을 서로 다른 값으로 취급해, 등장한 횟수만큼 결과에 포함됩니다.
댓글 남기기