Pandas는 Python으로 작성된 데이터 분석 도구입니다. Python의 패키지 형태로 제공되어, Python 언어 내에 쉽게 섞어 사용할 수 있습니다.
Python은 절차적(procedural)인 방식으로 동작을 기술하는 구조를 가지고 있는데 반하여, Pandas, NumPy 등의 패키지는 행, 열, 행렬 중심으로 동작을 기술하는 구조를 가지고 있어서, 그 동안 배웠던 것과 약간 차이가 있다고 느끼실 수 있습니다.
오히려 R을 배웠던 분들은 익숙하실 수 있는데, Python에서 R과 비슷한 환경을 제공하기 위해서 만들어졌기 때문에 비슷한 기능과 사용법을 가지고 있습니다.
- Series
- DataFrame
- Creation
- from_dict()
- .shape
- .columns
- Sort
- sort_index()
- sort_values()
- rank()
- rename()
- Creation
- Index
- reindex()
- set_index()
- reset_index()
Pandas에서 주요한 데이터 요소로, Series 와 DataFrame 이 있습니다.
Series 는 1차원 배열이라고 볼 수 있습니다. 간단한 예는 아래와 같습니다.
import pandas as pd
s = pd.Series([17.8, 25.3, 18.2, 3, 5])
s
0 1 1 2 2 3 3 4 4 5 dtype: int64
Series에 여러 가지 정보를 덧붙일 수 있습니다.
import pandas as pd
population = pd.Series([51181299, 8468555, 1281935911, 10248069, 53950935],
index=['한국', '타지키스탄', '인도', '요르단', '탄자니아'],
name='인구')
population
한국 51181299 타지키스탄 8468555 인도 1281935911 요르단 10248069 탄자니아 53950935 Name: 인구, dtype: int64
import pandas as pd
index = ['한국', '타지키스탄', '인도', '요르단', '탄자니아']
birth_rate = pd.Series([8.3, 23.3, 19, 23.9, 35.6],
index=index,
name='출산율(1000명당)')
unemployment_rate = pd.Series([0.107, 0.167, 0.107, 0.293, 0.094],
index=index,
name='실업율(15-24세)')
DataFrame 은 여러개의 Series 를 모아놓은 것입니다. 간단히 여러 행과 열로 이루어진 엑셀 시트를 생각하시면 되겠습니다.
import pandas as pd
df_countries = pd.DataFrame({'인구': population, '출산율(1000명당)': birth_rate, '실업율(15-24세)': unemployment_rate})
df_countries
| 실업율(15-24세) | 인구 | 출산율(1000명당) | |
|---|---|---|---|
| 한국 | 0.107 | 5.11813e+07 | 8.3 |
| 타지키스탄 | 0.167 | 8.46856e+06 | 23.3 |
| 인도 | 0.107 | 1.28194e+09 | 19 |
| 요르단 | 0.293 | 1.02481e+07 | 23.9 |
| 탄자니아 | 0.094 | 5.39509e+07 | 35.6 |
DataFrame 의 모양, 그리고 컬럼 목록을 아래와 같이 확인할 수 있습니다.
print(df_countries.shape)
print(df_countries.columns)
(5, 3) Index(['실업율(15-24세)', '인구', '출산율(1000명당)'], dtype='object')
Series 를 통하지 않고 곧바로 DataFrame 을 생성할 수 있습니다.
import pandas as pd
df_countries = pd.DataFrame([[51181299, 8.3, 0.107],
[8468555, 23.3, 0.167],
[1281935911, 19, 0.107],
[10248069, 23.9, 0.293],
[53950935, 35.6, 0.094]],
index=['한국', '타지키스탄', '인도', '요르단', '탄자니아'],
columns=['인구', '출산율(1000명당)', '실업율(15-24세)'])
df_countries
| 인구 | 출산율(1000명당) | 실업율(15-24세) | |
|---|---|---|---|
| 한국 | 5.11813e+07 | 8.3 | 0.107 |
| 타지키스탄 | 8.46856e+06 | 23.3 | 0.167 |
| 인도 | 1.28194e+09 | 19 | 0.107 |
| 요르단 | 1.02481e+07 | 23.9 | 0.293 |
| 탄자니아 | 5.39509e+07 | 35.6 | 0.094 |
웹으로부터 JSON 데이터를 가져오는 경우에는 데이터가 dict 형태가 됩니다.
json_data = [
{"이름": "한국", "인구": 51181299, "출산율(1000명당)": 8.3, "실업율(15-24세)": 0.107},
{"이름": "타지키스탄", "인구": 8468555, "출산율(1000명당)": 23.3, "실업율(15-24세)": 0.167},
{"이름": "인도", "인구": 1281935911, "출산율(1000명당)": 19, "실업율(15-24세)": 0.107},
{"이름": "요르단", "인구": 10248069, "출산율(1000명당)": 23.9, "실업율(15-24세)": 0.293},
{"이름": "탄자니아", "인구": 53950935, "출산율(1000명당)": 35.6, "실업율(15-24세)": 0.094}
]df_countries = pd.DataFrame.from_dict(json_data)
df_countries
| 실업율(15-24세) | 이름 | 인구 | 출산율(1000명당) | |
|---|---|---|---|---|
| 0 | 0.107 | 한국 | 51181299 | 8.3 |
| 1 | 0.167 | 타지키스탄 | 8468555 | 23.3 |
| 2 | 0.107 | 인도 | 1281935911 | 19 |
| 3 | 0.293 | 요르단 | 10248069 | 23.9 |
| 4 | 0.094 | 탄자니아 | 53950935 | 35.6 |
그런데 이 경우에는 다른 경우와는 달리 국가명이 일반 컬럼으로 들어가 있습니다. 이렇듯 특정 컬럼을 인덱스로 만들기 위해서는 아래와 같이 합니다.
df_countries = df_countries.set_index('이름')
df_countries
| 이름 | 실업율(15-24세) | 인구 | 출산율(1000명당) |
|---|---|---|---|
| 한국 | 0.107 | 5.11813e+07 | 8.3 |
| 타지키스탄 | 0.167 | 8.46856e+06 | 23.3 |
| 인도 | 0.107 | 1.28194e+09 | 19 |
| 요르단 | 0.293 | 1.02481e+07 | 23.9 |
| 탄자니아 | 0.094 | 5.39509e+07 | 35.6 |
인덱스를 해제하여 일반 컬럼으로 만들기 위해서는 reset_index 를 사용합니다.
df_countries.reset_index()
| 이름 | 실업율(15-24세) | 인구 | 출산율(1000명당) | |
|---|---|---|---|---|
| 0 | 한국 | 0.107 | 51181299 | 8.3 |
| 1 | 타지키스탄 | 0.167 | 8468555 | 23.3 |
| 2 | 인도 | 0.107 | 1281935911 | 19 |
| 3 | 요르단 | 0.293 | 10248069 | 23.9 |
| 4 | 탄자니아 | 0.094 | 53950935 | 35.6 |
행이나 열의 순서를 바꾸고자 할 때에는 reindex 메소드를 사용합니다. 이름은 reindex 이지만, 인덱스 외에도 컬럼의 순서도 조정할 수 있습니다.
df_countries.reset_index().reindex([2, 4, 3, 0, 1], columns=['출산율(1000명당)', '실업율(15-24세)', '인구'])
| 출산율(1000명당) | 실업율(15-24세) | 인구 | |
|---|---|---|---|
| 2 | 19 | 0.107 | 1.28194e+09 |
| 4 | 35.6 | 0.094 | 5.39509e+07 |
| 3 | 23.9 | 0.293 | 1.02481e+07 |
| 0 | 8.3 | 0.107 | 5.11813e+07 |
| 1 | 23.3 | 0.167 | 8.46856e+06 |
실업율이 높은 순서대로 한 번 살펴볼까요?
df_countries.sort_values('실업율(15-24세)', ascending=False)
| 실업율(15-24세) | 인구 | 출산율(1000명당) | |
|---|---|---|---|
| 요르단 | 0.293 | 1.02481e+07 | 23.9 |
| 타지키스탄 | 0.167 | 8.46856e+06 | 23.3 |
| 한국 | 0.107 | 5.11813e+07 | 8.3 |
| 인도 | 0.107 | 1.28194e+09 | 19 |
| 탄자니아 | 0.094 | 5.39509e+07 | 35.6 |
ascending 옵션을 주지 않으면 오름차순으로 정렬합니다.
이번엔 인덱스 순서, 즉 국가 이름 순서대로 정렬을 해봅시다.
df_countries.sort_index(ascending=False)
| 실업율(15-24세) | 인구 | 출산율(1000명당) | |
|---|---|---|---|
| 한국 | 0.107 | 5.11813e+07 | 8.3 |
| 탄자니아 | 0.094 | 5.39509e+07 | 35.6 |
| 타지키스탄 | 0.167 | 8.46856e+06 | 23.3 |
| 인도 | 0.107 | 1.28194e+09 | 19 |
| 요르단 | 0.293 | 1.02481e+07 | 23.9 |
위에서 index 지정이나 해제, 정렬 등을 할 때 살펴봤듯이, pandas에서 대부분의 행동은 원본 데이터를 수정하지 않고 복사본을 반환하는 형태로 동작합니다.
눈으로 정렬해서 보는 것 말고, 수치화해서 나타내려면 어떻게 할까요?
df_countries.rank()
| 실업율(15-24세) | 인구 | 출산율(1000명당) | |
|---|---|---|---|
| 한국 | 2.5 | 3 | 1 |
| 타지키스탄 | 4 | 1 | 3 |
| 인도 | 2.5 | 5 | 2 |
| 요르단 | 5 | 2 | 4 |
| 탄자니아 | 1 | 4 | 5 |
실업율(15-24세) 컬럼 이름이 너무 길어서 불편합니다. 인덱스나 컬럼의 이름을 변경하려면 어떻게 할까요?
df_countries.rename(index={'한국': '대한민국'}, columns={'실업율(15-24세)': '실업율'})
| 인구 | 출산율(1000명당) | 실업율 | |
|---|---|---|---|
| 대한민국 | 5.11813e+07 | 8.3 | 0.107 |
| 타지키스탄 | 8.46856e+06 | 23.3 | 0.167 |
| 인도 | 1.28194e+09 | 19 | 0.107 |
| 요르단 | 1.02481e+07 | 23.9 | 0.293 |
| 탄자니아 | 5.39509e+07 | 35.6 | 0.094 |
- head()
- tail()
- sampling
- sample(n=10)
- sample(frac=0.6)
- slicing
- Boolean indexing
- where()
조건에 부합하는 행이나 열을 골라내는 방법을 알아봅시다.
이 섹션을 진행하기 위해, 조금 더 많은 row를 가진 데이터를 불러들여봅시다.
df_dtype = pd.read_excel('assets/엑셀과정실습생.xlsx', sheet_name='Sheet1',
index_col=0,
dtype={'번호': 'object', '성별': 'object', '즐기는 음식': 'object'})
데이터가 잘 불러들여졌는지 확인해봅시다. 데이터가 큰 경우에는 전체 데이터를 출력해서 살펴보기가 어려울 수 있습니다. 이럴 때는 맨 앞의 데이터 일부, 맨 뒤의 데이터 일부를 살펴볼 수 있겠습니다.
df_dtype.head()
| 번호 | 나이 | 성별 | 신장(cm) | 몸무게(kg) | 즐기는 음식 |
|---|---|---|---|---|---|
| 1 | 30 | 1 | 183 | 82 | 1 |
| 2 | 28 | 2 | 160 | 62 | 3 |
| 3 | 27 | 1 | 178 | 77 | 2 |
| 4 | 23 | 1 | 172 | 70 | 2 |
| 5 | 25 | 1 | 168 | 72 | 3 |
df_dtype.tail()
| 번호 | 나이 | 성별 | 신장(cm) | 몸무게(kg) | 즐기는 음식 |
|---|---|---|---|---|---|
| 16 | 33 | 1 | 177 | 72 | 2 |
| 17 | 38 | 2 | 159 | 55 | 1 |
| 18 | 26 | 1 | 166 | 69 | 3 |
| 19 | 26 | 1 | 169 | 66 | 2 |
| 20 | 28 | 2 | 159 | 60 | 2 |
무작위로 데이터의 일부 row만을 샘플링해서 추출하려면 아래와 같이 할 수 있습니다.
df_dtype.sample(n=5)
| 번호 | 나이 | 성별 | 신장(cm) | 몸무게(kg) | 즐기는 음식 |
|---|---|---|---|---|---|
| 1 | 30 | 1 | 183 | 82 | 1 |
| 4 | 23 | 1 | 172 | 70 | 2 |
| 20 | 28 | 2 | 159 | 60 | 2 |
| 3 | 27 | 1 | 178 | 77 | 2 |
| 12 | 26 | 1 | 173 | 70 | 2 |
샘플의 크기를 비율로 정할 수도 있습니다.
df_dtype.sample(frac=0.2)
| 번호 | 나이 | 성별 | 신장(cm) | 몸무게(kg) | 즐기는 음식 |
|---|---|---|---|---|---|
| 10 | 31 | 1 | 183 | 77 | 3 |
| 19 | 26 | 1 | 169 | 66 | 2 |
| 18 | 26 | 1 | 166 | 69 | 3 |
| 17 | 38 | 2 | 159 | 55 | 1 |
행이나 열을 잘라내는 방법을 살펴봅시다.
아래와 같이 특정 구간의 행을 잘라낼 수 있습니다.
df_dtype[2:5]
| 번호 | 나이 | 성별 | 신장(cm) | 몸무게(kg) | 즐기는 음식 |
|---|---|---|---|---|---|
| 3 | 27 | 1 | 178 | 77 | 2 |
| 4 | 23 | 1 | 172 | 70 | 2 |
| 5 | 25 | 1 | 168 | 72 | 3 |
특정 컬럼을 선택하는 것은 아래와 같이 할 수 있습니다.
df_dtype[['즐기는 음식','성별']]
| 번호 | 즐기는 음식 | 성별 |
|---|---|---|
| 1 | 1 | 1 |
| 2 | 3 | 2 |
| 3 | 2 | 1 |
| 4 | 2 | 1 |
| 5 | 3 | 1 |
| 6 | 1 | 1 |
| 7 | 1 | 1 |
| 8 | 3 | 1 |
| 9 | 2 | 2 |
| 10 | 3 | 1 |
| 11 | 1 | 2 |
| 12 | 2 | 1 |
| 13 | 3 | 1 |
| 14 | 3 | 1 |
| 15 | 2 | 2 |
| 16 | 2 | 1 |
| 17 | 1 | 2 |
| 18 | 3 | 1 |
| 19 | 2 | 1 |
| 20 | 2 | 2 |
DataFrame 에 대해서는 조건문을 적용할 수 있습니다.
df_dtype > 2
| 번호 | 나이 | 성별 | 신장(cm) | 몸무게(kg) | 즐기는 음식 |
|---|---|---|---|---|---|
| 1 | 1 | 0 | 1 | 1 | 0 |
| 2 | 1 | 0 | 1 | 1 | 1 |
| 3 | 1 | 0 | 1 | 1 | 0 |
| 4 | 1 | 0 | 1 | 1 | 0 |
| 5 | 1 | 0 | 1 | 1 | 1 |
| 6 | 1 | 0 | 1 | 1 | 0 |
| 7 | 1 | 0 | 1 | 1 | 0 |
| 8 | 1 | 0 | 1 | 1 | 1 |
| 9 | 1 | 0 | 1 | 1 | 0 |
| 10 | 1 | 0 | 1 | 1 | 1 |
| 11 | 1 | 0 | 1 | 1 | 0 |
| 12 | 1 | 0 | 1 | 1 | 0 |
| 13 | 1 | 0 | 1 | 1 | 1 |
| 14 | 1 | 0 | 1 | 1 | 1 |
| 15 | 1 | 0 | 1 | 1 | 0 |
| 16 | 1 | 0 | 1 | 1 | 0 |
| 17 | 1 | 0 | 1 | 1 | 0 |
| 18 | 1 | 0 | 1 | 1 | 1 |
| 19 | 1 | 0 | 1 | 1 | 0 |
| 20 | 1 | 0 | 1 | 1 | 0 |
성별 값이 1인 경우를 선택해봅시다. 우선 성별이 1인지 여부를 나타내는 벡터를 생성합니다.
df_dtype['성별'] == 1
번호 1 True 2 False 3 True 4 True 5 True 6 True 7 True 8 True 9 False 10 True 11 False 12 True 13 True 14 True 15 False 16 True 17 False 18 True 19 True 20 False Name: 성별, dtype: bool
그리고 그 벡터를 DataFrame 에 다시 넣어줍니다.
df_dtype[df_dtype['성별'] == 1]
| 번호 | 나이 | 성별 | 신장(cm) | 몸무게(kg) | 즐기는 음식 |
|---|---|---|---|---|---|
| 1 | 30 | 1 | 183 | 82 | 1 |
| 3 | 27 | 1 | 178 | 77 | 2 |
| 4 | 23 | 1 | 172 | 70 | 2 |
| 5 | 25 | 1 | 168 | 72 | 3 |
| 6 | 27 | 1 | 179 | 77 | 1 |
| 7 | 26 | 1 | 169 | 71 | 1 |
| 8 | 29 | 1 | 171 | 75 | 3 |
| 10 | 31 | 1 | 183 | 77 | 3 |
| 12 | 26 | 1 | 173 | 70 | 2 |
| 13 | 35 | 1 | 173 | 68 | 3 |
| 14 | 24 | 1 | 176 | 66 | 3 |
| 16 | 33 | 1 | 177 | 72 | 2 |
| 18 | 26 | 1 | 166 | 69 | 3 |
| 19 | 26 | 1 | 169 | 66 | 2 |
특정한 조건의 셀에서 값을 없애고 싶은 경우가 있습니다. 그런 경우는 아래와 같이 where() 구문을 사용합니다.
df_dtype.where(df_dtype['성별'] > 1)
| 번호 | 나이 | 성별 | 신장(cm) | 몸무게(kg) | 즐기는 음식 |
|---|---|---|---|---|---|
| 1 | nan | nan | nan | nan | nan |
| 2 | 28 | 2 | 160 | 62 | 3 |
| 3 | nan | nan | nan | nan | nan |
| 4 | nan | nan | nan | nan | nan |
| 5 | nan | nan | nan | nan | nan |
| 6 | nan | nan | nan | nan | nan |
| 7 | nan | nan | nan | nan | nan |
| 8 | nan | nan | nan | nan | nan |
| 9 | 34 | 2 | 158 | 60 | 2 |
| 10 | nan | nan | nan | nan | nan |
| 11 | 26 | 2 | 162 | 59 | 1 |
| 12 | nan | nan | nan | nan | nan |
| 13 | nan | nan | nan | nan | nan |
| 14 | nan | nan | nan | nan | nan |
| 15 | 29 | 2 | 170 | 70 | 2 |
| 16 | nan | nan | nan | nan | nan |
| 17 | 38 | 2 | 159 | 55 | 1 |
| 18 | nan | nan | nan | nan | nan |
| 19 | nan | nan | nan | nan | nan |
| 20 | 28 | 2 | 159 | 60 | 2 |
where 구문에는 DataFrame 도 인자로 줄 수 있습니다.
df_dtype.where(df_dtype > 1)
| 번호 | 나이 | 성별 | 신장(cm) | 몸무게(kg) | 즐기는 음식 |
|---|---|---|---|---|---|
| 1 | 30 | nan | 183 | 82 | nan |
| 2 | 28 | 2 | 160 | 62 | 3 |
| 3 | 27 | nan | 178 | 77 | 2 |
| 4 | 23 | nan | 172 | 70 | 2 |
| 5 | 25 | nan | 168 | 72 | 3 |
| 6 | 27 | nan | 179 | 77 | nan |
| 7 | 26 | nan | 169 | 71 | nan |
| 8 | 29 | nan | 171 | 75 | 3 |
| 9 | 34 | 2 | 158 | 60 | 2 |
| 10 | 31 | nan | 183 | 77 | 3 |
| 11 | 26 | 2 | 162 | 59 | nan |
| 12 | 26 | nan | 173 | 70 | 2 |
| 13 | 35 | nan | 173 | 68 | 3 |
| 14 | 24 | nan | 176 | 66 | 3 |
| 15 | 29 | 2 | 170 | 70 | 2 |
| 16 | 33 | nan | 177 | 72 | 2 |
| 17 | 38 | 2 | 159 | 55 | nan |
| 18 | 26 | nan | 166 | 69 | 3 |
| 19 | 26 | nan | 169 | 66 | 2 |
| 20 | 28 | 2 | 159 | 60 | 2 |
데이터에 변경을 가하는 방법을 알아봅시다.
- Series.map()
- Series.apply()
- DataFrame.apply()
- DataFrame.applymap()
Series에서는 map 과 apply 를 사용할 수 있습니다.
map 은 주어진 원소의 값을 상응하는 다른 값으로 변환합니다. map은 하나의 기본인자를 받는데, dict 혹은 함수를 넣을 수 있습니다.
import pandas as pd
s = pd.Series([200, 300, 400, 500])
d = {200: 'OK', 300: 'Redirect', 400: 'Client error', 500: 'Server error'}
s.map(d)
0 OK 1 Redirect 2 Client error 3 Server error dtype: object
또는 모든 값이 포함된 dict 대신, 값을 계산하는 규칙이 담긴 함수를 넣을 수도 있습니다.
import pandas as pd
s = pd.Series([13, 22, 31, 44, 55])
s.map(lambda x: round(x, -1))
0 10 1 20 2 30 3 40 4 60 dtype: int64
이와 비슷하게, apply 함수도 인자로 함수를 주어 비슷한 결과를 줄 수 있습니다.
import pandas as pd
s = pd.Series([13, 22, 31, 44, 55])
s.apply(lambda x: round(x, -1))
0 10 1 20 2 30 3 40 4 60 dtype: int64
두 함수의 주요한 차이점은, apply 는 args 인자 등을 사용해 함수에 전달될 인자를 지정할 수 있다는 점에 있습니다.
DataFrame 에 대해서 값을 변경할 때도, 특정한 한 개의 컬럼만을 변화시킬 때는 그 컬럼에 대한 Series 를 다루는 셈이기 때문에, 위의 상황과 동일하다고 볼 수 있습니다.
(다시 한번 주의할 사항은, 여기서 apply, map 등 변환 함수는, 본 데이터를 변화시키지는 않고, 변화된 복사본을 반환합니다.)
DataFrame에 대해서는 apply, applymap 을 사용할 수 있습니다.
apply 와 applymap 이 비슷한 기능을 하는 것 같습니다. 하지만 apply 는 아래와 같이 각 열 전체, 각 행 전체에 대해 사용할 수 있습니다.
이것은 apply 에게 전달된 함수가 차원을 줄이는 함수인 경우에만 해당됩니다. 예를 들어, np.sum 이나 np.mean 함수는 배열을 주면 하나의 값을 반환합니다. apply 는 이런 함수도 처리할 수 있습니다. 하지만 applymap 은 하나하나의 원소를 함수에 전달하기 때문에 하나의 값을 받아 하나의 값을 반환하는, Numpy식 표현에 따르면 ufunc(universal function)만을 사용해야 합니다.
- merge()
- pivot_table()
- melt()
마지막으로, 데이터의 모양을 변경하는 기능을 살펴보겠습니다.
우선, merge 에 대해 살펴보겠습니다. merge 는 앞서 Excel 파일 다루기 1에서 설명했듯이, Excel의 vlookup 기능과 같다고 할 수 있습니다.
df_food = pd.DataFrame([('한식', 31), ('일식', 4), ('중식', 54)], index=[1, 2, 3], columns=['음식이름', '식당 수'])
df_food
pd.merge(df_dtype, df_food, left_on='즐기는 음식', right_index=True)
| 번호 | 나이 | 성별 | 신장(cm) | 몸무게(kg) | 즐기는 음식 | 음식이름 | 식당 수 |
|---|---|---|---|---|---|---|---|
| 1 | 30 | 1 | 183 | 82 | 1 | 한식 | 31 |
| 2 | 28 | 2 | 160 | 62 | 3 | 중식 | 54 |
| 3 | 27 | 1 | 178 | 77 | 2 | 일식 | 4 |
| 4 | 23 | 1 | 172 | 70 | 2 | 일식 | 4 |
| 5 | 25 | 1 | 168 | 72 | 3 | 중식 | 54 |
| 6 | 27 | 1 | 179 | 77 | 1 | 한식 | 31 |
| 7 | 26 | 1 | 169 | 71 | 1 | 한식 | 31 |
| 8 | 29 | 1 | 171 | 75 | 3 | 중식 | 54 |
| 9 | 34 | 2 | 158 | 60 | 2 | 일식 | 4 |
| 10 | 31 | 1 | 183 | 77 | 3 | 중식 | 54 |
| 11 | 26 | 2 | 162 | 59 | 1 | 한식 | 31 |
| 12 | 26 | 1 | 173 | 70 | 2 | 일식 | 4 |
| 13 | 35 | 1 | 173 | 68 | 3 | 중식 | 54 |
| 14 | 24 | 1 | 176 | 66 | 3 | 중식 | 54 |
| 15 | 29 | 2 | 170 | 70 | 2 | 일식 | 4 |
| 16 | 33 | 1 | 177 | 72 | 2 | 일식 | 4 |
| 17 | 38 | 2 | 159 | 55 | 1 | 한식 | 31 |
| 18 | 26 | 1 | 166 | 69 | 3 | 중식 | 54 |
| 19 | 26 | 1 | 169 | 66 | 2 | 일식 | 4 |
| 20 | 28 | 2 | 159 | 60 | 2 | 일식 | 4 |
merge 는 왼쪽, 오른쪽 두 개의 DataFrame 을 병합합니다. 병합할 때 기준이 되는 컬럼을 지정해야 하는데, 왼쪽 DataFrame 에서 한 개의 컬럼, 오른쪽 DataFrame 에서 한 개의 컬럼을 지정합니다. 양쪽 각 기준 컬럼의 값이 서로 일치할 때 해당 행을 병합합니다.
left_on='컬럼이름'left_index=Trueright_on='컬럼이름'right_index=Trueon='컬럼이름'
양쪽 DataFrame 에서 각각 기준이 되는 컬럼의 이름이 서로 같을 때는 on='컬럼이름' 인자를 사용합니다. 양쪽 DataFrame 의 기준 컬럼의 이름이 서로 다를 때는 left_on='컬럼이름', right_on='컬럼이름' 을 사용해서 각각 컬럼의 이름을 지정합니다. 만약 기준이 되는 컬럼이 인덱스 컬럼이라면 left_index=True, right_index=True 를 사용합니다.
df_food = pd.DataFrame([(1, '한식', 31), (2, '일식', 4), (3, '중식', 54), (4, '태국음식', 2)], columns=['즐기는 음식', '음식이름', '식당 수'])
df_food
pd.merge(df_dtype, df_food, on='즐기는 음식')
| 나이 | 성별 | 신장(cm) | 몸무게(kg) | 즐기는 음식 | 음식이름 | 식당 수 | |
|---|---|---|---|---|---|---|---|
| 0 | 30 | 1 | 183 | 82 | 1 | 한식 | 31 |
| 1 | 27 | 1 | 179 | 77 | 1 | 한식 | 31 |
| 2 | 26 | 1 | 169 | 71 | 1 | 한식 | 31 |
| 3 | 26 | 2 | 162 | 59 | 1 | 한식 | 31 |
| 4 | 38 | 2 | 159 | 55 | 1 | 한식 | 31 |
| 5 | 28 | 2 | 160 | 62 | 3 | 중식 | 54 |
| 6 | 25 | 1 | 168 | 72 | 3 | 중식 | 54 |
| 7 | 29 | 1 | 171 | 75 | 3 | 중식 | 54 |
| 8 | 31 | 1 | 183 | 77 | 3 | 중식 | 54 |
| 9 | 35 | 1 | 173 | 68 | 3 | 중식 | 54 |
| 10 | 24 | 1 | 176 | 66 | 3 | 중식 | 54 |
| 11 | 26 | 1 | 166 | 69 | 3 | 중식 | 54 |
| 12 | 27 | 1 | 178 | 77 | 2 | 일식 | 4 |
| 13 | 23 | 1 | 172 | 70 | 2 | 일식 | 4 |
| 14 | 34 | 2 | 158 | 60 | 2 | 일식 | 4 |
| 15 | 26 | 1 | 173 | 70 | 2 | 일식 | 4 |
| 16 | 29 | 2 | 170 | 70 | 2 | 일식 | 4 |
| 17 | 33 | 1 | 177 | 72 | 2 | 일식 | 4 |
| 18 | 26 | 1 | 169 | 66 | 2 | 일식 | 4 |
| 19 | 28 | 2 | 159 | 60 | 2 | 일식 | 4 |
merge 의 중요한 인자 중 하나인 how 에 대해서 살펴봅시다.
how 인자에 들어갈 수 있는 값으로는 아래와 같은 것들이 있습니다:
innerleftrightouter
how 인자를 지정하지 않으면 기본적으로는 inner 가 사용됩니다.
하나씩 간단히 살펴보겠습니다. 우선 예제로 사용할 데이터를 보겠습니다.
df_left = pd.DataFrame([(1, 'One'), (2, 'Two'), (3, 'Three'), (5, 'Five'), (7, 'Seven'), (9, 'Nine')], columns=['Key', 'EnName'])
df_left
| Key | EnName | |
|---|---|---|
| 0 | 1 | One |
| 1 | 2 | Two |
| 2 | 3 | Three |
| 3 | 5 | Five |
| 4 | 7 | Seven |
| 5 | 9 | Nine |
df_right = pd.DataFrame([(2, '이'), (4, '사'), (6, '육'), (8, '팔'), (9, '구'), (10, '십')], columns=['Key', 'KrName'])
df_right
| Key | KrName | |
|---|---|---|
| 0 | 2 | 이 |
| 1 | 4 | 사 |
| 2 | 6 | 육 |
| 3 | 8 | 팔 |
| 4 | 9 | 구 |
| 5 | 10 | 십 |
왼쪽 DataFrame 은 숫자와 영어 이름, 오른쪽 DataFrame 은 숫자와 한글 이름이 들어있습니다.
두 DataFrame 은 짝수 홀수로 이루어져 있는데, 2와 9만 양쪽에 모두 들어있습니다.
pd.merge(df_left, df_right, on='Key', how='inner')
| Key | EnName | KrName | |
|---|---|---|---|
| 0 | 2 | Two | 이 |
| 1 | 9 | Nine | 구 |
inner 방식은, 양쪽 모두에 존재하는 key만을 결과로 내놓습니다.
pd.merge(df_left, df_right, on='Key', how='left')
| Key | EnName | KrName | |
|---|---|---|---|
| 0 | 1 | One | nan |
| 1 | 2 | Two | 이 |
| 2 | 3 | Three | nan |
| 3 | 5 | Five | nan |
| 4 | 7 | Seven | nan |
| 5 | 9 | Nine | 구 |
left 방식을 사용하니, key로 사용된 값은 홀수값들, 즉 df_left 에 존재하는 key들만 남아있습니다. df_right 에도 등장하는 key들은 값이 병합되어 들어갔지만, df_right 에만 등장하고 df_left 에는 없는 key들은 아예 보이지 않습니다. 즉, ‘기준’이 left DataFrame 이 되어 결과가 나왔습니다.
그러면 이번엔 right 방식을 살펴볼까요?
pd.merge(df_left, df_right, on='Key', how='right')
| Key | EnName | KrName | |
|---|---|---|---|
| 0 | 2 | Two | 이 |
| 1 | 9 | Nine | 구 |
| 2 | 4 | nan | 사 |
| 3 | 6 | nan | 육 |
| 4 | 8 | nan | 팔 |
| 5 | 10 | nan | 십 |
이번에는 오른쪽 DataFrame 이 기준이 되었습니다.
그럼 outer 방식은 어떨까요?
pd.merge(df_left, df_right, on='Key', how='outer')
| Key | EnName | KrName | |
|---|---|---|---|
| 0 | 1 | One | nan |
| 1 | 2 | Two | 이 |
| 2 | 3 | Three | nan |
| 3 | 5 | Five | nan |
| 4 | 7 | Seven | nan |
| 5 | 9 | Nine | 구 |
| 6 | 4 | nan | 사 |
| 7 | 6 | nan | 육 |
| 8 | 8 | nan | 팔 |
| 9 | 10 | nan | 십 |
양쪽 한군데서라도 등장한 key는 모두 나왔습니다.
이렇듯, inner, outer~는 각각 두 ~DataFrame 의 교집합, 합집합인 결과를, left, right 는 각각 왼쪽 키 집합, 오른쪽 키 집합을 기준으로 한 결과를 돌려줍니다.
이외에도 merge는 한 컬럼이 아니라 여러 컬럼을 기준으로 병합할 수 있는 등, 다양한 조건에서 다양한 인자를 사용해서 수행될 수 있습니다. 여기서 그 모두를 설명하기에는 너무 복잡해질 것 같고, pandas 문서를 참고하세요.
다음으로는 pivot_table 에 대해서 살펴보겠습니다. 여러분도 Excel에서 종종 사용해보았을테지요.
pivot_table에서는 아래의 세 종류의 축을 지정해야 합니다.
- rows (index)
- columns
- values
row나 column 중 하나의 축만 지정해서 볼 수 있습니다. values를 지정하지 않으면 가능한 모든 values의 조합을 보여줍니다.
pd.pivot_table(df_dtype, index=['성별'])
| 성별 | 나이 | 몸무게(kg) | 신장(cm) |
|---|---|---|---|
| 1 | 27.7143 | 72.2857 | 174.071 |
| 2 | 30.5 | 61 | 161.333 |
pd.pivot_table(df_dtype, columns=['성별'])
| 1 | 2 | |
|---|---|---|
| 나이 | 27.7143 | 30.5 |
| 몸무게(kg) | 72.2857 | 61 |
| 신장(cm) | 174.071 | 161.333 |
특정 value에 대해서만 살펴보려면 values에 컬럼명을 적어줍니다.
pd.pivot_table(df_dtype, index=['성별'], values='몸무게(kg)')
| 성별 | 몸무게(kg) |
|---|---|
| 1 | 72.2857 |
| 2 | 61 |
pd.pivot_table(df_dtype, columns=['성별'], values='몸무게(kg)')
| 1 | 2 | |
|---|---|---|
| 몸무게(kg) | 72.2857 | 61 |
pd.pivot_table(df_dtype, index=['성별'], columns=['즐기는 음식'], values='몸무게(kg)')
행과 열로 동시에 펼쳐서 표현할 수 있습니다.
| 성별 | 1 | 2 | 3 |
|---|---|---|---|
| 1 | 76.6667 | 71 | 71.1667 |
| 2 | 57 | 63.3333 | 62 |
pivot_table 은 원래의 데이터를 위 세 개의 축에 맞춰서 변환합니다. 변환하는 과정에서 한 셀에 여러 개의 값이 들어가게 될 때는, aggfunc 인자에서 지정하는 축약 함수(aggregation function)를 사용하여 여러 개의 값을 하나의 값으로 축약합니다. aggfunc 인자가 주어지지 않았을 때 사용하는 기본 축약 함수(aggfunc)는 평균함수(np.mean) 입니다.
aggfunc 에는 다른 종류의 축약 함수를 사용할 수 있습니다.
pd.pivot_table(df_dtype, index=['성별'], columns=['즐기는 음식'], values='몸무게(kg)', aggfunc=np.median)
| 성별 | 1 | 2 | 3 |
|---|---|---|---|
| 1 | 77 | 70 | 70.5 |
| 2 | 57 | 60 | 62 |
아래와 같이 총계 행/열을 추가할 수 있습니다.
pd.pivot_table(df_dtype, index=['성별'], columns=['즐기는 음식'], values='몸무게(kg)', aggfunc=np.median, margins=True)
| 성별 | 1 | 2 | 3 | All |
|---|---|---|---|---|
| 1 | 77 | 70 | 70.5 | 71.5 |
| 2 | 57 | 60 | 62 | 60 |
| All | 71 | 70 | 69 | 70 |
values에 지정하는 컬럼은 대개의 경우 숫자형(numeric) 컬럼이어야 합니다. 단, pivot table을 만들어도 한 셀에 여러 값이 겹치지 않는다면, 숫자형 외에 카테고리 값이나 문자열이 올 수 있습니다.
d = pd.DataFrame([('A', '가', 'Y'), ('A', '나', 'Y'), ('B', '가', 'N'), ('B', '나', 'N')], columns=['Alphabet', '한글', 'YN'])
d
| Alphabet | 한글 | YN | |
|---|---|---|---|
| 0 | A | 가 | Y |
| 1 | A | 나 | Y |
| 2 | B | 가 | N |
| 3 | B | 나 | N |
pd.pivot_table(d, index='Alphabet', columns='한글', values='YN', aggfunc=lambda x: x)
| Alphabet | 가 | 나 |
|---|---|---|
| A | Y | Y |
| B | N | N |
pivot_table 의 반대 행동으로 melt 가 있습니다.
아래 데이터를 한번 보시죠.
df_pivot = pd.pivot_table(df_dtype, index=['성별'], columns=['즐기는 음식'], values='몸무게(kg)', aggfunc=np.median)
| 성별 | 1 | 2 | 3 |
|---|---|---|---|
| 1 | 77 | 70 | 70.5 |
| 2 | 57 | 60 | 62 |
df_pivot.columns
Int64Index([1, 2, 3], dtype='int64', name='즐기는 음식')
성별 컬럼이 컬럼 목록에 들어가 있지 않은걸 보니 인덱스 컬럼으로 지정되어 있는 것 같습니다. 일반 컬럼으로 변형해줍시다.
df_pivot = df_pivot.reset_index()
| 성별 | 1 | 2 | 3 | |
|---|---|---|---|---|
| 0 | 1 | 77 | 70 | 70.5 |
| 1 | 2 | 57 | 60 | 62 |
성별 컬럼이 일반 컬럼으로 들어왔습니다.
이 데이터를 보면, 성별은 행에 걸쳐서 기재되어 있고, 즐기는 음식은 컬럼에 걸쳐서 기재되어 있습니다. 이것을 아래와 같이 melt 해서 모두 행에 걸쳐서 기재되도록 변형할 수 있습니다.
pd.melt(df_pivot, id_vars=['성별'])
| 성별 | 즐기는 음식 | value | |
|---|---|---|---|
| 0 | 1 | 1 | 77 |
| 1 | 2 | 1 | 57 |
| 2 | 1 | 2 | 70 |
| 3 | 2 | 2 | 60 |
| 4 | 1 | 3 | 70.5 |
| 5 | 2 | 3 | 62 |
더 자세한 내용을 알고 싶으면, Pandas의 공식 문서를 참고하세요. 공식문서가 꽤 잘 만들어져 있습니다.
문서는 전반적으로 이렇게 구성되어 있습니다:
- 앞부분에는 간단한 사용법을 소개하는 튜토리얼이 있고요
- 중반에는 각 주제별(인덱스 다루기, 데이터 선택하기, merge, pivot table, 시계열 데이터 등)로 조금 더 자세한 설명과 사용법이 있고요
- 후반에는 각 함수의 구체적인 정의를 기재한 레퍼런스 문서가 있습니다