Skillset/Python

[pandas] 범주형 데이터 2개를 비교하는 교차분석표(crosstab, 크로스탭) 알아보기

Everly. 2022. 7. 10. 08:48

파이썬 판다스(pandas)에서 범주형 데이터 2개를 비교분석할 때 유용한 표, 교차분석표(crosstab)에 대해 알아보자.

이러한 교차분석표는 각 범주형 데이터의 개수를 행과 열로 cross해놓은 표를 의미한다.

다음은 pandas crosstab 공식 문서를 참고하였다.

 

pd.crosstab(index, columns, values=None, rownames=None, colnames=None, aggfunc=None, margins=False, margins_name='All', dropna=True, normalize=False)

 

  • 필수 input
    • index: 행으로 그룹화할 값 (array, series, list)
    • columns: 열로 그룹화할 값 (array, series, list)
  • Optional
    • rownames: 행 이름
    • colnames: 열 이름
    • values: 두 행/열에 따라 집계할 값. 반드시 aggfunc와 함께 사용
    • aggfunc: 집계 함수 ex. mean, sum 등
    • margins: True로 지정 시 행/열의 소계값이 함께 산출
    • margins_name: margins = True인 경우, 행/열의 소계를 뽑을 행/열을 지정(디폴트: 'All')
    • dropna: NaN을 포함하지 않고 반환 (디폴트: True)
    • normalize: 개수가 아닌 비율로 표시. 옵션은 3가지가 있다 
      • index: 행을 기준으로 비율 표시 (각 행의 합 = 100%)
      • columns: 열을 기준으로 비율 표시 (각 열의 합 = 100%)
      • all: 전체 기준으로 비율 표시 (각 데이터들의 합 = 100%)

 

예제 데이터로 어떻게 사용하는지 알아보자 :)

여기서 사용한 데이터는 캐글 Categorical Feature Encoding Challenge의 'train.csv' 데이터셋이다.

 

import pandas as pd

df = pd.read_csv('train.csv', index_col = 'id')
df.head()

 

데이터프레임 df에는 많은 컬럼들이 있는데, 여기서 활용할 컬럼은 nom_1과 target이라는 두 범주형 데이터이다.

nom_1는 총 6가지의 도형 값, target은 0, 1의 값을 가지고 있다.

각 값별로 행-열을 cross하여 개수를 셀 때 crosstab을 활용한다.

 

# 개수(빈도)

pd.crosstab(df['nom_1'], df['target'])

 

이렇게 교차표로 보니 각 값별로 몇 개나 있는지 한 눈에 들어와서 보기가 편하다.

이번엔 margins = True 옵션을 사용하자. 행/열 소계를 구해준다.

 

pd.crosstab(df['nom_1'], df['target'], margins = True)

# 이것도 동일
pd.crosstab(df['nom_1'], df['target'], margins = True, margins_name = 'All')

 

이렇게 'All' 이라는 새로운 값이 행, 열에 생긴다.

즉, 각 행별 합계 및 각 열별 합계값인 소계를 구해준다.


# 비율

나는 nom_1의 값에 따라 target이 1인 "비율"이 궁금하다. 그럼 normalize 옵션을 사용해보자.

 

pd.crosstab(df['nom_1'], df['target'], normalize = 'all')

 

먼저 normalize = 'all'을 사용하면 전체 기준으로 비율을 표시한다. 즉 저 셀값들을 전부 더하면 1 (100%)이 된다.

그러나 이는 내가 원하는 게 아니다. nom_1의 값에 따른 target의 비율이므로 normalize를 'nom_1'을 기준으로 해야 한다.

 

# 개수가 아닌 비율 - index 기준
pd.crosstab(df['nom_1'], df['target'], normalize = 'index')

 

위와 달라진 점을 눈치챘는가? index 즉, 'nom_1'를 기준으로 normalize하면 이렇게 행별 합이 1 (100%)이 된다.

이렇게 되면 'nom_1'이 Circle일 때 target이 1인 비율은 약 25%구나! 라는 것을 알 수 있다.

 

마찬가지로 normalize 기준을 columns로 할 수도 있다.

 

# 개수가 아닌 비율 - columns 기준
pd.crosstab(df['nom_1'], df['target'], normalize = 'columns')

 

columns를 기준으로 하면 'target' 별 'nom_1'의 값을 뽑는다.

즉 열별 합이 1 (100%)이 되며, target의 값이 0인 애들 중에 circle는 약 14%구나, polygon은 약 12%구나 등을 알 수 있다.

 

그리고 당연히 비율로 나타낸 상태에서도 margins 옵션을 사용해 소계를 뽑을 수 있다.

 

pd.crosstab(df['nom_1'], df['target'], normalize = 'columns', margins = True)

 

columns로 normalize하였으므로 각 열별 합이 1이다.

여기서 새로운 'All' 컬럼이 생겼는데, 이는 target의 값을 나누지 않고 nom_1 고유값들의 비율을 나타낸 것이다.

즉, Circle은 전체의 12%, Polygon은 전체의 12%를 차지한다는 의미!

 

# 백분율
pd.crosstab(df['nom_1'], df['target'], normalize = 'index')*100

 

뒤에 100을 곱해주면 손쉽게 백분율 값을 구할 수 있다.

 

마지막으로, 나는 nom_1의 각 고유값에 따른 target == 1인 비율을 구하고자 하였으므로...

 

ct = pd.crosstab(df['nom_1'], df['target'], normalize = 'index')*100
ct[1]

 

이렇게 crosstab을 'ct' 객체로 저장한 후 1번을 뽑으면 된다.

 

ct[1].values

 

values 옵션을 활용하면 쉽게 이 값들만 쏙 뽑을 수 있다.

반응형