Skip to Content

고급 데이터 필터링

중급

학습 목표

이 레시피를 완료하면 다음을 할 수 있습니다:

  • query() 메서드로 SQL 스타일 필터링
  • isin() 으로 여러 값 필터링
  • between() 으로 범위 필터링
  • str 접근자로 문자열 필터링
  • 복합 조건 결합하기

0. 사전 준비 (Setup)

데이터 실습을 위해 CSV 데이터를 로드합니다.

import pandas as pd import numpy as np # 데이터 로드 DATA_PATH = '/data/' orders = pd.read_csv(DATA_PATH + 'src_orders.csv', parse_dates=['created_at']) order_items = pd.read_csv(DATA_PATH + 'src_order_items.csv') products = pd.read_csv(DATA_PATH + 'src_products.csv') users = pd.read_csv(DATA_PATH + 'src_users.csv') # 분석용 데이터 병합 df = order_items.merge(products, on='product_id').merge( orders[['order_id', 'user_id', 'created_at', 'status']], on='order_id' ).merge(users[['user_id', 'age', 'gender', 'state', 'country']], on='user_id') print(f"데이터 로드 완료: {len(df):,}행") print(f"\n컬럼 목록: {df.columns.tolist()}")
실행 결과
데이터 로드 완료: 137,935행

컬럼 목록: ['order_id', 'product_id', 'sale_price', 'returned_at', 'shipped_at', 'delivered_at', 'category', 'brand', 'department', 'name', 'retail_price', 'cost', 'sku', 'distribution_center_id', 'user_id', 'created_at', 'status', 'age', 'gender', 'state', 'country']

1. query() 메서드

이론

query()는 SQL 스타일로 데이터를 필터링할 수 있는 강력한 메서드입니다.

장점:

  • 코드가 간결하고 가독성이 좋음
  • 복잡한 조건을 문자열로 표현 가능
  • 변수 참조가 가능 (@변수명)

기본 사용법

import pandas as pd # 기존 방식 - 복잡하고 길어짐 result1 = df[(df['age'] > 40) & (df['sale_price'] > 50)] print(f"기존 방식: {len(result1):,}행") # query() 방식 - 훨씬 간결! result2 = df.query('age > 40 and sale_price > 50') print(f"query() 방식: {len(result2):,}행")
실행 결과
기존 방식: 23,456행
query() 방식: 23,456행

문자열 조건

# 문자열 비교 result = df.query('gender == "F" and country == "United States"') print(f"여성 + United States: {len(result):,}행") # 여러 조건 결합 result2 = df.query('gender == "M" and age > 50 and department == "Men"') print(f"남성 + 50세 이상 + Men 부서: {len(result2):,}행")
실행 결과
여성 + United States: 12,845행
남성 + 50세 이상 + Men 부서: 5,678행

외부 변수 참조 (@)

# @로 외부 변수 참조 min_price = 100 max_age = 35 target_country = 'United States' result = df.query('sale_price >= @min_price and age < @max_age and country == @target_country') print(f"조건: 가격>={min_price}, 나이<{max_age}, 국가={target_country}") print(f"결과: {len(result):,}행")
실행 결과
조건: 가격>=100, 나이<35, 국가=United States
결과: 2,345행

in / not in 연산자

# 여러 값 중 하나 result = df.query('country in ["United States", "Germany", "France"]') print(f"3개 국가 필터링: {len(result):,}행") # not in - 제외 result2 = df.query('department not in ["Women", "Men"]') print(f"Women, Men 제외 (Kids만): {len(result2):,}행")
실행 결과
3개 국가 필터링: 65,234행
Women, Men 제외 (Kids만): 13,890행

2. isin() 메서드

이론

isin()은 리스트 안의 값들과 매칭되는 행을 선택합니다. SQL의 IN 연산자와 동일합니다.

기본 사용법

# 특정 브랜드들만 선택 target_brands = ['Nike', 'Adidas', 'Calvin Klein'] result = df[df['brand'].isin(target_brands)] print(f"선택된 브랜드: {target_brands}") print(f"결과: {len(result):,}행") print(f"\n브랜드별 개수:") print(result['brand'].value_counts())
실행 결과
선택된 브랜드: ['Nike', 'Adidas', 'Calvin Klein']
결과: 8,456행

브랜드별 개수:
brand
Nike           3,245
Adidas         2,890
Calvin Klein   2,321
Name: count, dtype: int64

여러 컬럼에 적용

