본문으로 건너뛰기
김신건의 로그

[Pandas] Boolean Indexing

· 수정 · 📖 약 1분 · 577자/단어 #python #pandas #selection #filter #boolean-mask
Pandas Boolean Indexing, pandas 마스킹, pandas 조건 필터, pandas bool 인덱싱

정의

Boolean IndexingTrue/False 의 Series 를 인덱서로 전달 해 행을 선택하는 패턴. pandas 의 가장 흔한 필터링 방법.

mask = df['age'] > 30      # bool Series
df[mask]                    # True 인 행만
df.loc[mask]                # 동등

마스크 생성과 적용 흐름

flowchart LR
    DF["DataFrame"] --> COMP["비교 연산"]
    COMP --> MASK["bool Series"]

    MASK -->|"단일 마스크"| RESULT["df[mask]"]
    MASK2["마스크 A"] -->|"&"| COMB["복합 마스크"]
    MASK3["마스크 B"] -->|"&"| COMB
    MASK4["마스크 C"] -->|"|"| COMB
    NOT["~마스크 A"] --> COMB
    COMB --> RESULT

    RESULT --> OUT["필터된 DataFrame"]

기본

python
import pandas as pd
df = pd.DataFrame({
  'name': ['Alice', 'Bob', 'Charlie', 'Dave'],
  'age': [25, 30, 35, 40],
})

mask = df['age'] >= 30
print(mask.tolist())
print(df[mask])
결과
[False, True, True, True]
    name  age
1      Bob   30
2  Charlie   35
3     Dave   40
nameage
1Bob30
2Charlie35
3Dave40

복합 조건

df[(df['age'] > 25) & (df['age'] < 40)]    # AND
df[(df['city'] == 'Seoul') | (df['city'] == 'Busan')]    # OR
df[~(df['city'] == 'Seoul')]               # NOT

IMPORTANT

and / or / not 대신 & / | / ~ 를 써야 한다. Python 키워드는 single boolean 만 처리, pandas 는 array 비교라 비트연산자를 사용. 각 조건은 괄호로 감싼다 (연산자 우선순위 때문).

& vs and, | vs or, ~ vs not 심화

and / or 가 안 되는가?

# Python 의 and 는 두 값 중 하나를 통째로 반환
True and False    # → False (스칼라)

# pandas Series 에는 진리값이 없음
bool(df['age'] > 30)  # ValueError: ambiguous truth value

bool() 로 변환할 때 Series 원소가 여러 개라 단일 bool 을 확정할 수 없다.

연산자동작pandas 에서
and단일 bool 반환ValueError
&원소별 비트 AND작동 ✅
or단일 bool 반환ValueError
|원소별 비트 OR작동 ✅
not단일 bool 반환ValueError
~원소별 비트 NOT작동 ✅

우선순위 함정

비트 연산자 & 는 비교 연산자 >, <, == 보다 우선순위가 높다.

# ❌ 잘못된 코드: 1 & df['b'] 가 먼저 계산됨
df[df['a'] > 1 & df['b'] > 2]

# ✓ 올바른 코드: 각 조건을 괄호로 묶는다
df[(df['a'] > 1) & (df['b'] > 2)]

isin / isna / notna / between

df[df['city'].isin(['Seoul', 'Busan'])]
df[df['age'].notna()]
df[df['age'].isna()]
df[df['age'].between(25, 35)]      # 양쪽 inclusive
df[df['age'].between(25, 35, inclusive='left')]   # [25, 35)

Pandas isin / isna 참고.

행 + 컬럼 동시 필터

df.loc[df['age'] > 30, 'salary']                 # Series
df.loc[df['age'] > 30, ['name', 'salary']]       # DataFrame
df.loc[df['age'] > 30, 'salary'] = df.loc[df['age'] > 30, 'salary'] * 1.1

조건부 갱신 (where / mask)

df['salary'] = df['salary'].where(df['age'] > 30, df['salary'] * 0.9)
# age > 30 이면 그대로, 아니면 * 0.9

df['salary'] = df['salary'].mask(df['age'] < 25, 0)
# age < 25 인 경우 0 으로

.where조건이 True 면 유지, False 면 치환. .mask 는 반대.

numpy.where 패턴

import numpy as np
df['category'] = np.where(df['age'] > 30, 'senior', 'junior')

벡터화된 if-else, 빠르다.

query() 대안

query() 는 문자열 표현식으로 필터링. 복잡한 &/| 체인보다 가독성이 좋을 때 유용.

# 동등한 표현
df[(df['age'] > 25) & (df['city'] == 'Seoul')]
df.query("age > 25 and city == 'Seoul'")

# 변수 참조는 @ 접두사
threshold = 30
df.query("age > @threshold")

# 여러 조건
df.query("age > 25 and city in ['Seoul', 'Busan'] and salary < 5000")

TIP

query() 는 내부적으로 numexpr 를 사용 가능해 대용량 DataFrame 에서 더 빠를 수 있다. 단, 복잡한 Python 함수 호출이나 메서드 체인은 지원 안 함.

query() vs Boolean Indexing 비교

