🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

Pandas 기본 통계와 집계 완벽 가이드 - 슬라이드 1/8
A

AI Generated

2025. 12. 16. · 9 Views

Pandas 기본 통계와 집계 완벽 가이드

데이터 분석의 시작, 통계와 집계 함수를 마스터해보세요. sum, mean, count부터 고급 집계까지 실무에서 바로 쓸 수 있는 핵심 기법들을 단계별로 소개합니다.


목차

  1. sum()으로_합계_구하기
  2. mean()으로_평균_계산
  3. max(),_min()으로_최대/최소값
  4. count()로_개수_세기
  5. value_counts()로_빈도_분석
  6. 여러_통계_한번에_계산하기
  7. 컬럼별_통계_비교

1. sum()으로 합계 구하기

어느 날 김개발 씨가 회사에서 첫 데이터 분석 업무를 받았습니다. "이번 달 전체 매출 합계를 구해주세요." 간단해 보이는 요청이었지만, 수천 개의 데이터를 어떻게 더해야 할지 막막했습니다.

sum() 함수는 데이터프레임이나 시리즈의 모든 값을 더해주는 가장 기본적인 집계 함수입니다. 마치 계산기의 합계 버튼을 누르는 것처럼, 복잡한 반복문 없이 단 한 줄로 모든 값을 합산할 수 있습니다.

데이터 분석의 첫 걸음이자 가장 많이 사용되는 함수입니다.

다음 코드를 살펴봅시다.

import pandas as pd

# 매출 데이터 생성
sales_data = pd.DataFrame({
    'product': ['노트북', '마우스', '키보드', '모니터'],
    'price': [1200000, 35000, 89000, 450000],
    'quantity': [5, 20, 15, 8]
})

# 전체 판매 금액 합계
total_price = sales_data['price'].sum()
print(f"총 상품 가격 합계: {total_price:,}원")

# 모든 숫자 컬럼의 합계 한번에
all_sums = sales_data.sum(numeric_only=True)
print(all_sums)

김개발 씨는 입사 2개월 차 주니어 데이터 분석가입니다. 오늘 팀장님으로부터 첫 분석 업무를 받았습니다.

"이번 달 전체 매출을 집계해서 보고서를 작성해주세요." 엑셀로 수천 개의 데이터를 일일이 더하기엔 너무 번거로웠습니다. 선배 개발자 박시니어 씨가 지나가다가 김개발 씨의 화면을 보고는 빙그레 웃으며 말했습니다.

"아직도 수작업으로 하시나요? Pandas의 sum() 함수를 사용하면 1초 만에 끝나는데요." sum() 함수란 무엇일까요? 쉽게 비유하자면, sum() 함수는 마치 마트 계산대의 자동 계산기와 같습니다.

손님이 장바구니에 담은 모든 상품을 자동으로 스캔해서 총 금액을 계산해주는 것처럼, sum() 함수도 데이터의 모든 값을 자동으로 읽어서 합계를 계산해줍니다. 우리가 일일이 더하기 버튼을 누를 필요가 없습니다.

sum() 함수가 없던 시절에는 어땠을까요? 과거 데이터 분석가들은 반복문을 작성해서 하나하나 값을 더해야 했습니다. 변수를 초기화하고, for 문을 돌리고, 매 반복마다 값을 누적하는 코드를 작성했습니다.

코드가 길어지고, 실수하기도 쉬웠습니다. 더 큰 문제는 데이터가 수만 건, 수십만 건으로 늘어날 때 성능이 현저히 떨어진다는 것이었습니다.

바로 이런 문제를 해결하기 위해 sum() 함수가 등장했습니다. sum() 함수를 사용하면 단 한 줄로 모든 값의 합계를 구할 수 있습니다. 또한 Pandas의 최적화된 알고리즘 덕분에 수십만 건의 데이터도 순식간에 처리할 수 있습니다.

무엇보다 코드가 직관적이어서 다른 사람이 봐도 한눈에 이해할 수 있다는 큰 이점이 있습니다. 위의 코드를 한 줄씩 살펴보겠습니다. 먼저 3번째 줄부터 7번째 줄을 보면 샘플 데이터프레임을 생성하는 것을 알 수 있습니다.

실무에서는 보통 CSV 파일이나 데이터베이스에서 데이터를 읽어오지만, 이해를 돕기 위해 직접 만들었습니다. 10번째 줄이 핵심입니다.

sales_data['price'].sum()은 price 컬럼의 모든 값을 더해줍니다. 이 한 줄로 1,200,000 + 35,000 + 89,000 + 450,000이 자동으로 계산됩니다.

14번째 줄에서는 numeric_only=True 옵션을 사용했습니다. 이렇게 하면 숫자 타입의 모든 컬럼을 자동으로 찾아서 각각의 합계를 구해줍니다.

문자열 컬럼인 'product'는 자동으로 제외됩니다. 실제 현업에서는 어떻게 활용할까요? 예를 들어 쇼핑몰 서비스를 운영한다고 가정해봅시다.

매일 아침 전날의 총 매출을 확인해야 합니다. 수천 건의 주문 데이터에서 sum() 함수를 사용하면 단 1초 만에 전체 매출 합계를 계산할 수 있습니다.

네이버, 쿠팡 같은 대형 서비스에서도 이런 기본 집계 함수를 활용해 일일 리포트를 자동 생성합니다. 하지만 주의할 점도 있습니다. 초보 분석가들이 흔히 하는 실수 중 하나는 **결측값(NaN)**을 고려하지 않는 것입니다.

sum() 함수는 기본적으로 NaN을 무시하고 계산하지만, 때로는 NaN이 있다는 사실 자체가 중요한 정보일 수 있습니다. 따라서 sum()을 사용하기 전에 isnull().sum()으로 결측값이 있는지 먼저 확인하는 습관을 들여야 합니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 설명을 들은 김개발 씨는 눈이 휘둥그레졌습니다. "와, 이렇게 간단했군요!" 김개발 씨는 즉시 sum() 함수로 매출 합계를 구했고, 단 5분 만에 보고서를 완성했습니다.

