[Pandas 기초] 피벗 테이블(pivot_table)과 멀티인덱스(MultiIndex)
업데이트:
개요
이번 포스팅에서는 엑셀에서 사용하는 피벗테이블과 같은 기능을 처리하는 방법과 부차적으로 알아야할 멀티인덱스에 대해 알아보자.
기본적인 개념과 사용법만 알고 있다면, 복잡한 데이터 처리를 거치지 않고 간단하게 원하는 데이터를 만들어 주는 경우가 많다.
예제로 타이타닉 데이터를 이용한다.
import pandas as pd
import seaborn as sns
df = sns.load_dataset('titanic')[['age','sex','class','fare','survived']]
df.head()
age | sex | class | fare | survived | |
---|---|---|---|---|---|
0 | 22.0 | male | Third | 7.2500 | 0 |
1 | 38.0 | female | First | 71.2833 | 1 |
2 | 26.0 | female | Third | 7.9250 | 1 |
3 | 35.0 | female | First | 53.1000 | 1 |
4 | 35.0 | male | Third | 8.0500 | 0 |
1. 피벗 테이블 함수 : pivot_table()
pivot_table() 함수의 기본 구성요소는 다음과 같다.
- 행 인덱스
- 열 인덱스
- 데이터 값
- 데이터 집계함수
각 구성요소에 적용할 데이터프레임의 열을 각각 함수의 인자로 전달한다.
4가지 구성요소를 적절히 입력하고 피벗테이블을 생성해보자.
pdf1 = pd.pivot_table(df, # 피벗할 데이터프레임
index = 'class', # 행 위치에 들어갈 열
columns = 'sex', # 열 위치에 들어갈 열
values = 'age', # 데이터로 사용할 열
aggfunc = 'mean') # 데이터 집계함수
pdf1
sex | female | male |
---|---|---|
class | ||
Third | 21.750000 | 26.507589 |
First | 34.611765 | 41.281386 |
Second | 28.722973 | 30.740707 |
행에는 class열의 3가지 그룹, 열에는 sex열의 2가지그룹, 값에는 age열을 평균값(mean)으로 집계한 값이 들어간 것을 확인할 수 있다.
이번에는 구조를 조금 바꾸고, 집계함수를 2개 넣어보자
pdf2 = pd.pivot_table(df, # 피벗할 데이터프레임
index = 'class', # 행 위치에 들어갈 열
columns = 'sex', # 열 위치에 들어갈 열
values = 'survived', # 데이터로 사용할 열
aggfunc = ['mean', 'sum']) # 데이터 집계함수
pdf2
mean | sum | |||
---|---|---|---|---|
sex | female | male | female | male |
class | ||||
Third | 0.500000 | 0.135447 | 72 | 47 |
First | 0.968085 | 0.368852 | 91 | 45 |
Second | 0.921053 | 0.157407 | 70 | 17 |
3등석(Third)에 탄 여자(female)들의 생존여부(survived)는 평균 50%정도,
1등석(First)에 탄 남자(male)들의 생존자는 모두 45명이다.
이번에는 더많은 열을 인자로 입력해보자.
pdf3 = pd.pivot_table(df,
index = ['class','sex'],
columns = 'survived',
values = ['age','fare'],
aggfunc = ['mean','max'])
pdf3
mean | max | ||||||||
---|---|---|---|---|---|---|---|---|---|
age | fare | age | fare | ||||||
survived | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | |
class | sex | ||||||||
Third | female | 23.818182 | 19.329787 | 19.773093 | 12.464526 | 48.0 | 63.0 | 69.55 | 31.3875 |
male | 27.255814 | 22.274211 | 12.204469 | 15.579696 | 74.0 | 45.0 | 69.55 | 56.4958 | |
First | female | 25.666667 | 34.939024 | 110.604167 | 105.978159 | 50.0 | 63.0 | 151.55 | 512.3292 |
male | 44.581967 | 36.248000 | 62.894910 | 74.637320 | 71.0 | 80.0 | 263.00 | 512.3292 | |
Second | female | 36.000000 | 28.080882 | 18.250000 | 22.288989 | 57.0 | 55.0 | 26.00 | 65.0000 |
male | 33.369048 | 16.022000 | 19.488965 | 21.095100 | 70.0 | 62.0 | 73.50 | 39.0000 |
age와 fare열을 값(values)으로 지정한 경우 보기 쉽게 두 열로 명시해준다.
3등석(Third)에 탄 여자(female)중 생존(survived)한 사람들의 최고(max) 연령은 63세이고,
2등석(Second)에 탄 남자(male)중 사망(survived)한 사람들이 지불한 평균(mean) 요금(fare)은 19.488965이다.
이처럼 그룹핑을 사용하지 않고도 편리하게 활용할 수 있는 장점이 있다.
2. 멀티 인덱스
앞의 그룹연산 포스팅에서 다중 열을 기준으로 그룹객체를 생성하면 멀티인덱스 인덱스를 구성하는 것을 봤었다.
이번 포스팅에서 이 멀티인덱스를 다루는 방법에 대해 간단하게만 알아보도록 하자.
타이타닉 데이터를 로드해서, class, age열에 대해 그룹객체를 생성한 후, 평균(mean)으로 집계해보자.
import pandas as pd
import seaborn as sns
df = sns.load_dataset('titanic')[['age','sex','class','fare','survived']]
grouped = df.groupby(['class','sex'])
gdf = grouped.mean()
gdf
age | fare | survived | ||
---|---|---|---|---|
class | sex | |||
First | female | 34.611765 | 106.125798 | 0.968085 |
male | 41.281386 | 67.226127 | 0.368852 | |
Second | female | 28.722973 | 21.970121 | 0.921053 |
male | 30.740707 | 19.741782 | 0.157407 | |
Third | female | 21.750000 | 16.118810 | 0.500000 |
male | 26.507589 | 12.661633 | 0.135447 |
두 그룹에 대해 멀티 행인덱스를 생성한 것을 확인할 수 있다.
2-1. 멀티 인덱스의 인덱싱(indexing)
멀티 인덱스도 그냥 데이터프레임을 인덱싱하는 방법과 동일하다.
앞에서 만든 그룹객체는 class열로 그룹한 후, sex열로 다시 그룹했다는 것을 알아두자.(순서)
이 순서에 따라 인덱싱도 같은 방법으로 적용하면 된다.
class열의 First 행만 인덱싱
gdf.loc['First']
age | fare | survived | |
---|---|---|---|
sex | |||
female | 34.611765 | 106.125798 | 0.968085 |
male | 41.281386 | 67.226127 | 0.368852 |
처음부터 class그룹, sex그룹에 대해 바로 인덱싱 하려면, 튜플 형태로 인덱싱하면 된다.
그룹객체를 생성하면 튜플형태로 저장되는 사실을 앞의 그룹연산 포스팅에서 배운바 있다.
gdf.loc[('First','female')]
[Output]
age 34.611765
fare 106.125798
survived 0.968085
Name: (First, female), dtype: float64
2-2. 멀티인덱서 : .xs
sex그룹의 male값을 갖는 행을 추출, 즉 등급(class)별 male에 대한 자료를 인덱싱하려면 다음과 같이 수행하면 된다.
gdf.xs('male', level='sex')
age | fare | survived | |
---|---|---|---|
class | |||
First | 41.281386 | 67.226127 | 0.368852 |
Second | 30.740707 | 19.741782 | 0.157407 |
Third | 26.507589 | 12.661633 | 0.135447 |
쉽게 말해 판다스의 기본 데이터프레임 인덱싱 함수 loc
와 iloc
를 이용하려면 큰 그룹부터 순차적으로 인덱싱을 해야하는데, 멀티인덱서 .xs
를 이용하면 그룹 범주와 상관없이 수준(level)만 명시해주면 인덱싱이 가능하다.
위에서 sex는 두번째 그룹범주이므로 ‘sex’또는 1로 입력해도 무방하다.
2-3. 멀티인덱스 해제
멀티 행인덱스를 풀고 싶다면
print(gdf.reset_index())
[Output]
class sex age fare survived
0 First female 34.611765 106.125798 0.968085
1 First male 41.281386 67.226127 0.368852
2 Second female 28.722973 21.970121 0.921053
3 Second male 30.740707 19.741782 0.157407
4 Third female 21.750000 16.118810 0.500000
5 Third male 26.507589 12.661633 0.135447
컬럼이 멀티인덱스인 경우,
gdf2 = df.groupby('class').agg(['mean','max'])[['age','fare']]
print(gdf2)
[Output]
age fare
mean max mean max
class
First 38.233441 80.0 84.154687 512.3292
Second 29.877630 70.0 20.662183 73.5000
Third 25.140620 74.0 13.675550 69.5500
이렇게 생긴 멀티 열인덱스가 어떤 목적을 위해 보기가 싫다면,
gdf2.columns = ['age_mean', 'age_max', 'fare_mean','fare_max']
print(gdf2.head())
[Output]
age_mean age_max fare_mean fare_max
class
First 38.233441 80.0 84.154687 512.3292
Second 29.877630 70.0 20.662183 73.5000
Third 25.140620 74.0 13.675550 69.5500
컬럼명을 새로 지정해주면 멀티인덱스가 해제된다.
2-4. 피벗테이블의 멀티인덱싱 추가 응용
1번 목차의 마지막에 생성했던 피벗테이블을 다시 생성해보자.
pdf3 = pd.pivot_table(df,
index = ['class','sex'],
columns = 'survived',
values = ['age','fare'],
aggfunc = ['mean','max'])
pdf3
mean | max | ||||||||
---|---|---|---|---|---|---|---|---|---|
age | fare | age | fare | ||||||
survived | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | |
class | sex | ||||||||
Third | female | 23.818182 | 19.329787 | 19.773093 | 12.464526 | 48.0 | 63.0 | 69.55 | 31.3875 |
male | 27.255814 | 22.274211 | 12.204469 | 15.579696 | 74.0 | 45.0 | 69.55 | 56.4958 | |
First | female | 25.666667 | 34.939024 | 110.604167 | 105.978159 | 50.0 | 63.0 | 151.55 | 512.3292 |
male | 44.581967 | 36.248000 | 62.894910 | 74.637320 | 71.0 | 80.0 | 263.00 | 512.3292 | |
Second | female | 36.000000 | 28.080882 | 18.250000 | 22.288989 | 57.0 | 55.0 | 26.00 | 65.0000 |
male | 33.369048 | 16.022000 | 19.488965 | 21.095100 | 70.0 | 62.0 | 73.50 | 39.0000 |
이 피벗테이블이 인덱싱이 굉장히 많아서 연습하기 좋을 것 같아서 말이다.
대범주 class열의 First그룹 인덱싱
pdf3.xs('First')
mean | max | |||||||
---|---|---|---|---|---|---|---|---|
age | fare | age | fare | |||||
survived | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 |
sex | ||||||||
female | 25.666667 | 34.939024 | 110.604167 | 105.978159 | 50.0 | 63.0 | 151.55 | 512.3292 |
male | 44.581967 | 36.248000 | 62.894910 | 74.637320 | 71.0 | 80.0 | 263.00 | 512.3292 |
두번째 행인덱스 범주 male을 멀티인덱서 xs
로 인덱싱(level='sex'
옵션 필수)
pdf3.xs('male', level='sex')
mean | max | |||||||
---|---|---|---|---|---|---|---|---|
age | fare | age | fare | |||||
survived | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 |
class | ||||||||
Third | 27.255814 | 22.274211 | 12.204469 | 15.579696 | 74.0 | 45.0 | 69.55 | 56.4958 |
First | 44.581967 | 36.248000 | 62.894910 | 74.637320 | 71.0 | 80.0 | 263.00 | 512.3292 |
Second | 33.369048 | 16.022000 | 19.488965 | 21.095100 | 70.0 | 62.0 | 73.50 | 39.0000 |
여기서 레벨(level)에 사용된 0은 컬렴명이 아니라 행인덱스의 레벨을 의미한다.
0은 ‘class’, 1은 ‘sex’를 의미하며, ‘sex’대신 1을 입력해도 같은 결과이다.
pdf3.xs(('Second','male'),level=[0,'sex'])
mean | max | ||||||||
---|---|---|---|---|---|---|---|---|---|
age | fare | age | fare | ||||||
survived | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | |
class | sex | ||||||||
Second | male | 33.369048 | 16.022 | 19.488965 | 21.0951 | 70.0 | 62.0 | 73.5 | 39.0 |
여기서 멀티인덱서 .xs
에 level을 추가하냐 안하냐의 차이는 범주의 크기에 따른 것도 있지만, 반환되는 객체의 형태에도 차이가 있다.
동일한 결과를 주는 다음 두 형태를 보자.
pdf3.xs(('First','female'))
[Output]
survived
mean age 0 25.666667
1 34.939024
fare 0 110.604167
1 105.978159
max age 0 50.000000
1 63.000000
fare 0 151.550000
1 512.329200
Name: (First, female), dtype: float64
pdf3.xs(('First','female'), level=['class','sex'])
mean | max | ||||||||
---|---|---|---|---|---|---|---|---|---|
age | fare | age | fare | ||||||
survived | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | |
class | sex | ||||||||
First | female | 25.666667 | 34.939024 | 110.604167 | 105.978159 | 50.0 | 63.0 | 151.55 | 512.3292 |
첫번째의 경우는 사실 튜플형태로 직접 입력했으므로 .xs
가 아닌 .loc
함수로도 인덱싱이 가능하다.
어쨋거나 level을 지정해주면 데이터프레임 객체를 반환한다는 점 알아두자.
이번엔 열의 멀티인덱스를 인덱싱해보자. 행인덱스 인덱싱방법과 동일하며 axis=1옵션만 추가해주면 된다.
mean열만 인덱싱
pdf3.xs('mean', axis=1)
age | fare | ||||
---|---|---|---|---|---|
survived | 0 | 1 | 0 | 1 | |
class | sex | ||||
Third | female | 23.818182 | 19.329787 | 19.773093 | 12.464526 |
male | 27.255814 | 22.274211 | 12.204469 | 15.579696 | |
First | female | 25.666667 | 34.939024 | 110.604167 | 105.978159 |
male | 44.581967 | 36.248000 | 62.894910 | 74.637320 | |
Second | female | 36.000000 | 28.080882 | 18.250000 | 22.288989 |
male | 33.369048 | 16.022000 | 19.488965 | 21.095100 |
mean열의 age열 인덱싱
pdf3.xs(('mean','age'), axis=1)
survived | 0 | 1 | |
---|---|---|---|
class | sex | ||
Third | female | 23.818182 | 19.329787 |
male | 27.255814 | 22.274211 | |
First | female | 25.666667 | 34.939024 |
male | 44.581967 | 36.248000 | |
Second | female | 36.000000 | 28.080882 |
male | 33.369048 | 16.022000 |
Reference
도서 [파이썬 머신러닝 판다스 데이터 분석]을 공부하며 작성하였습니다.
댓글남기기