이미지 로딩 중...

GroupBy 및 Aggregation 활용 완벽 가이드 - 슬라이드 1/11
A

AI Generated

2025. 11. 21. · 6 Views

GroupBy 및 Aggregation 활용 완벽 가이드

데이터를 그룹으로 묶어서 통계를 내는 GroupBy와 Aggregation 기법을 배워봅니다. 실무에서 자주 사용하는 데이터 분석 패턴과 최적화 방법을 다룹니다. Python Pandas를 활용한 실전 예제로 구성되어 있습니다.


목차

  1. GroupBy 기본 개념 이해하기
  2. 다양한 Aggregation 함수 활용하기
  3. 여러 컬럼으로 그룹화하기
  4. Transform으로 그룹 통계를 원본 데이터에 추가하기
  5. Filter로 조건에 맞는 그룹만 선택하기
  6. Apply로 그룹별 커스텀 연산 수행하기
  7. Named Aggregation으로 결과 컬럼명 깔끔하게 만들기
  8. 시계열 데이터를 시간 단위로 그룹화하기
  9. Pivot Table로 2차원 집계 테이블 만들기
  10. Cut과 Qcut으로 연속형 데이터를 그룹으로 나누기

1. GroupBy 기본 개념 이해하기

시작하며

여러분이 회사에서 월별 매출 데이터를 분석해야 하는 상황을 상상해보세요. 수천 개의 주문 데이터가 있는데, 각 월별로 총 매출이 얼마인지 알고 싶습니다.

일일이 손으로 계산하기엔 너무 많고, 어떻게 해야 할까요? 이런 문제는 데이터 분석 업무에서 매일같이 발생합니다.

카테고리별로 데이터를 묶고, 각 그룹의 통계를 계산하는 작업은 데이터 분석의 가장 기본이면서도 핵심적인 작업입니다. 바로 이럴 때 필요한 것이 GroupBy입니다.

GroupBy를 사용하면 데이터를 원하는 기준으로 그룹화하고, 각 그룹별 통계를 단 몇 줄의 코드로 계산할 수 있습니다.

개요

간단히 말해서, GroupBy는 데이터를 특정 기준(컬럼)으로 묶어서 그룹을 만드는 기능입니다. 마치 학급의 학생들을 성별로 나누거나, 반별로 나누는 것처럼 말이죠.

왜 이 개념이 필요할까요? 실무에서는 원본 데이터보다 요약된 통계가 훨씬 중요합니다.

예를 들어, 수백만 건의 주문 데이터에서 "지역별 평균 주문 금액"이나 "카테고리별 판매량"을 알아야 비즈니스 의사결정을 할 수 있습니다. 기존에는 반복문을 돌면서 직접 그룹을 만들고 계산해야 했다면, 이제는 groupby() 메서드 하나로 간단히 처리할 수 있습니다.

GroupBy의 핵심 특징은 세 가지입니다: (1) Split - 데이터를 그룹으로 나누기, (2) Apply - 각 그룹에 함수 적용하기, (3) Combine - 결과를 하나로 합치기. 이 세 단계를 이해하면 GroupBy의 모든 것을 이해한 것입니다.

코드 예제

import pandas as pd

# 샘플 매출 데이터 생성
sales_data = pd.DataFrame({
    'date': ['2024-01', '2024-01', '2024-02', '2024-02', '2024-03'],
    'product': ['노트북', '마우스', '노트북', '키보드', '마우스'],
    'amount': [1500000, 30000, 1600000, 80000, 35000],
    'quantity': [1, 2, 1, 1, 3]
})

# 월별로 그룹화하여 총 매출과 평균 매출 계산
monthly_sales = sales_data.groupby('date').agg({
    'amount': ['sum', 'mean'],  # 합계와 평균
    'quantity': 'sum'  # 수량 합계
})

print(monthly_sales)

설명

이것이 하는 일: 위 코드는 매출 데이터를 날짜(월)별로 그룹화하고, 각 월의 총 매출액, 평균 매출액, 총 판매 수량을 계산합니다. 첫 번째로, DataFrame을 생성하는 부분에서 날짜, 상품명, 금액, 수량 데이터를 담습니다.

실무에서는 이런 데이터가 CSV 파일이나 데이터베이스에서 읽어올 수 있습니다. 각 행은 하나의 주문을 나타냅니다.

그 다음으로, groupby('date')가 실행되면 Pandas는 내부적으로 date 컬럼의 값이 같은 행들을 하나의 그룹으로 묶습니다. 2024-01이 같은 행끼리, 2024-02가 같은 행끼리 그룹이 만들어집니다.

이때 실제 데이터는 복사되지 않고, 그룹 정보만 저장됩니다. agg() 메서드가 호출되면 각 그룹에 대해 지정된 집계 함수들이 적용됩니다.

amount 컬럼에는 sum(합계)과 mean(평균) 두 가지 함수가 적용되고, quantity 컬럼에는 sum만 적용됩니다. 마지막으로 모든 그룹의 결과가 하나의 DataFrame으로 합쳐집니다.

여러분이 이 코드를 사용하면 수천, 수만 건의 데이터도 순식간에 월별 통계로 요약할 수 있습니다. 반복문을 사용하는 것보다 10배 이상 빠르며, 코드도 훨씬 읽기 쉽고 유지보수하기 좋습니다.

또한 결과는 자동으로 정렬되고 인덱싱되어 나오므로 추가 처리 없이 바로 시각화나 리포트 생성에 사용할 수 있습니다.

실전 팁

💡 groupby() 후 바로 집계 함수를 호출하지 말고, 먼저 .groups로 어떤 그룹들이 생성되었는지 확인하면 디버깅이 쉽습니다

💡 여러 컬럼으로 그룹화할 때는 groupby(['col1', 'col2']) 형식으로 리스트를 전달하면 계층적 그룹이 만들어집니다

💡 대용량 데이터에서는 groupby 전에 필요한 컬럼만 선택(df[['col1', 'col2']])하면 메모리 사용량을 크게 줄일 수 있습니다

💡 NaN(결측값)이 있는 데이터는 기본적으로 그룹에서 제외되므로, dropna=False 옵션으로 NaN도 하나의 그룹으로 취급할 수 있습니다

💡 groupby 결과를 변수에 저장하면 여러 번 재사용할 수 있어서, 같은 그룹에 다양한 집계를 적용할 때 효율적입니다


2. 다양한 Aggregation 함수 활용하기

시작하며

여러분이 쇼핑몰 데이터를 분석하는데, 단순히 합계만 보는 게 아니라 최댓값, 최솟값, 표준편차까지 모두 보고 싶다면 어떻게 해야 할까요? 매번 다른 함수를 여러 번 호출하면 코드가 길어지고 비효율적입니다.

이런 문제는 데이터 분석에서 항상 발생합니다. 하나의 그룹에 대해 여러 가지 통계를 동시에 계산해야 하는 경우가 대부분이기 때문입니다.

각 통계마다 별도로 계산하면 시간도 오래 걸리고 실수할 가능성도 높아집니다. 바로 이럴 때 필요한 것이 다양한 Aggregation 함수들입니다.

Pandas는 sum, mean, count부터 std, var, quantile까지 거의 모든 통계 함수를 제공하며, 한 번에 여러 함수를 적용할 수 있습니다.

개요

간단히 말해서, Aggregation은 여러 데이터를 하나의 대표값으로 요약하는 과정입니다. 마치 반 전체 학생의 키를 측정한 후 평균 키를 계산하는 것처럼, 많은 값을 하나의 의미 있는 값으로 줄이는 것입니다.

왜 이 개념이 필요한지는 명확합니다. 비즈니스 의사결정을 할 때는 원본 데이터를 보는 게 아니라 요약된 통계를 봅니다.