팀장님은 빠른 업무 처리에 감탄했습니다. sum() 함수를 제대로 이해하면 데이터 분석의 첫 단추를 잘 끼울 수 있습니다.

여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.

실전 팁

💡 - axis=0(기본값)은 컬럼별 합계, axis=1은 행별 합계를 구합니다

  • skipna=False 옵션을 사용하면 NaN이 하나라도 있을 때 결과도 NaN이 됩니다

2. mean()으로 평균 계산

sum()으로 합계를 구한 김개발 씨에게 팀장님이 추가 요청을 했습니다. "상품별 평균 판매 가격도 함께 보고해주세요." 평균을 구하려면 합계를 개수로 나눠야 하는데, 더 쉬운 방법은 없을까요?

mean() 함수는 데이터의 평균값을 계산해주는 통계 함수입니다. 합계를 개수로 나누는 복잡한 과정을 거치지 않아도, 단 한 줄로 정확한 평균값을 구할 수 있습니다.

데이터의 중심 경향을 파악하는 가장 기본적이면서도 강력한 도구입니다.

다음 코드를 살펴봅시다.

import pandas as pd

# 직원 급여 데이터
salary_data = pd.DataFrame({
    'department': ['개발', '개발', '마케팅', '마케팅', '영업'],
    'name': ['김개발', '이코드', '박마케', '최홍보', '정세일'],
    'salary': [5500, 6200, 4800, 4500, 5000]
})

# 전체 평균 급여
avg_salary = salary_data['salary'].mean()
print(f"전체 평균 급여: {avg_salary:,.0f}만원")

# 부서별 평균 급여
dept_avg = salary_data.groupby('department')['salary'].mean()
print(dept_avg)

김개발 씨는 sum() 함수로 첫 번째 업무를 성공적으로 마쳤습니다. 하지만 팀장님의 추가 요청이 들어왔습니다.

"김 대리, 이번엔 상품별 평균 판매가를 분석해주세요. 어떤 카테고리의 상품이 평균적으로 비싼지 알고 싶어요." 김개발 씨는 고민에 빠졌습니다.

"평균을 구하려면 합계를 구하고, 개수를 세고, 나눠야 하는데..." 이때 옆자리의 박시니어 씨가 또 한 번 도움의 손길을 내밀었습니다. "mean() 함수 하나면 끝나요." mean() 함수란 무엇일까요? 쉽게 비유하자면, mean() 함수는 마치 학급 평균을 계산하는 선생님과 같습니다.

선생님은 학생들의 모든 시험 점수를 더한 다음, 학생 수로 나눠서 학급 평균을 구합니다. mean() 함수도 똑같이 모든 값을 더하고 개수로 나누는 과정을 자동으로 처리해줍니다.

우리는 결과만 받아보면 됩니다. mean() 함수가 왜 필요할까요? 데이터 분석에서 평균은 가장 자주 사용되는 통계 지표입니다.

평균 매출, 평균 체류 시간, 평균 구매액 등 거의 모든 KPI에 평균이 들어갑니다. 만약 매번 sum()을 구하고 count()로 개수를 센 다음 나누는 코드를 작성한다면 코드가 지저분해지고 실수할 가능성도 높아집니다.

mean() 함수를 사용하면 어떤 이점이 있을까요? 첫째, 코드가 간결해집니다. mean() 단 한 단어로 우리의 의도를 명확하게 표현할 수 있습니다.

둘째, 결측값을 자동으로 처리해줍니다. NaN 값은 자동으로 제외하고 평균을 계산하므로 별도의 전처리가 필요 없습니다.

셋째, 부동소수점 연산의 정밀도를 보장합니다. 직접 나눗셈을 할 때 발생할 수 있는 미세한 오차를 방지할 수 있습니다.

위의 코드를 한 줄씩 살펴보겠습니다. 4번째 줄부터 8번째 줄에서는 직원 급여 데이터를 만들었습니다. 실제 회사의 인사 데이터를 간단하게 재현한 것입니다.

11번째 줄이 핵심입니다. salary_data['salary'].mean()은 salary 컬럼의 모든 값을 더한 후 개수로 나눠줍니다.

(5500 + 6200 + 4800 + 4500 + 5000) ÷ 5 = 5200이 자동으로 계산됩니다. 15번째 줄은 조금 더 고급 기법입니다.

groupby()와 mean()을 조합하면 부서별로 그룹을 나눈 후 각 그룹의 평균을 구할 수 있습니다. 개발팀 평균, 마케팅팀 평균, 영업팀 평균이 한 번에 계산됩니다.

실제 현업에서는 어떻게 활용할까요? 예를 들어 음식 배달 서비스를 운영한다고 가정해봅시다. 고객들의 평균 주문 금액을 알면 마케팅 전략을 수립하는 데 큰 도움이 됩니다.

"평균 주문 금액이 15,000원이니, 20,000원 이상 주문 시 배달비 무료 이벤트를 하면 객단가를 높일 수 있겠다"는 인사이트를 얻을 수 있습니다. 실제로 배달의민족, 쿠팡이츠 같은 서비스들은 이런 평균 지표를 실시간으로 모니터링합니다.

하지만 주의할 점도 있습니다. 초보 분석가들이 흔히 하는 실수 중 하나는 평균만 맹신하는 것입니다. 예를 들어 직원 5명의 급여가 [3000, 3200, 3100, 3300, 15000]이라면 평균은 5520만원이 됩니다.

하지만 실제로는 4명이 3000만원대이고 1명만 1억 5천을 받는 상황입니다. 이처럼 극단값(outlier)이 있으면 평균이 왜곡될 수 있습니다.

따라서 평균과 함께 중앙값(median)도 함께 확인하는 습관을 들여야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 조언대로 mean() 함수를 사용한 김개발 씨는 단 몇 분 만에 부서별 평균 급여 리포트를 완성했습니다.

"이렇게 쉬웠다니!" 김개발 씨는 점점 데이터 분석의 재미에 빠져들었습니다. mean() 함수를 제대로 이해하면 데이터의 중심 경향을 빠르게 파악할 수 있습니다.

