고급 데이터 필터링
중급
학습 목표
이 레시피를 완료하면 다음을 할 수 있습니다:
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() 메서드를 사용하여:
- 나이 30 이상
- 판매가격 40~120 사이
- 성별은 남성(‘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() 결합
문제
다음 조건을 모두 만족하는 데이터를 필터링하세요:
- 카테고리가 ‘Jeans’, ‘Sweaters’, ‘Pants’ 중 하나
- 가격이 25~75 사이
- 부서가 ‘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: 복합 필터링 (고급)
문제
다음 조건으로 필터링하세요:
- 브랜드명에 공백(’ ‘)이 포함된 브랜드
- 판매가격이 소매가격의 60% 이하 (할인율 40% 이상)
- 국가(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 비교
| SQL | Pandas |
|---|---|
WHERE age > 30 | df[df['age'] > 30] |
WHERE age > 30 AND price < 100 | df[(df['age'] > 30) & (df['price'] < 100)] |
WHERE country IN ('US', 'DE') | df[df['country'].isin(['US', 'DE'])] |
WHERE price BETWEEN 50 AND 100 | df[df['price'].between(50, 100)] |
WHERE name LIKE '%Kim%' | df[df['name'].str.contains('Kim')] |
다음 단계
필터링을 마스터했습니다! 다음으로 그룹화와 집계에서 groupby(), agg(), transform() 등 고급 집계 기법을 배워보세요.
Last updated on