예를 들어, "이번 달 고객들의 평균 구매 금액은 얼마인가?", "가장 많이 팔린 상품은?", "판매량의 편차는 어느 정도인가?" 같은 질문에 답하려면 Aggregation이 필수입니다. 기존에는 각 통계를 따로따로 계산해서 수동으로 합쳐야 했다면, 이제는 agg() 메서드에 원하는 함수들을 나열하기만 하면 됩니다.

Aggregation 함수의 핵심 특징은: (1) 다양성 - sum, mean, median, std, min, max, count 등 거의 모든 통계 함수 지원, (2) 유연성 - 컬럼마다 다른 함수 적용 가능, (3) 확장성 - 사용자 정의 함수도 적용 가능. 이 세 가지 특징 덕분에 어떤 복잡한 분석도 간단하게 처리할 수 있습니다.

코드 예제

import pandas as pd
import numpy as np

# 고객 주문 데이터
orders = pd.DataFrame({
    'customer_id': [1, 1, 2, 2, 3, 3, 3],
    'order_amount': [50000, 30000, 120000, 80000, 45000, 60000, 55000],
    'order_date': pd.date_range('2024-01-01', periods=7)
})

# 고객별 다양한 통계 계산
customer_stats = orders.groupby('customer_id')['order_amount'].agg([
    'count',        # 주문 횟수
    'sum',          # 총 구매액
    'mean',         # 평균 구매액
    'median',       # 중앙값
    'std',          # 표준편차
    ('max', 'max'), # 최고 구매액
    ('min', 'min')  # 최저 구매액
])

print(customer_stats)

설명

이것이 하는 일: 위 코드는 각 고객별로 주문 횟수, 총 구매액, 평균 구매액, 중앙값, 표준편차, 최고/최저 구매액을 모두 계산하여 한눈에 보여줍니다. 첫 번째로, 고객 주문 데이터를 생성합니다.

customer_id는 고객을 식별하는 키이고, order_amount는 각 주문의 금액입니다. 실무에서는 이런 데이터가 주문 관리 시스템에서 나올 것입니다.

그 다음으로, groupby('customer_id')로 고객별로 그룹을 만듭니다. 이때 ['order_amount']를 추가로 선택하여 order_amount 컬럼에만 집계 함수를 적용하겠다고 명시합니다.

이렇게 하면 결과가 더 깔끔한 DataFrame으로 나옵니다. agg() 메서드에 리스트 형태로 여러 함수명을 전달하면, 각 함수가 순차적으로 적용됩니다.

'count'는 각 고객의 주문 횟수를, 'sum'은 총액을, 'mean'은 평균을 계산합니다. 'std'(표준편차)는 구매 패턴의 일관성을 보여주는데, 값이 크면 구매 금액이 들쭉날쭉하다는 의미입니다.

특히 주목할 점은 ('max', 'max') 같은 튜플 형식입니다. 첫 번째는 결과 컬럼명, 두 번째는 함수명입니다.

이렇게 하면 결과 컬럼의 이름을 직접 지정할 수 있어서, 나중에 데이터를 다룰 때 더 편리합니다. 여러분이 이 코드를 사용하면 고객별 구매 패턴을 한눈에 파악할 수 있습니다.

예를 들어, 평균은 높지만 표준편차도 높은 고객은 큰 금액과 작은 금액을 번갈아 구매하는 고객이고, 평균은 낮지만 count가 높은 고객은 자주 소액 구매하는 고객입니다. 이런 인사이트를 바탕으로 맞춤형 마케팅 전략을 수립할 수 있습니다.

실전 팁

💡 컬럼별로 다른 집계 함수를 적용하려면 딕셔너리 형태로 {'col1': 'sum', 'col2': ['mean', 'std']}처럼 전달하면 됩니다

💡 사용자 정의 함수도 적용 가능한데, lambda x: x.max() - x.min() 같은 식으로 범위(range)를 계산할 수 있습니다

💡 quantile 함수로 사분위수를 계산할 때는 agg([('q25', lambda x: x.quantile(0.25))]) 형식을 사용하면 됩니다

💡 결과 컬럼명이 복잡해지면 .columns = ['new_name1', 'new_name2']로 나중에 변경할 수 있습니다

💡 성능이 중요한 경우 numpy 함수(np.sum, np.mean)를 직접 전달하면 pandas 함수보다 약간 더 빠릅니다


3. 여러 컬럼으로 그룹화하기

시작하며

여러분이 전국 매장의 판매 데이터를 분석하는데, 지역별로만 보는 게 아니라 "지역별, 제품 카테고리별" 매출을 동시에 보고 싶다면 어떻게 해야 할까요? 두 번 groupby를 해야 할까요?

이런 다차원 분석은 실무에서 매우 흔합니다. 비즈니스 데이터는 보통 여러 차원(지역, 시간, 제품, 고객 등)으로 나뉘기 때문에, 한 가지 기준으로만 그룹화하면 충분한 인사이트를 얻기 어렵습니다.

하지만 여러 번 그룹화하고 합치는 건 복잡하고 실수하기 쉽습니다. 바로 이럴 때 필요한 것이 다중 컬럼 그룹화입니다.

groupby()에 여러 컬럼을 리스트로 전달하면, 해당 컬럼들의 조합별로 그룹이 만들어집니다.

개요

간단히 말해서, 다중 컬럼 그룹화는 여러 기준을 동시에 적용하여 더 세밀한 그룹을 만드는 방법입니다. 마치 학생을 "학년별, 반별"로 나누면 "1학년 1반", "1학년 2반", "2학년 1반" 같은 조합 그룹이 만들어지는 것과 같습니다.

왜 이 개념이 필요한지는 비즈니스 분석의 특성 때문입니다. 예를 들어, "서울에서 노트북이 많이 팔렸다"는 정보보다 "서울의 강남구에서 게이밍 노트북이 20대 남성에게 많이 팔렸다"는 정보가 훨씬 가치 있습니다.

여러 차원을 동시에 분석해야 의미 있는 패턴을 발견할 수 있습니다. 기존에는 첫 번째 컬럼으로 그룹화한 후, 각 그룹 내에서 다시 두 번째 컬럼으로 그룹화하는 복잡한 과정을 거쳐야 했다면, 이제는 groupby(['col1', 'col2'])만으로 한 번에 처리됩니다.

다중 컬럼 그룹화의 핵심 특징은: (1) 계층적 인덱스(MultiIndex) 생성 - 결과가 여러 레벨의 인덱스를 가짐, (2) 조합 그룹 - 컬럼 값들의 모든 유니크 조합이 그룹이 됨, (3) 유연한 집계 - 각 조합별로 독립적인 통계 계산 가능. 이 특징들 덕분에 복잡한 다차원 분석도 직관적으로 수행할 수 있습니다.

코드 예제

import pandas as pd

# 지역별, 카테고리별 판매 데이터
sales = pd.DataFrame({
    'region': ['서울', '서울', '부산', '부산', '서울', '부산'],
    'category': ['전자', '가구', '전자', '가구', '전자', '전자'],
    'product': ['노트북', '책상', '마우스', '의자', '키보드', '모니터'],
    'sales': [15000000, 500000, 30000, 350000, 80000, 400000],
    'quantity': [10, 5, 20, 3, 8, 4]
})

# 지역별, 카테고리별 통계 계산
region_category_sales = sales.groupby(['region', 'category']).agg({
    'sales': ['sum', 'mean'],     # 매출 합계와 평균
    'quantity': 'sum',             # 판매 수량 합계
    'product': 'count'             # 제품 종류 수
}).round(0)  # 소수점 제거

print(region_category_sales)

설명

이것이 하는 일: 위 코드는 지역과 카테고리의 모든 조합(서울-전자, 서울-가구, 부산-전자, 부산-가구)별로 매출 합계, 평균, 판매 수량, 제품 종류 수를 계산합니다. 첫 번째로, 판매 데이터를 생성합니다.