여러분도 오늘 배운 내용을 실제 분석에 활용해 보세요.

실전 팁

💡 - median() 함수를 사용하면 중앙값을 구할 수 있어 극단값의 영향을 줄일 수 있습니다

  • round() 함수와 함께 사용하면 소수점 자릿수를 조절할 수 있습니다

3. max(), min()으로 최대/최소값

평균 급여 분석을 마친 김개발 씨에게 인사팀에서 연락이 왔습니다. "우리 회사에서 급여가 가장 높은 사람과 가장 낮은 사람의 금액을 알려주세요." 수백 명의 데이터에서 어떻게 최대값과 최소값을 찾아야 할까요?

max()와 min() 함수는 데이터에서 최대값과 최소값을 찾아주는 함수입니다. 마치 시험에서 1등과 꼴등을 찾는 것처럼, 데이터 전체를 비교해서 가장 큰 값과 가장 작은 값을 자동으로 찾아줍니다.

데이터의 범위를 파악하는 데 필수적인 도구입니다.

다음 코드를 살펴봅시다.

import pandas as pd

# 상품 재고 데이터
inventory = pd.DataFrame({
    'product': ['노트북', '마우스', '키보드', '모니터', '헤드셋'],
    'stock': [15, 234, 89, 5, 167],
    'price': [1200000, 35000, 89000, 450000, 120000]
})

# 재고가 가장 많은 상품과 적은 상품
max_stock = inventory['stock'].max()
min_stock = inventory['stock'].min()
print(f"최대 재고: {max_stock}개, 최소 재고: {min_stock}개")

# 가장 비싼 상품과 저렴한 상품
most_expensive = inventory.loc[inventory['price'].idxmax()]
print(f"\n가장 비싼 상품: {most_expensive['product']} ({most_expensive['price']:,}원)")

김개발 씨의 데이터 분석 실력이 회사에 소문이 났습니다. 이번엔 인사팀 최팀장님이 급하게 요청을 보냈습니다.

"김 대리, 급여 조정 회의가 내일인데 우리 회사 최고 급여와 최저 급여가 얼마인지 지금 당장 알려줄 수 있어요?" 김개발 씨는 당황했습니다. 직원이 200명이 넘는데, 일일이 비교해서 찾으려면 시간이 너무 오래 걸릴 것 같았습니다.

이때 박시니어 씨가 커피를 마시며 지나가다가 한마디 했습니다. "max()와 min() 알아요?

3초면 끝나요." max()와 min() 함수란 무엇일까요? 쉽게 비유하자면, 이 함수들은 마치 육상 경기의 심판과 같습니다. 100명의 선수가 달리기를 마쳤을 때, 심판은 기록을 보고 1등과 꼴등을 즉시 발표합니다.

max()는 1등 기록을, min()은 꼴등 기록을 찾아줍니다. 우리가 일일이 비교하지 않아도 자동으로 최고값과 최저값을 찾아주는 것입니다.

왜 max()와 min()이 필요할까요? 데이터 분석에서 범위(range)를 파악하는 것은 매우 중요합니다. 최대값과 최소값을 알면 데이터가 얼마나 퍼져있는지, 극단적인 값이 있는지 바로 알 수 있습니다.

예를 들어 쇼핑몰의 상품 가격이 최소 1,000원에서 최대 100만원까지라면, 가격 필터 범위를 이에 맞춰 설정할 수 있습니다. 이 함수들이 없었다면 어땠을까요? 과거에는 반복문으로 모든 데이터를 순회하면서 비교 연산을 수행해야 했습니다.

첫 번째 값을 임시 최대값으로 설정하고, 나머지 값들과 하나씩 비교하면서 더 큰 값이 나오면 업데이트하는 방식이었습니다. 코드가 길고 복잡했으며, 실수로 비교 조건을 잘못 쓰면 버그가 발생했습니다.

max()와 min() 함수를 사용하면 어떤 이점이 있을까요? 첫째, 코드가 극도로 간결해집니다. .max().min() 단 두 단어면 충분합니다.

둘째, 성능이 최적화되어 있습니다. Pandas는 C언어로 구현된 최적화 알고리즘을 사용하기 때문에 순수 Python 반복문보다 훨씬 빠릅니다.

셋째, 가독성이 뛰어납니다. 코드를 보는 순간 "최대값을 구하는구나"라고 직관적으로 이해할 수 있습니다.

위의 코드를 한 줄씩 살펴보겠습니다. 4번째 줄부터 8번째 줄에서는 상품 재고 데이터를 생성했습니다. 실제 쇼핑몰의 재고 관리 시스템과 유사한 구조입니다.

11번째와 12번째 줄이 핵심입니다. inventory['stock'].max()는 stock 컬럼에서 가장 큰 값인 234를 찾아줍니다.

inventory['stock'].min()는 가장 작은 값인 5를 찾아줍니다. 16번째 줄은 조금 더 고급 기법입니다.

idxmax()는 최대값의 인덱스를 반환합니다. 이를 loc[]와 함께 사용하면 최대값을 가진 행 전체를 가져올 수 있습니다.

단순히 "1200000원"이라는 값뿐만 아니라 "노트북이 1200000원"이라는 전체 정보를 얻을 수 있습니다. 실제 현업에서는 어떻게 활용할까요? 예를 들어 물류 센터를 관리한다고 가정해봅시다.

재고가 가장 적은 상품을 찾아서 긴급 발주를 해야 합니다. min() 함수를 사용하면 수천 개의 상품 중에서 재고가 가장 적은 상품을 1초 만에 찾을 수 있습니다.

반대로 max()로 재고가 과도하게 쌓인 상품을 찾아서 할인 프로모션을 기획할 수도 있습니다. 쿠팡의 물류 시스템에서도 이런 방식으로 재고를 최적화합니다.

하지만 주의할 점도 있습니다. 초보 분석가들이 흔히 하는 실수 중 하나는 문자열 데이터에 max()를 사용하는 것입니다. 숫자가 아닌 문자열에도 max()를 사용할 수 있지만, 결과가 예상과 다를 수 있습니다.

