본문으로 건너뛰기
김신건의 로그

[Pandas] crosstab

· 수정 · 📖 약 1분 · 557자/단어 #python #pandas #aggregation #contingency #pivot
Pandas crosstab, pd.crosstab, 교차표, 분할표, contingency table pandas

정의

pd.crosstab(index, columns) 는 두 (또는 그 이상의) Series 로 교차 빈도표 (contingency table) 를 만든다. SQL 의 PIVOT 또는 Excel 의 피벗 테이블과 비슷.

pd.crosstab(df['gender'], df['plan'])

사용 상황

  • 범주형 변수 간 분포 탐색: 성별 x 구독 플랜, 지역 x 연령대 등 교차 분포 파악
  • 비율 분석: normalize='index' 로 행 기준 비율, normalize='all' 로 전체 비율
  • 데이터 검증: 특정 조합의 존재 여부, 결측 조합 탐지
  • 집계 보고서: values + aggfunc 로 빠른 요약 테이블 생성

pivot_table 과 기능이 비슷하지만, 입력이 DataFrame 컬럼명이 아닌 Series 직접 전달이라는 점이 다르다.

분석 흐름 시각화

flowchart LR
    S1["index Series"] -->|"pd.crosstab()"| CT["교차 빈도표"]
    S2["columns Series"] -->|"pd.crosstab()"| CT

    CT -->|"normalize='index'"| NR["행 기준 비율"]
    CT -->|"margins=True"| MR["소계 포함"]
    CT -->|"values + aggfunc='sum'"| AG["합계 집계"]

기본 (count)

python
import pandas as pd
df = pd.DataFrame({
  'gender': ['M','F','M','F','M','F'],
  'plan': ['basic','pro','basic','basic','pro','pro'],
})
print(pd.crosstab(df['gender'], df['plan']))
결과
plan    basic  pro
gender
F           1    2
M           2    1
gender \ planbasicpro
F12
M21

margins (소계 / 총계 행)

pd.crosstab(df['gender'], df['plan'], margins=True, margins_name='Total')
python
import pandas as pd
df = pd.DataFrame({
  'gender': ['M','F','M','F','M','F'],
  'plan': ['basic','pro','basic','basic','pro','pro'],
})
print(pd.crosstab(df['gender'], df['plan'], margins=True))
결과
plan    basic  pro  All
gender
F           1    2    3
M           2    1    3
All         3    3    6

normalize (비율)

pd.crosstab(df['gender'], df['plan'], normalize='index')    # 행 기준 비율
pd.crosstab(df['gender'], df['plan'], normalize='columns')  # 열 기준
pd.crosstab(df['gender'], df['plan'], normalize='all')      # 전체
python
import pandas as pd
df = pd.DataFrame({
  'gender': ['M','F','M','F','M','F'],
  'plan': ['basic','pro','basic','basic','pro','pro'],
})
print(pd.crosstab(df['gender'], df['plan'], normalize='index'))
결과
plan       basic       pro
gender
F       0.333333  0.666667
M       0.666667  0.333333

각 행 합이 1. 행 기준 분포를 보여준다.

values + aggfunc (합계, 평균 등)

pd.crosstab(
    df['gender'],
    df['plan'],
    values=df['amount'],
    aggfunc='sum',
)

count 가 아닌 sum/mean 등의 집계. 사실상 Pandas pivot_table 과 같다.

다중 index / 다중 columns

pd.crosstab(
    [df['gender'], df['region']],
    df['plan']
)
# MultiIndex 행

pd.crosstab(
    df['gender'],
    [df['plan'], df['region']]
)
# MultiIndex 컬럼

crosstab vs pivot_table

항목crosstabpivot_table
입력Series (또는 array)DataFrame + 컬럼명
기본 aggfunccountmean
marginsmargins=margins=
다중 그룹list 전달list 전달
normalizenormalize=없음 (수동 계산)
# 같은 결과
pd.crosstab(df['gender'], df['plan'])
df.pivot_table(index='gender', columns='plan', aggfunc='size')

실전 패턴

EDA: 범주형 변수 간 관계 탐색

import pandas as pd

df = pd.read_csv('customers.csv')

# 성별 x 구독 플랜 분포
ct = pd.crosstab(df['gender'], df['plan'])
print(ct)

# 행 기준 비율로 패턴 파악
ct_pct = pd.crosstab(df['gender'], df['plan'], normalize='index')
print(ct_pct.round(2))

결측 조합 탐지

# 특정 조합이 존재하는지 확인
ct = pd.crosstab(df['region'], df['product_code'])
# 0 인 셀: 해당 지역에 그 제품 주문 없음
missing_combos = (ct == 0).stack()
missing_combos[missing_combos].reset_index()[['region', 'product_code']]