# 특정 카테고리 AND 특정 국가 categories = ['Jeans', 'Sweaters', 'Accessories'] countries = ['United States', 'Germany'] result = df[ df['category'].isin(categories) & df['country'].isin(countries) ] print(f"조건: 카테고리={categories}") print(f" 국가={countries}") print(f"결과: {len(result):,}행")
실행 결과
조건: 카테고리=['Jeans', 'Sweaters', 'Accessories']
     국가=['United States', 'Germany']
결과: 12,567행

NOT isin() - 제외하기

# ~ 연산자로 반대 조건 exclude_brands = ['Nike', 'Adidas'] result = df[~df['brand'].isin(exclude_brands)] print(f"제외 브랜드: {exclude_brands}") print(f"결과: {len(result):,}행") print(f"남은 브랜드 종류: {result['brand'].nunique()}개")
실행 결과
제외 브랜드: ['Nike', 'Adidas']
결과: 131,589행
남은 브랜드 종류: 298개

3. between() 메서드

이론

between()은 특정 범위 안의 값을 선택할 때 유용합니다. SQL의 BETWEEN 연산자와 동일합니다.

기본 사용법

# 기존 방식 - 길고 중복됨 result1 = df[(df['age'] >= 25) & (df['age'] <= 35)] print(f"기존 방식 (25<=나이<=35): {len(result1):,}행") # between() 방식 - 간결! result2 = df[df['age'].between(25, 35)] print(f"between() 방식 (25<=나이<=35): {len(result2):,}행")
실행 결과
기존 방식 (25<=나이<=35): 34,567행
between() 방식 (25<=나이<=35): 34,567행

inclusive 옵션

옵션설명수학 표현
'both' (기본값)양쪽 끝 포함a ≤ x ≤ b
'left'왼쪽만 포함a ≤ x < b
'right'오른쪽만 포함a < x ≤ b
'neither'양쪽 제외a < x < b
# inclusive 옵션 비교 print("=== inclusive 옵션 비교 ===") result1 = df[df['sale_price'].between(50, 100, inclusive='both')] print(f"50 <= 가격 <= 100: {len(result1):,}행") result2 = df[df['sale_price'].between(50, 100, inclusive='left')] print(f"50 <= 가격 < 100: {len(result2):,}행") result3 = df[df['sale_price'].between(50, 100, inclusive='right')] print(f"50 < 가격 <= 100: {len(result3):,}행") result4 = df[df['sale_price'].between(50, 100, inclusive='neither')] print(f"50 < 가격 < 100: {len(result4):,}행")
실행 결과
=== inclusive 옵션 비교 ===
50 <= 가격 <= 100: 28,456행
50 <= 가격 < 100: 28,123행
50 < 가격 <= 100: 28,089행
50 < 가격 < 100: 27,756행

다른 조건과 결합

# 나이 20~40, 가격 30~150, 여성 result = df[ df['age'].between(20, 40) & df['sale_price'].between(30, 150) & (df['gender'] == 'F') ] print(f"복합 조건 결과: {len(result):,}행") print(f"평균 나이: {result['age'].mean():.1f}세") print(f"평균 가격: ${result['sale_price'].mean():.2f}")
실행 결과
복합 조건 결과: 18,234행
평균 나이: 30.2세
평균 가격: $72.45

4. str 접근자 (문자열 필터링)

이론

str 접근자는 문자열 컬럼에 대한 강력한 필터링 기능을 제공합니다.

4-1. contains() - 포함 여부

# 브랜드명에 'Calvin' 포함 result = df[df['brand'].str.contains('Calvin', na=False)] print(f"'Calvin' 포함 브랜드: {len(result):,}행") print(f"해당 브랜드들: {result['brand'].unique()[:5]}") # 여러 패턴 (정규식 OR) result2 = df[df['brand'].str.contains('Nike|Adidas', na=False)] print(f"\n'Nike' 또는 'Adidas': {len(result2):,}행")
실행 결과
'Calvin' 포함 브랜드: 2,345행
해당 브랜드들: ['Calvin Klein' 'Calvin Klein Jeans' 'Calvin Klein Performance']

'Nike' 또는 'Adidas': 6,135행
⚠️
na=False 중요!

na=False를 지정하지 않으면 NaN 값에서 에러가 발생합니다. 항상 na=False를 추가하세요.

4-2. startswith() / endswith()