문자열은 사전순으로 비교되기 때문에 '10'이 '9'보다 작다고 판단됩니다. 따라서 숫자 데이터인지 먼저 확인하고, 필요하면 astype(int)로 타입을 변환해야 합니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 조언대로 max()와 min()을 사용한 김개발 씨는 정말 3초 만에 결과를 얻었습니다. "최고 급여 8500만원, 최저 급여 3000만원입니다!" 최팀장님은 깜짝 놀라며 엄지를 치켜세웠습니다.

"역시 데이터 분석팀은 빠르네요!" max()와 min() 함수를 제대로 이해하면 데이터의 경계를 빠르게 파악할 수 있습니다. 여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.

실전 팁

💡 - nlargest(n)와 nsmallest(n)를 사용하면 상위/하위 n개의 값을 찾을 수 있습니다

  • idxmax()와 idxmin()으로 최대값과 최소값의 인덱스를 구할 수 있습니다

4. count()로 개수 세기

김개발 씨가 이제 제법 자신감이 붙었습니다. 그런데 이번에는 영업팀에서 색다른 요청이 들어왔습니다.

"각 지역별로 고객이 몇 명이나 되는지 세어주세요." 데이터를 세는 것, 생각보다 까다로울 수 있습니다.

count() 함수는 데이터의 개수를 세어주는 함수입니다. 결측값(NaN)을 제외하고 실제로 값이 있는 데이터만 정확하게 카운트해줍니다.

마치 출석을 부르는 선생님처럼, 실제로 존재하는 데이터만 세어줍니다.

다음 코드를 살펴봅시다.

import pandas as pd
import numpy as np

# 고객 데이터 (일부 누락된 정보 포함)
customer_data = pd.DataFrame({
    'name': ['김철수', '이영희', '박민수', '최지원', '정다은'],
    'age': [25, 32, np.nan, 28, 35],
    'city': ['서울', '부산', '서울', np.nan, '대구'],
    'purchase_count': [5, 3, 7, 2, 4]
})

# 각 컬럼의 데이터 개수
print("컬럼별 데이터 개수:")
print(customer_data.count())

# 전체 데이터 개수
total_customers = len(customer_data)
valid_ages = customer_data['age'].count()
print(f"\n전체 고객: {total_customers}명, 나이 정보 있음: {valid_ages}명")

김개발 씨는 이제 sum, mean, max, min을 자유자재로 사용할 수 있게 되었습니다. 그런데 오늘 영업팀 정과장님이 새로운 분석을 요청했습니다.

"김 대리, 우리 고객 데이터베이스에 고객이 총 몇 명이나 되는지, 그리고 각 정보가 얼마나 잘 입력되어 있는지 확인해주세요." 김개발 씨는 엑셀 파일을 열어봤습니다. 그런데 뭔가 이상했습니다.

어떤 고객은 나이가 비어있고, 어떤 고객은 지역 정보가 없었습니다. "이런 건 세야 하나, 말아야 하나?" 고민하던 김개발 씨에게 박시니어 씨가 다가왔습니다.

"count() 함수가 딱 그 일을 해줘요." count() 함수란 무엇일까요? 쉽게 비유하자면, count() 함수는 마치 학교 출석부를 확인하는 담임 선생님과 같습니다. 선생님은 출석부를 부르면서 "김철수?" "네!" 하고 대답하는 학생만 출석으로 체크합니다.

결석이나 조퇴로 빈 칸인 학생은 세지 않습니다. count() 함수도 똑같이 실제로 값이 있는 데이터만 세고, NaN(결측값)은 자동으로 제외합니다.

왜 count() 함수가 필요할까요? 실무에서 데이터는 항상 완벽하지 않습니다. 고객이 회원가입할 때 선택 항목을 입력하지 않을 수도 있고, 시스템 오류로 데이터가 누락될 수도 있습니다.

이럴 때 단순히 len()으로 전체 행 수를 세면 실제 유효한 데이터 개수를 알 수 없습니다. count()를 사용하면 정말로 사용 가능한 데이터가 몇 개인지 정확히 파악할 수 있습니다.

count() 함수가 없었다면 어땠을까요? 과거에는 반복문을 돌면서 각 값이 None인지, NaN인지 일일이 확인해야 했습니다. if pd.notna(value): counter += 1 같은 코드를 매번 작성했습니다.

코드가 길어지고, 실수로 조건을 빠뜨리면 잘못된 개수를 세게 됩니다. 더욱이 데이터프레임의 모든 컬럼을 검사하려면 이중 반복문이 필요했습니다.

count() 함수를 사용하면 어떤 이점이 있을까요? 첫째, 결측값 처리가 자동입니다. NaN, None, pd.NA 등 모든 종류의 결측값을 자동으로 인식하고 제외합니다.

둘째, 데이터프레임 전체에 한 번에 적용할 수 있습니다. df.count()만 실행하면 모든 컬럼의 개수가 한눈에 보입니다.

셋째, 데이터 품질 검사에 활용할 수 있습니다. 전체 행 수와 count() 결과를 비교하면 얼마나 많은 데이터가 누락되었는지 바로 알 수 있습니다.

위의 코드를 한 줄씩 살펴보겠습니다. 5번째 줄부터 10번째 줄에서는 의도적으로 결측값(np.nan)이 포함된 고객 데이터를 만들었습니다. 실제 데이터베이스에서 흔히 볼 수 있는 상황입니다.

13번째 줄이 핵심입니다. customer_data.count()는 각 컬럼의 유효한 데이터 개수를 세어줍니다.

name은 5개, age는 4개(박민수의 나이가 NaN이므로), city는 4개(최지원의 도시가 NaN이므로)가 출력됩니다. 16번째와 17번째 줄을 비교해보세요.

len(customer_data)는 전체 행 수인 5를 반환합니다. 하지만 customer_data['age'].count()는 실제로 나이 정보가 있는 4명만 세어줍니다.

이 차이를 이용하면 "5명 중 4명만 나이 정보가 있고, 1명은 누락되었다"는 사실을 알 수 있습니다. 실제 현업에서는 어떻게 활용할까요? 예를 들어 설문조사 데이터를 분석한다고 가정해봅시다.

