데이터 변형(Data Wrangling)¶
In [1]:
import pandas as pd
import numpy as np
rng = np.random.RandomState(1234)
계층적 인덱싱¶
판다스는 계층적 인덱싱을 통하여 한 축에 여러 개의 인덱스 수준을 갖도록한다. 약간 추상적으로 말하면 고차원 자료를 저차원 자료로 변형해서 작업할 수 있게 한다. 다음은 인덱스를 리스트의 리스트로 갖는 시리즈이다.
In [2]:
ser = pd.Series(rng.randn(9), index=[['a', 'a', 'a', 'b', 'b', 'c', 'c', 'd', 'd'], [1, 2, 3, 1, 3, 1, 2, 2, 3]])
ser
Out[2]:
a 1 0.471435
2 -1.190976
3 1.432707
b 1 -0.312652
3 -0.720589
c 1 0.887163
2 0.859588
d 2 -0.636524
3 0.015696
dtype: float64
인덱스는 상위 인덱스 'a', 'b', 'c', 'd'
와 하위 인덱스
1, 2, 3
으로 구성된 다중 인덱스이다.
In [3]:
ser.index
Out[3]:
MultiIndex(levels=[['a', 'b', 'c', 'd'], [1, 2, 3]],
labels=[[0, 0, 0, 1, 1, 2, 2, 3, 3], [0, 1, 2, 0, 2, 0, 1, 1, 2]])
계층적 인덱스 객체는 부분 인덱싱을 통해서 자료를 선택할 수 있다. 다음은
인덱스 'b'
에 속해있는 자료를 골라낸 것이다.
In [4]:
ser['b']
Out[4]:
1 -0.312652
3 -0.720589
dtype: float64
슬라이싱을 이용해서 골라낼 수 있다.
In [5]:
ser['b':'c']
Out[5]:
b 1 -0.312652
3 -0.720589
c 1 0.887163
2 0.859588
dtype: float64
loc
속성을 이용해도 된다.
In [6]:
ser.loc[['a', 'd']]
Out[6]:
a 1 0.471435
2 -1.190976
3 1.432707
d 2 -0.636524
3 0.015696
dtype: float64
하위 수준의 인덱스를 이용해서 골라낼 수도 있다. loc[up, lo]
에서
up
은 상위 수준 인덱스이고 lo
는 하위 수준 인덱스를 의미한다.
In [7]:
ser.loc[:, 2]
Out[7]:
a -1.190976
c 0.859588
d -0.636524
dtype: float64
계층적 인덱싱은 기준축 표(pivot table)를 만드는 것과 같은 그룹형식의
연산이나 자료를 변형하는 작업과 같은 중요한 역할을 한다. 예를 들어
unstack
메소드를 이용해 시리즈를 데이터프레임으로 변형할 수 있다.
unstack
은 행 인덱스의 지정된 수준을 열 인덱스의 가장 안쪽 수준으로
변경하는 메소드이다. unstack
은 level=
인자를 이용해 원하는
수준의 인덱스를 열 인덱스로 보낼 수 있다.
In [8]:
ser.unstack()
Out[8]:
1 | 2 | 3 | |
---|---|---|---|
a | 0.471435 | -1.190976 | 1.432707 |
b | -0.312652 | NaN | -0.720589 |
c | 0.887163 | 0.859588 | NaN |
d | NaN | -0.636524 | 0.015696 |
역으로 unstack
자료를 원래 자료로 stack
을 이용해 만들 수 있다.
stack
은 역으로 열 인덱스중에서 지정된 수준을 행 인덱스의 가장 안쪽
수준으로 변경한다.
In [9]:
ser.unstack().stack()
Out[9]:
a 1 0.471435
2 -1.190976
3 1.432707
b 1 -0.312652
3 -0.720589
c 1 0.887163
2 0.859588
d 2 -0.636524
3 0.015696
dtype: float64
데이터프레임은 열 또는 행 인덱스를 다중 인덱스로 사용할 수 있다.
In [10]:
frame = pd.DataFrame(np.arange(12).reshape((4, 3)),
index=[['a', 'a', 'b', 'b'], [1, 2, 1, 2]],
columns=[['Ohio', 'Ohio', 'Colorado'], ['Green', 'Red', 'Green']])
In [11]:
frame
Out[11]:
Ohio | Colorado | |||
---|---|---|---|---|
Green | Red | Green | ||
a | 1 | 0 | 1 | 2 |
2 | 3 | 4 | 5 | |
b | 1 | 6 | 7 | 8 |
2 | 9 | 10 | 11 |
문자열 또는 어떠한 파이썬 객체도 계층적 인덱스 수준의 이름이 될 수 있다.
In [12]:
frame.index.names = ['key1', 'key2']
In [13]:
frame.columns.names = ['state', 'color']
In [14]:
frame
Out[14]:
state | Ohio | Colorado | ||
---|---|---|---|---|
color | Green | Red | Green | |
key1 | key2 | |||
a | 1 | 0 | 1 | 2 |
2 | 3 | 4 | 5 | |
b | 1 | 6 | 7 | 8 |
2 | 9 | 10 | 11 |
부분적 열 인덱싱을 통해 열들의 그룹을 선택할 수 있다.
In [15]:
frame['Ohio']
Out[15]:
color | Green | Red | |
---|---|---|---|
key1 | key2 | ||
a | 1 | 0 | 1 |
2 | 3 | 4 | |
b | 1 | 6 | 7 |
2 | 9 | 10 |
다중인덱스 객체를 만들어서 필요할 때 재사용할 수 있다. 열 인덱스는
다음과 같이 다중 인덱스 객체의 from_arrays
를 이용해서 만들수도
있다.
In [16]:
col_index = pd.MultiIndex.from_arrays([['Ohio', 'Ohio', 'Colorado'], ['Green', 'Red', 'Green']], names=['state', 'color'])
In [17]:
col_index
Out[17]:
MultiIndex(levels=[['Colorado', 'Ohio'], ['Green', 'Red']],
labels=[[1, 1, 0], [0, 1, 0]],
names=['state', 'color'])
In [18]:
pd.DataFrame(np.arange(4 * 3).reshape(4, 3), columns=col_index)
Out[18]:
state | Ohio | Colorado | |
---|---|---|---|
color | Green | Red | Green |
0 | 0 | 1 | 2 |
1 | 3 | 4 | 5 |
2 | 6 | 7 | 8 |
3 | 9 | 10 | 11 |
다음과 같이 행 인덱스를 만들어 사용할 수도 있다.
In [19]:
row_index = pd.MultiIndex.from_product([['a', 'b'], [1, 2]], names=['key1', 'key2'])
row_index
Out[19]:
MultiIndex(levels=[['a', 'b'], [1, 2]],
labels=[[0, 0, 1, 1], [0, 1, 0, 1]],
names=['key1', 'key2'])
from_product([[상위 수준 인덱스], [하위 수준 인덱스]])
는
상위 수준 인덱스
각각에 하위 수준 인덱스
가 만들어 진다.
여기서는 'a'
상위 수준에 1, 2
하위 수준이 만들어지고 'b'
에
대해서도 마찬가지이다.
In [20]:
pd.DataFrame(np.arange(4 * 3).reshape(4, 3), index=row_index)
Out[20]:
0 | 1 | 2 | ||
---|---|---|---|---|
key1 | key2 | |||
a | 1 | 0 | 1 | 2 |
2 | 3 | 4 | 5 | |
b | 1 | 6 | 7 | 8 |
2 | 9 | 10 | 11 |
다음은 행, 열에 대한 인덱스를 모두 사용한 경우이다.
In [21]:
pd.DataFrame(np.arange(4 * 3).reshape(4, 3), columns=col_index, index=row_index)
Out[21]:
state | Ohio | Colorado | ||
---|---|---|---|---|
color | Green | Red | Green | |
key1 | key2 | |||
a | 1 | 0 | 1 | 2 |
2 | 3 | 4 | 5 | |
b | 1 | 6 | 7 | 8 |
2 | 9 | 10 | 11 |
수준별 정렬¶
가끔씩 특정 축에서 특정 수준에 대해서 정렬을 할 필요가 생기거나 특정
수준에 대한 값으로 정렬을 할 필요가 생긴다. swaplevel
은 두 개의
수준을 인자로 받아 두 수준을 서로 바꾼 형태의 새로운 객체를 반환한다.
In [22]:
frame.swaplevel('key1', 'key2')
Out[22]:
state | Ohio | Colorado | ||
---|---|---|---|---|
color | Green | Red | Green | |
key2 | key1 | |||
1 | a | 0 | 1 | 2 |
2 | a | 3 | 4 | 5 |
1 | b | 6 | 7 | 8 |
2 | b | 9 | 10 | 11 |
다중인덱스에서 sort_index
를 사용하여 특정 수준에 대한 정렬을
시행할 수 있다.
In [23]:
frame.sort_index(level=1)
Out[23]:
state | Ohio | Colorado | ||
---|---|---|---|---|
color | Green | Red | Green | |
key1 | key2 | |||
a | 1 | 0 | 1 | 2 |
b | 1 | 6 | 7 | 8 |
a | 2 | 3 | 4 | 5 |
b | 2 | 9 | 10 | 11 |
axis=
선택인자를 이용해 열에 대해서도 정렬이 가능하다.
In [24]:
frame.sort_index(axis='columns', level=1)
Out[24]:
state | Colorado | Ohio | ||
---|---|---|---|---|
color | Green | Green | Red | |
key1 | key2 | |||
a | 1 | 2 | 0 | 1 |
2 | 5 | 3 | 4 | |
b | 1 | 8 | 6 | 7 |
2 | 11 | 9 | 10 |
Summary Statistics by Level¶
데이터프레임과 시리즈는 특정 축과 지정된 수준에 대해서 집계할 수 있는
level=
인자를 제공한다. 행 이름 수준이 key2
인 것에 대해서 합을
구하면 다음과 같다.
In [25]:
frame.sum(level='key2')
Out[25]:
state | Ohio | Colorado | |
---|---|---|---|
color | Green | Red | Green |
key2 | |||
1 | 6 | 8 | 10 |
2 | 12 | 14 | 16 |
다음은 열이름 수준이 color
인 것에 대해서 합을 구한 것이다.
In [26]:
frame.sum(level='color', axis='columns')
Out[26]:
color | Green | Red | |
---|---|---|---|
key1 | key2 | ||
a | 1 | 2 | 1 |
2 | 8 | 4 | |
b | 1 | 14 | 7 |
2 | 20 | 10 |
판다스 groupby
연산 부분에서 더 자세히 다룬다.
Indexing with a DataFrame’s columns¶
자료의 열 인덱스를 행 인덱스로 설정하고 싶을 때가 있다. 또는 반대로 행 인덱스를 열 인덱스로 나타내고 싶을 때가 있을 것이다.
In [27]:
frame = pd.DataFrame({'a': range(7),
'b': range(7, 0, -1),
'c': ['one', 'one', 'one', 'two', 'two', 'two', 'two'],
'd': [0, 1, 2, 0, 1, 2, 3]})
frame
Out[27]:
a | b | c | d | |
---|---|---|---|---|
0 | 0 | 7 | one | 0 |
1 | 1 | 6 | one | 1 |
2 | 2 | 5 | one | 2 |
3 | 3 | 4 | two | 0 |
4 | 4 | 3 | two | 1 |
5 | 5 | 2 | two | 2 |
6 | 6 | 1 | two | 3 |
데이터프레임의 set_index
메소드는 데이터프레임 열들을 행 인덱스로
설정할 수 있게 한다.
In [28]:
frame2 = frame.set_index(['c', 'd'])
frame2
Out[28]:
a | b | ||
---|---|---|---|
c | d | ||
one | 0 | 0 | 7 |
1 | 1 | 6 | |
2 | 2 | 5 | |
two | 0 | 3 | 4 |
1 | 4 | 3 | |
2 | 5 | 2 | |
3 | 6 | 1 |
기본적으로 열이름을 제거하면서 행 인덱스를 설정하지만 drop=False
옵션을 이용해서 제거하지 않을 수도 있다.
In [29]:
frame.set_index(['c', 'd'], drop=False)
Out[29]:
a | b | c | d | ||
---|---|---|---|---|---|
c | d | ||||
one | 0 | 0 | 7 | one | 0 |
1 | 1 | 6 | one | 1 | |
2 | 2 | 5 | one | 2 | |
two | 0 | 3 | 4 | two | 0 |
1 | 4 | 3 | two | 1 | |
2 | 5 | 2 | two | 2 | |
3 | 6 | 1 | two | 3 |
reset_index
메소드를 이용해서 원래 상태로 되돌릴 수 있다.
reset_index
는 행 인덱스들을 열 이름으로 변경한다. level=
인자를 이용해 지정된 수준에 대해서만 열로 만들 수 있다.
In [30]:
frame2.reset_index()
Out[30]:
c | d | a | b | |
---|---|---|---|---|
0 | one | 0 | 0 | 7 |
1 | one | 1 | 1 | 6 |
2 | one | 2 | 2 | 5 |
3 | two | 0 | 3 | 4 |
4 | two | 1 | 4 | 3 |
5 | two | 2 | 5 | 2 |
6 | two | 3 | 6 | 1 |
Combining and Merging Datasets¶
데이터프레임들을 병합하기 위해서 판다스는 두가지 함수
pandas.merge
와 pandas.concat
를 제공한다. merge
는 두
데이터프레임들을 데이터베이스 형식의 병합 방법을 사용하고 concat
는
여러 개의 데이터프레임을 이어 붙일 때 사용한다.
Database-Style DataFrame Joins¶
두 데이터프레임을 merge
메소드를 이용해서 병합하기 위해서는 두
데이터프레임이 적어도 한 개 이상의 같은 열이름을 갖고 있어야 한다.
병합할 때 병합하는 축들(열이름)과 병합하는 방법을 지정할 수 있다. 병합
축은 on=
인자를 이용하고 병합 방법은 how=
인자를 사용한다.
how
인자에 지정될 수 있는 방법으로는 inner
, outer
, left
,
right
등이 있다.
In [31]:
df1 = pd.DataFrame({'A': ['A'+str(i) for i in range(4)]})
df1
Out[31]:
A | |
---|---|
0 | A0 |
1 | A1 |
2 | A2 |
3 | A3 |
In [32]:
df2 = pd.DataFrame({'B': ['B'+str(i) for i in range(4)]})
df2
Out[32]:
B | |
---|---|
0 | B0 |
1 | B1 |
2 | B2 |
3 | B3 |
두 데이터프레임을 병합하면 일치하는 열이름이 없기 때문에 에러가 발생한다.
In [33]:
pd.merge(df1, df2)
---------------------------------------------------------------------------
MergeError Traceback (most recent call last)
<ipython-input-33-bf676d91be05> in <module>()
----> 1 pd.merge(df1, df2)
~\Anaconda3\lib\site-packages\pandas\core\reshape\merge.py in merge(left, right, how, on, left_on, right_on, left_index, right_index, sort, suffixes, copy, indicator, validate)
58 right_index=right_index, sort=sort, suffixes=suffixes,
59 copy=copy, indicator=indicator,
---> 60 validate=validate)
61 return op.get_result()
62
~\Anaconda3\lib\site-packages\pandas\core\reshape\merge.py in __init__(self, left, right, how, on, left_on, right_on, axis, left_index, right_index, sort, suffixes, copy, indicator, validate)
543 warnings.warn(msg, UserWarning)
544
--> 545 self._validate_specification()
546
547 # note this function has side effects
~\Anaconda3\lib\site-packages\pandas\core\reshape\merge.py in _validate_specification(self)
1027 'left_index={lidx}, right_index={ridx}'
1028 .format(lon=self.left_on, ron=self.right_on,
-> 1029 lidx=self.left_index, ridx=self.right_index))
1030 if not common_cols.is_unique:
1031 raise MergeError("Data columns not unique: {common!r}"
MergeError: No common columns to perform merge on. Merge options: left_on=None, right_on=None, left_index=False, right_index=False
다음과 같이 데이터프레임 df3
을 만들자.
In [34]:
df3 = pd.DataFrame({'B': ['B'+str(i) for i in range(4)],
'C': ['C'+str(i) for i in range(4)]})
df3.loc[4] = ['BX', 'C4']
df3
Out[34]:
B | C | |
---|---|---|
0 | B0 | C0 |
1 | B1 | C1 |
2 | B2 | C2 |
3 | B3 | C3 |
4 | BX | C4 |
df2, df3는 열이름 B
가 공통이기 때문에 판다스는 공통인 이름에
대해서 병합을 한다.
In [35]:
pd.merge(df2, df3)
Out[35]:
B | C | |
---|---|---|
0 | B0 | C0 |
1 | B1 | C1 |
2 | B2 | C2 |
3 | B3 | C3 |
df2
와 df3
의 병합 축 B
에서 교집합에 해당하는 행들만
골라낸다. how
기본 설정은 'inner'
이다. 만일 공통인 부분이
여러개 있으면 공통인 것에 대한 모든 경우에 해당되는 행들을 골라낸다.
In [36]:
df4 = df2.copy()
df4.loc[3] = 'B0'
df4
Out[36]:
B | |
---|---|
0 | B0 |
1 | B1 |
2 | B2 |
3 | B0 |
In [37]:
df5 = df3.copy()
df5.loc[4, 'B'] = 'B0'
df5
Out[37]:
B | C | |
---|---|---|
0 | B0 | C0 |
1 | B1 | C1 |
2 | B2 | C2 |
3 | B3 | C3 |
4 | B0 | C4 |
df4
, df5
는 B
열에 각각 2개씩 B0
를 가지고 있는 것을 알
수 있다. 따라서 2 x 2 = 4
개의 B0
행이 만들어진다. 즉,
df4
의 0번째 행 B0
에 대해서 df5
의 0, 4번째 행의
B0
에 해당하는 경우를 만들고 df4
의 3번째 행의 B0
에 대해
마찬가지로 df5
의 0
, 4
번째 행의 B0
경우에 대해서
만든다.
In [38]:
pd.merge(df4, df5)
Out[38]:
B | C | |
---|---|---|
0 | B0 | C0 |
1 | B0 | C4 |
2 | B0 | C0 |
3 | B0 | C4 |
4 | B1 | C1 |
5 | B2 | C2 |
merge
의 대상은 병합축에 대한 조건(how=
)을 만족하는 행들이다.
how='outer'
는 병합축에 해당하는 열들의 합집합을 만들어 생각한다.
'inner'
와 마찬가지로 같은 이름에 대해서는 곱하기 갯수만큼을
만든다.
In [39]:
pd.merge(df4, df5, how='outer')
Out[39]:
B | C | |
---|---|---|
0 | B0 | C0 |
1 | B0 | C4 |
2 | B0 | C0 |
3 | B0 | C4 |
4 | B1 | C1 |
5 | B2 | C2 |
6 | B3 | C3 |
합집합이므로 'inner'
에서 없었던 B3
행이 추가되었다.
how='left'
는 왼쪽 데이터프레임의 병합축만 사용한다.
In [40]:
pd.merge(df4, df5, how='left')
Out[40]:
B | C | |
---|---|---|
0 | B0 | C0 |
1 | B0 | C4 |
2 | B1 | C1 |
3 | B2 | C2 |
4 | B0 | C0 |
5 | B0 | C4 |
여기서도 공통되는 항목에 대해서는 모든 경우의 수가 포함되는 것을 알 수
있다. how='right'
은 오른쪽 데이터프레임의 병합축만 사용한다.
In [41]:
pd.merge(df4, df5, how='right')
Out[41]:
B | C | |
---|---|---|
0 | B0 | C0 |
1 | B0 | C0 |
2 | B0 | C4 |
3 | B0 | C4 |
4 | B1 | C1 |
5 | B2 | C2 |
6 | B3 | C3 |
선택인자 | SQL JOIN 이름 | 설명 |
---|---|---|
'inner' |
INNER JOIN |
두 데이터프레임의 키들의 교집합을 사용한다. |
'outer' |
FULL OUTER JOIN |
두 데이터프레임의 키들의 합집합을 사용한다. |
'left' |
LEFT OUTER JOIN |
왼쪽 데이터프레임의 키만 사용한다. |
'right' |
RIGHT OUTER JOIN |
오른쪽 데이터프레임의 키만 사용한다. |
여러 개의 키들을 이용해서 병합할 수 있다.
In [42]:
left = pd.DataFrame({'key1': ['foo', 'foo', 'bar'],
'key2': ['one', 'two', 'one'],
'lval': [1, 2, 3]})
In [43]:
right = pd.DataFrame({'key1': ['foo', 'foo', 'bar', 'bar'],
'key2': ['one', 'one', 'one', 'two'],
'rval': [4, 5, 6, 7]})
In [44]:
left
Out[44]:
key1 | key2 | lval | |
---|---|---|---|
0 | foo | one | 1 |
1 | foo | two | 2 |
2 | bar | one | 3 |
In [45]:
right
Out[45]:
key1 | key2 | rval | |
---|---|---|---|
0 | foo | one | 4 |
1 | foo | one | 5 |
2 | bar | one | 6 |
3 | bar | two | 7 |
병합키를 지정하려면 on=
인자를 사용하면 된다.
In [46]:
pd.merge(left, right, on=['key1', 'key2'], how='outer')
Out[46]:
key1 | key2 | lval | rval | |
---|---|---|---|---|
0 | foo | one | 1.0 | 4.0 |
1 | foo | one | 1.0 | 5.0 |
2 | foo | two | 2.0 | NaN |
3 | bar | one | 3.0 | 6.0 |
4 | bar | two | NaN | 7.0 |
'outer'
이므로 두 개의 키 조합의 합집합을 찾는다.
Caution
`pd.merge`를 이용해서 나온 결과 데이터프레임은 인덱스는 이전 데이터프레임의 인덱스와 상관없다.
중복되는 열이름이 있을 경우 suffixes=
선택인자를 이용해서 열이름
첨자를 지정할 수 있다. suffixes
를 설정하지 않으면 왼쪽은 _x
,
오른쪽은 _y
를 붙인다.
In [47]:
pd.merge(left, right, on='key1')
Out[47]:
key1 | key2_x | lval | key2_y | rval | |
---|---|---|---|---|---|
0 | foo | one | 1 | one | 4 |
1 | foo | one | 1 | one | 5 |
2 | foo | two | 2 | one | 4 |
3 | foo | two | 2 | one | 5 |
4 | bar | one | 3 | one | 6 |
5 | bar | one | 3 | two | 7 |
In [48]:
pd.merge(left, right, on='key1', suffixes=('_left', '_right'))
Out[48]:
key1 | key2_left | lval | key2_right | rval | |
---|---|---|---|---|---|
0 | foo | one | 1 | one | 4 |
1 | foo | one | 1 | one | 5 |
2 | foo | two | 2 | one | 4 |
3 | foo | two | 2 | one | 5 |
4 | bar | one | 3 | one | 6 |
5 | bar | one | 3 | two | 7 |
다음은 merge 함수의 인자들이다.
Argument | Description |
---|---|
left | DataFrame to be merged on the left side. |
right | DataFrame to be merged on the right side. |
how | One of ‘inner’, ‘outer’, ‘left’, or ‘right’; defaults to ‘inner’. |
on | Column names to join on. Must be found in both DataFrame objects. If not specified and no other join keys given, will use the intersection of the column names in left and right as the join keys. |
left_on | Columns in left DataFrame to use as join keys. |
right_on | Analogous to left_on for left DataFrame. |
left_index | Use row index in left as its join key (or keys, if a MultiIndex). |
right_index | Analogous to left_index. |
sort | Sort merged data lexicographically by join keys; True by default (disable to get better performance in some cases on large datasets). |
suffixes | Tuple of string values to append to column names in case of overlap; defaults to (‘_x’, ‘_y’) (e.g., if ‘data’ in both DataFrame objects, would appear as ‘data_x’ and ‘data_y’ in result). |
copy | If False, avoid copying data into resulting data structure in some exceptional cases; by default always copies. |
indicator | Adds a special column _merge that indicates the source of each row; values will be ‘left_only’, ‘right_only’, or ‘both’ based on the origin of the joined data in each row. |
Merging on Index¶
데이터프레임의 인덱스를 병합키로 사용하려면 left_index=True
,
right_index=True
를 사용한다.
In [49]:
left1 = pd.DataFrame({'key': ['a', 'b', 'a', 'a', 'b', 'c'],
'value': range(6)})
left1
Out[49]:
key | value | |
---|---|---|
0 | a | 0 |
1 | b | 1 |
2 | a | 2 |
3 | a | 3 |
4 | b | 4 |
5 | c | 5 |
In [50]:
right1 = pd.DataFrame({'group_val': [3.5, 7]}, index=['a', 'b'])
right1
Out[50]:
group_val | |
---|---|
a | 3.5 |
b | 7.0 |
right_index=True
는 오른쪽 데이터프레임의 병합키는 인덱스를
사용하고 left_on='key'
는 왼쪽 병합키는 'key'
를 사용한다는
뜻이다.
In [51]:
pd.merge(left1, right1, left_on='key', right_index=True)
Out[51]:
key | value | group_val | |
---|---|---|---|
0 | a | 0 | 3.5 |
2 | a | 2 | 3.5 |
3 | a | 3 | 3.5 |
1 | b | 1 | 7.0 |
4 | b | 4 | 7.0 |
다중 인덱스를 갖는 데이터프레임도 인덱스를 키로 사용해서 병합할 수 있다.
In [52]:
lefth = pd.DataFrame({'key1': ['Ohio', 'Ohio', 'Ohio',
'Nevada', 'Nevada'],
'key2': [2000, 2001, 2002, 2001, 2002],
'data': np.arange(5.)})
righth = pd.DataFrame(np.arange(12).reshape((6, 2)),
index=[['Nevada', 'Nevada', 'Ohio', 'Ohio', 'Ohio', 'Ohio'],
[2001, 2000, 2000, 2000, 2001, 2002]],
columns=['event1', 'event2'])
In [53]:
lefth
Out[53]:
key1 | key2 | data | |
---|---|---|---|
0 | Ohio | 2000 | 0.0 |
1 | Ohio | 2001 | 1.0 |
2 | Ohio | 2002 | 2.0 |
3 | Nevada | 2001 | 3.0 |
4 | Nevada | 2002 | 4.0 |
In [54]:
righth
Out[54]:
event1 | event2 | ||
---|---|---|---|
Nevada | 2001 | 0 | 1 |
2000 | 2 | 3 | |
Ohio | 2000 | 4 | 5 |
2000 | 6 | 7 | |
2001 | 8 | 9 | |
2002 | 10 | 11 |
다중 인덱스를 키로 사용하려면 다음과 같이 병합키를 리스트로 지정해야 한다.
In [55]:
pd.merge(lefth, righth, left_on=['key1', 'key2'], right_index=True)
Out[55]:
key1 | key2 | data | event1 | event2 | |
---|---|---|---|---|---|
0 | Ohio | 2000 | 0.0 | 4 | 5 |
0 | Ohio | 2000 | 0.0 | 6 | 7 |
1 | Ohio | 2001 | 1.0 | 8 | 9 |
2 | Ohio | 2002 | 2.0 | 10 | 11 |
3 | Nevada | 2001 | 3.0 | 0 | 1 |
두 데이터프레임의 인덱스 모두를 사용해도 된다.
In [56]:
left2 = pd.DataFrame([[1., 2.], [3., 4.], [5., 6.]],
index=['a', 'c', 'e'],
columns=['Ohio', 'Nevada'])
right2 = pd.DataFrame([[7., 8.], [9., 10.], [11., 12.], [13, 14]],
index=['b', 'c', 'd', 'e'],
columns=['Missouri', 'Alabama'])
In [57]:
left2
Out[57]:
Ohio | Nevada | |
---|---|---|
a | 1.0 | 2.0 |
c | 3.0 | 4.0 |
e | 5.0 | 6.0 |
In [58]:
right2
Out[58]:
Missouri | Alabama | |
---|---|---|
b | 7.0 | 8.0 |
c | 9.0 | 10.0 |
d | 11.0 | 12.0 |
e | 13.0 | 14.0 |
In [59]:
pd.merge(left2, right2, left_index=True, right_index=True, how='outer')
Out[59]:
Ohio | Nevada | Missouri | Alabama | |
---|---|---|---|---|
a | 1.0 | 2.0 | NaN | NaN |
b | NaN | NaN | 7.0 | 8.0 |
c | 3.0 | 4.0 | 9.0 | 10.0 |
d | NaN | NaN | 11.0 | 12.0 |
e | 5.0 | 6.0 | 13.0 | 14.0 |
데이터프레임 자체도 join
메소드를 제공한다. 데이터프레임
join
은 병합키로 자신은 지정된 열을 사용할 수 있는 반면에 다른
데이터프레임의 키는 반드시 인덱스를 사용해야한다.
In [60]:
left2.join(right2)
Out[60]:
Ohio | Nevada | Missouri | Alabama | |
---|---|---|---|---|
a | 1.0 | 2.0 | NaN | NaN |
c | 3.0 | 4.0 | 9.0 | 10.0 |
e | 5.0 | 6.0 | 13.0 | 14.0 |
join
메소드의 how
기본값은 left
이고 병합키는 기본적으로 둘
다 인덱스를 사용한다. 호출 데이터프레임(join
메소드를 호출하는
데이터프레임)은 다른 병합키를 사용할 수 있다.
다음은 left1
의 키는 key
열을 사용하고 right1
의 키는
인덱스를 사용한 예이다.
In [61]:
left1.join(right1, on='key')
Out[61]:
key | value | group_val | |
---|---|---|---|
0 | a | 0 | 3.5 |
1 | b | 1 | 7.0 |
2 | a | 2 | 3.5 |
3 | a | 3 | 3.5 |
4 | b | 4 | 7.0 |
5 | c | 5 | NaN |
데이터프레임들의 리스트를 이용해서 여러 개의 데이터프레임들을 결합할 수 있다.
In [62]:
another = pd.DataFrame([[7., 8.], [9., 10.], [11., 12.], [16., 17.]],
index=['a', 'c', 'e', 'f'],
columns=['New York', 'Oregon'])
In [63]:
left2.join([right2, another])
Out[63]:
Ohio | Nevada | Missouri | Alabama | New York | Oregon | |
---|---|---|---|---|---|---|
a | 1.0 | 2.0 | NaN | NaN | 7.0 | 8.0 |
c | 3.0 | 4.0 | 9.0 | 10.0 | 9.0 | 10.0 |
e | 5.0 | 6.0 | 13.0 | 14.0 | 11.0 | 12.0 |
In [64]:
left2.join([right2, another], how='outer')
C:\Users\dyoon\Anaconda3\lib\site-packages\pandas\core\frame.py:6359: FutureWarning: Sorting because non-concatenation axis is not aligned. A future version
of pandas will change to not sort by default.
To accept the future behavior, pass 'sort=True'.
To retain the current behavior and silence the warning, pass sort=False
verify_integrity=True)
Out[64]:
Ohio | Nevada | Missouri | Alabama | New York | Oregon | |
---|---|---|---|---|---|---|
a | 1.0 | 2.0 | NaN | NaN | 7.0 | 8.0 |
b | NaN | NaN | 7.0 | 8.0 | NaN | NaN |
c | 3.0 | 4.0 | 9.0 | 10.0 | 9.0 | 10.0 |
d | NaN | NaN | 11.0 | 12.0 | NaN | NaN |
e | 5.0 | 6.0 | 13.0 | 14.0 | 11.0 | 12.0 |
f | NaN | NaN | NaN | NaN | 16.0 | 17.0 |
Concatenating Along an Axis¶
자료 합치는 또 다른 연산으로 붙이기(concatenation)가 있다.
pandas.concat
함수를 사용해서 주어진 축 방향으로 자료들을 합칠 수
있다.
In [65]:
s1 = pd.Series([0, 1], index=['a', 'b'])
s2 = pd.Series([2, 3, 4], index=['c', 'd', 'e'])
s3 = pd.Series([5, 6], index=['f', 'g'])
concat
함수의 인자로 시리즈 리스트를 대입하면 행 방향으로 차례로
시리즈들을 합친다.
In [66]:
pd.concat([s1, s2, s3])
Out[66]:
a 0
b 1
c 2
d 3
e 4
f 5
g 6
dtype: int64
기본적으로 axis=0
값으로 지정되어 있다. 열방향으로 자료들을 붙이기
위해서는 axis=1
을 지정하면 된다.
In [67]:
pd.concat([s1, s2, s3], axis=1)
C:\Users\dyoon\Anaconda3\lib\site-packages\ipykernel_launcher.py:1: FutureWarning: Sorting because non-concatenation axis is not aligned. A future version
of pandas will change to not sort by default.
To accept the future behavior, pass 'sort=True'.
To retain the current behavior and silence the warning, pass sort=False
"""Entry point for launching an IPython kernel.
Out[67]:
0 | 1 | 2 | |
---|---|---|---|
a | 0.0 | NaN | NaN |
b | 1.0 | NaN | NaN |
c | NaN | 2.0 | NaN |
d | NaN | 3.0 | NaN |
e | NaN | 4.0 | NaN |
f | NaN | NaN | 5.0 |
g | NaN | NaN | 6.0 |
다른 축(행방향)방향으로는 공통되는 항목이 없기 때문에 NaN
으로
채우는 것을 알 수 있다. 이것은 다른 축방향으로 outer
병합하는 것과
같다. 여기서 inner
병합을 하고 싶으면 join='inner'
인자를 건네
주면 된다.
In [68]:
s4 = pd.concat([s1, s3])
s4
Out[68]:
a 0
b 1
f 5
g 6
dtype: int64
In [69]:
pd.concat([s1, s4], axis=1, join='inner')
Out[69]:
0 | 1 | |
---|---|---|
a | 0 | 0 |
b | 1 | 1 |
여기서 주의해야 할 점은 join
방법은 붙이기 하는 축(axis=1
)인
열방향이 아닌 다른 축(행방향)으로 적용이 된다는 것이다. 따라서 f
,
g
가 사라지고 공통인 항목 a
, b
만 나오는 것을 볼 수 있다.
join_axes=
선택인자를 사용하면 다른 축방향의 인덱스들을 선택적으로
사용할 수 있다.
In [70]:
pd.concat([s1, s4], axis=1, join_axes=[['a', 'c', 'b', 'd']])
Out[70]:
0 | 1 | |
---|---|---|
a | 0.0 | 0.0 |
c | NaN | NaN |
b | 1.0 | 1.0 |
d | NaN | NaN |
붙이기를 하면 자료들의 정체성을 잃어버리게 된다. 이러한 것을 위해서
keys=
인자를 지정하면 원래 자료에 인덱스를 부여할 수 있다.
In [71]:
res = pd.concat([s1, s1, s3], keys=['one', 'two', 'three'])
res
Out[71]:
one a 0
b 1
two a 0
b 1
three f 5
g 6
dtype: int64
unstack
메소드를 이용해서 데이터프레임으로 변경할 수 있다.
unstack
메소드는 인덱스(행) 레벨 중 하나를 선택해서(기본값은
level=-1
, 즉 가장 안쪽 레벨) 행 수준의 가장 안쪽 수준으로 설정한다.
In [72]:
res.unstack()
Out[72]:
a | b | f | g | |
---|---|---|---|---|
one | 0.0 | 1.0 | NaN | NaN |
two | 0.0 | 1.0 | NaN | NaN |
three | NaN | NaN | 5.0 | 6.0 |
시리즈를 합치면서 axis=1
과 keys=
인자를 사용하면 열이름으로
설정할 수 있다.
In [73]:
pd.concat([s1, s2, s3], axis=1, keys=['one', 'two', 'three'])
C:\Users\dyoon\Anaconda3\lib\site-packages\ipykernel_launcher.py:1: FutureWarning: Sorting because non-concatenation axis is not aligned. A future version
of pandas will change to not sort by default.
To accept the future behavior, pass 'sort=True'.
To retain the current behavior and silence the warning, pass sort=False
"""Entry point for launching an IPython kernel.
Out[73]:
one | two | three | |
---|---|---|---|
a | 0.0 | NaN | NaN |
b | 1.0 | NaN | NaN |
c | NaN | 2.0 | NaN |
d | NaN | 3.0 | NaN |
e | NaN | 4.0 | NaN |
f | NaN | NaN | 5.0 |
g | NaN | NaN | 6.0 |
데이터프레임에 대해서도 마찬가지로 적용할 수 있다.
In [74]:
df1 = pd.DataFrame(np.arange(6).reshape(3, 2), index=['a', 'b', 'c'],
columns=['one', 'two'])
df1
Out[74]:
one | two | |
---|---|---|
a | 0 | 1 |
b | 2 | 3 |
c | 4 | 5 |
In [75]:
df2 = pd.DataFrame(5 + np.arange(4).reshape(2, 2), index=['a', 'c'],
columns=['three', 'four'])
df2
Out[75]:
three | four | |
---|---|---|
a | 5 | 6 |
c | 7 | 8 |
In [76]:
pd.concat([df1, df2], axis=1, keys=['key1', 'key2'])
C:\Users\dyoon\Anaconda3\lib\site-packages\ipykernel_launcher.py:1: FutureWarning: Sorting because non-concatenation axis is not aligned. A future version
of pandas will change to not sort by default.
To accept the future behavior, pass 'sort=True'.
To retain the current behavior and silence the warning, pass sort=False
"""Entry point for launching an IPython kernel.
Out[76]:
key1 | key2 | |||
---|---|---|---|---|
one | two | three | four | |
a | 0 | 1 | 5.0 | 6.0 |
b | 2 | 3 | NaN | NaN |
c | 4 | 5 | 7.0 | 8.0 |
리스트가 아닌 사전 객체를 입력하면 열쇠가 keys=
인자에 대응된다.
In [77]:
pd.concat({'key1': df1, 'key2': df2}, axis=1)
C:\Users\dyoon\Anaconda3\lib\site-packages\ipykernel_launcher.py:1: FutureWarning: Sorting because non-concatenation axis is not aligned. A future version
of pandas will change to not sort by default.
To accept the future behavior, pass 'sort=True'.
To retain the current behavior and silence the warning, pass sort=False
"""Entry point for launching an IPython kernel.
Out[77]:
key1 | key2 | |||
---|---|---|---|---|
one | two | three | four | |
a | 0 | 1 | 5.0 | 6.0 |
b | 2 | 3 | NaN | NaN |
c | 4 | 5 | 7.0 | 8.0 |
names=
인자를 이용해서 수준의 이름을 설정할 수 있다.
In [78]:
pd.concat([df1, df2], axis=1, keys=['key1', 'key2'], names=['level0', 'level1'])
C:\Users\dyoon\Anaconda3\lib\site-packages\ipykernel_launcher.py:1: FutureWarning: Sorting because non-concatenation axis is not aligned. A future version
of pandas will change to not sort by default.
To accept the future behavior, pass 'sort=True'.
To retain the current behavior and silence the warning, pass sort=False
"""Entry point for launching an IPython kernel.
Out[78]:
level0 | key1 | key2 | ||
---|---|---|---|---|
level1 | one | two | three | four |
a | 0 | 1 | 5.0 | 6.0 |
b | 2 | 3 | NaN | NaN |
c | 4 | 5 | 7.0 | 8.0 |
데이터프레임의 행이름들이 의미가 없을 때는 ignore_index=True
를
이용해서 무시할 수 있다.
In [79]:
df1 = pd.DataFrame(np.random.randn(3, 4), columns=['a', 'b', 'c', 'd'])
df1
Out[79]:
a | b | c | d | |
---|---|---|---|---|
0 | 0.897854 | -0.121771 | 1.337716 | -1.263141 |
1 | 0.812211 | -1.965854 | 0.232949 | 1.840888 |
2 | 0.281830 | 1.444992 | 0.525127 | -0.564983 |
In [80]:
df2 = pd.DataFrame(np.random.randn(2, 3), columns=['b', 'd', 'a'])
df2
Out[80]:
b | d | a | |
---|---|---|---|
0 | -0.456583 | 0.598502 | -0.669677 |
1 | -0.558903 | 0.293322 | -1.450108 |
In [81]:
pd.concat([df1, df2], ignore_index=True)
C:\Users\dyoon\Anaconda3\lib\site-packages\ipykernel_launcher.py:1: FutureWarning: Sorting because non-concatenation axis is not aligned. A future version
of pandas will change to not sort by default.
To accept the future behavior, pass 'sort=True'.
To retain the current behavior and silence the warning, pass sort=False
"""Entry point for launching an IPython kernel.
Out[81]:
a | b | c | d | |
---|---|---|---|---|
0 | 0.897854 | -0.121771 | 1.337716 | -1.263141 |
1 | 0.812211 | -1.965854 | 0.232949 | 1.840888 |
2 | 0.281830 | 1.444992 | 0.525127 | -0.564983 |
3 | -0.669677 | -0.456583 | NaN | 0.598502 |
4 | -1.450108 | -0.558903 | NaN | 0.293322 |
Argument | Description |
---|---|
objs | List or dict of pandas objects to be concatenated; this is the only required argument |
axis | Axis to concatenate along; defaults to 0 (along rows) |
join | Either ‘inner’ or ‘outer’ (‘outer’ by default); whether to intersection (inner) or union (outer) together indexes along the other axes |
join_axes | Specific indexes to use for the other n–1 axes instead of performing union/intersection logic |
keys | Values to associate with objects being concatenated, forming a hierarchical index along the concatenation axis; can either be a list or array of arbitrary values, an array of tuples, or a list of arrays (if multiple-level arrays passed in levels) |
levels | Specific indexes to use as hierarchical index level or levels if keys passed |
names | Names for created hierarchical levels if keys and/or levels passed |
verify_integrity | Check new axis in concatenated object for duplicates and raise exception if so; by default (False) allows duplicates |
ignore_index | Do not preserve indexes along concatenation axis, instead producing a new range(total_length) index |
Combining Data with Overlap¶
행 또는 열이름들이 중복되는 두 자료들의 값들을 합치기를 원할 경우가
있다. 넘파이 배열의 where
함수가 비슷한 작업을 할 수있다.
In [82]:
a = pd.Series([np.nan, 2.5, 0.0, 3.5, 4.5, np.nan],
index=['f', 'e', 'd', 'c', 'b', 'a'])
a
Out[82]:
f NaN
e 2.5
d 0.0
c 3.5
b 4.5
a NaN
dtype: float64
In [83]:
b = pd.Series([0., np.nan, 2., np.nan, np.nan, 5.],
index=['a', 'b', 'c', 'd', 'e', 'f'])
b
Out[83]:
a 0.0
b NaN
c 2.0
d NaN
e NaN
f 5.0
dtype: float64
In [84]:
np.where(pd.isnull(a), b, a)
Out[84]:
array([0. , 2.5, 0. , 3.5, 4.5, 5. ])
a
성분 중 NaN
이 있으면 대응되는 위치의 b
의 값으로
설정하는 것이다. 하지만 이것은 인덱스와 상관없이 배열의 위치로만 값을
대입하는 것을 알 수 있다.
시리즈의 인덱스에 대응해서 값을 지정하려면 combine_first
메소드를
사용하면 된다.
In [85]:
b.combine_first(a)
Out[85]:
a 0.0
b 4.5
c 2.0
d 0.0
e 2.5
f 5.0
dtype: float64
데이터프레임에서는 열별로 연산을 한다.
In [86]:
df1 = pd.DataFrame({'a': [1., np.nan, 5., np.nan],
'b': [np.nan, 2., np.nan, 6.],
'c': range(2, 18, 4)})
df1
Out[86]:
a | b | c | |
---|---|---|---|
0 | 1.0 | NaN | 2 |
1 | NaN | 2.0 | 6 |
2 | 5.0 | NaN | 10 |
3 | NaN | 6.0 | 14 |
In [87]:
df2 = pd.DataFrame({'a': [5., 4., np.nan, 3., 7.],
'b': [np.nan, 3., 4., 6., 8.]})
df2
Out[87]:
a | b | |
---|---|---|
0 | 5.0 | NaN |
1 | 4.0 | 3.0 |
2 | NaN | 4.0 |
3 | 3.0 | 6.0 |
4 | 7.0 | 8.0 |
새로 만들어진 데이터프레임의 행, 열 인덱스는 두 데이터프레임의 합집합이다.
In [88]:
df1.combine_first(df2)
Out[88]:
a | b | c | |
---|---|---|---|
0 | 1.0 | NaN | 2.0 |
1 | 4.0 | 2.0 | 6.0 |
2 | 5.0 | 4.0 | 10.0 |
3 | 3.0 | 6.0 | 14.0 |
4 | 7.0 | 8.0 | NaN |
Reshaping and Pivoting¶
계층적 인덱싱을 이용한 변형(Reshaping with Hierarchical Indexing)¶
다음과 같은 두 개의 메소드를 이용해서 데이터프레임의 형태를 변경할 수 있다.
stack
: 지정된 열 수준을 행의 가장 안쪽 수준으로 변경한다.unstack
: 지정된 행 수준을 열의 가장 안쪽 수준으로 변경한다.
In [89]:
data = pd.DataFrame(np.arange(6).reshape((2, 3)),
index=pd.Index(['Ohio', 'Colorado'], name='state'),
columns=pd.Index(['one', 'two', 'three'], name='number'))
data
Out[89]:
number | one | two | three |
---|---|---|---|
state | |||
Ohio | 0 | 1 | 2 |
Colorado | 3 | 4 | 5 |
stack
은 열축(number
)을 행축의 가장 안쪽으로 위치시킨다.
그러면서 열축이 없어지기 때문에 결과는 시리즈가 된다.
In [90]:
res = data.stack()
res
Out[90]:
state number
Ohio one 0
two 1
three 2
Colorado one 3
two 4
three 5
dtype: int32
unstack
을 이용하면 역으로 행축의 가장 안쪽 수준인 number
를
열축으로 위치시킨다. 따라서 원래 모양으로 돌아온다.
In [91]:
res.unstack()
Out[91]:
number | one | two | three |
---|---|---|---|
state | |||
Ohio | 0 | 1 | 2 |
Colorado | 3 | 4 | 5 |
다음과 같이 다중 인덱스의 경우 stack
의 경우 기본적으로 열축의 가장
안쪽의 수준을 행축의 가장 안쪽 수준으로 변경한다.
In [92]:
df_multi_level = pd.DataFrame([[1.0, 2.0], [3.0, 4.0]],
index=['cat', 'dog'],
columns=pd.MultiIndex([['weight', 'height'], ['kg', 'm']], labels=[[0, 1], [0, 1]], names=['parts', 'units']))
df_multi_level
Out[92]:
parts | weight | height |
---|---|---|
units | kg | m |
cat | 1.0 | 2.0 |
dog | 3.0 | 4.0 |
즉, 가장 안쪽 수준인 kg
, m
을 행축의 가장 안쪽으로 위치시킨다.
In [93]:
df_multi_level.stack()
Out[93]:
parts | weight | height | |
---|---|---|---|
units | |||
cat | kg | 1.0 | NaN |
m | NaN | 2.0 | |
dog | kg | 3.0 | NaN |
m | NaN | 4.0 |
변형을 했을 때 값이 대응되지 않으면 NaN
값이 설정된다.
level=
인자를 이용해서 변경하고자하는 수준을 직접 지정할 수 있다.
수준은 위에서부터 0, 1, … 이다.
In [94]:
stck_df = df_multi_level.stack(level=0)
stck_df
Out[94]:
units | kg | m | |
---|---|---|---|
parts | |||
cat | weight | 1.0 | NaN |
height | NaN | 2.0 | |
dog | weight | 3.0 | NaN |
height | NaN | 4.0 |
unstack
의 경우도 수준을 직접 지정해서 변경할 수 있다. 행 수준은
왼쪽부터 0, 1, …이다.
In [95]:
stck_df.unstack(level=0)
Out[95]:
units | kg | m | ||
---|---|---|---|---|
cat | dog | cat | dog | |
parts | ||||
weight | 1.0 | 3.0 | NaN | NaN |
height | NaN | NaN | 2.0 | 4.0 |
stack
과 unstack
메소드 사용시 level=
인자에 수준 이름을
대입해도 된다.
In [96]:
df_multi_level.stack(level='units')
Out[96]:
parts | weight | height | |
---|---|---|---|
units | |||
cat | kg | 1.0 | NaN |
m | NaN | 2.0 | |
dog | kg | 3.0 | NaN |
m | NaN | 4.0 |
In [97]:
s1 = pd.Series([0, 1, 2, 3], index=['a', 'b', 'c', 'd'])
s1
Out[97]:
a 0
b 1
c 2
d 3
dtype: int64
In [98]:
s2 = pd.Series([4, 5, 6], index=['c', 'd', 'e'])
s2
Out[98]:
c 4
d 5
e 6
dtype: int64
In [99]:
data2 = pd.concat([s1, s2], keys=['one', 'two'])
data2
Out[99]:
one a 0
b 1
c 2
d 3
two c 4
d 5
e 6
dtype: int64
변형했을 때 값이 대응되지 않으면 NaN
이 설정된다.
In [100]:
data2.unstack()
Out[100]:
a | b | c | d | e | |
---|---|---|---|---|---|
one | 0.0 | 1.0 | 2.0 | 3.0 | NaN |
two | NaN | NaN | 4.0 | 5.0 | 6.0 |
unstack
한 것을 다시 stack
하면 NaN
값은 자동으로 제거한다.
In [101]:
data2.unstack().stack()
Out[101]:
one a 0.0
b 1.0
c 2.0
d 3.0
two c 4.0
d 5.0
e 6.0
dtype: float64
하지만 dropna=False
인자를 건네면 NaN
값이 그대로 나오는 것을 알
수 있다.
In [102]:
data2.unstack().stack(dropna=False)
Out[102]:
one a 0.0
b 1.0
c 2.0
d 3.0
e NaN
two a NaN
b NaN
c 4.0
d 5.0
e 6.0
dtype: float64
Pivoting “Long” to “Wide” Format¶
In [103]:
url = 'https://raw.githubusercontent.com/wesm/pydata-book/2nd-edition/examples/'
data = pd.read_csv(url + 'macrodata.csv')
In [104]:
data.head()
Out[104]:
year | quarter | realgdp | realcons | realinv | realgovt | realdpi | cpi | m1 | tbilrate | unemp | pop | infl | realint | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1959.0 | 1.0 | 2710.349 | 1707.4 | 286.898 | 470.045 | 1886.9 | 28.98 | 139.7 | 2.82 | 5.8 | 177.146 | 0.00 | 0.00 |
1 | 1959.0 | 2.0 | 2778.801 | 1733.7 | 310.859 | 481.301 | 1919.7 | 29.15 | 141.7 | 3.08 | 5.1 | 177.830 | 2.34 | 0.74 |
2 | 1959.0 | 3.0 | 2775.488 | 1751.8 | 289.226 | 491.260 | 1916.4 | 29.35 | 140.5 | 3.82 | 5.3 | 178.657 | 2.74 | 1.09 |
3 | 1959.0 | 4.0 | 2785.204 | 1753.7 | 299.356 | 484.052 | 1931.3 | 29.37 | 140.0 | 4.33 | 5.6 | 179.386 | 0.27 | 4.06 |
4 | 1960.0 | 1.0 | 2847.699 | 1770.5 | 331.722 | 462.199 | 1955.5 | 29.54 | 139.6 | 3.50 | 5.2 | 180.007 | 2.31 | 1.19 |
시간 주기 인덱스를 PeriodIndex
를 이용해서 만든다.
In [105]:
periods = pd.PeriodIndex(year=data.year, quarter=data.quarter, name='date')
periods
Out[105]:
PeriodIndex(['1959Q1', '1959Q2', '1959Q3', '1959Q4', '1960Q1', '1960Q2',
'1960Q3', '1960Q4', '1961Q1', '1961Q2',
...
'2007Q2', '2007Q3', '2007Q4', '2008Q1', '2008Q2', '2008Q3',
'2008Q4', '2009Q1', '2009Q2', '2009Q3'],
dtype='period[Q-DEC]', name='date', length=203, freq='Q-DEC')
1959Q1
은 1959년 1사분기라는 뜻이다. freq='Q-DEC'
는 매년
분기의 마지막 달이 12월이란 뜻이다. 자세한 설명은
이곳을
참조한다. 시간 객체에 대해서는 시계열 부분에서 자세히 다룬다.
다음과 같이 열 인덱스를 만든다.
In [106]:
columns = pd.Index(['realgdp', 'infl', 'unemp'], name='item')
columns
Out[106]:
Index(['realgdp', 'infl', 'unemp'], dtype='object', name='item')
위에서 만든 data
중에서 새로 만든 열 인덱스에 맞춰 조정한다.
In [107]:
data = data.reindex(columns=columns)
data.head()
Out[107]:
item | realgdp | infl | unemp |
---|---|---|---|
0 | 2710.349 | 0.00 | 5.8 |
1 | 2778.801 | 2.34 | 5.1 |
2 | 2775.488 | 2.74 | 5.3 |
3 | 2785.204 | 0.27 | 5.6 |
4 | 2847.699 | 2.31 | 5.2 |
data
의 행 인덱스를 위에서 만든 시간 주기 인덱스를 이용하여
시간도장(Timestamp)으로 변경한다. D
는 일자로 end
주기의 끝을
표시하라는 뜻이다. 즉 분기의 끝 날짜로 표시하라는 뜻이다.
In [108]:
data.index = periods.to_timestamp('D', 'end')
data.head()
Out[108]:
item | realgdp | infl | unemp |
---|---|---|---|
date | |||
1959-03-31 | 2710.349 | 0.00 | 5.8 |
1959-06-30 | 2778.801 | 2.34 | 5.1 |
1959-09-30 | 2775.488 | 2.74 | 5.3 |
1959-12-31 | 2785.204 | 0.27 | 5.6 |
1960-03-31 | 2847.699 | 2.31 | 5.2 |
stack
을 이용해 열축을 행축의 가장 안쪽 수준으로 변경하여 시리즈를
만든다. 이 시리즈를 reset_index
를 이용해서 다중 인덱스를
열이름으로 변경해서 데이터프레임을 만든다. rename
은 데이터프레임
열이름 중에서 0
을 value
로 변경한다.
In [109]:
ldata = data.stack().reset_index().rename(columns={0: 'value'})
ldata.head()
Out[109]:
date | item | value | |
---|---|---|---|
0 | 1959-03-31 | realgdp | 2710.349 |
1 | 1959-03-31 | infl | 0.000 |
2 | 1959-03-31 | unemp | 5.800 |
3 | 1959-06-30 | realgdp | 2778.801 |
4 | 1959-06-30 | infl | 2.340 |
만들어지 ldata
는 긴 형식의 자료라고 부른다. 이러한 형식은
데이터베이스 자료 구조에서 흔히 볼 수 있다. 데이터베이스에서
date
와 item
을 주키(primary key)라고 부르며 자료를 빠르고
효율적으로 처리할 수 있도록 한다. 이러한 자료는 같은 날짜(date
)에
대해서 항목(item
)이 반복되는 구조로 분석하기 불편할 수 있다.
item
열을 데이터프레임의 열이름으로 바꾸면 계산하기 편리한 구조로
변경된다. 이러한 용도로 사용될 수 있는 메소드가 pivot
이다.
In [110]:
pivoted = ldata.pivot(index='date', columns='item', values='value')
pivoted.head()
Out[110]:
item | infl | realgdp | unemp |
---|---|---|---|
date | |||
1959-03-31 | 0.00 | 2710.349 | 5.8 |
1959-06-30 | 2.34 | 2778.801 | 5.1 |
1959-09-30 | 2.74 | 2775.488 | 5.3 |
1959-12-31 | 0.27 | 2785.204 | 5.6 |
1960-03-31 | 2.31 | 2847.699 | 5.2 |
pivot
은 첫번째 인자로 인덱스가 될 열을 입력하고 두번째 인자로 열
이름들이 될 열을 입력하고 세번째 인자는 각 열에 대응되는 값의 열을
선택하면 된다.
만일 값이 될 열이 여러 개일 때는 계층적 열 인덱스를 가진 자료를 만들 수 있다.
In [111]:
ldata['value2'] = np.random.randn(len(ldata))
ldata.head()
Out[111]:
date | item | value | value2 | |
---|---|---|---|---|
0 | 1959-03-31 | realgdp | 2710.349 | -0.690426 |
1 | 1959-03-31 | infl | 0.000 | -0.034682 |
2 | 1959-03-31 | unemp | 5.800 | -0.641208 |
3 | 1959-06-30 | realgdp | 2778.801 | 1.658025 |
4 | 1959-06-30 | infl | 2.340 | -0.930612 |
다음과 같이 values=
에 값에 해당하는 열을 리스트로 입력하면 다중
인덱스 열을 갖는 데이터프레임이 된다.
In [112]:
ldata.pivot(index='date', columns='item', values=['value', 'value2']).head()
Out[112]:
value | value2 | |||||
---|---|---|---|---|---|---|
item | infl | realgdp | unemp | infl | realgdp | unemp |
date | ||||||
1959-03-31 | 0.00 | 2710.349 | 5.8 | -0.034682 | -0.690426 | -0.641208 |
1959-06-30 | 2.34 | 2778.801 | 5.1 | -0.930612 | 1.658025 | -0.896335 |
1959-09-30 | 2.74 | 2775.488 | 5.3 | -1.207501 | 0.338003 | -0.258428 |
1959-12-31 | 0.27 | 2785.204 | 5.6 | 0.918907 | 1.102117 | -0.216872 |
1960-03-31 | 2.31 | 2847.699 | 5.2 | 0.625715 | -0.037569 | -0.479985 |
Pivoting “Wide” to “Long” Format¶
melt
는 pivot
의 반대 개념으로 가로 넓은 자료를 세로로 길게
만드는데 사용한다. melt
는 자료를 변형했을 때 키에 해당되는
id_vars
과 살펴보고자 하는 값에 대응되는 value_vars
들로
구분된다. 변형된 자료는 variable
과 value
라는 새로운 열이름이
추가된 데이터프레임을 만든다.
In [114]:
df = pd.DataFrame({'A': {0: 'a', 1: 'b', 2: 'c'},
'B': {0: 1, 1: 3, 2: 5},
'C': {0: 2, 1: 4, 2: 6}})
df
Out[114]:
A | B | C | |
---|---|---|---|
0 | a | 1 | 2 |
1 | b | 3 | 4 |
2 | c | 5 | 6 |
아무것도 설정하지 않으면 모든 열이름들을 value_vars
로 간주하여
변형을 한다.
In [115]:
df.melt()
Out[115]:
variable | value | |
---|---|---|
0 | A | a |
1 | A | b |
2 | A | c |
3 | B | 1 |
4 | B | 3 |
5 | B | 5 |
6 | C | 2 |
7 | C | 4 |
8 | C | 6 |
id_vars=
만 설정하면 나머지 열이름들은 모두 value_vars
로
설정된다.
In [116]:
df.melt(id_vars=['A'])
Out[116]:
A | variable | value | |
---|---|---|---|
0 | a | B | 1 |
1 | b | B | 3 |
2 | c | B | 5 |
3 | a | C | 2 |
4 | b | C | 4 |
5 | c | C | 6 |
value_vars=
만 설정하면 설정된 열이름들에 대해서만 자료를 만든다.
In [118]:
df.melt(value_vars=['A', 'B'])
Out[118]:
variable | value | |
---|---|---|
0 | A | a |
1 | A | b |
2 | A | c |
3 | B | 1 |
4 | B | 3 |
5 | B | 5 |
id_vars=
와 value_vars=
를 동시에 설정할 수 있다.
In [121]:
melted = df.melt(id_vars=['A'], value_vars=['B', 'C'])
melted
Out[121]:
A | variable | value | |
---|---|---|---|
0 | a | B | 1 |
1 | b | B | 3 |
2 | c | B | 5 |
3 | a | C | 2 |
4 | b | C | 4 |
5 | c | C | 6 |
pivot
메소드를 이용해서 원 자료로 되돌릴 수 있다.
In [127]:
restored = melted.pivot(index='A', columns='variable', values='value')
restored
Out[127]:
variable | B | C |
---|---|---|
A | ||
a | 1 | 2 |
b | 3 | 4 |
c | 5 | 6 |
In [128]:
restored.reset_index()
Out[128]:
variable | A | B | C |
---|---|---|---|
0 | a | 1 | 2 |
1 | b | 3 | 4 |
2 | c | 5 | 6 |
첫번째 인자를 데이터프레임으로 건네주어 pd.melt
함수를 이용해서도
똑같은 결과를 낼 수 있다.
In [130]:
pd.melt(df, id_vars='A', value_vars=['B', 'C'])
Out[130]:
A | variable | value | |
---|---|---|---|
0 | a | B | 1 |
1 | b | B | 3 |
2 | c | B | 5 |
3 | a | C | 2 |
4 | b | C | 4 |
5 | c | C | 6 |