지난 포스팅의 MultiIndex 객체에 이어 이번에는 DataFrame과 피벗테이블에 대해 정리를 해보려고 합니다.
혹시 MultiIndex가 무엇이고 어떻게 다룰 수 있는지 헷갈린다면 지난 정리글을 참고하길 바랍니다.
import pandas as pd
피벗 테이블 생성하기
DataFrame.pivot_table(index, values, aggfunc, columns, fill_value, margins, margins_name)
집계는 여러 값들을 요약하는 계산을 의미합니다.
피벗 테이블은 특정 데이터프레임 열의 값을 집계하고 다른 열의 값을 사용해 결과를 그룹화합니다.
데이터프레임의 pivot_table 메서드는 피벗 테이블을 생성합니다.
다음은 사용 가능한 매개변수들의 리스트입니다.
- index (기존 데이터프레임에서 피벗 테이블의 인덱스 레이블을 구성할 열을 지정)
- values (집계할 데이터프레임의 열을 지정)
- aggfunc (집계 연산의 종류)
- columns (피벗 테이블의 열 헤더를 지정)
- fill_value (피벗 테이블의 결측값을 특정 값으로 대체)
- margins (피벗 테이블의 각 행과 열의 합계를 계산)
sales = pd.read_csv(
'./data/sales_by_employee.csv',
parse_dates=['Date']
)
sales.head()
지금부터 피벗 테이블을 만들어보도록 하겠습니다.
아래 그림은 정리를 진행하며 사용할 sales 예제 데이터프레임입니다.
sales.pivot_table(
index='Date',
values=['Revenue', 'Expenses'],
aggfunc='sum'
)
디폴트 집계 연산은 평균으로 설정되어 있습니다.
하지만 aggfunc 매개변수에 별도의 연산 관련 인수를 전달하면 연산의 종류를 바꿀 수 있습니다.
values 매개변수에는 단일 문자열 뿐만 아니라 리스트를 전달해 2개 이상의 열에 대한 집계가 가능합니다.
위 코드는 날짜별 비용과 수입의 총합을 집계합니다.
sales.pivot_table(
index='Date',
values='Revenue',
columns='Name',
aggfunc='sum'
)
columns 매개변수를 사용해서 생성한 피벗 테이블의 컬럼 헤더를 지정할 수 있습니다.
위 코드는 날짜별 각 직원의 수익을 집계합니다.
sales.pivot_table(
index='Date',
values='Revenue',
columns='Name',
aggfunc='sum',
fill_value=0,
margins=True,
margins_name='Total'
)
이전 코드를 실행한 결과 피벗 테이블의 값으로 결측값(NaN)이 존재하는 것을 확인할 수 있습니다.
fill_value 매개변수를 사용하면 결측값을 다른 값으로 대체할 수 있습니다.
이뿐만 아니라 margins 매개변수에 True를 인수로 전달하면 피벗 테이블의 각 행과 열에 대한 합계를 구할 수 있습니다.
위 코드는 margins_name 매개변수에 'Total'이라는 문자열을 전달해 소계 레이블을 지정합니다.
sales.pivot_table(
index='Date',
columns='Name',
values='Revenue',
aggfunc=['sum', 'count'],
fill_value=0
)
aggfunc 매개변수에 리스트를 전달해서 2개 이상의 집계 연산을 수행할 수 있습니다.
위 코드는 날짜별 각 직원의 수익과 거래 횟수를 집계합니다.
sales.pivot_table(
index='Date',
columns='Name',
values=['Revenue', 'Expenses'],
fill_value=0,
aggfunc={'Revenue': 'min', 'Expenses': 'min'}
)
aggfunc 매개변수에 딕셔너리를 전달하면 열별로 서로 다른 집계를 적용할 수 있습니다.
위 코드는 날짜별 각 직원의 최소 수익과 최소 비용을 집계합니다.
sales.pivot_table(
index=['Name', 'Date'],
values='Revenue',
aggfunc='sum'
)
index 매개변수에 리스트를 전달하면 MultiIndex 레이블을 지정합니다.
이때 리스트에 전달하는 열의 순서는 MultiIndex 레벨에 영향을 미칩니다.
인덱스 레벨 스택과 언스택
인덱스 레벨을 한 축에서 다른 축으로 이동하고 싶은 경우가 있습니다.
그 결과 기존과 다른 방식으로 데이터를 표현할 수 있습니다.
스택과 언스택을 통해 기존 데이터를 다르게 표현할 수 있는데 관련 예제를 살펴보기 위해 기존 데이터프레임에서 또 다른 피벗 테이블을 생성하겠습니다.
by_name_and_date= sales.pivot_table(
index='Name',
columns='Date',
values='Revenue',
aggfunc='sum'
)
by_name_and_date.head()
column → row
DataFrame.stack()
by_name_and_date.stack().head()
stack 메서드를 사용하면 인덱스 레벨을 열 축에서 행 축으로 이동시킵니다.
그 결과 MultiIndex Series 객체를 얻게 됩니다.
결측값은 제외됩니다.
row → column
DataFrame.unstack()
sales_by_customer = sales.pivot_table(
index=['Customer', 'Name'],
values='Revenue',
aggfunc='sum'
)
sales_by_customer.head()
위 코드로 생성한 피벗 테이블에 unstack 메서드를 적용해보겠습니다.
sales_by_customer.unstack().head()
unstack 메서드를 사용하면 행 인덱스의 가장 안쪽 레벨을 컬럼 인덱스로 이동시킵니다.
그 결과 열 축에는 2레벨의 MultiIndex가 존재합니다.
sales_by_customer.unstack().columns
columns 속성을 통해 컬럼 축의 MultiIndex 객체를 살펴보겠습니다.
피벗 해제하기
💡 피벗 해제 → 피벗 테이블 생성
DataFrame.melt(id_vars, value_vars)
피벗을 해제한다는 것은 넓은 데이터셋을 좁은 데이터셋으로 변환하는 과정을 의미합니다.
이를 멜팅이라고 부르기도 합니다.
우선 넓은 데이터셋과 좁은 데이터셋이 무엇인지 알아보겠습니다.
데이터셋이 좁은 형식인지 파악하려면 한 행의 값을 탐색해서 해당 값이 열 헤더가 설명하는 변수의 단일 측정값인지 살펴보면 됩니다.
위 데이터셋은 좁은 데이터셋의 예시입니다.
'2020-01-01'은 날짜(Date)이며 'Oscar'은 이름(Name)을 나타냅니다.
이는 나머지 컬럼들에 대해서도 마찬가지입니다.
즉, 행의 각 값이 주어진 변수에 대한 단일 관측값을 나타내고 있는 것입니다.
좁은 데이터셋은 여러 열에 걸쳐 같은 변수의 값이 반복되지 않습니다.
반면에 위 데이터셋은 넓은 데이터셋의 예시입니다.
Name 열을 제외한 나머지 열에서 판매량이라는 변수의 값이 반복되기 때문입니다.
판매 지역을 추가하면 데이터셋이 가로로 확장됩니다.
이때 Sales라는 새로운 범주를 생성하면 NA, EU 등의 컬럼을 그룹화할 수 있습니다.
이렇게 공통 범주로 2개 이상의 열 헤더를 그룹화 할 수 있다면 해당 데이터셋은 넓은 데이터셋입니다.
위와 같은 형태의 데이터프레임을 피벗 해제 해보겠습니다.
피벗 해제를 위해 melt 메서드를 사용하는데 전달 가능한 매개변수는 다음과 같습니다.
- id_vars (넓은 데이터셋이 데이터를 집계하는 열을 지정)
- value_vars (피벗 해제 후 새로운 열에 저장될 컬럼을 지정)
video_game_sales.melt(
id_vars='Name',
value_vars='NA'
).head()
우선 기존 데이터프레임의 NA 컬럼만 피벗 해제 해보겠습니다.
video_game_sales.melt(
id_vars='Name',
value_vars=['NA', 'EU', 'JP', 'Other']
)
다음으로는 4개의 지역 컬럼을 모두 피벗 해제 해보겠습니다.
이때 value_vars 매개변수에 인수로 리스트를 전달합니다.
video_game_sales_by_region = video_game_sales.melt(
id_vars='Name',
value_vars=['NA', 'EU', 'JP', 'Other'],
var_name='Region',
value_name='Sales'
)
video_game_sales_by_region.head()
추가로 var_name, value_name 매개변수를 사용하면 각 컬럼의 이름을 지정할 수 있습니다.
이렇게 넓은 데이터셋을 좁은 데이터셋으로 변환했습니다.
video_game_sales_by_region.pivot_table(
index='Name',
values='Sales',
aggfunc='sum'
).head()
멜팅 과정을 거쳐서 얻은 좁은 데이터셋을 사용해 피벗 테이블을 생성해보겠습니다.
위 코드는 비디오 게임 별 판매 수익을 집계합니다.
좁은 데이터셋은 넓은 데이터셋보다 집계하기가 쉽다는 특징이 있습니다.
값의 목록 확장
DataFrame.explode(x)
같은 셀에 여러 값을 저장하는 데이터셋이 존재하는 경우가 있습니다.
위 recipes 데이터프레임의 Ingredients 컬럼을 보면 각 행에 여러 값이 쉼표를 기준으로 저장되어 있는 것을 확인할 수 있습니다.
이를 str.split 메서드를 사용해서 분할해보겠습니다.
recipes['Ingredients'] = recipes['Ingredients'].str.split(',')
recipes
그 결과 Ingredients 열의 각 값이 리스트로 바뀌었습니다.
이제 리스트를 구성하는 각 값을 여러 행에 분산시키려면 어떡해야 할까요?
recipes.explode('Ingredients')
explode 메서드를 사용하면 각 리스트의 요소 별도의 행으로 생성합니다.
메서드를 사용하기 위해서는 리스트 Series가 필요합니다.
마치며
이상으로 데이터프레임으로부터 값을 집계하는 유용한 기능들을 살펴봤는데요,
넓은 데이터셋과 좁은 데이터셋 그리고 피벗 테이블의 의미에 대해 이해할 수 있었던 시간이었습니다.
이렇게 데이터셋을 재구성하면서 똑같은 데이터라도 새로운 관점으로 바라볼 수 있다는 점을 배웠습니다.
'파이썬・ML > pandas' 카테고리의 다른 글
판다스 데이터프레임 정리(2) 결측값/중복값/필터링/정렬 등 (0) | 2023.07.30 |
---|---|
판다스 데이터프레임 정리(1) 주요속성/메서드/조회 (0) | 2023.07.28 |
판다스 Series 정리(2) 인덱싱/필터링/정렬/연산/결측값 (0) | 2023.07.27 |
판다스 Series 정리(1) 특징/속성/생성법 (0) | 2023.07.27 |
판다스 데이터프레임 MultiIndex (0) | 2023.07.02 |