설문지에는 필수 질문과 선택 질문이 있습니다. count()를 사용하면 각 질문에 몇 명이 응답했는지 즉시 파악할 수 있습니다.

"1번 문항은 100명 전원이 답했지만, 5번 문항은 78명만 답했네요. 이 문항이 너무 어려웠나 봅니다"라는 인사이트를 얻을 수 있습니다.

실제로 여론조사 기관들은 이런 방식으로 응답률을 분석합니다. 하지만 주의할 점도 있습니다. 초보 분석가들이 흔히 하는 실수 중 하나는 count()와 len()을 혼동하는 것입니다.

len()은 전체 행 수를 반환하고, count()는 결측값을 제외한 개수를 반환합니다. 예를 들어 100명의 고객 데이터가 있는데 50명만 이메일 주소를 입력했다면, len()은 100을, count()는 50을 반환합니다.

상황에 맞는 함수를 선택해야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 조언으로 count()를 사용한 김개발 씨는 데이터 품질 리포트를 완성했습니다.

"전체 고객 500명 중 나이 정보는 487명, 주소 정보는 456명이 입력했습니다." 정과장님은 이 리포트를 보고 "주소 정보 수집률이 낮으니 다음 마케팅 캠페인 때 주소 입력 이벤트를 해야겠군요"라고 전략을 세웠습니다. count() 함수를 제대로 이해하면 데이터 품질을 정확히 파악할 수 있습니다.

여러분도 오늘 배운 내용을 실제 데이터 검증에 활용해 보세요.

실전 팁

💡 - isnull().sum()을 사용하면 반대로 결측값의 개수를 셀 수 있습니다

  • nunique()를 사용하면 고유한 값의 개수를 셀 수 있습니다

5. value counts()로 빈도 분석

count()로 개수를 세는 법을 배운 김개발 씨에게 마케팅팀에서 요청이 들어왔습니다. "각 도시별로 고객이 몇 명씩 있는지 알고 싶어요." 단순히 전체 개수가 아니라, 값별로 개수를 세어야 하는 상황입니다.

value_counts() 함수는 각 고유값이 몇 번 등장하는지 빈도를 계산해주는 함수입니다. 마치 투표 개표를 하는 것처럼, 각 선택지별로 몇 표를 받았는지 자동으로 집계해줍니다.

범주형 데이터 분석의 핵심 도구입니다.

다음 코드를 살펴봅시다.

import pandas as pd

# 제품 판매 데이터
sales_log = pd.DataFrame({
    'product': ['노트북', '마우스', '노트북', '키보드', '마우스',
                '마우스', '노트북', '모니터', '마우스', '키보드'],
    'region': ['서울', '서울', '부산', '서울', '대구',
               '서울', '서울', '부산', '부산', '대구']
})

# 제품별 판매 횟수
product_freq = sales_log['product'].value_counts()
print("제품별 판매 횟수:")
print(product_freq)

# 비율로 확인 (normalize=True)
print("\n제품별 판매 비율:")
print(sales_log['product'].value_counts(normalize=True))

김개발 씨의 명성은 이제 회사 전체에 퍼졌습니다. 마케팅팀 강차장님이 직접 찾아왔습니다.

"김 대리, 우리 제품이 어느 지역에서 가장 많이 팔리는지 알고 싶어요. 지역별 판매 순위를 만들어줄 수 있나요?" 김개발 씨는 고민에 빠졌습니다.

"서울에서 판매된 건수를 세고, 부산에서 판매된 건수를 세고... 지역이 100개면 100번 세어야 하나?" 복잡해 보였습니다.

그때 점심을 먹고 돌아온 박시니어 씨가 말했습니다. "value_counts() 한 줄이면 끝나요." value_counts() 함수란 무엇일까요? 쉽게 비유하자면, value_counts() 함수는 마치 선거 개표 시스템과 같습니다.

투표함에서 투표지를 꺼내면서 "1번 후보 한 표!", "2번 후보 한 표!" 하고 집계하는 것처럼, value_counts()도 데이터를 하나씩 확인하면서 각 값의 등장 횟수를 자동으로 세어줍니다. 그리고 많이 나온 순서대로 정렬까지 해줍니다.

왜 value_counts()가 필요할까요? 실무에서 가장 흔한 분석 중 하나가 "어떤 게 가장 많아?"입니다. 어떤 제품이 가장 많이 팔렸는지, 어느 지역 고객이 가장 많은지, 어떤 오류가 가장 자주 발생하는지 등 범주형 데이터의 분포를 파악해야 할 때가 많습니다.

value_counts()가 없으면 groupby()와 count()를 조합해서 복잡하게 작성해야 합니다. value_counts() 함수가 없던 시절에는 어땠을까요? 과거에는 딕셔너리를 만들고 반복문을 돌면서 각 값의 카운터를 증가시켰습니다.

counter = {}; for item in data: counter[item] = counter.get(item, 0) + 1 같은 코드를 매번 작성했습니다. 그 다음 정렬까지 하려면 추가 코드가 더 필요했습니다.

번거롭고 실수하기 쉬웠습니다. value_counts()를 사용하면 어떤 이점이 있을까요? 첫째, 코드가 극도로 간결합니다.

.value_counts() 단 한 줄로 집계와 정렬이 동시에 끝납니다. 둘째, 자동으로 내림차순 정렬됩니다.

가장 많이 나온 값부터 차례로 보여주므로 인사이트를 빠르게 얻을 수 있습니다. 셋째, normalize 옵션으로 비율을 쉽게 계산할 수 있습니다.

절대 개수뿐만 아니라 백분율도 한 번에 확인 가능합니다. 위의 코드를 한 줄씩 살펴보겠습니다. 4번째 줄부터 9번째 줄에서는 제품 판매 로그 데이터를 만들었습니다.

실제 전자상거래 시스템의 주문 로그와 유사한 구조입니다. 12번째 줄이 핵심입니다.

