본문 바로가기
Data Analysis Basic

pandas의 boolean indexing 개념과 팁

by mjk0618 2023. 9. 20.

판다스(pandas) 라이브러리를 사용하여 데이터 분석을 할 때, 데이터프레임에서 원하는 정보만을 추출해야 하는 상황은 항상 발생합니다. 판다스 데이터프레임에서 데이터를 선택하는 방법은 다양한데, 그 중에서도 특정 조건을 만족하는 데이터만을 선택하는 boolean indexing 기법은 매우 유용하고 자주 사용됩니다. 판다스에서 데이터를 선택하고 필터링하는 자세한 방법에 대해서는 공식문서를 참고해주세요. 이 글에서는 boolean indexing 개념에 대해서 알아보고 이와 관련한 몇 가지 팁을 소개하겠습니다. 글에서 pd는 pandas 라이브러리를 불러올때 사용하는 별칭입니다.

 

boolean indexing 개념

boolean indexing은 boolean vector를 사용하여 데이터를 필터링하는 연산입니다. 쉽게 표현하면, 주어진 조건에 만족하는 데이터만을 추출한다는 의미입니다. 간단한 예시를 코드를 통해 살펴보겠습니다. 여기서 사용하는 포켓몬 데이터는 링크에서 다운로드받을 수 있습니다.

 

 

raw_data.head(10) 를 실행하여 위와 같이 처음 10개의 데이터를 확인하였습니다. 먼저 boolean vector의 개념을 알아보기 위해 다음 코드를 실행합니다.

 

>>> raw_data["Type 1"] == "Grass"

0       True
1       True
2       True
3       True
4      False
       ...  
795    False
796    False
797    False
798    False
799    False
Name: Type 1, Length: 800, dtype: bool

 

boolean vector은 위처럼 boolean 값으로 이루어진 배열을 의미합니다. 여기서는 출력 타입이 pd.Series이지만 pandas는 결국 NumPy를 기반으로 하기 때문에 본체는 배열이라고 볼 수 있습니다. boolean vector가 무엇인지 알았으니 boolean indexing에 대해서도 자연스럽게 이해할 수 있습니다. 결국 위와 같은 배열을 index로 사용하여 데이터를 추출한다는 것입니다. 파이썬에서 순환 가능한 객체의 특정 인덱스에 [대괄호]를 사용하여 접근할 수 있기 때문에 다음과 같이 boolean indexing을 수행할 수 있습니다.

 

raw_data[raw_data["Type 1"] == "Grass"]

 

위와 같은 코드로 Type 1 컬럼의 값이 Grass인 포켓몬만을 추출하였습니다. 언급한 개념을 정리하면 각 데이터에 대해서 주어진 조건을 만족하는지를 확인한 결과(boolean 값)를 담은 배열을 인덱스로 전달하여, 값이 True인 데이터만을 필터링한 것입니다.

 

boolean indexing은 masking이라고 부르기도 하는데, 조건식이 복잡해질 경우 다음과 같이 mask 변수에 미리 조건식을 저장해 두는 방법을 사용할 수 있습니다.

 

is_type_grass_mask = (raw_data["Type 1"] == "Grass")
raw_data[is_type_grass_mask]

 

중복된 데이터를 필터링하기

이제 boolean indexing을 응용하여 중복 데이터를 필터링하는 방법을 알아보겠습니다. 현재 데이터는 중복된 데이터가 없으므로 다음 코드를 사용하여 임의로 중복된 데이터를 추가해주었습니다.

 

duplicated_data = raw_data.sample(frac=0.01, random_state=42)
concat_data = pd.concat([raw_data, duplicated_data]).sample(frac=1, random_state=42).reset_index(drop=True)

 

pd.sample 메서드는 frac 파라미터의 값에 해당하는 비율만큼 데이터를 임의로 샘플링합니다. 코드에서는 전체 데이터의 1%만큼을 임의로 샘플링한 후 pd.concat 메서드를 사용하여 원본 데이터와 연결하였습니다.

 

>>> concat_data.duplicated()