region은 지역, category는 제품 카테고리, sales는 매출액, quantity는 판매 수량입니다. 실제 데이터에서는 이런 정보가 POS 시스템이나 ERP에서 추출될 것입니다.

그 다음으로, groupby(['region', 'category'])가 실행되면 Pandas는 region과 category의 값을 조합하여 그룹을 만듭니다. 예를 들어, region이 '서울'이면서 category가 '전자'인 모든 행이 하나의 그룹이 됩니다.

이렇게 만들어진 그룹은 계층적 구조를 가지게 됩니다. agg() 메서드에서는 각 컬럼별로 다른 집계 함수를 적용합니다.

sales 컬럼에는 합계와 평균을, quantity에는 합계를, product에는 count(개수)를 적용합니다. 여기서 product의 count는 해당 지역-카테고리 조합에서 몇 종류의 제품이 팔렸는지를 보여줍니다.

마지막으로 round(0)를 사용하여 소수점을 제거합니다. 금액 데이터에서 소수점은 보통 불필요하므로, 결과를 더 읽기 쉽게 만듭니다.

결과는 MultiIndex DataFrame으로 나오는데, 첫 번째 레벨은 region, 두 번째 레벨은 category가 됩니다. 여러분이 이 코드를 사용하면 지역별, 카테고리별 판매 성과를 한눈에 비교할 수 있습니다.

예를 들어, "서울의 전자 제품 평균 판매액이 부산보다 높은가?", "부산에서는 어떤 카테고리가 더 많이 팔리는가?" 같은 질문에 즉시 답할 수 있습니다. 이런 분석을 바탕으로 지역별 재고 배치나 마케팅 전략을 최적화할 수 있습니다.

실전 팁

💡 결과의 MultiIndex가 불편하면 .reset_index()를 사용하여 일반 컬럼으로 변환할 수 있습니다

💡 세 개 이상의 컬럼으로도 그룹화 가능하지만, 너무 많으면 결과가 복잡해지므로 보통 2-3개가 적당합니다

💡 특정 레벨만 선택하려면 결과에 .xs('서울', level='region')처럼 사용하면 해당 지역의 데이터만 추출할 수 있습니다

💡 그룹 순서를 바꾸려면 groupby(['category', 'region'])처럼 컬럼 순서를 변경하면 됩니다 - 결과의 계층 구조가 달라집니다

💡 각 그룹의 크기(데이터 개수)를 빠르게 확인하려면 .size() 메서드를 사용하면 됩니다


4. Transform으로 그룹 통계를 원본 데이터에 추가하기

시작하며

여러분이 각 학생의 시험 점수가 있는데, 각 학생의 점수 옆에 "반 평균 점수"를 추가하고 싶다면 어떻게 해야 할까요? groupby로 평균을 계산하면 데이터가 줄어들어서, 원본 행 수와 맞지 않게 됩니다.

이런 문제는 데이터 분석에서 자주 발생합니다. 그룹 통계는 계산하고 싶지만, 원본 데이터의 각 행에 그 통계를 붙이고 싶을 때가 많습니다.

예를 들어, "전체 평균 대비 이 값은 어떤가?"를 각 행마다 비교하고 싶은 경우입니다. 바로 이럴 때 필요한 것이 transform() 메서드입니다.

transform은 집계 결과를 원본 데이터의 크기에 맞춰 브로드캐스팅하여 반환합니다.

개요

간단히 말해서, transform은 그룹별 통계를 계산하되, 결과를 원본 데이터와 같은 길이로 만들어주는 기능입니다. 마치 각 학생에게 "네가 속한 반의 평균 점수"를 알려주는 것처럼, 각 데이터 포인트에 그룹 통계를 붙여줍니다.

왜 이 개념이 필요한지는 비교 분석의 필요성 때문입니다. 절댓값보다 상대적 위치가 중요할 때가 많습니다.

예를 들어, "이 고객의 구매액은 같은 연령대 평균보다 높은가?", "이 제품의 판매량은 같은 카테고리 평균 대비 어떤가?" 같은 질문에 답하려면, 원본 데이터에 그룹 평균이 함께 있어야 합니다. 기존에는 groupby로 통계를 계산한 후, merge나 join으로 원본 데이터에 다시 붙여야 했다면, 이제는 transform() 한 번으로 끝납니다.

transform의 핵심 특징은: (1) 크기 보존 - 결과가 항상 원본과 같은 길이, (2) 자동 매핑 - 각 그룹의 값이 해당 그룹의 모든 행에 자동 복사, (3) 계산 편의성 - 원본 값과 그룹 통계를 바로 비교 가능. 이 특징들 덕분에 상대적 분석이 매우 쉬워집니다.

코드 예제

import pandas as pd

# 학생 성적 데이터
students = pd.DataFrame({
    'name': ['철수', '영희', '민수', '지훈', '수진'],
    'class': ['A', 'A', 'B', 'B', 'B'],
    'score': [85, 92, 78, 88, 95]
})

# 각 학생의 점수와 반 평균을 함께 표시
students['class_avg'] = students.groupby('class')['score'].transform('mean')

# 평균 대비 차이 계산
students['diff_from_avg'] = students['score'] - students['class_avg']

# 평균보다 높으면 True
students['above_avg'] = students['diff_from_avg'] > 0

print(students)

설명

이것이 하는 일: 위 코드는 각 학생이 속한 반의 평균 점수를 계산하여 각 학생 행에 추가하고, 평균 대비 차이와 평균 이상 여부를 표시합니다. 첫 번째로, 학생 데이터를 생성합니다.

name은 학생 이름, class는 반, score는 시험 점수입니다. A반에는 2명, B반에는 3명의 학생이 있습니다.

그 다음으로, groupby('class')로 반별로 그룹을 만들고, ['score']로 점수 컬럼을 선택합니다. 여기서 transform('mean')을 호출하면, 각 반의 평균 점수가 계산되지만, 결과는 그룹당 1개의 값이 아니라 원본 데이터와 같은 5개의 값으로 반환됩니다.

A반 학생 2명에게는 A반 평균이, B반 학생 3명에게는 B반 평균이 각각 할당됩니다. 이렇게 만들어진 'class_avg' 컬럼을 사용하여 각 학생의 점수에서 반 평균을 빼면, 평균 대비 얼마나 높거나 낮은지를 알 수 있습니다.

'diff_from_avg' 컬럼이 양수면 평균보다 높은 것이고, 음수면 낮은 것입니다. 마지막으로, 'diff_from_avg' > 0 조건으로 불린(True/False) 컬럼을 만듭니다.

이렇게 하면 각 학생이 반 평균 이상인지 아닌지를 한눈에 볼 수 있습니다. 이런 정보는 개인별 피드백이나 맞춤형 학습 계획을 수립할 때 매우 유용합니다.

여러분이 이 코드를 사용하면 각 데이터 포인트의 상대적 위치를 쉽게 파악할 수 있습니다. 비즈니스에서는 "이 고객이 같은 세그먼트 내에서 어느 위치에 있는가?", "이 상품이 같은 카테고리에서 얼마나 잘 팔리는가?" 같은 질문에 답하는 데 활용할 수 있습니다.

또한 이상치(outlier) 탐지나 등급 분류에도 유용합니다.

실전 팁

💡 transform에는 'mean', 'sum', 'std' 같은 문자열뿐만 아니라 lambda 함수도 전달할 수 있어서 커스텀 계산이 가능합니다

💡 여러 통계를 동시에 추가하려면 transform을 여러 번 호출하거나, apply를 사용하여 여러 컬럼을 반환하는 함수를 만들면 됩니다

