이전 포스팅에서는 단일 인덱스(single index) 데이터프레임을 다루는 방법들에 대해 정리했습니다.
이번 포스팅에서는 2개 이상의 인덱스(multi index)를 갖는 데이터프레임을 다루는 방법에 대해 알아보도록 하겠습니다.
MultiIndex 객체는 주로 한 열의 값이 다른 열 값의 하위 범주에 속하는 계층적 데이터에 적용하기 좋습니다.
import pandas as pd
import numpy as np
MultiIndex 객체
이전에 하나의 요소로 구성된 인덱스 레이블로 데이터프레임의 특정 행을 참조할 수 있다는 사실을 확인한 바 있습니다.
하지만 지금부터 알아볼 인덱스 레이블은 여러 요소로 구성된다는 차이가 있습니다.
이를 통해 MultiIndex 객체는 여러 데이터 조각을 저장하는 인덱스라는 사실을 알 수 있습니다.
addresses = [
('8809 Flair Square', 'Toddside', 'IL', '37206'),
('9901 Austin Street', 'Toddside', 'IL', '37206'),
('905 Hogan Quarter', 'Franklin', 'IL', '37206')
]
# pd.MultiIndex.from_tuples(addresses)와 동일
pd.MultiIndex.from_tuples(tuple=addresses)
from_tuples 메서드를 사용하면 MultiIndex 객체를 인스턴스화할 수 있습니다.
위 코드는 4개의 요소로 구성된 튜플들을 저장하는 MultiIndex 객체를 생성합니다.
각 튜플에서 값들의 위치를 레벨이라고 부릅니다.
row_index = pd.MultiIndex.from_tuples(
tuples=addresses,
names=['Street', 'City', 'State', 'Zip']
)
names 매개변수에 리스트 인수를 전달하면 MultiIndex의 각 레벨에 이름을 할당할 수 있습니다.
예를 들면 'Toddside'가 위치한 레벨의 명칭은 'City', 'IL'이 위치한 레벨의 명칭은 'State'가 됩니다.
data = [
['A', 'B+'],
['C+', 'C'],
['D-', 'A']
]
columns = ['Schools', 'Cost of Living']
area_grades = pd.DataFrame(
data=data,
index=row_index,
columns=columns
)
area_grades
index 매개변수에 MultiIndex 객체를 인수로 전달해 데이터프레임에 연결하겠습니다.
data = [
['C-', 'B+', 'B-', 'A'],
['D+', 'C', 'A', 'C+'],
['A-', 'A', 'D+', 'F']
]
column_index = pd.MultiIndex.from_tuples([
('Culture', 'Restaurants'),
('Culture', 'Museums'),
('Services', 'Police'),
('Services', 'Schools')
])
area_grades = pd.DataFrame(
data=data,
index=row_index,
columns=column_index
)
area_grades
columns 매개변수에도 MultiIndex 객체를 인수로 전달할 수 있습니다.
이를 통해 행과 열에 모두 MultiIndex 객체를 적용할 수 있다는 사실을 확인했습니다.
MultiIndex 데이터프레임
neighborhoods = pd.read_csv(
'./data/neighborhoods.csv',
index_col=[0, 1, 2],
header=[0, 1]
)
neighborhoods.head()
다음은 정리를 진행하면서 살펴볼 neighborhoods 예제 데이터프레임을 살펴보겠습니다.
열과 행 모두 MultiIndex 객체로 구성된 상태입니다.
데이터프레임의 속성과 메서드
✅ index 속성
neighborhoods.index
index 속성을 통해 행 인덱스 레이블을 확인할 수 있습니다.
각 인덱스 레이블은 3개의 요소로 구성되어 있습니다.
주목할 특징은 하나의 행 인덱스 레이블이 튜플로 구성된다는 점입니다.
✅ columns 속성
neighborhoods.columns
columns 속성을 통해 컬럼의 MultiIndex 객체에 접근할 수 있습니다.
마찬가지로 하나의 열 인덱스는 튜플로 구성됩니다.
✅ info 메서드
neighborhoods.info()
info 메서드를 통해 데이터프레임의 일반적인 정보를 확인할 수 있습니다.
MultiIndex 객체의 속성과 메서드
✅ names 속성
neighborhoods.index.names
# FrozenList(['State', 'City', 'Street'])
neighborhoods.index.names[0]
# 'State'
neighborhoods.index.names[2]
# 'Street'
MultiIndex 객체의 names 속성을 통해 인덱스 이름 리스트에 접근이 가능합니다.
위 코드는 neighborhoods 데이터프레임의 행 인덱스 레이블의 이름 리스트에 접근합니다.
참고로 MultiIndex 내의 각 중첩 레벨에는 순서가 할당됩니다.
✅ get_level_values 메서드
# neighborhoods.index.get_level_values('Street')과 동일
neighborhoods.index.get_level_values(2)
get_level_values 메서드를 사용하면 행 MultiIndex 객체의 특정 레벨별 Index 객체를 추출할 수 있습니다.
위 코드는 'Street' 레벨의 인덱스를 추출합니다.
neighborhoods.columns.names = ['Category', 'Subcategory']
print(neighborhoods.columns.names)
# FrozenList(['Category', 'Subcategory'])
# neighborhoods.columns.get_level_values('Category')와 동일
neighborhoods.columns.get_level_values(0)
# Index(['Culture', 'Culture', 'Services', 'Services'], dtype='object', name='Category')
데이터프레임의 columns 속성을 사용해서 열 MultiIndex 객체에도 접근이 가능합니다.
neighborhoods.nunique()
멀티 인덱스 데이터프레임에 nunique 메서드를 적용하면 다음 형태로 컬럼별 고유값의 개수를 표시합니다.
MultiIndex 정렬
DataFrame.sort_index(ascending, level)
neighborhoods.sort_index()
멀티 인덱스 데이터프레임에서 값을 찾기 전에 인덱스를 정렬하면 내부적으로 더 빠르게 해당 값을 탐색할 수 있습니다.
sort_index 메서드를 사용하면 멀티 인덱스 데이터프레임을 정렬할 수 있는데 기본적으로는 바깥쪽 인덱스 레벨부터 정렬합니다.
ascending 매개변수에 False를 전달하면 바깥쪽에서 안쪽 레벨 순으로 각 레벨을 내림차순으로 정렬합니다.
위 코드는 'State → City → Street' 레벨 순으로 각 행의 인덱스 레이블을 오름차순으로 정렬합니다.
neighborhoods.sort_index(ascending=[True, False, True]).head()
각 레벨의 정렬 순서를 다르게 지정하고 싶으면 ascending 매개변수에 불리언 리스트를 전달하면 됩니다.
위 코드는 State 레벨을 오름차순으로, City 레벨을 내림차순으로, Street 레벨을 오름차순으로 정렬합니다.
neighborhoods.sort_index(level='City')
단일 레벨을 기준으로 정렬할 수도 있습니다.
위 코드는 City 레벨을 기준으로 행을 정렬합니다.
이때 나머지 레벨은 무시됩니다.
neighborhoods.sort_index(level=['City', 'Street']).head()
level 매개변수에 리스트를 전달하면 2개 이상의 레벨을 기준으로 정렬할 수 있습니다.
위 코드는 우선 City 레벨의 값을 먼저 정렬한 다음 Street 레벨의 값을 정렬합니다.
이때 ascending 매개변수에 불리언 리스트를 전달하면 각 레벨을 서로 다른 방식으로 정렬할 수 있습니다.
neighborhoods.sort_index(axis='columns').head()
neighborhoods.sort_index(axis=1).head()
axis 매개변수에 1을 인수로 전달하면 열의 정렬 순서를 지정할 수 있습니다.
위 코드는 Category 레벨을 먼저 정렬하고 Subcategory 레벨을 두 번째로 정렬합니다.
MultiIndex 행과 열 선택
단일 인덱스 데이터프레임에서 loc 속성과 대괄호를 사용해 행과 열을 선택한 것처럼 멀티 인덱스 데이터프레임도 똑같이 행과 열을 선택할 수 있습니다.
하나 이상의 열 선택
neighborhoods['Services'].head()
대괄호를 사용하면 멀티 인덱스 데이터프레임에서 열을 선택할 수 있습니다.
이때 열 MultiIndex 객체의 가장 바깥쪽 레벨에서 해당 값을 찾습니다.
하지만 해당 값이 존재하지 않으면 KeyError가 발생합니다.
print(neighborhoods[('Services', 'Schools')].head())
print(type(neighborhoods[('Services', 'Schools')].head()))
튜플을 사용하면 MultiIndex에서 여러 레벨에 걸친 값을 지정할 수 있습니다.
그 결과 Series 객체를 리턴합니다.
columns = [
('Services', 'Schools'),
('Culture', 'Museums')
]
neighborhoods[columns].head()
여러 개의 열을 추출하는 경우에는 튜플의 리스트를 대괄호 안에 전달하면 됩니다.
하나 이상의 행 선택
DataFrame.loc[행 인덱스 레이블, 열 인덱스 레이블]
neighborhoods.loc['CA']
loc 속성을 사용하면 멀티 인덱스 데이터프레임에서 행을 선택할 수 있습니다.
대괄호 안에 단일 레이블을 넣으면 가장 바깥쪽 MultiIndex 레벨에서 해당 레이블을 찾습니다.
위 코드는 행 인덱스 'CA' 레벨에 포함된 행들을 선택합니다.
neighborhoods.loc[('CA', 'Dustinmouth')]
State 레벨의 값이 'CA'이고 City 레벨의 값이 'Dustinmouth'인 행을 선택해보겠습니다.
튜플을 사용해서 행 인덱스 레이블 값들을 모아뒀습니다.
neighborhoods.loc[('CA', 'Dustinmouth'), ('Services',)]
첫 번째 인수로 행 인덱스 레이블을 모아 놓은 튜플을 전달했고 두 번째 인수로 열 인덱스 레이블이 담긴 튜플을 전달했습니다.
이 경우에는 행 인덱스에서 State 레벨이 'CA'이고 City 레벨이 'Dustinmouth'이면서 category가 'Services'인 값을 선택합니다.
튜플을 사용해서 인수를 구분한 것을 참고하세요.
neighborhoods['NE':'NH']
파이썬 슬라이싱 구문을 사용해서 연속된 행을 선택하는 방법도 있습니다.
이때 범위의 끝점은 결과에 포함됩니다.
start = ('NE', 'Shawnchester')
end = ('NH', 'North Latoya')
neighborhoods.loc[start:end]
튜플을 사용해서 행과 열 인덱스별 특정 레벨을 선택해 슬라이싱 하는 방법도 있습니다.
MultiIndex 단면 추출
DataFrame.xs(key, level, axis)
xs 메서드를 사용해서 원하는 행을 추출하는 방법도 있습니다.
다음은 전달 가능한 매개변수의 리스트입니다.
- key (찾고자 하는 값 지정)
- level (값을 찾을 인덱스 레벨)
neighborhoods.xs(key='Lake Nicole', level='City')
위 코드는 행 인덱스 레벨 'City'의 값이 'Lake Nicole'인 행들을 추출합니다.
neighborhoods.xs(
axis=1,
key='Museums',
level='Subcategory'
)
axis 매개변수에 1을 인수로 전달하면 동일한 추출 기법을 컬럼에 적용할 수 있습니다.
위 코드는 열 인덱스 레벨 'Subcategory'의 값이 'Museums'인 행들을 추출합니다.
neighborhoods.xs(
key=('AK', '238 Andrew Rue'),
level=['State', 'Street']
)
비연속적인 레벨을 선택하는 방법도 있습니다.
neighborhoods 데이터프레임 행 인덱스 레이블의 레벨은 'State → City → Street' 순입니다.
위 코드는 State 레벨의 값과 Street 레벨의 값을 튜플로 묶어 key 매개변수에 전달합니다.
이때 State과 Street 레벨의 각 값들은 비연속적입니다.
MultiIndex 인덱스 조작
인덱스 순서 바꾸기
DataFrame.reorder_levels(order)
new_order = ['City', 'State', 'Street']
neighborhoods.reorder_levels(
order=new_order
).head()
reorder_levels 메서드를 사용하면 기존 인덱스의 순서를 바꿀 수 있습니다.
order 매개변수에 원하는 순서로 레벨 리스트를 구성한 뒤에 이를 전달하면 됩니다.
인덱스 제거하기
DataFrame.reset_index(col_level, col_fill, level, drop)
neighborhoods.reset_index().index
# RangeIndex(start=0, stop=251, step=1)
reset_index 메서드를 사용하면 MultiIndex 레벨을 기존 컬럼으로 통합한 새 DataFrame을 리턴합니다.
이때 기존의 MultiIndex 객체는 RangeIndex 객체로 대체됩니다.
MultiIndex 레벨이었던 'State, City, 'Street'은 열 MultiIndex의 가장 바깥쪽 레벨의 새로운 값이 됩니다.
neighborhoods.reset_index(col_level='Subcategory').head()
col_level 매개변수를 사용하면 기존 MultiIndex 레벨을 다른 열 MultiIndex 레벨에 추가할 수 있습니다.
위 코드는 이전 코드와 달리 기존 MultiIndex 레벨을 열 MultiIndex 'Subcategory' 레벨의 새로운 값으로 추가합니다.
neighborhoods.reset_index(level='Street').head()
level 매개변수를 사용하면 여러 MultiIndex 레벨 중 하나의 레벨만을 일반 컬럼으로 옮길 수 있습니다.
인수로 리스트를 전달하면 2개 이상의 레벨을 일반 컬럼으로 옮길 수 있습니다.
neighborhoods.reset_index(
level='Street',
drop=True
).head()
drop 매개변수를 사용하면 MultiIndex에서 특정 레벨을 제거할 수 있습니다.
위 코드는 Street 레벨을 삭제한 새 DataFrame을 리턴합니다.
인덱스 설정하기
DataFrame.set_index(keys)
neighborhoods.set_index(
keys='City'
).head()
set_index 메서드를 사용하면 DataFrame의 기존 열을 새로운 인덱스로 설정합니다.
위 코드는 City 컬럼을 새로운 인덱스로 설정합니다.
마치며
이번에 데이터프레임 멀티 인덱스 관련 개념을 정리하면서 다음과 같은 물음의 중요성을 느꼈습니다.
- "데이터프레임의 인덱스를 정의할 때 현재 가장 중요한 값은 무엇일까?"
- "핵심 정보는 무엇일까?"
- "여러 데이터 조각을 함께 묶을 필요가 있을까?"
- "어떤 데이터 포인트를 행과 열로 저장해야 할까?"
- "행이나 열을 그룹화(범주화) 할 수 있을까?"
다음에 정리할 pivot table과 관련해서 복습을 철저히 해야겠습니다! 👊🏻
'파이썬・ML > pandas' 카테고리의 다른 글
판다스 데이터프레임 정리(2) 결측값/중복값/필터링/정렬 등 (0) | 2023.07.30 |
---|---|
판다스 데이터프레임 정리(1) 주요속성/메서드/조회 (0) | 2023.07.28 |
판다스 Series 정리(2) 인덱싱/필터링/정렬/연산/결측값 (0) | 2023.07.27 |
판다스 Series 정리(1) 특징/속성/생성법 (0) | 2023.07.27 |
판다스 데이터프레임과 피벗테이블 (0) | 2023.07.03 |