항목Boolean Indexingquery()
가독성복잡 조건 시 장황간결
성능기본numexpr 활용 시 더 빠름
컬럼명제한 없음예약어 컬럼명에 backtick 필요
외부 변수직접 참조@var 필요
복잡한 메서드.str.contains() 사용 가능제한적
# 컬럼명이 예약어인 경우 backtick 사용
df.query("`class` == 'A'")

# str 메서드는 boolean indexing 이 편함
df[df['name'].str.startswith('A')]

실전 패턴

여러 조건 분기 처리 (np.select)

import numpy as np

conditions = [
    (df['age'] < 18),
    (df['age'] >= 18) & (df['age'] < 65),
    (df['age'] >= 65),
]
choices = ['미성년', '성인', '노인']
df['age_group'] = np.select(conditions, choices, default='알수없음')

문자열 패턴 필터

df[df['email'].str.endswith('@company.com')]
df[df['name'].str.contains(r'^[A-Z]', regex=True)]
df[df['code'].str.match(r'\d{4}-[A-Z]{2}')]

여러 컬럼 동시 조건

# 상위 10% AND 특정 카테고리
top10_threshold = df['score'].quantile(0.9)
mask = (df['score'] >= top10_threshold) & (df['category'] == 'premium')
df[mask]

마스크 미리 만들어 재사용

# 여러 곳에서 같은 필터 재사용
active_mask  = df['status'] == 'active'
adult_mask   = df['age'] >= 18

df[active_mask]                          # active 유저
df[active_mask & adult_mask]             # active + 성인
df.loc[active_mask, 'last_login'] = ...  # active 에만 업데이트

성능 특성

방법속도비고
Boolean Indexing빠름벡터 연산, pandas 기본
query()빠름/더 빠름numexpr 있으면 대용량 유리
loc[mask]동등df[mask] 와 동일
Python for 루프매우 느림절대 피할 것
# 대용량 DataFrame에서 query()가 더 빠른 경우
import pandas as pd
df = pd.DataFrame({'a': range(1_000_000), 'b': range(1_000_000)})

# 내부적으로 numexpr 사용 (설치된 경우)
df.query("a > 500000 and b < 800000")

함정

1. and / or 사용

df[df['age'] > 25 and df['age'] < 40]    # ❌ ValueError
df[(df['age'] > 25) & (df['age'] < 40)]  # ✓

2. 괄호 빠뜨림

df[df['a'] > 1 & df['b'] > 2]            # ❌ `1 & df['b']` 가 먼저 평가됨
df[(df['a'] > 1) & (df['b'] > 2)]        # ✓

&> 보다 우선순위가 높아서 사고가 자주 난다.

3. SettingWithCopyWarning

sub = df[df['age'] > 30]
sub['new'] = 1     # ⚠️ Warning (pandas 2.x: CoW 도입)
# 해법
sub = df[df['age'] > 30].copy()
sub['new'] = 1

pandas 2.x 부터 Copy-on-Write(CoW) 가 도입되어 경고 대신 에러로 바뀌는 방향. 명시적 .copy() 가 안전.

4. NaN 의 boolean 처리

mask = df['age'] > 30
# age 가 NaN 인 행 → mask 도 NaN (Falsy 가 아님!)
# 명시적 처리
mask = (df['age'] > 30) & df['age'].notna()

5. 체이닝 비교 불가

# Python 은 10 < age < 40 이 동작하지만 pandas 에서는 안 됨
df[10 < df['age'] < 40]   # ❌
df[(df['age'] > 10) & (df['age'] < 40)]  # ✓
df[df['age'].between(10, 40)]             # ✓ 간결

관련 위키

이 글의 용어 (6개)
[Pandas] .loc / .ilocpandas
정의 - : 라벨 기반 인덱싱 (index/column 이름) - : 정수 위치 기반 인덱싱 (0-based) 이 두 indexer 가 pandas 선택의 표준. 만으로는 헷갈리…
[Pandas] 성능 / 메모리 최적화pandas
정의 pandas 의 흔한 성능 함정과 최적화 패턴. 벡터화 + dtype 선택 + 알고리즘 의 조합이 핵심. 사용 상황 | 상황 | 권장 접근 | |:---|:---| | 단순…
[Pandas] DataFramepandas
정의 은 2차원 레이블 테이블. 각 열이 , 모든 열이 같은 (행 라벨) 를 공유. SQL 테이블 / Excel 시트 / R data.frame 의 Python 대응체. 구조 시…
[Pandas] isin / isna / notnapandas
정의 | 메서드 | 의미 | |:---|:---| | | 각 원소가 values 안에 있는지 (boolean Series) | | | NaN/NaT/None 여부 ( 별칭) | …
[Pandas] query / evalpandas
정의 - : 문자열로 boolean 표현식 을 전달해 행 필터링 - : 문자열로 계산 표현식 을 평가 의 가독성 있는 대안. 사용 상황 - 조건이 복잡해 boolean index…
[Pandas] SettingWithCopyWarningpandas
정의 은 pandas 가 가장 자주 던지는 경고. chained indexing 으로 view 인지 copy 인지 모호한 상태에 값을 할당 할 때 발생. 사용 상황 다음 상황에서…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기