프로그래밍/Python 관련 정보

[Pandas] Advanced Indexing - Boolean vector

물박사의 저장공간 2025. 2. 23. 14:52

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만 들어가는 것은 아닙니다 .

https://leetcode.com/problems/managers-with-at-least-5-direct-reports/solutions/3872861/pandas-vs-sql-elegant-short-all-30-days-of-pandas-solutions/?envType=study-plan-v2&envId=30-days-of-pandas&lang=pythondata

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)