sales_log['product'].value_counts()는 product 컬럼의 각 값이 몇 번씩 나오는지 세어줍니다. 결과를 보면 "마우스 4번, 노트북 3번, 키보드 2번, 모니터 1번" 순서로 자동 정렬됩니다.

18번째 줄은 고급 활용법입니다. normalize=True 옵션을 추가하면 개수 대신 비율을 반환합니다.

마우스가 전체의 40%(4/10), 노트북이 30%(3/10)를 차지한다는 걸 바로 알 수 있습니다. 이 정보는 재고 관리나 마케팅 예산 배분에 매우 유용합니다.

실제 현업에서는 어떻게 활용할까요? 예를 들어 고객 지원 센터를 운영한다고 가정해봅시다. 하루에 수백 건의 문의가 들어오는데, 어떤 유형의 문의가 가장 많은지 파악해야 합니다.

value_counts()를 사용하면 "로그인 오류 35%, 결제 문제 28%, 배송 문의 20%..." 순서로 집계할 수 있습니다. 가장 많은 문의 유형에 대한 FAQ를 강화하거나 시스템을 개선하는 식으로 대응할 수 있습니다.

실제로 카카오톡 고객센터 같은 곳에서 이런 분석으로 서비스를 개선합니다. 하지만 주의할 점도 있습니다. 초보 분석가들이 흔히 하는 실수 중 하나는 결측값 처리를 간과하는 것입니다.

기본적으로 value_counts()는 NaN을 제외하고 집계합니다. 하지만 때로는 "미입력"이 얼마나 많은지도 중요한 정보입니다.

이럴 땐 dropna=False 옵션을 사용해서 NaN도 함께 집계해야 합니다. 또한 value_counts()는 시리즈를 반환하므로, 데이터프레임으로 변환하려면 .reset_index()를 추가해야 합니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 조언대로 value_counts()를 사용한 김개발 씨는 단 1분 만에 지역별 판매 순위를 완성했습니다. "서울이 35%로 1위, 부산이 25%로 2위입니다!" 강차장님은 이 데이터를 보고 "서울 지역 마케팅 예산을 늘려야겠어요"라고 즉시 의사결정을 내렸습니다.

value_counts() 함수를 제대로 이해하면 범주형 데이터의 분포를 한눈에 파악할 수 있습니다. 여러분도 오늘 배운 내용을 실제 빈도 분석에 활용해 보세요.

실전 팁

💡 - bins 옵션을 사용하면 연속형 데이터를 구간으로 나눠서 빈도를 계산할 수 있습니다

  • sort=False 옵션으로 정렬하지 않고 원본 순서를 유지할 수 있습니다

6. 여러 통계 한번에 계산하기

김개발 씨는 이제 sum, mean, max, min, count를 자유자재로 사용합니다. 그런데 CEO가 직접 요청을 보냈습니다.

"제품별로 평균, 최대, 최소, 합계를 모두 보고해주세요." 함수를 다섯 번이나 호출해야 할까요?

describe()와 agg() 함수는 여러 통계를 한 번에 계산해주는 함수입니다. describe()는 기본 통계 요약을, agg()는 원하는 통계를 자유롭게 조합해서 계산할 수 있습니다.

마치 종합 건강검진처럼, 한 번에 모든 지표를 확인할 수 있습니다.

다음 코드를 살펴봅시다.

import pandas as pd

# 영업 실적 데이터
performance = pd.DataFrame({
    'sales_person': ['김철수', '이영희', '박민수', '김철수', '이영희'],
    'month': [1, 1, 1, 2, 2],
    'sales': [5000, 7000, 6000, 5500, 7500]
})

# describe()로 기본 통계 요약
print("기본 통계 요약:")
print(performance['sales'].describe())

# agg()로 원하는 통계만 선택
print("\n맞춤 통계:")
stats = performance['sales'].agg(['sum', 'mean', 'max', 'min', 'std'])
print(stats)

# 여러 컬럼에 각각 다른 통계 적용
print("\n영업사원별 상세 통계:")
result = performance.groupby('sales_person')['sales'].agg(['count', 'sum', 'mean'])
print(result)

김개발 씨는 이제 회사에서 없어서는 안 될 데이터 분석 전문가가 되었습니다. 그런데 오늘은 CEO 오사장님이 직접 이메일을 보냈습니다.

"김 대리, 내일 임원 회의에서 사용할 전체 영업 실적 리포트를 만들어주세요. 평균, 최대, 최소, 표준편차 등 모든 통계를 보고 싶습니다." 김개발 씨는 당황했습니다.

"mean()도 써야 하고, max()도 써야 하고, min()도... 코드가 엄청 길어지겠네." 고민하던 김개발 씨에게 박시니어 씨가 여유롭게 말했습니다.

"describe() 하나면 끝이에요. 또는 agg()로 원하는 것만 골라서 볼 수도 있고요." describe()와 agg() 함수란 무엇일까요? 쉽게 비유하자면, 이 함수들은 마치 종합 건강검진 패키지와 같습니다.

병원에 가면 혈압, 혈당, 콜레스테롤 등을 개별적으로 검사할 수도 있지만, 종합검진 패키지를 선택하면 한 번에 모든 검사를 받을 수 있습니다. describe()는 기본 패키지처럼 자주 쓰는 통계를 자동으로 계산해주고, agg()는 맞춤 패키지처럼 원하는 통계만 골라서 계산할 수 있습니다.

왜 이런 함수들이 필요할까요? 실무에서는 하나의 통계만 보는 경우가 거의 없습니다. 평균만 보면 극단값의 영향을 놓치고, 최대값만 보면 전체적인 경향을 파악하기 어렵습니다.

따라서 평균, 중앙값, 최대, 최소, 표준편차를 함께 봐야 데이터를 제대로 이해할 수 있습니다. 이걸 함수마다 일일이 호출하면 코드가 길어지고 가독성이 떨어집니다.

이런 함수가 없었다면 어땠을까요? 과거에는 mean(), max(), min(), std() 등을 각각 호출하고, 그 결과를 딕셔너리나 리스트에 담아서 정리해야 했습니다. 코드가 10줄 넘게 길어지고, 나중에 통계 항목을 추가하려면 여러 곳을 수정해야 했습니다.