0      False
1      False
2      False
3      False
4      False
       ...  
803    False
804    False
805    False
806    False
807    False
Length: 808, dtype: bool

>>> concat_data.duplicated().unique()
array([False,  True])

 

위 코드와 같이 pd.duplicated 메서드를 사용하면 데이터의 중복 여부를 체크한 배열을 반환합니다. 이제 이 배열을 boolean vector로 사용하여 인덱스로 전달하면 중복된 데이터만을 추출하여 확인할 수 있습니다.

 

여러 개의 조건식을 사용하기

boolean indexing, 또는 masking을 사용할 때는 당연히 여러 개의 조건의 만족 여부를 확인할 수도 있습니다. 이 때 주의할 점이 두 가지가 있는데, 첫 번째는 논리 연산자인 and나 or 대신 비트 연산자인 &나 | 를 사용해야 합니다. 두 번째는 각 조건식을 괄호로 묶어주어야 합니다. 이에 대해서 조금 더 자세히 알아보겠습니다.

 

비트연산자를 사용하는 이유

논리 연산자(and, or)은 양 변에 하나의 boolean 값이 놓일 것을 기대하는데, 우리가 전달할 객체는 boolean vector이기 때문입니다. 따라서 비트 연산자를 사용하여 배열의 각 원소별로 논리 연산을 수행해야 합니다. 실제로 and 연산자를 사용하여 두 가지 조건을 한 번에 확인하려고 하면, 다음과 같은 에러가 발생합니다.

 

>>> is_type_grass_mask = (raw_data["Type 1"] == "Grass")
>>> is_legendary_mask = (raw_data["Legendary"] == True)
>>> raw_data[is_type_grass_mask and is_legendary_mask]

ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().

 

조건식을 괄호로 묶는 이유

파이썬의 연산자는 종류에 따라 우선순위가 다릅니다. 아래의 표는 파이썬 공식문서에서 설명하는 operation precedence입니다. 자세한 내용은 링크를 참고해주세요.

Operator

Description

(expressions...),
[expressions...], {key: value...}, {expressions...}
Binding or parenthesized expression, list display, dictionary display, set display
x[index], x[index:index], x(arguments...), x.attribute Subscription, slicing, call, attribute reference
await x Await expression
** Exponentiation
+x, -x, ~x Positive, negative, bitwise NOT
*, @, /, //, % Multiplication, matrix multiplication, division, floor division, remainder
+, - Addition and subtraction
<<, >> Shifts
& Bitwise AND
^ Bitwise XOR
| Bitwise OR
in, not in, is, is not, <, <=, >, >=, !=, == Comparisons, including membership tests and identity tests
not x Boolean NOT
and Boolean AND
or Boolean OR
ifelse Conditional expression
lambda Lambda expression
:= Assignment expression

 

핵심은 비교 연산자보다 비트 연산자가 우선 순위가 높기 때문에 조건식이 의도한 대로 동작하지 않거나, 예상치 못한 에러가 발생할 수 있습니다. 따라서 각 조건식은 반드시 괄호로 감싸주어야 합니다. 예를 들어서 다음 코드에서, 첫째 줄은 의도한 대로 작동하지만, 두 번째 줄에서는 오류가 발생합니다. 이런 오류를 피하기 위해서는 앞서 언급했듯이 미리 mask를 정의한 후 boolean indexing을 수행하는 것도 한 가지 방법입니다.

 

raw_data[(raw_data["Type 1"] == "Grass") & (raw_data["Legendary"] == True)]  # 정상 작동
raw_data[raw_data["Type 1"] == "Grass" & raw_data["Legendary"] == True]  # 오류 발생


# mask를 정의하여 boolean indexing을 수행
is_type_grass_mask = raw_data["Type 1"] == "Grass"
is_legendary_mask = raw_data["Legendary"] == True
raw_data[is_type_grass_mask & is_legendary_mask]

 

 

'Data Analysis Basic' 카테고리의 다른 글

pandas 실행시간 최적화하기  (0) 2023.09.20

댓글