💡 표준화(standardization)를 그룹별로 적용할 때 transform이 매우 유용합니다: (x - group_mean) / group_std

💡 결측값(NaN)이 있으면 transform도 NaN을 반환하므로, fillna()로 미리 처리하거나 transform 후에 처리해야 합니다

💡 대용량 데이터에서는 transform보다 merge가 더 빠를 수 있으니, 성능 테스트를 해보고 선택하세요


5. Filter로 조건에 맞는 그룹만 선택하기

시작하며

여러분이 여러 매장의 판매 데이터가 있는데, "총 매출이 1000만원 이상인 매장"의 데이터만 보고 싶다면 어떻게 해야 할까요? 일반적인 필터링으로는 행 단위로만 걸러지지, 그룹 단위로 걸러지지 않습니다.

이런 문제는 그룹 레벨에서의 조건 필터링이 필요할 때 발생합니다. 개별 행이 아니라 그룹 전체가 특정 조건을 만족하는지 확인하고, 조건을 만족하는 그룹에 속한 모든 행을 가져오고 싶은 경우입니다.

일반적인 boolean 인덱싱으로는 이런 작업이 복잡합니다. 바로 이럴 때 필요한 것이 filter() 메서드입니다.

filter는 각 그룹에 조건 함수를 적용하고, True를 반환하는 그룹만 남깁니다.

개요

간단히 말해서, filter는 그룹 단위로 조건을 검사하여 조건을 만족하는 그룹만 남기는 기능입니다. 마치 "평균 점수가 80점 이상인 반"만 선택하면, 해당 반의 모든 학생이 선택되는 것과 같습니다.

왜 이 개념이 필요한지는 그룹 레벨 의사결정 때문입니다. 실무에서는 "성과가 좋은 팀", "활성 사용자가 많은 지역", "재구매율이 높은 고객 세그먼트"처럼 그룹 전체의 특성으로 판단해야 할 때가 많습니다.

개별 행 조건으로는 이런 분석이 불가능하거나 매우 복잡해집니다. 기존에는 groupby로 통계를 계산하고, 조건을 만족하는 그룹의 키를 찾아서, 다시 원본 데이터에서 해당 키를 가진 행만 필터링하는 여러 단계를 거쳐야 했다면, 이제는 filter() 한 번으로 끝납니다.

filter의 핵심 특징은: (1) 그룹 단위 조건 - 개별 행이 아닌 그룹 전체 평가, (2) 원본 형태 유지 - 조건을 만족하는 그룹의 모든 행 반환, (3) 유연한 조건 - 람다 함수로 복잡한 조건도 표현 가능. 이 특징들 덕분에 그룹 레벨 필터링이 직관적이고 간단해집니다.

코드 예제

import pandas as pd

# 매장별 일별 판매 데이터
store_sales = pd.DataFrame({
    'store_id': [1, 1, 1, 2, 2, 2, 3, 3],
    'date': ['2024-01', '2024-02', '2024-03', '2024-01', '2024-02', '2024-03', '2024-01', '2024-02'],
    'sales': [500000, 600000, 700000, 300000, 200000, 250000, 1200000, 1300000]
})

# 총 매출이 1500만원 이상인 매장만 선택
high_sales_stores = store_sales.groupby('store_id').filter(
    lambda x: x['sales'].sum() >= 1500000
)

print("필터링 후 데이터:")
print(high_sales_stores)

# 비교: 각 매장의 총 매출 확인
print("\n매장별 총 매출:")
print(store_sales.groupby('store_id')['sales'].sum())

설명

이것이 하는 일: 위 코드는 각 매장의 총 매출을 계산하여, 총 매출이 150만원 이상인 매장의 모든 거래 기록만 남깁니다. 첫 번째로, 매장별 일별 판매 데이터를 생성합니다.

store_id는 매장 식별자, date는 날짜, sales는 해당 날짜의 매출액입니다. 각 매장이 여러 날짜의 거래 기록을 가지고 있습니다.

그 다음으로, groupby('store_id')로 매장별로 그룹을 만듭니다. 여기서 filter() 메서드를 호출하면, 각 그룹(매장)에 대해 람다 함수가 실행됩니다.

람다 함수는 x로 각 그룹의 DataFrame을 받아서, 해당 그룹의 sales 컬럼 합계가 150만원 이상인지 검사합니다. 조건 검사 결과, 매장 1은 총 180만원(50+60+70), 매장 2는 총 75만원(30+20+25), 매장 3은 총 250만원(120+130)입니다.

따라서 매장 1과 3만 조건을 만족하므로, 이 두 매장의 모든 행(총 5개 행)이 결과로 반환됩니다. 매장 2의 행들은 모두 제외됩니다.

마지막으로, 비교를 위해 각 매장의 총 매출도 출력합니다. 이렇게 하면 어떤 매장이 필터링되었고 왜 그런지를 명확히 이해할 수 있습니다.

여러분이 이 코드를 사용하면 성과 기준으로 그룹을 선별할 수 있습니다. 예를 들어, "활성 사용자가 100명 이상인 지역", "평균 주문 금액이 5만원 이상인 고객 세그먼트", "클릭율이 5% 이상인 광고 캠페인" 같은 조건으로 데이터를 필터링하여, 성공 사례만 분석하거나 개선이 필요한 그룹을 찾아낼 수 있습니다.

이는 리소스를 효율적으로 배분하는 데 큰 도움이 됩니다.

실전 팁

💡 filter에 전달하는 함수는 반드시 True/False(불린값)를 반환해야 하며, 각 그룹에 대해 한 번씩 호출됩니다

💡 여러 조건을 동시에 적용하려면 and(&), or(|) 연산자를 사용하세요: lambda x: (x['sales'].sum() > 1000000) & (x['sales'].count() >= 3)

💡 조건에 통계 함수뿐만 아니라 고유값 개수(nunique), 최댓값/최솟값 등 어떤 집계 함수도 사용할 수 있습니다

💡 filter는 원본 행을 유지하므로 인덱스도 그대로 유지됩니다 - 필요하면 reset_index()로 재설정하세요

💡 대용량 데이터에서는 filter보다 groupby 후 조건을 만족하는 그룹 키를 찾아 isin()으로 필터링하는 게 더 빠를 수 있습니다


6. Apply로 그룹별 커스텀 연산 수행하기

시작하며

여러분이 각 그룹에서 "상위 2개 값만 선택"하거나 "특정 비율로 샘플링"하는 등 복잡한 작업을 하고 싶다면 어떻게 해야 할까요? agg, transform, filter는 모두 간단한 집계나 필터링만 가능합니다.

이런 문제는 비즈니스 요구사항이 복잡할 때 발생합니다. 예를 들어, "각 카테고리에서 가장 비싼 상품 3개만 추천하기", "각 지역에서 매출 상위 10% 고객만 VIP로 분류하기" 같은 작업은 단순한 집계 함수로는 불가능합니다.

바로 이럴 때 필요한 것이 apply() 메서드입니다. apply는 각 그룹을 DataFrame으로 받아서 자유롭게 처리한 후, 원하는 형태로 반환할 수 있는 가장 강력하고 유연한 도구입니다.

개요

간단히 말해서, apply는 각 그룹에 사용자 정의 함수를 적용하는 만능 도구입니다. 마치 각 반에 대해 "원하는 모든 작업"을 수행할 수 있는 자유로운 함수를 만들 수 있는 것과 같습니다.

왜 이 개념이 필요한지는 실무의 복잡성 때문입니다. 데이터 분석에서는 표준적인 집계만으로는 부족한 경우가 많습니다.

