Skip to content
주제
Pandas
Pandas 행 필터링: Python에서 조건별로 데이터 선택하기

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 사용을 고려하세요.

📚