고급 집계: 여러 aggfunc 동시 적용

# crosstab 자체는 단일 aggfunc 만 지원
# 여러 집계가 필요하면 pivot_table 사용
summary = df.pivot_table(
    index='gender',
    columns='plan',
    values='amount',
    aggfunc=['sum', 'mean', 'count'],
)
# MultiIndex 컬럼: ('sum', 'basic'), ('sum', 'pro'), ...

비율 히트맵 시각화 준비

import pandas as pd

ct_pct = pd.crosstab(
    df['age_group'],
    df['plan'],
    normalize='index'
).round(3)

# 시각화 (matplotlib / seaborn)
# import seaborn as sns
# sns.heatmap(ct_pct, annot=True, fmt='.1%', cmap='Blues')

다중 레벨 교차표 + margins

ct = pd.crosstab(
    [df['region'], df['gender']],
    df['plan'],
    values=df['revenue'],
    aggfunc='sum',
    margins=True,
    margins_name='합계',
)

성능

상황권장
단순 countpd.crosstab() 간결, 빠름
count + normalizepd.crosstab(normalize=...) 한 번에
다중 집계pivot_table(aggfunc=['sum', 'mean'])
대용량 + 다중 그룹groupby().unstack() 이 더 빠를 수 있음
범주형 컬럼Categorical dtype 으로 변환 후 crosstab -> 속도 향상
# 대용량: groupby + unstack 이 더 빠를 수 있음
result = (
    df.groupby(['gender', 'plan'])
    .size()
    .unstack('plan', fill_value=0)
)

# Categorical 변환으로 속도 향상
df['plan'] = pd.Categorical(df['plan'], categories=['basic', 'pro', 'enterprise'])
pd.crosstab(df['gender'], df['plan'])
# 카테고리 순서 보장 + 속도 향상

자주 만나는 함정

1. 합산 vs 카운트

pd.crosstab(df['gender'], df['plan'])                                            # count
pd.crosstab(df['gender'], df['plan'], values=df['amount'], aggfunc='sum')        # sum

valuesaggfunc 를 같이 줘야 합산 가능. 어느 하나만 주면 에러.

2. 결측치

pd.crosstab(df['gender'], df['plan'], dropna=False)
# NaN 도 그룹으로 포함

기본으로 dropna=True 라 NaN 카테고리가 보이지 않는다. 데이터 품질 검증 시 dropna=False 권장.

3. normalize 의 유효값

'index' / 'columns' / 'all' / True / False 만 유효. True'all' 과 같다.

4. 다중 aggfunc 불가

# ❌ crosstab 은 단일 aggfunc 만 허용
pd.crosstab(..., aggfunc=['sum', 'mean'])  # TypeError

# ✓ pivot_table 사용
df.pivot_table(..., aggfunc={'amount': ['sum', 'mean']})

WARNING

crosstabnormalizevalues + aggfunc 를 동시에 사용할 수 없다. 비율 집계가 필요하면 수동으로 나눠야 한다.

관련 위키

이 글의 용어 (6개)
[Pandas] 성능 / 메모리 최적화pandas
정의 pandas 의 흔한 성능 함정과 최적화 패턴. 벡터화 + dtype 선택 + 알고리즘 의 조합이 핵심. 사용 상황 | 상황 | 권장 접근 | |:---|:---| | 단순…
[Pandas] DataFramepandas
정의 은 2차원 레이블 테이블. 각 열이 , 모든 열이 같은 (행 라벨) 를 공유. SQL 테이블 / Excel 시트 / R data.frame 의 Python 대응체. 구조 시…
[Pandas] groupbypandas
정의 는 데이터를 그룹으로 나누고 (split), 각 그룹에 함수를 적용 (apply), 결과를 합쳐 (combine) 새 DataFrame 으로 만드는 split-apply-c…
[Pandas] MultiIndexpandas
정의 MultiIndex 는 여러 단계(level)의 라벨로 구성된 . SQL의 composite key와 비슷하며, 그룹화된 데이터를 자연스럽게 표현한다. , , 등이 자동으로…
[Pandas] pivotpandas
정의 는 long-format → wide-format 변환. 한 열의 고유 값들이 새 컬럼이 되고, 원래 데이터는 그 자리에 배치된다. 집계가 없으므로 같은 (index, co…
[Pandas] pivot_tablepandas
정의 는 의 확장. 집계 함수를 동반 해 중복 (index, columns) 도 처리. Excel 의 피벗 테이블과 가장 가까운 기능. pivottable 처리 흐름 사용 상황 …

이 개념을 다룬 위키 페이지 (2)

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기