예를 들어, "각 고객의 최근 3개월 구매 패턴 분석", "각 제품의 계절별 판매 트렌드 계산", "각 지역의 이상치 탐지 및 제거" 같은 작업은 커스텀 로직이 필요합니다. 기존에는 groupby로 그룹을 나눈 후 반복문으로 각 그룹을 처리하고 수동으로 결과를 합쳐야 했다면, 이제는 apply()에 함수를 전달하기만 하면 자동으로 모든 그룹에 적용되고 결과가 합쳐집니다.

apply의 핵심 특징은: (1) 완전한 유연성 - 어떤 복잡한 로직도 구현 가능, (2) DataFrame 처리 - 각 그룹을 완전한 DataFrame으로 받아 모든 pandas 기능 사용 가능, (3) 자동 결합 - 각 그룹의 결과를 자동으로 하나로 합침. 이 특징들 덕분에 그룹 단위의 모든 복잡한 작업을 간결하게 처리할 수 있습니다.

코드 예제

import pandas as pd

# 제품 판매 데이터
products = pd.DataFrame({
    'category': ['전자', '전자', '전자', '가구', '가구', '가구', '가구'],
    'product': ['노트북', '마우스', '키보드', '책상', '의자', '서랍', '책장'],
    'price': [1500000, 30000, 80000, 500000, 350000, 200000, 450000],
    'sales': [100, 500, 300, 50, 80, 40, 45]
})

# 각 카테고리에서 판매량 기준 상위 2개 제품만 선택
def get_top_products(group):
    # 판매량으로 내림차순 정렬 후 상위 2개 반환
    return group.nlargest(2, 'sales')

top_products = products.groupby('category').apply(get_top_products).reset_index(drop=True)

print("각 카테고리 상위 2개 제품:")
print(top_products)

설명

이것이 하는 일: 위 코드는 각 제품 카테고리에서 판매량이 가장 높은 상위 2개 제품만 선택하여 새로운 DataFrame을 만듭니다. 첫 번째로, 제품 판매 데이터를 생성합니다.

category는 제품 카테고리, product는 제품명, price는 가격, sales는 판매량입니다. 전자 카테고리에 3개, 가구 카테고리에 4개 제품이 있습니다.

그 다음으로, get_top_products라는 함수를 정의합니다. 이 함수는 하나의 그룹(DataFrame)을 받아서, nlargest() 메서드로 sales 컬럼 기준 상위 2개 행을 반환합니다.

nlargest는 pandas의 편리한 메서드로, 정렬 없이 바로 상위 N개를 가져올 수 있어 효율적입니다. groupby('category')로 카테고리별 그룹을 만든 후, apply(get_top_products)를 호출하면, '전자' 그룹과 '가구' 그룹 각각에 대해 get_top_products 함수가 실행됩니다.

'전자' 그룹에서는 마우스(500)와 키보드(300)가 선택되고, '가구' 그룹에서는 의자(80)와 책장(45)이 선택됩니다. 마지막으로, apply의 결과는 MultiIndex를 가지므로, reset_index(drop=True)로 인덱스를 재설정하여 깔끔한 DataFrame을 만듭니다.

drop=True는 기존 인덱스를 버리고 새로 만든다는 의미입니다. 여러분이 이 코드를 사용하면 각 그룹에서 조건에 맞는 데이터만 선택할 수 있습니다.

실무에서는 "각 고객 세그먼트에서 구매액 상위 10%", "각 지역에서 평점이 높은 매장 5곳", "각 시간대에서 트래픽이 가장 높은 페이지" 같은 분석에 활용할 수 있습니다. 또한 apply 내에서 복잡한 계산, 머신러닝 모델 적용, 외부 API 호출 등 거의 모든 작업이 가능합니다.

실전 팁

💡 apply는 매우 유연하지만 느릴 수 있으므로, agg나 transform으로 해결 가능하면 그것을 우선 사용하세요

💡 함수 내에서 여러 컬럼을 반환하려면 Series나 DataFrame을 반환하면 되고, 단일 값이면 스칼라를 반환하면 됩니다

💡 각 그룹의 크기가 다르면 결과도 크기가 다를 수 있는데, apply는 이를 자동으로 처리합니다

💡 디버깅할 때는 apply 전에 먼저 첫 번째 그룹만 가져와서(.get_group()) 함수를 테스트해보세요

💡 성능이 중요하면 apply 대신 vectorized 연산이나 numba를 사용하는 것을 고려하세요


7. Named Aggregation으로 결과 컬럼명 깔끔하게 만들기

시작하며

여러분이 agg()로 여러 집계를 수행하면, 결과 컬럼명이 ('amount', 'sum'), ('amount', 'mean')처럼 튜플 형태로 나와서 불편했던 적이 있나요? 이런 컬럼명은 사용하기도 어렵고 보기에도 좋지 않습니다.

이런 문제는 복잡한 집계를 수행할 때마다 발생합니다. 특히 여러 컬럼에 다양한 집계 함수를 적용하면, 결과 컬럼명이 MultiIndex나 튜플 형태가 되어 나중에 컬럼을 참조하기가 매우 번거롭습니다.

수동으로 컬럼명을 변경하는 것도 귀찮고 실수하기 쉽습니다. 바로 이럴 때 필요한 것이 Named Aggregation입니다.

Pandas 0.25 버전부터 추가된 이 기능을 사용하면, 집계와 동시에 원하는 컬럼명을 지정할 수 있습니다.

개요

간단히 말해서, Named Aggregation은 집계를 수행하면서 결과 컬럼명을 직접 지정하는 방법입니다. 마치 "합계"라는 이름으로 sum을 저장하고, "평균"이라는 이름으로 mean을 저장하는 것처럼, 의미 있는 이름을 직접 부여할 수 있습니다.

왜 이 개념이 필요한지는 코드 가독성과 유지보수 때문입니다. 데이터 분석 결과를 다른 사람과 공유하거나, 나중에 다시 봤을 때, '총매출', '평균주문금액' 같은 명확한 컬럼명이 있으면 이해하기 훨씬 쉽습니다.

또한 이런 컬럼을 다시 참조할 때도 df['총매출']처럼 직관적으로 접근할 수 있습니다. 기존에는 agg() 후에 .columns = ['new_name1', 'new_name2']로 수동으로 변경하거나, rename()을 사용해야 했다면, 이제는 집계와 동시에 이름을 지정할 수 있어서 코드가 간결하고 명확해집니다.

Named Aggregation의 핵심 특징은: (1) 명확한 컬럼명 - 의미 있는 이름을 직접 지정, (2) 깔끔한 결과 - MultiIndex가 아닌 단순한 컬럼명, (3) 가독성 향상 - 코드만 봐도 어떤 집계인지 명확히 알 수 있음. 이 특징들 덕분에 분석 코드가 훨씬 전문적이고 읽기 쉬워집니다.

코드 예제

import pandas as pd

# 고객 주문 데이터
orders = pd.DataFrame({
    'customer_id': [1, 1, 2, 2, 3, 3, 3],
    'product': ['A', 'B', 'C', 'A', 'B', 'C', 'A'],
    'amount': [50000, 30000, 120000, 80000, 45000, 60000, 55000],
    'quantity': [1, 2, 1, 1, 3, 2, 1]
})

# Named Aggregation으로 깔끔한 컬럼명 지정
customer_summary = orders.groupby('customer_id').agg(
    주문횟수=('product', 'count'),        # product 컬럼의 개수를 "주문횟수"로
    총구매금액=('amount', 'sum'),         # amount 합계를 "총구매금액"으로
    평균구매금액=('amount', 'mean'),       # amount 평균을 "평균구매금액"으로
    총구매수량=('quantity', 'sum'),       # quantity 합계를 "총구매수량"으로
    최대구매금액=('amount', 'max')        # amount 최댓값을 "최대구매금액"으로
).round(0)  # 소수점 제거

print(customer_summary)

설명