특히 groupby()와 조합할 때는 코드가 매우 복잡해졌습니다. describe()와 agg()를 사용하면 어떤 이점이 있을까요? 첫째, 코드가 극도로 간결해집니다.

describe() 한 줄로 8가지 통계(개수, 평균, 표준편차, 최소, 25%, 50%, 75%, 최대)를 모두 얻을 수 있습니다. 둘째, 일관성 있는 출력 형식을 제공합니다.

모든 통계가 깔끔한 시리즈나 데이터프레임으로 정리되어 나옵니다. 셋째, agg()는 커스터마이징이 자유롭습니다.

필요한 통계만 골라서 계산하거나, 심지어 사용자 정의 함수도 사용할 수 있습니다. 위의 코드를 한 줄씩 살펴보겠습니다. 4번째 줄부터 8번째 줄에서는 영업사원별 월별 실적 데이터를 만들었습니다.

실제 CRM 시스템의 판매 데이터와 유사한 구조입니다. 12번째 줄이 첫 번째 핵심입니다.

performance['sales'].describe()는 sales 컬럼의 모든 기본 통계를 자동으로 계산해줍니다. count(개수), mean(평균), std(표준편차), min(최소), 25%(1사분위), 50%(중앙값), 75%(3사분위), max(최대)가 한꺼번에 출력됩니다.

16번째 줄은 두 번째 핵심입니다. agg(['sum', 'mean', 'max', 'min', 'std'])는 우리가 원하는 통계만 골라서 계산할 수 있습니다.

describe()에는 없는 sum(합계)을 추가하거나, 필요 없는 사분위수를 제외할 수 있습니다. 21번째 줄은 고급 활용법입니다.

groupby()agg()를 조합하면 그룹별로 여러 통계를 한 번에 계산할 수 있습니다. 영업사원별로 판매 건수, 총 매출, 평균 매출이 깔끔한 테이블 형태로 정리됩니다.

실제 현업에서는 어떻게 활용할까요? 예를 들어 앱 서비스를 운영한다고 가정해봅시다. 사용자들의 일일 사용 시간 데이터를 분석해야 합니다.

describe()를 사용하면 "평균 사용 시간 45분, 중앙값 30분, 최대 240분"처럼 데이터의 전체적인 모습을 한눈에 파악할 수 있습니다. 여기서 평균이 중앙값보다 훨씬 크다는 걸 발견하면 "일부 헤비유저가 평균을 끌어올리는구나"라는 인사이트를 얻을 수 있습니다.

토스, 카카오뱅크 같은 핀테크 회사들은 이런 방식으로 사용자 행동 패턴을 분석합니다. 하지만 주의할 점도 있습니다. 초보 분석가들이 흔히 하는 실수 중 하나는 describe()의 결과를 맹신하는 것입니다.

describe()는 편리하지만 모든 통계를 보여주지는 않습니다. 예를 들어 왜도(skewness)나 첨도(kurtosis) 같은 분포의 형태는 알려주지 않습니다.

또한 범주형 데이터에 describe()를 사용하면 숫자형 통계 대신 빈도 정보를 제공하므로, 데이터 타입을 먼저 확인해야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 조언대로 describe()와 agg()를 활용한 김개발 씨는 단 10분 만에 완벽한 통계 리포트를 완성했습니다.

다음 날 임원 회의에서 오사장님은 김개발 씨의 리포트를 보고 크게 만족했습니다. "이런 리포트가 필요했어요.

모든 통계가 한눈에 보이니 의사결정이 훨씬 쉽네요." describe()와 agg() 함수를 제대로 이해하면 효율적으로 종합 통계를 계산할 수 있습니다. 여러분도 오늘 배운 내용을 실제 리포트 작성에 활용해 보세요.

실전 팁

💡 - include='all' 옵션을 추가하면 범주형 컬럼의 통계도 함께 볼 수 있습니다

  • percentiles 옵션으로 원하는 백분위수를 지정할 수 있습니다

7. 컬럼별 통계 비교

김개발 씨는 이제 통계 마스터가 되었습니다. 그런데 데이터 과학팀 팀장님이 새로운 과제를 주었습니다.

"제품별로, 지역별로 통계를 비교해서 보고해주세요." 여러 그룹을 동시에 분석하는 방법은 무엇일까요?

groupby()와 통계 함수의 조합은 데이터를 그룹으로 나눈 후 각 그룹별로 통계를 계산하는 강력한 기법입니다. 마치 반별로 평균 점수를 계산하는 것처럼, 원하는 기준으로 데이터를 나눠서 비교 분석할 수 있습니다.

세그먼트별 인사이트를 발견하는 핵심 도구입니다.

다음 코드를 살펴봅시다.

import pandas as pd

# 지역별 제품 판매 데이터
regional_sales = pd.DataFrame({
    'region': ['서울', '서울', '부산', '부산', '대구', '대구'],
    'product': ['노트북', '마우스', '노트북', '마우스', '노트북', '마우스'],
    'quantity': [10, 50, 8, 30, 5, 25],
    'revenue': [12000000, 1750000, 9600000, 1050000, 6000000, 875000]
})

# 지역별 총 매출과 평균 매출
region_stats = regional_sales.groupby('region')['revenue'].agg(['sum', 'mean'])
print("지역별 매출 통계:")
print(region_stats)

# 지역별, 제품별 이중 그룹화
print("\n지역별 제품별 판매량:")
detail_stats = regional_sales.groupby(['region', 'product'])['quantity'].sum()
print(detail_stats)

# 피벗 테이블로 한눈에 비교
print("\n지역-제품 매트릭스:")
pivot = regional_sales.pivot_table(values='revenue', index='region',
                                    columns='product', aggfunc='sum', fill_value=0)
print(pivot)

김개발 씨는 어느덧 회사의 데이터 분석 에이스가 되었습니다. 팀장님이 김개발 씨를 회의실로 불렀습니다.

