Pandas 행 필터링: Python에서 조건별로 데이터 선택하기
Updated on
pandas DataFrame의 행을 필터링하는 것은 데이터 분석에서 가장 일반적인 작업 중 하나입니다. 데이터를 정리하거나, 패턴을 탐색하거나, 머신러닝을 위한 데이터셋을 준비할 때 조건에 따라 특정 행을 선택해야 합니다. 문제는 pandas의 여러 필터링 접근 방식 중에서 올바른 방법을 선택하는 것입니다. 각 방법은 서로 다른 구문, 성능 특성 및 사용 사례를 가지고 있습니다.
많은 데이터 과학자들은 특히 대규모 데이터셋이나 복잡한 조건을 다룰 때 필터링 효율성으로 어려움을 겪습니다. 잘못된 방법을 사용하면 분석 속도가 몇 배나 느려질 수 있습니다. 불린 인덱싱과 .query() 또는 .loc[]를 언제 사용해야 하는지 이해하는 것은 몇 초 안에 실행되는 스크립트와 몇 분이 걸리는 스크립트의 차이를 의미할 수 있습니다.
이 가이드는 실용적인 예제, 성능 비교 및 모범 사례를 포함한 모든 pandas 필터링 방법을 다룹니다. 불린 인덱싱, .query() 메서드, .loc[] 선택, .where() 함수 및 .filter() 메서드를 배우게 됩니다. 마지막에는 모든 필터링 시나리오에 어떤 접근 방식을 사용해야 하는지 정확히 알게 될 것입니다.
Pandas 행 필터링 메서드 이해하기
Pandas는 DataFrame 행을 필터링하기 위한 5가지 주요 메서드를 제공하며, 각각 다른 시나리오에 적합합니다.
| 메서드 | 최적 용도 | 구문 예제 | 성능 |
|---|---|---|---|
| 불린 인덱싱 | 간단한 조건, 가독성 | df[df['age'] > 25] | 소~중형 데이터에서 빠름 |
.query() | 복잡한 조건, 문자열 기반 | df.query('age > 25 and city == "NYC"') | 대형 데이터에서 더 빠름 |
.loc[] | 레이블 기반, 조건부 | df.loc[df['age'] > 25, ['name', 'age']] | 유연한 열 선택 |
.where() | 구조 유지, 값 대체 | df.where(df['age'] > 25, np.nan) | DataFrame 형태 보존 |
.filter() | 열/인덱스 이름으로 필터 | df.filter(like='total', axis=1) | 열/인덱스 이름 패턴 |
각 메서드를 자세한 예제와 함께 살펴보겠습니다.
불린 인덱싱: 가장 일반적인 접근 방식
불린 인덱싱은 불린 마스크(True/False 값)를 생성하고 이를 DataFrame에 적용하여 행을 필터링합니다. 이것은 초보자에게 가장 직관적인 방법입니다.
import pandas as pd
import numpy as np
# 샘플 DataFrame 생성
df = pd.DataFrame({
'name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve'],
'age': [25, 30, 35, 28, 32],
'city': ['NYC', 'LA', 'NYC', 'Chicago', 'LA'],
'salary': [70000, 80000, 90000, 75000, 85000]
})
# age가 30보다 큰 행 필터링
filtered = df[df['age'] > 30]
print(filtered)
# name age city salary
# 2 Charlie 35 NYC 90000
# 4 Eve 32 LA 85000
# 불린 마스크 보기
print(df['age'] > 30)
# 0 False
# 1 False
# 2 True
# 3 False
# 4 True불린 인덱싱은 조건 df['age'] > 30을 평가하여 작동하며, True/False 값의 Series를 반환합니다. 이 마스크를 df[mask]로 DataFrame에 전달하면 pandas는 마스크가 True인 행만 반환합니다.
여러 조건으로 필터링
논리 연산자를 사용하여 여러 조건을 결합합니다. 중요: Python의 and, or, not 키워드 대신 &(and), |(or), ~(not)를 사용하세요. 항상 각 조건을 괄호로 감싸세요.
# AND로 여러 조건
filtered = df[(df['age'] > 25) & (df['city'] == 'NYC')]
print(filtered)
# name age city salary
# 2 Charlie 35 NYC 90000
# OR로 여러 조건
filtered = df[(df['age'] > 30) | (df['salary'] > 80000)]
print(filtered)
# name age city salary
# 2 Charlie 35 NYC 90000
# 4 Eve 32 LA 85000
# NOT 연산자
filtered = df[~(df['city'] == 'NYC')]
print(filtered)
# name age city salary
# 1 Bob 30 LA 80000
# 3 David 28 Chicago 75000
# 4 Eve 32 LA 85000여러 값에 .isin() 사용
.isin()을 사용하여 열이 목록의 값과 일치하는 행을 필터링합니다.
# city가 NYC 또는 LA인 행 필터링
cities = ['NYC', 'LA']
filtered = df[df['city'].isin(cities)]
print(filtered)
# name age city salary
# 0 Alice 25 NYC 70000
# 1 Bob 30 LA 80000
# 2 Charlie 35 NYC 90000
# 4 Eve 32 LA 85000
# 역: 목록에 없는 도시
filtered = df[~df['city'].isin(cities)]
print(filtered)
# name age city salary
# 3 David 28 Chicago 75000.between()으로 필터링
.between() 메서드는 범위 내의 값을 필터링합니다(기본적으로 경계 포함).
# 28에서 32 사이의 나이 필터링
filtered = df[df['age'].between(28, 32)]
print(filtered)
# name age city salary
# 1 Bob 30 LA 80000
# 3 David 28 Chicago 75000
# 4 Eve 32 LA 85000
# 배타적 경계
filtered = df[df['age'].between(28, 32, inclusive='neither')]
print(filtered)
# name age city salary
# 1 Bob 30 LA 80000.query() 메서드: 문자열 기반 필터링
.query() 메서드는 문자열 표현식을 허용하여 복잡한 조건에 대해 읽기 쉽게 만듭니다. numexpr를 사용한 최적화로 인해 대규모 DataFrame에 특히 효율적입니다.
# 간단한 쿼리
filtered = df.query('age > 30')
print(filtered)
# name age city salary
# 2 Charlie 35 NYC 90000
# 4 Eve 32 LA 85000
# 여러 조건
filtered = df.query('age > 25 and city == "NYC"')
print(filtered)
# name age city salary
# 2 Charlie 35 NYC 90000
# @ 기호로 변수 사용
min_age = 30
filtered = df.query('age > @min_age')
print(filtered)
# name age city salary
# 2 Charlie 35 NYC 90000
# 4 Eve 32 LA 85000고급 .query() 표현식
# query에서 .isin() 사용
cities = ['NYC', 'LA']
filtered = df.query('city in @cities')
print(filtered)
# 범위 조건
filtered = df.query('28 <= age <= 32')
print(filtered)
# 문자열 메서드
filtered = df.query('city.str.contains("LA")', engine='python')
print(filtered)레이블 기반 필터링을 위한 .loc[]
.loc[] 인덱서는 행 필터링과 열 선택을 결합합니다. 필터링된 행에서 특정 열이 필요할 때 사용하세요.
# 행 필터링 및 열 선택
filtered = df.loc[df['age'] > 30, ['name', 'age']]
print(filtered)
# name age
# 2 Charlie 35
# 4 Eve 32
# 여러 조건
filtered = df.loc[(df['age'] > 25) & (df['salary'] > 75000), ['name', 'salary']]
print(filtered)
# name salary
# 1 Bob 80000
# 2 Charlie 90000
# 4 Eve 85000
# 모든 열
filtered = df.loc[df['city'] == 'NYC', :]
print(filtered).where() 메서드: 조건부 값 대체
다른 메서드와 달리 .where()는 조건을 충족하지 않는 값을 NaN(또는 지정된 값)으로 대체하여 DataFrame 형태를 보존합니다.
# age > 30인 값 유지, 나머지는 NaN으로 대체
result = df.where(df['age'] > 30)
print(result)
# name age city salary
# 0 NaN NaN NaN NaN
# 1 NaN NaN NaN NaN
# 2 Charlie 35.0 NYC 90000.0
# 3 NaN NaN NaN NaN
# 4 Eve 32.0 LA 85000.0
# 사용자 정의 값으로 대체
result = df.where(df['age'] > 30, 'FILTERED')
print(result)
# .where() 후 NaN이 있는 행 삭제
result = df.where(df['age'] > 30).dropna()
print(result).filter() 메서드: 열/인덱스 이름으로 필터링
.filter() 메서드는 값이 아닌 레이블(이름)로 열이나 행을 필터링합니다. 패턴 기반 열 선택에 사용하세요.
# 여러 열이 있는 DataFrame 생성
df_wide = pd.DataFrame({
'total_sales': [100, 200, 300],
'total_profit': [20, 40, 60],
'monthly_sales': [10, 20, 30],
'yearly_sales': [120, 240, 360],
'region': ['East', 'West', 'North']
})
# 'total'을 포함하는 열 필터링
filtered = df_wide.filter(like='total')
print(filtered)
# total_sales total_profit
# 0 100 20
# 1 200 40
# 2 300 60
# 정규 표현식을 사용하여 열 필터링
filtered = df_wide.filter(regex=r'.*sales$')
print(filtered)
# total_sales monthly_sales yearly_sales
# 0 100 10 120
# 1 200 20 240
# 2 300 30 360
# 정확한 이름으로 열 필터링
filtered = df_wide.filter(items=['total_sales', 'region'])
print(filtered)문자열 데이터 필터링
Pandas는 텍스트 열을 필터링하기 위한 .str 접근자를 통해 문자열 메서드를 제공합니다.
# 텍스트 데이터가 있는 DataFrame 생성
df_text = pd.DataFrame({
'name': ['Alice Smith', 'Bob Johnson', 'Charlie Brown', 'David Lee'],
'email': ['alice@gmail.com', 'bob@yahoo.com', 'charlie@gmail.com', 'david@outlook.com']
})
# name에 'Smith'가 포함된 행 필터링
filtered = df_text[df_text['name'].str.contains('Smith')]
print(filtered)
# name email
# 0 Alice Smith alice@gmail.com
# 대소문자 구분 없는 검색
filtered = df_text[df_text['name'].str.contains('smith', case=False)]
print(filtered)
# 이메일 도메인으로 필터링
filtered = df_text[df_text['email'].str.endswith('gmail.com')]
print(filtered)
# name email
# 0 Alice Smith alice@gmail.com
# 2 Charlie Brown charlie@gmail.com
# 정규 표현식으로 필터링
filtered = df_text[df_text['name'].str.match(r'^[A-C]')]
print(filtered)Null 및 비Null 값 필터링
결측 데이터를 기반으로 필터링하려면 .isna(), .notna(), .isnull() 및 .notnull()을 사용하세요.
# 결측값이 있는 DataFrame 생성
df_missing = pd.DataFrame({
'A': [1, 2, np.nan, 4],
'B': [5, np.nan, 7, 8],
'C': [9, 10, 11, 12]
})
# 열 A가 null이 아닌 행 필터링
filtered = df_missing[df_missing['A'].notna()]
print(filtered)
# A B C
# 0 1.0 5.0 9
# 1 2.0 NaN 10
# 3 4.0 8.0 12
# 어떤 열이라도 null인 행 필터링
filtered = df_missing[df_missing.isna().any(axis=1)]
print(filtered)
# A B C
# 1 2.0 NaN 10
# 2 NaN 7.0 11
# 모든 열이 null이 아닌 행 필터링
filtered = df_missing[df_missing.notna().all(axis=1)]
print(filtered)
# A B C
# 0 1.0 5.0 9
# 3 4.0 8.0 12날짜 범위로 필터링
datetime 열을 다룰 때 표준 비교 연산자를 사용하여 날짜 범위로 필터링할 수 있습니다.
# 날짜가 있는 DataFrame 생성
df_dates = pd.DataFrame({
'date': pd.date_range('2026-01-01', periods=10, freq='D'),
'value': range(10)
})
# 특정 날짜 이후의 날짜 필터링
filtered = df_dates[df_dates['date'] > '2026-01-05']
print(filtered)
# 날짜 범위 필터링
start_date = '2026-01-03'
end_date = '2026-01-07'
filtered = df_dates[(df_dates['date'] >= start_date) & (df_dates['date'] <= end_date)]
print(filtered)
# 날짜에 .between() 사용
filtered = df_dates[df_dates['date'].between('2026-01-03', '2026-01-07')]
print(filtered)성능 비교: 대규모 DataFrame
서로 다른 필터링 메서드는 서로 다른 성능 특성을 가지고 있습니다. 다음은 100만 행의 DataFrame에 대한 비교입니다.
import time
# 대규모 DataFrame 생성
np.random.seed(42)
df_large = pd.DataFrame({
'A': np.random.randint(0, 100, 1000000),
'B': np.random.randint(0, 100, 1000000),
'C': np.random.choice(['X', 'Y', 'Z'], 1000000)
})
# 불린 인덱싱
start = time.time()
result = df_large[(df_large['A'] > 50) & (df_large['B'] < 30)]
print(f"불린 인덱싱: {time.time() - start:.4f}초")
# .query() 메서드
start = time.time()
result = df_large.query('A > 50 and B < 30')
print(f".query() 메서드: {time.time() - start:.4f}초")
# .loc[] 메서드
start = time.time()
result = df_large.loc[(df_large['A'] > 50) & (df_large['B'] < 30)]
print(f".loc[] 메서드: {time.time() - start:.4f}초")성능 인사이트:
- 불린 인덱싱: 간단한 조건에서는 빠르지만 복잡한 다중 조건 필터에서는 느림
- .query(): 여러 조건이 있는 대규모 DataFrame에서 가장 빠름(numexpr 최적화 사용)
- .loc[]: 불린 인덱싱과 유사하지만 열 선택에 더 유연함
- .where(): 전체 DataFrame 순회로 인해 느림, 형태를 보존해야 할 때만 사용
여러 조건이 있는 100,000행 이상의 데이터셋의 경우 .query()는 일반적으로 불린 인덱싱보다 20-40% 더 뛰어난 성능을 보입니다.
일반적인 실수와 회피 방법
실수 1: '&' 대신 'and' 사용
# 잘못됨 - ValueError 발생
# filtered = df[df['age'] > 25 and df['city'] == 'NYC']
# 올바름
filtered = df[(df['age'] > 25) & (df['city'] == 'NYC')]실수 2: 괄호 잊어버리기
# 잘못됨 - 연산자 우선순위 문제
# filtered = df[df['age'] > 25 & df['city'] == 'NYC']
# 올바름 - 각 조건을 감쌈
filtered = df[(df['age'] > 25) & (df['city'] == 'NYC')]실수 3: 원본 DataFrame 수정
# 필터링은 복사본이 아닌 뷰를 생성
filtered = df[df['age'] > 30]
# 잘못됨 - SettingWithCopyWarning
# filtered['new_col'] = 100
# 올바름 - 명시적 복사본 생성
filtered = df[df['age'] > 30].copy()
filtered['new_col'] = 100실수 4: NaN 처리 없이 문자열 필터링
df_with_nan = pd.DataFrame({
'name': ['Alice', np.nan, 'Charlie']
})
# 잘못됨 - NaN이 있으면 오류 발생
# filtered = df_with_nan[df_with_nan['name'].str.contains('li')]
# 올바름 - na 매개변수로 NaN 처리
filtered = df_with_nan[df_with_nan['name'].str.contains('li', na=False)]PyGWalker로 필터링된 데이터 시각화
pandas DataFrame을 필터링한 후 결과를 시각화하면 패턴과 인사이트를 발견하는 데 도움이 됩니다. PyGWalker (opens in a new tab)는 필터링된 DataFrame을 Python 노트북에서 직접 Tableau와 유사한 인터랙티브 인터페이스로 변환합니다. 데이터를 내보내거나 복잡한 플로팅 코드를 작성할 필요가 없습니다.
PyGWalker는 필터링된 데이터셋을 탐색할 때 특히 유용합니다. 다음을 수행할 수 있습니다.
- 열을 드래그 앤 드롭하여 즉시 차트 생성
- 코드를 작성하지 않고 시각적으로 추가 필터 적용
- 차트 유형(막대, 선, 산점도, 히트맵) 간에 몇 초 만에 전환
- 보고서나 프레젠테이션용 시각화 내보내기
import pygwalker as pyg
# DataFrame 필터링
filtered_df = df[(df['age'] > 25) & (df['salary'] > 75000)]
# 인터랙티브 시각화 시작
pyg.walk(filtered_df)이렇게 하면 필터링된 열을 차트 빌더로 드래그하여 시각화를 만들 수 있는 인터랙티브 인터페이스가 열립니다. 여러 필터 조건을 다루는 데이터 분석가의 경우 PyGWalker는 필터 → 플롯 → 조정 → 재플롯의 반복 주기를 제거합니다.
자주 묻는 질문
조건별로 pandas DataFrame 행을 필터링하려면 어떻게 해야 하나요?
간단한 조건의 경우 df[df['column'] > value]로 불린 인덱싱을 사용하세요. 여러 조건의 경우 괄호와 함께 &(and), |(or), ~(not)를 사용하세요: df[(df['A'] > 10) & (df['B'] < 20)]. 또는 읽기 쉬운 구문을 위해 .query()를 사용하세요: df.query('A > 10 and B < 20').
.loc[]과 불린 인덱싱의 차이점은 무엇인가요?
불린 인덱싱(df[df['col'] > 5])은 필터링된 행의 모든 열을 반환합니다. .loc[]는 동시에 열 선택을 허용합니다: df.loc[df['col'] > 5, ['col1', 'col2']]. 두 메서드 모두 유사한 성능을 가지지만 .loc[]은 더 명시적이며 레이블 기반 행 선택을 지원합니다.
여러 조건으로 pandas DataFrame을 필터링하려면 어떻게 해야 하나요?
&(and), |(or), ~(not) 연산자를 사용하여 조건을 결합하세요. 항상 각 조건을 괄호로 감싸세요: df[(df['age'] > 25) & (df['city'] == 'NYC') | (df['salary'] > 80000)]. 복잡한 조건의 경우 .query()를 사용하세요: df.query('age > 25 and (city == "NYC" or salary > 80000)').
불린 인덱싱 대신 .query()를 언제 사용해야 하나요?
여러 조건이 있는 대규모 DataFrame(>100k 행)에는 .query()를 사용하세요. numexpr 최적화로 인해 20-40% 더 빠릅니다. 복잡한 조건에도 더 읽기 쉽습니다. 간단한 필터나 최대 호환성이 필요한 경우 불린 인덱싱을 사용하세요(.query()는 디버깅하기 어려운 문자열 표현식이 필요함).
열 값이 목록에 있는 행을 필터링하려면 어떻게 해야 하나요?
.isin() 메서드를 사용하세요: df[df['city'].isin(['NYC', 'LA', 'Chicago'])]. 반대(목록에 없음)의 경우 ~ 연산자를 사용하세요: df[~df['city'].isin(['NYC', 'LA'])]. 이것은 큰 목록에 대해 여러 | 조건을 연결하는 것보다 더 효율적입니다.
결론
pandas DataFrame의 행을 필터링하는 것은 Python에서 데이터 분석을 위한 기본 기술입니다. 이제 5가지 필터링 메서드를 이해하게 되었습니다: 단순성을 위한 불린 인덱싱, 성능을 위한 .query(), 유연한 열 선택을 위한 .loc[], 값 대체를 위한 .where(), 열 이름 패턴을 위한 .filter().
주요 요점:
- 소규모에서 중간 규모 데이터셋의 빠르고 읽기 쉬운 필터에는 불린 인덱싱을 사용하세요
- 복잡한 조건이 있는 대규모 데이터셋에는 **.query()**를 선택하세요(20-40% 더 빠름)
- 행 필터링과 열 선택이 모두 필요할 때 **.loc[]**를 적용하세요
and,or,not대신&,|,~연산자를 사용해야 함을 기억하세요- 연산자 우선순위 문제를 피하기 위해 항상 조건을 괄호로 감싸세요
이러한 필터링 기술을 마스터하면 모든 데이터 선택 시나리오를 효율적으로 처리할 수 있습니다. 필터링된 데이터의 인터랙티브 탐색을 위해 추가 플로팅 코드를 작성하지 않고 결과를 시각화하려면 PyGWalker 사용을 고려하세요.