이것이 하는 일: 위 코드는 고객별로 주문 횟수, 총 구매금액, 평균 구매금액, 총 구매수량, 최대 구매금액을 계산하면서, 각 결과에 한글로 명확한 이름을 부여합니다. 첫 번째로, 고객 주문 데이터를 생성합니다.

각 행은 하나의 주문을 나타내며, customer_id로 고객을 구분합니다. 고객 1은 2번, 고객 2는 2번, 고객 3은 3번 주문했습니다.

그 다음으로, groupby('customer_id')로 고객별 그룹을 만들고, agg() 메서드에 Named Aggregation 문법을 사용합니다. 각 줄은 "결과_컬럼명=(원본_컬럼, 집계_함수)" 형식입니다.

예를 들어, 주문횟수=('product', 'count')는 "product 컬럼의 개수를 세어서 '주문횟수'라는 이름으로 저장하라"는 의미입니다. 이 문법의 장점은 어떤 컬럼에 어떤 함수를 적용하는지가 명확하고, 결과 컬럼명도 바로 지정된다는 것입니다.

총구매금액=('amount', 'sum')을 보면, amount 컬럼의 합계가 '총구매금액'이라는 이름으로 저장된다는 것을 한눈에 알 수 있습니다. 모든 집계가 완료되면, round(0)로 소수점을 제거합니다.

특히 평균구매금액 같은 경우 소수점이 길게 나올 수 있는데, 금액은 보통 정수로 표시하는 게 보기 좋습니다. 여러분이 이 코드를 사용하면 분석 결과를 다른 사람과 공유할 때 훨씬 전문적으로 보입니다.

한글 컬럼명은 비개발자도 쉽게 이해할 수 있어서, 리포트나 대시보드에 바로 사용할 수 있습니다. 또한 나중에 이 DataFrame을 다시 사용할 때도 df['총구매금액']처럼 직관적으로 컬럼을 참조할 수 있어서 코드 유지보수가 쉬워집니다.

실전 팁

💡 영어 컬럼명을 선호한다면 total_amount=('amount', 'sum') 형식으로 사용하면 됩니다

💡 같은 컬럼에 여러 함수를 적용할 때 Named Aggregation이 특히 유용합니다 - 각 결과에 다른 이름을 부여할 수 있으니까요

💡 람다 함수도 사용 가능합니다: 범위=('amount', lambda x: x.max() - x.min())

💡 컬럼명에 공백이나 특수문자가 있으면 나중에 불편할 수 있으니, 언더스코어(_)를 사용하는 것을 권장합니다

💡 팀 내에서 컬럼 명명 규칙을 정해두면 일관성 있는 코드를 유지할 수 있습니다


8. 시계열 데이터를 시간 단위로 그룹화하기

시작하며

여러분이 시간별 웹사이트 방문 데이터가 있는데, 이를 일별, 주별, 월별로 집계하고 싶다면 어떻게 해야 할까요? 날짜를 문자열로 변환해서 groupby를 사용하는 것은 번거롭고 유연하지 않습니다.

이런 문제는 시계열 데이터 분석에서 항상 발생합니다. 비즈니스에서는 같은 데이터를 다양한 시간 단위로 봐야 합니다.

일별 트렌드를 보다가 주별로 보고, 월별로 보고, 분기별로 보는 것이 일반적입니다. 매번 날짜를 가공하는 것은 매우 비효율적입니다.

바로 이럴 때 필요한 것이 resample() 또는 Grouper입니다. 이들은 시계열 데이터를 자동으로 원하는 시간 단위로 그룹화해주는 특화된 도구입니다.

개요

간단히 말해서, resample과 Grouper는 날짜/시간 데이터를 특정 주기로 자동 그룹화하는 기능입니다. 마치 매일의 온도 데이터를 "주별 평균 온도"나 "월별 평균 온도"로 바꾸는 것처럼, 시간 단위를 변경할 수 있습니다.

왜 이 개념이 필요한지는 시계열 분석의 특성 때문입니다. 비즈니스 데이터는 대부분 시간 요소를 포함하며, 다양한 시간 단위로 패턴을 찾아야 합니다.

예를 들어, "이번 주 일평균 매출은?", "지난 분기 월평균 신규 가입자는?", "올해 분기별 성장률은?" 같은 질문에 답하려면 시간 단위 집계가 필수입니다. 기존에는 날짜 컬럼을 추출해서 연-월-일을 조합하고 groupby를 사용해야 했다면, 이제는 resample('D'), resample('W'), resample('M')처럼 간단한 코드로 일별, 주별, 월별 집계가 가능합니다.

시계열 그룹화의 핵심 특징은: (1) 자동 시간 단위 변환 - D(일), W(주), M(월), Q(분기), Y(연) 등 다양한 단위 지원, (2) 연속성 유지 - 데이터가 없는 기간도 인덱스에 포함 가능, (3) 집계 호환 - groupby처럼 agg, sum, mean 등 모든 집계 함수 사용 가능. 이 특징들 덕분에 시계열 분석이 매우 간단하고 직관적으로 처리됩니다.

코드 예제

import pandas as pd

# 일별 웹사이트 방문 데이터
visits = pd.DataFrame({
    'date': pd.date_range('2024-01-01', '2024-03-31', freq='D'),
    'visits': [100, 120, 95, 150, 200, 180, 160] * 13 + [100, 120, 95]  # 91일 데이터
})

# date를 인덱스로 설정 (resample은 datetime 인덱스 필요)
visits.set_index('date', inplace=True)

# 주별 집계 (일요일 시작)
weekly = visits.resample('W').agg({
    'visits': ['sum', 'mean', 'max']
})

print("주별 방문 통계:")
print(weekly.head())

# 월별 집계
monthly = visits.resample('M').sum()
print("\n월별 총 방문:")
print(monthly)

설명

이것이 하는 일: 위 코드는 일별 웹사이트 방문 데이터를 주별과 월별로 집계하여, 각 기간의 총 방문, 평균 방문, 최대 방문을 계산합니다. 첫 번째로, 일별 방문 데이터를 생성합니다.

date_range()로 2024년 1월 1일부터 3월 31일까지 91일의 날짜를 만들고, 각 날짜마다 방문자 수를 할당합니다. 실무에서는 이런 데이터가 Google Analytics나 자체 로그 시스템에서 나올 것입니다.

그 다음으로, date 컬럼을 인덱스로 설정합니다. resample()은 반드시 DatetimeIndex가 필요하므로 이 단계가 필수입니다.

set_index('date', inplace=True)는 date 컬럼을 인덱스로 만들고, 원본 DataFrame을 직접 수정합니다. resample('W')를 호출하면 데이터가 주 단위로 그룹화됩니다.

'W'는 일요일을 기준으로 한 주를 의미합니다(월요일 기준으로 하려면 'W-MON' 사용). 그 다음 agg()로 각 주의 총 방문(sum), 평균 방문(mean), 최대 방문(max)을 계산합니다.

결과는 각 주의 마지막 날짜를 인덱스로 하는 DataFrame입니다. 월별 집계는 더 간단합니다.

resample('M').sum()은 각 월의 총 방문자를 계산합니다. 'M'은 월말(Month End)을 기준으로 그룹화하며, 1월 31일, 2월 29일(또는 28일), 3월 31일이 각 월의 인덱스가 됩니다.

여러분이 이 코드를 사용하면 시계열 데이터를 다양한 시간 단위로 쉽게 분석할 수 있습니다. 예를 들어, "이번 주는 지난주보다 방문이 늘었나?", "어떤 월이 가장 방문이 많았나?", "분기별 성장세는 어떤가?" 같은 질문에 빠르게 답할 수 있습니다.

또한 시계열 시각화(라인 차트)를 만들 때도 적절한 시간 단위로 집계하면 트렌드가 더 명확히 보입니다.

실전 팁

