[Pandas] Advanced Indexing - Boolean vector
2025.02.24 - [프로그래밍/Python 관련 정보] - [Pandas] Table of Contents
제가 pandas를 처음 사용할 때 정말 많이 에러(특히나 ambiguous error)가 발생했던 부분인데요. 이 방법은 Boolean vector나 함수(callable)를 이용하여 데이터를 선택하는 방법입니다. 핵심은 Boolean indexing은 True/False 값을 가진 시리즈 또는 배열을 인덱서로 사용한다는 것입니다.
1. 기본 select 방법
사실 말로는 크게 와 닿지 않을 것이기에 예시를 살펴볼까요?
import pandas as pd
# 샘플 데이터프레임
df = pd.DataFrame({
'A': [1, 2, 3, 4, 5],
'B': [5, 4, 3, 2, 1],
'C': ['a', 'b', 'c', 'd', 'e']
})
아마 다들 많이 알고 계시는 아래와 같은 방법이 이번 방법론의 가장 기초적인 적용법일 겁니다.
# 조건: A 열의 값이 3 이상인 행만 필터링
filtered_df = df[df['A'] >= 3]
print(filtered_df)
2. 함수를 활용한 select 방법
그런데 여기에 함수를 활용해 줄 수 있습니다. 함수가 True나 False를 반환하도록 하면 되겠지요.
# 함수 정의: A 열의 값이 짝수인 경우만 선택
def filter_even(x):
return x % 2 == 0
# A 열에 함수 적용
filtered_df = df[df['A'].apply(filter_even)]
print(filtered_df)
A B C
1 2 4 b
3 4 2 d
apply에서 살펴봤던 것처럼 당연히 열 방향의 적용외에도 행 방향으로 적용하는 것도 가능합니다.
# 사용자 정의 함수로 여러 조건을 결합하여 필터링
def custom_filter(row):
return row['A'] > 2 and row['B'] < 4
# 각 행에 대해 함수 적용
filtered_df = df[df.apply(custom_filter, axis=1)]
print(filtered_df)
A B C
2 3 3 c
3 4 2 d
3. Boolean vector 활용 select에 유용한 method
- isin : 특정 열의 값이 주어진 리스트(또는 배열)에 포함되어 있는지 확인하여 Boolean Series 반환
df[column].isin([value1, value2, ...])
- isnull/isna : 각 값이 NaN(결측치)인지 여부를 Boolean Series로 반환
- nota : 결측치가 아닌 값을 True로 표시하는 Boolean Series 반환
- duplicated : 중복된 행을 True로 표시하는 Boolean Series 반환
꼭 수동으로 입력된 list만 들어가는 것은 아닙니다 .
def find_managers(employee: pd.DataFrame) -> pd.DataFrame:
managers = employee.groupby(
'managerId', as_index=False
).agg(
reporting=('id', 'count'),
).query(
'5 <= reporting'
)['managerId']
return employee[
employee['id'].isin(managers)
][['name']]
와 같이 다른 series가 들어갈 수도 있습니다. (SQL에서 in 도 비슷하게 사용되었던 것을 기억해보시면 좋을거 같습니다)
df.duplicated(subset=None, keep='first')
keep='first'
'first': 첫 번째 값은 유지하고 이후 중복을 True로 처리.
'last': 마지막 값만 유지하고 앞의 중복을 True로 처리.
False: 모든 중복값을 True로 처리.
- between : 숫자 범위를 기준으로 Boolean Series 반환
df[column].between(lower, upper, inclusive='both')
- where/mask : 조건을 만족하지 않는 값/만족하는 값을 NaN으로 대체한 Boolean Series 반환
df = pd.DataFrame({'A': [1, 2, 3, 4, 5]})
# A 열 값이 3 이상인 경우만 유지, 나머지는 NaN
filtered_df = df.where(df['A'] >= 3)
print(filtered_df)
A
0 NaN
1 NaN
2 3.0
3 4.0
4 5.0
df = pd.DataFrame({'A': [1, 2, 3, 4, 5]})
# A 열 값이 3 이상인 경우 NaN으로 변경
filtered_df = df.mask(df['A'] >= 3)
print(filtered_df)
A
0 1.0
1 2.0
2 NaN
3 NaN
4 NaN
4. Ambiguous Error의 발생 원인
그런데 서두에 말씀드린바와 같이 이러한 방식으로 인덱싱을 적용하려 할 때 ambiguous error가 종종 발생하기도 합니다. 그런 에러가 발생하는 대표적인 2가지 이유를 짚어보고 가겠습니다.
A. 여러 조건을 결합할 때, 각 조건을 괄호로 감싸지 않은 경우 연산자의 우선순위가 명확하지 않아서 에러 발생
# 조건: A 열이 3 이상이고 B 열이 3 이하인 행 필터링
df_filtered = df[df['A'] >= 3 & df['B'] <= 3]
올바르게 고쳐주려면
# 괄호로 조건식 감싸기
df_filtered = df[(df['A'] >= 3) & (df['B'] <= 3)]
print(df_filtered)
A B
2 3 3
B. Pandas에서 series와 series 간 비교는 각각의 원소별로 반환되기 때문에 이를 바로 if 나 다른 비교 연산에 사용할 경우 에러 발생
import pandas as pd
# 예시 데이터프레임
df = pd.DataFrame({
'A': [1, 2, 3, 4],
'B': [4, 3, 2, 1]
})
# 조건: A열이 2보다 크고 B열이 3보다 작은 행을 선택하려고 할 때
filtered_df = df[(df['A'] > 2) and (df['B'] < 3)]
print(filtered_df.head())
The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
df['A'] > 2와 df['B'] < 3은 각각 Boolean Series로 반환됩니다. 그런데 and나 if와 같은 연산들은 스칼라(단일 값)에서만 사용할 수 있기 때문에 Series 를 비교할 때는 & 연산자를 사용해야 합니다.
==> not 대신 ~, and 대신 &, or 대신 |
참고) all(), any() 함수
만일 Boolean Series에 and, if와 같은 연산들을 굳이 적용하고싶다면 위에서 살펴본 것처럼 함수를 만든 뒤 apply를 적용하거나 all(), any()같은 Boolean reduction을 활용하는 방법이 있습니다. Boolean Series에 이런 함수를 적용하면 Boolean이 되어 if, and 등의 연산이 적용가능해집니다.
import pandas as pd
df = pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]})
# all() 사용: 모든 요소가 조건을 만족하는지 확인
if (df['A'] > df['B']).all():
print("All values in A are greater than B")
elif (df['A'] > df['B']).any():
print("Some values in A are greater than B")
else:
print("No values in A are greater than B")
# 특정 조건에 맞는 행 선택
result = df[df['A'] > 2]
print(result)