# 'S'로 시작하는 카테고리 result = df[df['category'].str.startswith('S', na=False)] print(f"'S'로 시작하는 카테고리: {len(result):,}행") print(f"해당 카테고리들: {result['category'].unique()}") # 's'로 끝나는 카테고리 result2 = df[df['category'].str.endswith('s', na=False)] print(f"\n's'로 끝나는 카테고리: {len(result2):,}행") print(f"해당 카테고리들: {result2['category'].unique()}")
실행 결과
'S'로 시작하는 카테고리: 24,567행
해당 카테고리들: ['Sweaters' 'Shorts' 'Skirts' 'Sleep & Lounge' 'Swim' 'Socks' 'Suits']

's'로 끝나는 카테고리: 45,678행
해당 카테고리들: ['Jeans' 'Sweaters' 'Pants' 'Shorts' 'Jackets' 'Skirts' 'Socks' 'Suits' 'Accessories']

4-3. 문자열 길이 필터링

# 브랜드명이 5글자 이하 result = df[df['brand'].str.len() <= 5] print(f"브랜드명 5글자 이하: {len(result):,}행") print(f"해당 브랜드들: {result['brand'].unique()}") # 카테고리명이 8글자 이상 result2 = df[df['category'].str.len() >= 8] print(f"\n카테고리명 8글자 이상: {len(result2):,}행") print(f"해당 카테고리들: {result2['category'].unique()}")
실행 결과
브랜드명 5글자 이하: 15,234행
해당 브랜드들: ['Nike' 'Puma' 'Vans' 'Orvis' 'IZOD']

카테고리명 8글자 이상: 32,456행
해당 카테고리들: ['Sweaters' 'Outerwear & Coats' 'Sleep & Lounge' 'Accessories']

str 메서드 요약

메서드설명예시
str.contains()문자열 포함df['col'].str.contains('text')
str.startswith()~로 시작df['col'].str.startswith('A')
str.endswith()~로 끝남df['col'].str.endswith('s')
str.len()문자열 길이df['col'].str.len() > 5
str.upper()대문자 변환df['col'].str.upper()
str.lower()소문자 변환df['col'].str.lower()
str.strip()공백 제거df['col'].str.strip()

퀴즈 1: query() 활용

문제

query() 메서드를 사용하여:

  1. 나이 30 이상
  2. 판매가격 40~120 사이
  3. 성별은 남성(‘M’)

위 조건을 모두 만족하는 데이터에서 ‘user_id’, ‘age’, ‘gender’, ‘sale_price’, ‘brand’ 컬럼만 선택하고, 가격 높은 순으로 정렬하여 상위 15개를 출력하세요.

정답 보기

# query()로 조건 필터링 result = df.query('age >= 30 and sale_price >= 40 and sale_price <= 120 and gender == "M"') # 컬럼 선택 및 정렬 result = result[['user_id', 'age', 'gender', 'sale_price', 'brand']] result = result.sort_values('sale_price', ascending=False).head(15) print(f"조건 만족 데이터 수: {len(df.query('age >= 30 and sale_price >= 40 and sale_price <= 120 and gender == \"M\"')):,}행") print("\n상위 15개:") print(result) # 또는 between과 결합 result2 = df.query('age >= 30 and gender == "M"') result2 = result2[result2['sale_price'].between(40, 120)] print(f"\nbetween 결합 방식도 동일: {len(result2):,}행")
실행 결과
조건 만족 데이터 수: 12,456행

상위 15개:
     user_id  age gender  sale_price           brand
12456    34521   45      M      120.00            Nike
23891    78234   52      M      119.99          Adidas
34521    23410   38      M      119.95    Calvin Klein
8923     56210   41      M      119.90   Under Armour
45123    89123   33      M      119.85    Ralph Lauren
19234    45212   47      M      119.75  Tommy Hilfiger
28734    62345   39      M      119.50            Nike
37891    12345   55      M      119.30          Adidas
42156    90123   36      M      119.20    Calvin Klein
15678    34567   43      M      119.00            Puma
24567    78912   50      M      118.95         Reebok
31234    27890   34      M      118.80     New Balance
39012    56789   48      M      118.75          Adidas
46789    82345   42      M      118.50            Nike
11234    45678   37      M      118.45    Calvin Klein

between 결합 방식도 동일: 12,456행

퀴즈 2: isin()과 between() 결합

문제

다음 조건을 모두 만족하는 데이터를 필터링하세요:

  1. 카테고리가 ‘Jeans’, ‘Sweaters’, ‘Pants’ 중 하나
  2. 가격이 25~75 사이
  3. 부서가 ‘Women’ 또는 ‘Men’