💡 다양한 시간 단위: 'D'(일), 'W'(주), 'M'(월말), 'MS'(월초), 'Q'(분기), 'Y'(연말), 'H'(시간), 'T'(분) 등

💡 주의 시작 요일 변경: 'W-MON'(월요일), 'W-TUE'(화요일) 같은 식으로 지정 가능

💡 데이터가 없는 기간을 0으로 채우려면 resample().sum().fillna(0) 사용

💡 Grouper를 사용하면 날짜 컬럼과 다른 컬럼을 동시에 그룹화 가능: groupby([pd.Grouper(key='date', freq='M'), 'category'])

💡 다운샘플링(고빈도→저빈도)뿐만 아니라 업샘플링(저빈도→고빈도)도 가능하지만, 값을 채우는 방법(ffill, bfill, interpolate)을 선택해야 합니다


9. Pivot Table로 2차원 집계 테이블 만들기

시작하며

여러분이 "지역별, 제품별 매출"을 한눈에 보고 싶은데, 일반 groupby 결과는 세로로 길게 나와서 비교하기 어렵습니다. 엑셀의 피벗 테이블처럼 행과 열로 나뉜 2차원 표를 만들 수는 없을까요?

이런 문제는 크로스 분석(교차 분석)을 할 때 항상 발생합니다. 비즈니스 리포트에서는 한 차원을 행에, 다른 차원을 열에 배치한 표를 선호합니다.

이렇게 하면 여러 그룹을 한눈에 비교할 수 있고, 패턴을 쉽게 발견할 수 있습니다. 바로 이럴 때 필요한 것이 pivot_table()입니다.

pivot_table은 엑셀의 피벗 테이블과 같은 기능으로, 데이터를 행과 열의 2차원 표로 재구성하면서 동시에 집계를 수행합니다.

개요

간단히 말해서, pivot_table은 데이터를 행과 열의 교차점에 집계값을 배치한 2차원 테이블로 만드는 기능입니다. 마치 "각 반(행)의 각 과목(열)별 평균 점수"를 표로 만드는 것처럼, 두 차원을 동시에 펼쳐서 볼 수 있습니다.

왜 이 개념이 필요한지는 데이터 시각화와 비교 분석 때문입니다. 긴 세로 형태의 데이터보다 가로로 펼쳐진 표가 훨씬 읽기 쉽고 비교하기 좋습니다.

예를 들어, "각 영업 사원(행)의 월별(열) 매출"을 표로 보면, 누가 어느 달에 실적이 좋았는지 한눈에 파악할 수 있습니다. 기존에는 groupby로 집계한 후 unstack()으로 인덱스를 컬럼으로 변환하는 복잡한 과정을 거쳐야 했다면, 이제는 pivot_table() 한 번으로 행, 열, 집계 값을 모두 지정할 수 있습니다.

pivot_table의 핵심 특징은: (1) 2차원 구조 - 행과 열로 데이터 재구성, (2) 자동 집계 - 교차점에 자동으로 집계 함수 적용, (3) 유연성 - 여러 행, 여러 열, 여러 값 동시 지정 가능. 이 특징들 덕분에 복잡한 크로스 분석도 간단하게 수행할 수 있습니다.

코드 예제

import pandas as pd

# 지역별, 제품별, 월별 판매 데이터
sales = pd.DataFrame({
    'region': ['서울', '서울', '부산', '부산', '서울', '부산'] * 3,
    'product': ['노트북', '마우스', '노트북', '마우스', '노트북', '마우스'] * 3,
    'month': ['1월', '1월', '1월', '1월', '2월', '2월'] + ['2월', '2월', '3월', '3월', '3월', '3월'],
    'sales': [1500, 30, 1200, 25, 1600, 28, 35, 32, 1400, 27, 1550, 29]
})

# 지역(행) x 제품(열)의 총 매출 피벗 테이블
pivot = pd.pivot_table(
    sales,
    values='sales',        # 집계할 값
    index='region',        # 행이 될 컬럼
    columns='product',     # 열이 될 컬럼
    aggfunc='sum',         # 집계 함수
    fill_value=0,          # 결측값을 0으로 채움
    margins=True,          # 총계 행/열 추가
    margins_name='총계'     # 총계 레이블
)

print("지역별, 제품별 총 매출:")
print(pivot)

설명

이것이 하는 일: 위 코드는 지역을 행으로, 제품을 열로 배치하여, 각 지역-제품 조합의 총 매출을 2차원 표로 보여줍니다. 추가로 각 행과 열의 총계도 표시합니다.

첫 번째로, 지역별, 제품별, 월별 판매 데이터를 생성합니다. 각 행은 특정 지역, 특정 제품, 특정 월의 매출을 나타냅니다.

실무에서는 이런 트랜잭션 데이터가 매일 쌓입니다. 그 다음으로, pivot_table()을 호출하면서 여러 파라미터를 전달합니다.

values='sales'는 집계할 값이 sales 컬럼이라는 의미입니다. index='region'은 지역을 행으로 배치하고, columns='product'는 제품을 열로 배치합니다.

이렇게 하면 "서울-노트북", "서울-마우스", "부산-노트북", "부산-마우스"의 네 가지 조합이 표의 셀이 됩니다. aggfunc='sum'은 각 조합(셀)에서 sales 값들을 합산하라는 의미입니다.

만약 서울에서 노트북 판매 데이터가 여러 개(여러 달) 있다면, 그 값들이 모두 합쳐집니다. aggfunc='mean'으로 바꾸면 평균이 계산됩니다.

fill_value=0은 데이터가 없는 조합의 셀을 0으로 채웁니다. 예를 들어, 특정 지역에서 특정 제품이 한 번도 팔리지 않았다면, 기본적으로 NaN이 나오는데, 0으로 바꿔주면 표가 더 깔끔해집니다.

margins=True는 각 행의 합계(오른쪽에 추가 열)와 각 열의 합계(아래에 추가 행)를 자동으로 계산해줍니다. margins_name='총계'는 그 합계 행/열의 레이블을 '총계'로 지정합니다.

여러분이 이 코드를 사용하면 복잡한 데이터를 직관적인 표로 만들 수 있습니다. 예를 들어, "어느 지역에서 어느 제품이 가장 잘 팔리는가?", "서울과 부산의 제품별 매출 차이는?" 같은 질문에 표를 한번 보는 것만으로 답할 수 있습니다.

이런 피벗 테이블은 그대로 리포트에 넣거나, heatmap 같은 시각화로 변환하기에도 매우 적합합니다.

실전 팁

💡 여러 값을 동시에 집계하려면 values=['sales', 'quantity']처럼 리스트로 전달하면 됩니다

💡 여러 집계 함수를 동시 적용: aggfunc=['sum', 'mean', 'count']로 한 번에 여러 통계 계산 가능

💡 행이나 열에 여러 컬럼 지정: index=['region', 'category']로 계층적 피벗 테이블 생성

💡 결과를 Excel처럼 보려면 .style.background_gradient()로 색상을 입혀서 히트맵처럼 만들 수 있습니다

💡 pivot_table 결과를 .to_excel()로 저장하면 엑셀 리포트로 바로 사용 가능합니다


10. Cut과 Qcut으로 연속형 데이터를 그룹으로 나누기

시작하며

여러분이 고객의 나이나 구매 금액처럼 연속적인 숫자 데이터를 "20대, 30대, 40대" 또는 "저가, 중가, 고가"처럼 구간으로 나눠서 분석하고 싶다면 어떻게 해야 할까요? 하나하나 if 문으로 분류하는 것은 너무 번거롭습니다.

이런 문제는 비즈니스에서 매우 흔합니다. 연속형 데이터를 그대로 사용하는 것보다, 의미 있는 구간으로 나누면 패턴을 파악하기 훨씬 쉽습니다.