"김 대리, 이제 단순 통계를 넘어서 비교 분석을 해봅시다. 서울, 부산, 대구 중 어느 지역이 매출이 높은지, 그리고 각 지역에서는 어떤 제품이 잘 팔리는지 분석해주세요." 김개발 씨는 처음엔 막막했습니다.

"지역마다 따로 필터링해서 통계를 내야 하나?" 하지만 이제 옆에는 든든한 박시니어 씨가 있습니다. "groupby()를 배울 시간이 왔네요.

이게 진짜 Pandas의 핵심이에요." groupby()란 무엇일까요? 쉽게 비유하자면, groupby()는 마치 학생들을 반별로 나누는 것과 같습니다. 전교생 300명의 시험 점수가 있을 때, 1반 학생들, 2반 학생들, 3반 학생들로 나눈 다음 각 반의 평균을 계산하는 것입니다.

groupby()도 똑같이 데이터를 원하는 기준(지역, 제품, 날짜 등)으로 그룹을 만든 후, 각 그룹에 통계 함수를 적용합니다. 왜 groupby()가 필요할까요? 실무에서 가장 가치 있는 분석은 비교 분석입니다.

"전체 평균 매출이 얼마다"보다 "서울은 평균 3천만원인데 부산은 2천만원이다"가 훨씬 유용한 정보입니다. 이런 비교를 통해 "왜 서울이 높을까?", "부산 매출을 올리려면?"같은 액션 아이템이 나옵니다.

groupby()가 없으면 지역마다 필터링해서 통계를 구하는 코드를 여러 번 작성해야 합니다. groupby()가 없던 시절에는 어땠을까요? 과거에는 유니크한 값을 먼저 추출하고(서울, 부산, 대구), 각 값별로 필터링한 후 통계를 계산하는 방식이었습니다.

지역이 3개면 코드를 3번 작성해야 했고, 지역이 100개면? 상상도 하기 싫습니다.

코드가 엄청나게 길어지고, 새로운 지역이 추가되면 코드를 수정해야 했습니다. groupby()를 사용하면 어떤 이점이 있을까요? 첫째, 자동화됩니다.

지역이 3개든 100개든 코드는 똑같습니다. 데이터만 바뀌면 자동으로 모든 그룹을 처리합니다.

둘째, 가독성이 뛰어납니다. groupby('region')['revenue'].mean()이라는 코드만 봐도 "지역별 평균 매출을 구하는구나"라고 즉시 이해할 수 있습니다.

셋째, 다중 그룹화가 가능합니다. 지역별로 나눈 다음, 각 지역 안에서 다시 제품별로 나눌 수 있습니다.

위의 코드를 한 줄씩 살펴보겠습니다. 4번째 줄부터 9번째 줄에서는 지역별 제품별 판매 데이터를 만들었습니다. 실제 ERP 시스템의 판매 데이터와 유사한 구조입니다.

12번째 줄이 첫 번째 핵심입니다. groupby('region')['revenue'].agg(['sum', 'mean'])은 지역별로 그룹을 나눈 후, 각 그룹의 매출 합계와 평균을 계산합니다.

서울, 부산, 대구의 통계가 한꺼번에 나옵니다. 18번째 줄은 두 번째 핵심입니다.

groupby(['region', 'product'])처럼 리스트로 여러 컬럼을 전달하면 다중 그룹화가 됩니다. 서울-노트북, 서울-마우스, 부산-노트북, 부산-마우스...

모든 조합의 통계가 계산됩니다. 23번째 줄은 고급 기법입니다.

pivot_table()은 groupby()의 결과를 2차원 테이블로 예쁘게 정리해줍니다. 행은 지역, 열은 제품으로 배치되어 마치 엑셀 피벗 테이블처럼 한눈에 비교할 수 있습니다.

fill_value=0으로 빈 칸을 0으로 채워줍니다. 실제 현업에서는 어떻게 활용할까요? 예를 들어 온라인 광고를 운영한다고 가정해봅시다.

광고 채널(네이버, 구글, 페이스북)별로, 그리고 연령대별로 클릭률과 전환율을 비교 분석해야 합니다. groupby()를 사용하면 "20대는 페이스북 광고의 전환율이 5%로 가장 높고, 40대는 네이버가 7%로 가장 높다"는 인사이트를 얻을 수 있습니다.

그러면 연령대별로 광고 예산을 다르게 배분하는 전략을 세울 수 있습니다. 실제로 쿠팡, 무신사 같은 이커머스 회사들은 이런 세그먼트 분석으로 마케팅 ROI를 극대화합니다.

하지만 주의할 점도 있습니다. 초보 분석가들이 흔히 하는 실수 중 하나는 너무 세밀하게 그룹화하는 것입니다. 예를 들어 날짜별, 시간별, 제품별, 지역별로 4중 그룹화를 하면 각 그룹의 데이터 개수가 1~2개밖에 안 될 수 있습니다.

그러면 통계적으로 의미가 없습니다. 적절한 그룹화 수준을 선택하는 게 중요합니다.

또한 groupby() 후에는 멀티인덱스가 생성되므로, reset_index()로 평탄화하는 것도 잊지 말아야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 가르침 덕분에 groupby()를 마스터한 김개발 씨는 완벽한 비교 분석 리포트를 완성했습니다.

"서울이 총 매출 1위지만 건당 평균 매출은 부산이 더 높습니다. 부산은 고가 제품 위주로 판매되는 것 같습니다." 팀장님은 감탄했습니다.

"이제 진짜 데이터 분석가네요!" groupby()와 통계 함수의 조합을 제대로 이해하면 세그먼트별 비교 분석을 자유자재로 할 수 있습니다. 여러분도 오늘 배운 내용을 실제 비교 분석에 활용해 보세요.

실전 팁

💡 - as_index=False 옵션을 사용하면 그룹 컬럼이 인덱스가 아닌 일반 컬럼으로 유지됩니다

  • transform()을 사용하면 그룹 통계를 원본 데이터프레임에 브로드캐스팅할 수 있습니다

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#Python#Pandas#Statistics#Aggregation#DataAnalysis

댓글 (0)

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