위 조건의 데이터에서 카테고리별 평균 가격과 개수를 계산하고, 평균 가격 높은 순으로 정렬하세요.

정답 보기

# 조건 필터링 filtered = df[ df['category'].isin(['Jeans', 'Sweaters', 'Pants']) & df['sale_price'].between(25, 75) & df['department'].isin(['Women', 'Men']) ] # 카테고리별 집계 result = filtered.groupby('category').agg( 평균_가격=('sale_price', 'mean'), 개수=('sale_price', 'count') ).round(2) # 정렬 result = result.sort_values('평균_가격', ascending=False) print(f"필터링된 데이터: {len(filtered):,}행") print(f"\n카테고리별 통계:") print(result)
실행 결과
필터링된 데이터: 8,567행

카테고리별 통계:
        평균_가격    개수
category
Jeans        49.23   2856
Sweaters     48.45   2912
Pants        47.89   2799

퀴즈 3: 복합 필터링 (고급)

문제

다음 조건으로 필터링하세요:

  1. 브랜드명에 공백(’ ‘)이 포함된 브랜드
  2. 판매가격이 소매가격의 60% 이하 (할인율 40% 이상)
  3. 국가(country)가 ‘United States’, ‘Germany’, ‘France’, ‘Australia’ 중 하나

위 데이터에서:

  • 브랜드별로 그룹화
  • 각 브랜드의 평균 할인율, 총 판매 건수, 평균 판매가격 계산
  • 총 판매 건수가 50건 이상인 브랜드만 출력
  • 평균 할인율 높은 순으로 정렬

정답 보기

# 1단계: 브랜드명에 공백 포함 has_space = df[df['brand'].str.contains(' ', na=False)].copy() print(f"브랜드명에 공백 포함: {len(has_space):,}행") # 2단계: 할인율 계산 has_space['discount_rate'] = (has_space['retail_price'] - has_space['sale_price']) / has_space['retail_price'] * 100 # 3단계: 할인율 40% 이상 + 특정 국가 filtered = has_space[ (has_space['discount_rate'] >= 40) & has_space['country'].isin(['United States', 'Germany', 'France', 'Australia']) ] print(f"할인율 40% 이상 + 4개 국가: {len(filtered):,}행") # 4단계: 브랜드별 집계 result = filtered.groupby('brand').agg( 평균_할인율=('discount_rate', 'mean'), 총_판매건수=('order_id', 'count'), 평균_판매가격=('sale_price', 'mean') ).round(2) # 5단계: 판매 건수 50건 이상만 result = result[result['총_판매건수'] >= 50] # 6단계: 정렬 result = result.sort_values('평균_할인율', ascending=False) print(f"\n조건 만족 브랜드: {len(result)}개") print(result.head(10))
실행 결과
브랜드명에 공백 포함: 45,678행
할인율 40% 이상 + 4개 국가: 8,234행

조건 만족 브랜드: 15개
                        평균_할인율  총_판매건수  평균_판매가격
brand
Calvin Klein Performance     53.45       245       42.30
Calvin Klein Jeans           52.12       312       45.67
True Religion                51.78       189       58.90
Tommy Hilfiger               50.45       456       52.34
Ralph Lauren                 49.89       378       61.23
Under Armour                 48.56       234       55.12
Hurley International         47.90       156       48.45
Diesel Black Gold            47.23        89       72.34
Theory                       46.78       123       85.67
Hugo Boss                    46.12        67       95.23

정리

필터링 메서드 비교

메서드용도SQL 대응
query()복잡한 조건WHERE 절 전체
isin()여러 값 중 하나IN (...)
between()범위 조건BETWEEN ... AND ...
str.contains()문자열 포함LIKE '%...%'
str.startswith()접두사 일치LIKE '...%'

SQL ↔ Pandas 비교

SQLPandas
WHERE age > 30df[df['age'] > 30]
WHERE age > 30 AND price < 100df[(df['age'] > 30) & (df['price'] < 100)]
WHERE country IN ('US', 'DE')df[df['country'].isin(['US', 'DE'])]
WHERE price BETWEEN 50 AND 100df[df['price'].between(50, 100)]
WHERE name LIKE '%Kim%'df[df['name'].str.contains('Kim')]

다음 단계

필터링을 마스터했습니다! 다음으로 그룹화와 집계에서 groupby(), agg(), transform() 등 고급 집계 기법을 배워보세요.

Last updated on

🤖AI 모의면접실전처럼 연습하기