예를 들어, "구매 금액 5만원대, 10만원대, 20만원대 고객의 특성은 어떻게 다른가?" 같은 분석을 하려면 구간화가 필수입니다. 바로 이럴 때 필요한 것이 cut()과 qcut()입니다.

cut은 값의 범위를 기준으로 균등하게 나누고, qcut은 데이터의 분포를 기준으로 동일한 개수가 되도록 나눕니다.

개요

간단히 말해서, cut과 qcut은 연속형 숫자 데이터를 카테고리(구간)로 변환하는 기능입니다. 마치 학생들의 키를 "작음, 보통, 큼"으로 분류하거나, 소득을 "하위, 중위, 상위"로 나누는 것처럼 말이죠.

왜 이 개념이 필요한지는 데이터 해석의 편의성 때문입니다. 연속형 데이터는 값이 너무 다양해서 groupby로 그룹화하기 어렵습니다.

예를 들어, 나이가 23, 24, 25세인 사람들을 각각 다른 그룹으로 보는 것보다, "20대"라는 하나의 그룹으로 보는 게 훨씬 의미 있습니다. 기존에는 apply나 lambda로 복잡한 조건문을 작성해야 했다면, 이제는 cut(data, bins=[0, 30, 60, 100])처럼 간단하게 구간을 지정할 수 있습니다.

cut과 qcut의 핵심 특징은: (1) cut - 동일한 너비의 구간으로 분할(예: 0-100을 0-33, 34-66, 67-100으로), (2) qcut - 동일한 개수가 되도록 분할(예: 데이터를 3등분하여 각 구간에 같은 수의 데이터), (3) 레이블 지정 - 각 구간에 의미 있는 이름 부여 가능. 이 특징들 덕분에 데이터를 의미 있는 카테고리로 쉽게 변환할 수 있습니다.

코드 예제

import pandas as pd
import numpy as np

# 고객 데이터 (나이와 구매 금액)
customers = pd.DataFrame({
    'customer_id': range(1, 21),
    'age': [23, 35, 42, 28, 55, 31, 47, 29, 38, 52, 26, 44, 33, 58, 27, 41, 36, 49, 30, 53],
    'purchase': [50000, 120000, 80000, 45000, 200000, 70000, 150000, 55000, 90000, 180000,
                 60000, 130000, 75000, 210000, 48000, 140000, 85000, 160000, 65000, 190000]
})

# 나이를 10년 단위 구간으로 나누기 (cut - 동일 너비)
customers['age_group'] = pd.cut(
    customers['age'],
    bins=[20, 30, 40, 50, 60],
    labels=['20대', '30대', '40대', '50대']
)

# 구매 금액을 4분위로 나누기 (qcut - 동일 개수)
customers['purchase_tier'] = pd.qcut(
    customers['purchase'],
    q=4,
    labels=['하위', '중하위', '중상위', '상위']
)

# 연령대별, 구매등급별 고객 수 집계
summary = customers.groupby(['age_group', 'purchase_tier']).size()
print(summary)

설명

이것이 하는 일: 위 코드는 고객의 나이를 10년 단위 연령대로, 구매 금액을 4개 등급으로 나눈 후, 각 조합별 고객 수를 집계합니다. 첫 번째로, 고객 데이터를 생성합니다.

20명의 고객에 대해 나이와 구매 금액 정보가 있습니다. 나이는 23세부터 58세까지 다양하고, 구매 금액도 4만5천원부터 21만원까지 넓게 분포합니다.

그 다음으로, pd.cut()을 사용하여 나이를 구간으로 나눕니다. bins=[20, 30, 40, 50, 60]은 20-30, 30-40, 40-50, 50-60의 네 구간을 의미합니다(기본적으로 왼쪽은 열려있고 오른쪽은 닫혀있음, 즉 30은 30대에 포함).

labels=['20대', '30대', '40대', '50대']로 각 구간에 이름을 붙입니다. 이제 23세는 '20대', 35세는 '30대'로 자동 분류됩니다.

pd.qcut()은 조금 다르게 작동합니다. q=4는 데이터를 4등분하라는 의미로, 전체 20명을 5명씩 나눕니다.

구매 금액을 오름차순으로 정렬했을 때, 가장 낮은 5명은 '하위', 다음 5명은 '중하위', 그 다음 5명은 '중상위', 가장 높은 5명은 '상위'가 됩니다. 이렇게 하면 각 등급의 고객 수가 동일해집니다.

마지막으로, groupby(['age_group', 'purchase_tier']).size()로 각 연령대-구매등급 조합별 고객 수를 세어봅니다. 예를 들어, "30대 중상위 구매 고객이 몇 명인가?", "50대 상위 구매 고객은 몇 명인가?" 같은 질문에 답할 수 있습니다.

여러분이 이 코드를 사용하면 연속형 데이터를 의미 있는 세그먼트로 나눌 수 있습니다. 마케팅에서는 "20대 저가 구매 고객에게는 할인 쿠폰, 40대 고가 구매 고객에게는 프리미엄 상품 추천" 같은 타겟 전략을 수립할 수 있습니다.

또한 연령대별, 소득 구간별 소비 패턴을 분석하여 제품 기획이나 가격 정책에 반영할 수 있습니다.

실전 팁

💡 cut에서 구간의 경계를 포함하는 방식 변경: right=False로 설정하면 왼쪽 경계를 포함하고 오른쪽을 제외합니다

💡 qcut에서 동일 값이 많으면 에러가 날 수 있는데, duplicates='drop'으로 중복 경계를 제거할 수 있습니다

💡 구간 경계를 확인하려면 retbins=True 옵션으로 실제 사용된 bins를 반환받을 수 있습니다

💡 무한대 경계를 사용하려면 bins=[0, 50, 100, np.inf]처럼 np.inf를 사용하면 됩니다

💡 labels=False로 설정하면 레이블 대신 0, 1, 2, 3 같은 숫자 인덱스가 반환되어 계산에 사용하기 편리합니다


#Python#GroupBy#Aggregation#Pandas#DataAnalysis#Data Science

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.

함께 보면 좋은 카드 뉴스

데이터 증강과 정규화 완벽 가이드

머신러닝 모델의 성능을 극대화하는 핵심 기법인 데이터 증강과 정규화에 대해 알아봅니다. 실무에서 바로 활용할 수 있는 다양한 기법과 실전 예제를 통해 과적합을 방지하고 모델 성능을 향상시키는 방법을 배웁니다.

ResNet과 Skip Connection 완벽 가이드

딥러닝 모델이 깊어질수록 성능이 떨어지는 문제를 해결한 혁신적인 기법, ResNet과 Skip Connection을 초급자도 이해할 수 있도록 쉽게 설명합니다. 실제 구현 코드와 함께 배워보세요.

CNN 아키텍처 완벽 가이드 LeNet AlexNet VGGNet

컴퓨터 비전의 기초가 되는 세 가지 핵심 CNN 아키텍처를 배웁니다. 손글씨 인식부터 이미지 분류까지, 딥러닝의 발전 과정을 따라가며 각 모델의 구조와 특징을 실습 코드와 함께 이해합니다.

CNN 기초 Convolution과 Pooling 완벽 가이드

CNN의 핵심인 Convolution과 Pooling을 초급자도 쉽게 이해할 수 있도록 설명합니다. 이미지 인식의 원리부터 실제 코드 구현까지, 실무에서 바로 활용 가능한 내용을 담았습니다.

TensorFlow와 Keras 완벽 입문 가이드

머신러닝과 딥러닝의 세계로 들어가는 첫걸음! TensorFlow와 Keras 프레임워크를 처음 접하는 분들을 위한 친절한 가이드입니다. 실무에서 바로 활용할 수 있는 핵심 개념과 예제를 통해 AI 모델 개발의 기초를 탄탄히 다져보세요.