데이터 준비: 조인, 병합, 변형
계층적 색인
계층적인 색인(hierarchical indexing)은 축에 대해 둘 이상의 인덱스 단계를 지정할 수 있도록 함
> 고차원 데이터를 낮은 차원의 형식으로 다룰 수 있게 해주는 기능
In [43]: data = pd.Series(np.random.uniform(size=9),
...: index=[['a', 'a', 'a', 'b', 'b', 'c', 'c', 'd', 'd'],
...: [1, 2, 3, 1, 3, 1, 2, 2, 3]])
In [44]: data
Out[44]:
a 1 0.317961
2 0.034492
3 0.213850
b 1 0.604714
3 0.786766
c 1 0.564637
2 0.672718
d 2 0.159659
3 0.301101
dtype: float64
# MultiIndex를 인덱스로 하는 Series이며 인덱스의 계층 확인 가능
# 바로 위 단계 인덱스를 이용해 하위 계층 직접 접근 가능
In [45]: data.index
Out[45]:
MultiIndex([('a', 1),
('a', 2),
('a', 3),
('b', 1),
('b', 3),
('c', 1),
('c', 2),
('d', 2),
('d', 3)],
)
# 계층적으로 인덱싱된 객체는 데이터의 부분집합을 부분적 색인(partical indexing)으로 접근 가능
In [46]: data['b']
Out[46]:
1 0.604714
3 0.786766
dtype: float64
In [47]: data['b':'c']
Out[47]:
b 1 0.604714
3 0.786766
c 1 0.564637
2 0.672718
dtype: float64
In [48]: data[['b','d']]
Out[48]:
b 1 0.604714
3 0.786766
d 2 0.159659
3 0.301101
dtype: float64
# 하위 계층 객체 선택 가능
In [49]: data
Out[49]:
a 1 0.317961
2 0.034492
3 0.213850
b 1 0.604714
3 0.786766
c 1 0.564637
2 0.672718
d 2 0.159659
3 0.301101
dtype: float64
In [50]: data.loc[:, 2]
Out[50]:
a 0.034492
c 0.672718
d 0.159659
dtype: float64
계층적 색인은 데이터를 재구성하고 피벗 테이블 생성 같은 그룹 기반으로 작업할 때 중요하게 사용됨
# unstack 메서드로 새롭게 배열
In [51]: data.unstack()
Out[51]:
1 2 3
a 0.317961 0.034492 0.213850
b 0.604714 NaN 0.786766
c 0.564637 0.672718 NaN
d NaN 0.159659 0.301101
# unstack <-> stack
In [52]: data.unstack().stack()
Out[52]:
a 1 0.317961
2 0.034492
3 0.213850
b 1 0.604714
3 0.786766
c 1 0.564637
2 0.672718
d 2 0.159659
3 0.301101
dtype: float64
DataFrame에서는 두 축 모두 계층적 색인을 가질 수 있음
In [53]: 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 [54]: frame
Out[54]:
Ohio Colorado
Green Red Green
a 1 0 1 2
2 3 4 5
b 1 6 7 8
2 9 10 11
In [55]: frame.index.names = ['key1', 'key2']
In [56]: frame.columns.names = ['state', 'color']
In [57]: frame
Out[57]:
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
# nlevels 속성으로 색인이 몇 개의 계층을 갖는지 확인
In [58]: frame.index.nlevels
Out[58]: 2
In [59]: frame.columns.nlevels
Out[59]: 2
# 열의 부분집합을 부분적 색인으로 접근
In [60]: frame['Ohio']
Out[60]:
color Green Red
key1 key2
a 1 0 1
2 3 4
b 1 6 7
2 9 10
계층의 순서를 바꾸고 정렬
# swaplevel은 넘겨받은 두 개의 계층 번호나 이름이 뒤바뀐 새로운 객체 반환
# (데이터는 변경되지 않음)
In [61]: frame.swaplevel('key1', 'key2')
Out[61]:
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 메서드는 모든 색인 계층을 사용해 사전 순으로 데이터 정렬
# level 인수를 사용해 단일 계층만 사용하거나 일부 계층만 선택해 정렬할 수 있음
In [62]: frame.sort_index(level=1)
Out[62]:
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
In [63]: frame
Out[63]:
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 [64]: frame.swaplevel(0, 1).sort_index(level=0)
Out[64]:
state Ohio Colorado
color Green Red Green
key2 key1
1 a 0 1 2
b 6 7 8
2 a 3 4 5
b 9 10 11
객체가 계층적 색인으로 상위 계층부터 사전 순으로 정렬되어 있다면 (sort_index(level=0)이나 sort_index()의 결과처럼) 데이터를 선택하는 성능이 훨씬 좋아짐
계층별 요약 통계
행이나 열의 합을 계층별로 구할 수 있음
In [65]: frame.groupby(level='key2').sum()
Out[65]:
state Ohio Colorado
color Green Red Green
key2
1 6 8 10
2 12 14 16
In [66]: frame.groupby(level='color', axis='columns').sum()
Out[66]:
color Green Red
key1 key2
a 1 2 1
2 8 4
b 1 14 7
2 20 10
DataFrame의 열 사용
In [67]: 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]})
In [68]: frame
Out[68]:
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
# DataFrame의 set_index 함수는 하나 이상의 열을 인덱스로 하는 새로운 DataFrame 생성
In [69]: frame2 = frame.set_index(['c', 'd'])
In [70]: frame2
Out[70]:
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
# 아래와 같이 set_index 메서드에 drop=False를 명시적으로 지정하지 않으면
# 인덱스로 지정한 열은 DataFrame에서 삭제됨
In [71]: frame.set_index(['c', 'd'], drop=False)
Out[71]:
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 <-> set_index
# 계층적 색인 단계가 열로 이동
In [78]: frame2.reset_index()
Out[78]:
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
데이터 합치기
판다스 객체에 저장된 데이터 합치는 방법
· pandas.merge: 하나 이상의 키를 기준으로 DataFrame의 행을 연결, SQL이나 다른 관계형 데이터베이스의 조인 연산과 유사
· pandas.concat: 하나의 축을 따라 객체를 이어 붙임
· combine_first: 두 객체를 겹쳐서 한 객체에서 누락된 데이터를 다른 객체에 있는 값으로 채움
데이터베이스 스타일로 DataFrame 합치기
병합(머지 merge)나 조인 연산은 하나 이상의 키를 사용해 데이터 집합의 행을 합침
In [79]: df1 = pd.DataFrame({'key': ['b', 'b', 'a', 'c', 'a', 'a', 'b'],
...: 'data1': pd.Series(range(7), dtype='Int64')})
In [80]: df2 = pd.DataFrame({'key': ['a', 'b', 'd'],
...: 'data2': pd.Series(range(3), dtype='Int64')})
In [81]: df1
Out[81]:
key data1
0 b 0
1 b 1
2 a 2
3 c 3
4 a 4
5 a 5
6 b 6
In [82]: df2
Out[82]:
key data2
0 a 0
1 b 1
2 d 2
In [83]: pd.merge(df1, df2)
Out[83]:
key data1 data2
0 b 0 1
1 b 1 1
2 b 6 1
3 a 2 0
4 a 4 0
5 a 5 0
# 어떤 열을 병합할 것인지 명시하지 않았으나 pandas.merge 함수는 겹치는 열의 이름을 키로 사용
# 그러나 명시적으로 지정하는 습관 들일 것
In [84]: pd.merge(df1, df2, on='key')
Out[84]:
key data1 data2
0 b 0 1
1 b 1 1
2 b 6 1
3 a 2 0
4 a 4 0
5 a 5 0
# 두 객체에 공통되는 열 이름이 하나도 없는 경우 별도로 지정
In [85]: df3 = pd.DataFrame({'lkey': ['b', 'b', 'a', 'c', 'a', 'a', 'b'],
...: 'data1': pd.Series(range(7), dtype='Int64')})
In [86]: df4 = pd.DataFrame({'rkey': ['a', 'b', 'd'],
...: 'data2': pd.Series(range(3), dtype='Int64')})
In [87]: pd.merge(df3, df4, left_on='lkey', right_on='rkey')
Out[87]:
lkey data1 rkey data2
0 b 0 b 1
1 b 1 b 1
2 b 6 b 1
3 a 2 a 0
4 a 4 a 0
5 a 5 a 0
결과에서 c와 d에 해당하는 값이 빠진 것 확인 > merge 함수는 기본적으로 내부 조인(inner join)을 수행해 교집합 결과를 반환함
merge 함수의 how에 left, right, outer를 넘기면 조인 방식 설정 가능
In [88]: pd.merge(df1, df2, how='outer')
Out[88]:
key data1 data2
0 b 0 1
1 b 1 1
2 b 6 1
3 a 2 0
4 a 4 0
5 a 5 0
6 c 3 <NA>
7 d <NA> 2
In [89]: pd.merge(df3, df4, left_on='lkey', right_on='rkey', how='outer')
Out[89]:
lkey data1 rkey data2
0 b 0 b 1
1 b 1 b 1
2 b 6 b 1
3 a 2 a 0
4 a 4 a 0
5 a 5 a 0
6 c 3 NaN <NA>
7 NaN <NA> d 2
In [90]: pd.merge(df3, df4, left_on='lkey', right_on='rkey', how='left')
Out[90]:
lkey data1 rkey data2
0 b 0 b 1
1 b 1 b 1
2 a 2 a 0
3 c 3 NaN <NA>
4 a 4 a 0
5 a 5 a 0
6 b 6 b 1
In [91]: pd.merge(df3, df4, left_on='lkey', right_on='rkey', how='right')
Out[91]:
lkey data1 rkey data2
0 a 2 a 0
1 a 4 a 0
2 a 5 a 0
3 b 0 b 1
4 b 1 b 1
5 b 6 b 1
6 NaN <NA> d 2
In [92]: pd.merge(df1, df2, how='left')
Out[92]:
key data1 data2
0 b 0 1
1 b 1 1
2 a 2 0
3 c 3 <NA>
4 a 4 0
5 a 5 0
6 b 6 1
In [93]: pd.merge(df1, df2, on='key', how='left')
Out[93]:
key data1 data2
0 b 0 1
1 b 1 1
2 a 2 0
3 c 3 <NA>
4 a 4 0
5 a 5 0
6 b 6 1
외부 조인에서는 다른 DataFrame의 인덱스와 일치하지 않는 왼쪽 또는 오른쪽 DataFrame 객체의 행을 NA 값으로 표시
옵션 | 동작 |
how='inner' / 아무것도 지정하지 않았을 경우 inner join | 양쪽 테이블 모두에 존재하는 키 조합 사용(교집합) |
how='left' | 왼쪽 테이블에 존재하는 모든 키 조합 사용 |
how='right' | 오른쪽 테이블에 존재하는 모든 키 조합 사용 |
how='outer' | 양쪽 테이블에 존재하는 모든 키 조합 사용(합집합) |
다대다 병합은 일치하는 키에 대한 데카르트 곱(곱집합) Cartesian product을 생성
In [94]: df1 = pd.DataFrame({'key': ['b', 'b', 'a', 'c', 'a', 'b'],
...: 'data1': pd.Series(range(6), dtype='Int64')})
In [95]: df2 = pd.DataFrame({'key': ['a', 'b', 'a', 'b', 'd'],
...: 'data2': pd.Series(range(5), dtype='Int64')})
In [96]: pd.merge(df1, df2, how='left')
Out[96]:
key data1 data2
0 b 0 1
1 b 0 3
2 b 1 1
3 b 1 3
4 a 2 0
5 a 2 2
6 c 3 <NA>
7 a 4 0
8 a 4 2
9 b 5 1
10 b 5 3
In [97]: pd.merge(df1, df2, on='key', how='left')
Out[97]:
key data1 data2
0 b 0 1
1 b 0 3
2 b 1 1
3 b 1 3
4 a 2 0
5 a 2 2
6 c 3 <NA>
7 a 4 0
8 a 4 2
9 b 5 1
10 b 5 3
여러 개의 키를 병합하려면 열 이름이 담긴 리스트를 넘김
In [98]: left = pd.DataFrame({'key1': ['foo', 'foo', 'bar'],
...: 'key2': ['one', 'two', 'one'],
...: 'lval': pd.Series([1, 2, 3], dtype='Int64')})
In [99]: right = pd.DataFrame({'key1': ['foo', 'foo', 'bar', 'bar'],
...: 'key2': ['one', 'one', 'one', 'two'],
...: 'rval': pd.Series([4, 5, 6, 7], dtype='Int64')})
In [100]: pd.merge(left, right, on=['key1', 'key2'], how='outer')
Out[100]:
key1 key2 lval rval
0 foo one 1 4
1 foo one 1 5
2 foo two 2 <NA>
3 bar one 3 6
4 bar two <NA> 7
병합 연산에서 고려해야 할 마지막 사항은 겹치는 열 이름 처리하는 방식
In [101]: pd.merge(left, right, on='key1')
Out[101]:
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
# suffixes 옵션으로 두 DataFrame 객체에서 겹치는 열 이름 뒤에 붙일 문자열 지정 가능
In [102]: pd.merge(left, right, on='key1', suffixes=('_left', '_right'))
Out[102]:
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
인덱스 병합
병합하려는 키가 DataFrame의 색인행 레이블일 경우 left_index=True 또는 right_index=True 옵션으로 해당 인덱스를 병합 키로 사용 가능
In [103]: left1 = pd.DataFrame({'key': ['a', 'b', 'a', 'a', 'b', 'c'],
...: 'value': pd.Series(range(6), dtype='Int64')})
In [104]: right1 = pd.DataFrame({'group_val': [3.5, 7]}, index=['a', 'b'])
In [105]: left1
Out[105]:
key value
0 a 0
1 b 1
2 a 2
3 a 3
4 b 4
5 c 5
In [106]: right1
Out[106]:
group_val
a 3.5
b 7.0
In [107]: pd.merge(left1, right1, left_on='key', right_index=True)
Out[107]:
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 [108]: pd.merge(left1, right1, left_on='key', right_index=True, how='outer')
Out[108]:
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
5 c 5 NaN
In [109]: lefth = pd.DataFrame({'key1': ['Ohio', 'Ohio', 'Ohio',
...: 'Nevada', 'Nevada'],
...: 'key2': [2000, 2001, 2002, 2001, 2002],
...: 'data': pd.Series(range(5), dtype='Int64')})
In [110]: righth_index = pd.MultiIndex.from_arrays(
...: [
...: ['Nevada', 'Nevada', 'Ohio', 'Ohio', 'Ohio', 'Ohio'],
...: [2001, 2000, 2000, 2000, 2001, 2002]
...: ]
...: )
In [111]: righth = pd.DataFrame({'event1': pd.Series([0, 2, 4, 6, 8, 10], dtype='Int64',
...: index=righth_index),
...: 'event2': pd.Series([1, 3, 5, 7, 9, 11], dtype='Int64',
...: index=righth_index)})
In [112]: lefth
Out[112]:
key1 key2 data
0 Ohio 2000 0
1 Ohio 2001 1
2 Ohio 2002 2
3 Nevada 2001 3
4 Nevada 2002 4
In [113]: righth
Out[113]:
event1 event2
Nevada 2001 0 1
2000 2 3
Ohio 2000 4 5
2000 6 7
2001 8 9
2002 10 11
# 리스트로 여러 개의 열을 지정해 병합(중복되는 인덱스 값을 다룰 때는 how='outer' 옵션 사용)
In [115]: pd.merge(lefth, righth, left_on=['key1', 'key2'], right_index=True)
Out[115]:
key1 key2 data event1 event2
0 Ohio 2000 0 4 5
0 Ohio 2000 0 6 7
1 Ohio 2001 1 8 9
2 Ohio 2002 2 10 11
3 Nevada 2001 3 0 1
In [116]: pd.merge(lefth, righth, left_on=['key1', 'key2'],
...: right_index=True, how='outer')
Out[116]:
key1 key2 data event1 event2
0 Ohio 2000 0 4 5
0 Ohio 2000 0 6 7
1 Ohio 2001 1 8 9
2 Ohio 2002 2 10 11
3 Nevada 2001 3 0 1
4 Nevada 2002 4 <NA> <NA>
4 Nevada 2000 <NA> 2 3
# 양쪽에 공통으로 존재하는 여러 개의 인덱스를 병합할 수도 있음
In [117]: left2 = pd.DataFrame([[1., 2.], [3., 4.], [5., 6.]],
...: index=['a', 'c', 'e'],
...: columns=['Ohio', 'Nevada']).astype('Int64')
In [118]: right2 = pd.DataFrame([[7., 8.], [9., 10.], [11., 12.], [13, 14]],
...: index=['b', 'c', 'd', 'e'],
...: columns=['Missouri', 'Alabama']).astype('Int64')
In [119]: left2
Out[119]:
Ohio Nevada
a 1 2
c 3 4
e 5 6
In [120]: right2
Out[120]:
Missouri Alabama
b 7 8
c 9 10
d 11 12
e 13 14
In [121]: pd.merge(left2, right2, how='outer', left_index=True, right_index=True)
Out[121]:
Ohio Nevada Missouri Alabama
a 1 2 <NA> <NA>
b <NA> <NA> 7 8
c 3 4 9 10
d <NA> <NA> 11 12
e 5 6 13 14
DataFrame의 join 메서드는 열이 겹치지 않으며 완전히 같거나 유사한 인덱스 구조를 가진 여러 개의 DataFrame 객체를 병합할 때 사용
In [122]: left2.join(right2, how='outer')
Out[122]:
Ohio Nevada Missouri Alabama
a 1 2 <NA> <NA>
b <NA> <NA> 7 8
c 3 4 9 10
d <NA> <NA> 11 12
e 5 6 13 14
# pandas.merge와 비교하면 DataFrame의 join 메서드는 기본적으로 왼쪽 조인 수행
# join 메서드를 호출한 DataFrame의 열 중 하나에 대해 조인을 수행할 수도 있음
In [123]: left1.join(right1, on='key')
Out[123]:
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
마지막으로 인덱스 대 인덱스로 두 DataFrame을 합치려면 간단히 병합하려는 DataFrame의 리스트를 join 메서드에 넘기면 됨
> 보통 이런 병합은 pandas.concat 사용
In [124]: another = pd.DataFrame([[7., 8.], [9., 10.], [11., 12.], [16., 17.]],
...: index=['a', 'c', 'e', 'f'],
...: columns=['New York', 'Oregon'])
In [125]: another
Out[125]:
New York Oregon
a 7.0 8.0
c 9.0 10.0
e 11.0 12.0
f 16.0 17.0
In [126]: left2.join([right2, another])
Out[126]:
Ohio Nevada Missouri Alabama New York Oregon
a 1 2 <NA> <NA> 7.0 8.0
c 3 4 9 10 9.0 10.0
e 5 6 13 14 11.0 12.0
In [127]: left2.join([right2, another], how='outer')
Out[127]:
Ohio Nevada Missouri Alabama New York Oregon
a 1 2 <NA> <NA> 7.0 8.0
c 3 4 9 10 9.0 10.0
e 5 6 13 14 11.0 12.0
b <NA> <NA> 7 8 NaN NaN
d <NA> <NA> 11 12 NaN NaN
f <NA> <NA> <NA> <NA> 16.0 17.0
축 따라 이어 붙이기
데이터를 합치는 다른 방법에는 이어 붙이기(결합) concatenation (적층 stacking으로도 부름) 방법이 있음
# 넘파이의 ndarray 연결하는 concatenate 함수
In [3]: arr = np.arange(12).reshape((3, 4))
In [4]: arr
Out[4]:
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
In [5]: np.concatenate([arr, arr])
Out[5]:
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
In [6]: np.concatenate([arr, arr], axis=1)
Out[6]:
array([[ 0, 1, 2, 3, 0, 1, 2, 3],
[ 4, 5, 6, 7, 4, 5, 6, 7],
[ 8, 9, 10, 11, 8, 9, 10, 11]])
판다스의 concat 함수
In [7]: s1 = pd.Series([0, 1], index=['a', 'b'], dtype='Int64')
In [8]: s2 = pd.Series([2, 3, 4], index=['c', 'd', 'e'], dtype='Int64')
In [9]: s3 = pd.Series([5, 6], index=['f', 'g'], dtype='Int64')
In [10]: s1
Out[10]:
a 0
b 1
dtype: Int64
In [11]: s2
Out[11]:
c 2
d 3
e 4
dtype: Int64
In [12]: s3
Out[12]:
f 5
g 6
dtype: Int64
In [13]: pd.concat([s1, s2, s3])
Out[13]:
a 0
b 1
c 2
d 3
e 4
f 5
g 6
dtype: Int64
# pandas.concat 함수는 axis='index'를 기본값으로 함
# axis='columns'를 넘기면 결과는 Series가 아닌 DataFrame이 됨
In [14]: pd.concat([s1, s2, s3], axis='columns')
Out[14]:
0 1 2
a 0 <NA> <NA>
b 1 <NA> <NA>
c <NA> 2 <NA>
d <NA> 3 <NA>
e <NA> 4 <NA>
f <NA> <NA> 5
g <NA> <NA> 6
겹치는 축이 없어 외부 조인(outer 메서드)으로 합집합을 얻었지만 join='inner'를 넘겨 교집합을 구할 수도 있음
In [15]: s4 = pd.concat([s1, s3])
In [16]: pd.concat([s1, s4], axis='columns')
Out[16]:
0 1
a 0 0
b 1 1
f <NA> 5
g <NA> 6
In [17]: pd.concat([s1, s4], axis='columns', join='inner')
Out[17]:
0 1
a 0 0
b 1 1
Series를 이어 붙이기 전의 개별 Series를 결과에서 구분할 수 없는 잠재적 문제가 생길 수도 있음
이어 붙인 축에 대해서 계층적 인덱스를 생성해 식별 가능하도록 만들 수 있음
> 계층적 인덱스를 생성하려면 keys 인수 사용
In [18]: result = pd.concat([s1, s1, s3], keys=['one', 'two', 'three'])
In [19]: result
Out[19]:
one a 0
b 1
two a 0
b 1
three f 5
g 6
dtype: Int64
In [20]: result.unstack()
Out[20]:
a b f g
one 0 1 <NA> <NA>
two 0 1 <NA> <NA>
three <NA> <NA> 5 6
In [21]: result = pd.concat([s1, s2, s3], keys=['one', 'two', 'three'])
In [22]: result
Out[22]:
one a 0
b 1
two c 2
d 3
e 4
three f 5
g 6
dtype: Int64
In [23]: result.unstack()
Out[23]:
a b c d e f g
one 0 1 <NA> <NA> <NA> <NA> <NA>
two <NA> <NA> 2 3 4 <NA> <NA>
three <NA> <NA> <NA> <NA> <NA> 5 6
In [24]: result.unstack().stack()
Out[24]:
one a 0
b 1
two c 2
d 3
e 4
three f 5
g 6
dtype: Int64
# Series를 axis='columns'로 병합할 경우 keys는 DataFrame의 열 제목이 됨
In [25]: result = pd.concat([s1, s2, s3], axis='columns', keys=['one', 'two', 'three'])
In [26]: result
Out[26]:
one two three
a 0 <NA> <NA>
b 1 <NA> <NA>
c <NA> 2 <NA>
d <NA> 3 <NA>
e <NA> 4 <NA>
f <NA> <NA> 5
g <NA> <NA> 6
동일한 방식 DataFrame 객체에도 적용
In [27]: df1 = pd.DataFrame(np.arange(6).reshape(3, 2), index=['a', 'b', 'c'], columns=['one', 'two'])
In [28]: df2 = pd.DataFrame(5 + np.arange(4).reshape(2, 2), index=['a', 'c'], columns=['three', 'four'])
In [29]: df1
Out[29]:
one two
a 0 1
b 2 3
c 4 5
In [30]: df2
Out[30]:
three four
a 5 6
c 7 8
In [31]: pd.concat([df1, df2], axis='columns', keys=['level1', 'level2'])
Out[31]:
level1 level2
one two three four
a 0 1 5.0 6.0
b 2 3 NaN NaN
c 4 5 7.0 8.0
여기서 keys 인수는 계층적 인덱스를 생성하기 위해 사용
첫 번째 계층은 이어 붙인 각 DataFrame 객체를 구분하는 용도로 사용
# 리스트 대신 객체의 딕셔너리를 넘기면 keys 옵션으로 딕셔너리 키를 사용
In [32]: pd.concat({'level1': df1, 'level2': df2}, axis='columns')
Out[32]:
level1 level2
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 [33]: pd.concat([df1, df2], axis='columns', keys=['level1', 'level2'], names=['upper', 'lower'])
Out[33]:
upper level1 level2
lower one two three four
a 0 1 5.0 6.0
b 2 3 NaN NaN
c 4 5 7.0 8.0
# DataFrame의 행 인덱스가 분석에 필요한 데이터를 포함하고 있지 않은 경우
In [34]: df1 = pd.DataFrame(np.random.standard_normal((3, 4)), columns=['a', 'b', 'c', 'd'])
In [35]: df2 = pd.DataFrame(np.random.standard_normal((2, 3)), columns=['b', 'd', 'a'])
In [36]: df1
Out[36]:
a b c d
0 0.593673 -0.487405 1.239291 -0.576333
1 1.037841 1.336549 -1.352392 0.123572
2 0.215907 1.324048 0.383819 -1.311291
In [37]: df2
Out[37]:
b d a
0 -1.037233 -0.900059 -0.363276
1 0.395054 0.827801 0.252252
# ignore_index=True 옵션으로 각 DataFrame의 인덱스 무시,
# 열에 있는 데이터만 이어 붙인 후 새로운 기본 인덱스 할당
In [38]: pd.concat([df1, df2], ignore_index=True)
Out[38]:
a b c d
0 0.593673 -0.487405 1.239291 -0.576333
1 1.037841 1.336549 -1.352392 0.123572
2 0.215907 1.324048 0.383819 -1.311291
3 -0.363276 -1.037233 NaN -0.900059
4 0.252252 0.395054 NaN 0.827801
겹치는 데이터 합치기
두 데이터셋의 색인 중 일부거나 겹치거나 전체가 겹치는 경우 병합이나 이어 붙이기로 데이터를 합칠 수 없음
# 배열 기반 if-else 구문과 동일한 기능을 하는 넘파이의 where 함수 사용
In [39]: a = pd.Series([np.nan, 2.5, 0, 3.5, 4.5, np.nan], index=['f', 'e', 'd', 'c', 'b', 'a'])
In [40]: b = pd.Series([0., np.nan, 2., np.nan, np.nan, 5.], index=['a', 'b', 'c', 'd', 'e', 'f'])
In [41]: a
Out[41]:
f NaN
e 2.5
d 0.0
c 3.5
b 4.5
a NaN
dtype: float64
In [42]: b
Out[42]:
a 0.0
b NaN
c 2.0
d NaN
e NaN
f 5.0
dtype: float64
In [43]: np.where(pd.isna(a), b, a)
Out[43]: array([0. , 2.5, 0. , 3.5, 4.5, 5. ])
여기서 a 값이 null일 때 b가 선택되고 그렇지 않으면 null이 아닌 a 값 선택, numpy.where를 사용하면 인덱스 레이블의 정렬 여부 확인 x
인덱스에 따라 값을 정렬하고 싶다면 Series의 combine_first 메서드 사용
In [44]: pd.concat([a, b], axis='columns')
Out[44]:
0 1
f NaN 5.0
e 2.5 NaN
d 0.0 NaN
c 3.5 2.0
b 4.5 NaN
a NaN 0.0
In [45]: a.combine_first(b)
Out[45]:
a 0.0
b 4.5
c 3.5
d 0.0
e 2.5
f 5.0
dtype: float64
DataFrame 객체의 combine_first 결괏값은 모든 열 이름의 합집합
In [46]: df1 = pd.DataFrame({'a': [1., np.nan, 5., np.nan],
...: 'b': [np.nan, 2., np.nan, 6],
...: 'c': range(2, 18, 4)})
In [47]: df2 = pd.DataFrame({'a': [5., 4, np.nan, 3, 7],
...: 'b': [np.nan, 3., 4, 6, 8]})
In [48]: df1
Out[48]:
a b c
0 1.0 NaN 2
1 NaN 2.0 6
2 5.0 NaN 10
3 NaN 6.0 14
In [49]: df2
Out[49]:
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 [50]: df1.combine_first(df2)
Out[50]:
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
재구성과 피벗
표 형식의 데이터를 재배치하는 다양한 기본 연산을 재구성(reshape) 또는 피벗(pivot) 연산이라 부름
계층적 인덱스로 재구성
· stack: 데이터의 열을 행으로 피벗(또는 회전)
· unstack: 행을 열로 피벗
In [51]: data = pd.DataFrame(np.arange(6).reshape((2, 3)),
...: index=pd.Index(['Ohio', 'Colorado'], name='state'),
...: columns=pd.Index(['one', 'two', 'three'], name='number'))
In [52]: data
Out[52]:
number one two three
state
Ohio 0 1 2
Colorado 3 4 5
# stack 메서드를 사용해 열이 행으로 피벗
In [53]: result = data.stack()
In [54]: result
Out[54]:
state number
Ohio one 0
two 1
three 2
Colorado one 3
two 4
three 5
dtype: int32
# unstack 메서드를 사용하면 계층적 인덱스를 가진 Series로부터 다시 DataFrame 얻을 수 있음
In [55]: result.unstack()
Out[55]:
number one two three
state
Ohio 0 1 2
Colorado 3 4 5
# 기본적으로 가장 안쪽에 있는 것부터 끄집어냄(stack도 동일)
# level 이름이나 숫자를 전달해 끄집어낼 단계 지정 가능
In [57]: result.unstack(level=0)
Out[57]:
state Ohio Colorado
number
one 0 3
two 1 4
three 2 5
In [58]: result.unstack(level='state')
Out[58]:
state Ohio Colorado
number
one 0 3
two 1 4
three 2 5
In [59]: result.unstack(level=1)
Out[59]:
number one two three
state
Ohio 0 1 2
Colorado 3 4 5
In [60]: result.unstack(level='number')
Out[60]:
number one two three
state
Ohio 0 1 2
Colorado 3 4 5
해당 단계에 있는 모든 값이 하위 그룹에 속하지 않을 경우 unstack을 수행했을 때 누락된 데이터가 생김
In [62]: s1 = pd.Series([0, 1, 2, 3], index=['a', 'b', 'c', 'd'], dtype='Int64')
In [63]: s2 = pd.Series([4, 5, 6], index=['c', 'd', 'e'], dtype='Int64')
In [64]: data2 = pd.concat([s1, s2], keys=['one', 'two'])
In [65]: data2
Out[65]:
one a 0
b 1
c 2
d 3
two c 4
d 5
e 6
dtype: Int64
In [66]: data2.unstack()
Out[66]:
a b c d e
one 0 1 2 3 <NA>
two <NA> <NA> 4 5 6
In [67]: data2.unstack().stack()
Out[67]:
one a 0
b 1
c 2
d 3
two c 4
d 5
e 6
dtype: Int64
In [68]: data2.unstack().stack(dropna=False)
Out[68]:
one a 0
b 1
c 2
d 3
e <NA>
two a <NA>
b <NA>
c 4
d 5
e 6
dtype: Int64
DataFrame을 unstack하면 unstack은 결과에서 가장 낮은 단계가 됨
In [69]: df = pd.DataFrame({'left': result, 'right': result + 5},
...: columns=pd.Index(['left', 'right'], name='side'))
In [70]: df
Out[70]:
side left right
state number
Ohio one 0 5
two 1 6
three 2 7
Colorado one 3 8
two 4 9
three 5 10
In [71]: df.unstack(level='state')
Out[71]:
side left right
state Ohio Colorado Ohio Colorado
number
one 0 3 5 8
two 1 4 6 9
three 2 5 7 10
# stack도 쌓을 축의 이름 지정 가능
In [72]: df.unstack(level='state').stack(level='side')
Out[72]:
state Colorado Ohio
number side
one left 3 0
right 8 5
two left 4 1
right 9 6
three left 5 2
right 10 7
In [73]: df
Out[73]:
side left right
state number
Ohio one 0 5
two 1 6
three 2 7
Colorado one 3 8
two 4 9
three 5 10
In [74]: df.unstack()
Out[74]:
side left right
number one two three one two three
state
Ohio 0 1 2 5 6 7
Colorado 3 4 5 8 9 10
In [75]: df.unstack(level='state')
Out[75]:
side left right
state Ohio Colorado Ohio Colorado
number
one 0 3 5 8
two 1 4 6 9
three 2 5 7 10
긴 형식에서 넓은 형식으로 피벗 (가로 세로 바꾸기)
데이터베이스나 csv 파일에 여러 개의 시계열 데이터를 저장하는 일반적인 방법은 시간 순서대로 나열하는 방법(긴 형식 또는 적층 형식이라고도 부름), 이 형식에서 개별 값은 표에서 단일 행으로 표현됨
In [77]: # ipython 환경에서 현재 경로 보기
In [78]: %pwd
Out[78]: 'C:\\Users\\Desktop\\project\\basic'
In [79]: data = pd.read_csv('macrodata.csv')
In [80]: data = data.loc[:, ['year', 'quarter', 'realgdp', 'infl', 'unemp']]
In [81]: data.head() # 앞에서 5줄만 보기
Out[81]:
year quarter realgdp infl unemp
0 1959 1 2710.349 0.00 5.8
1 1959 2 2778.801 2.34 5.1
2 1959 3 2775.488 2.74 5.3
3 1959 4 2785.204 0.27 5.6
4 1960 1 2847.699 2.31 5.2
특정한 시간이 아닌 시간 간격을 나타내는 pandas.PeriodIndex를 이용해 연도(year)와 분기(quarter)열을 합친 후 각 분기 말에 날짜/시간 값(datetime)을 포함하도록 인덱스로 설정
In [83]: periods = pd.PeriodIndex(year=data.pop('year'),
...: quarter=data.pop('quarter'),
...: name='date')
In [84]: periods
Out[84]:
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)
In [85]: data.index = periods.to_timestamp('D')
In [86]: data.head()
Out[86]:
realgdp infl unemp
date
1959-01-01 2710.349 0.00 5.8
1959-04-01 2778.801 2.34 5.1
1959-07-01 2775.488 2.74 5.3
1959-10-01 2785.204 0.27 5.6
1960-01-01 2847.699 2.31 5.2
행의 일부분 선택하고 열 인덱스에 'item' 이름 지정
In [87]: data = data.reindex(columns=['realqdp', 'infl', 'unemp'])
In [88]: data.columns.name = 'item'
In [89]: data.head()
Out[89]:
item realqdp infl unemp
date
1959-01-01 NaN 0.00 5.8
1959-04-01 NaN 2.34 5.1
1959-07-01 NaN 2.74 5.3
1959-10-01 NaN 0.27 5.6
1960-01-01 NaN 2.31 5.2
# stack으로 재구성, reset_index를 사용해 새로운 인덱스 계층을 열로 바꿈
# 최종적으로 데이터 값을 담고 있는 열의 이름을 'value'로 지정
In [90]: long_data = (data.stack().reset_index().rename(columns={0: 'value'}))
In [91]: long_data[:10]
Out[91]:
date item value
0 1959-01-01 infl 0.00
1 1959-01-01 unemp 5.80
2 1959-04-01 infl 2.34
3 1959-04-01 unemp 5.10
4 1959-07-01 infl 2.74
5 1959-07-01 unemp 5.30
6 1959-10-01 infl 0.27
7 1959-10-01 unemp 5.60
8 1960-01-01 infl 2.31
9 1960-01-01 unemp 5.20
DataFrame의 pivot 메서드 사용
In [92]: pivoted = long_data.pivot(index='date', columns='item', values='value')
In [93]: pivoted.head()
Out[93]:
item infl unemp
date
1959-01-01 0.00 5.8
1959-04-01 2.34 5.1
1959-07-01 2.74 5.3
1959-10-01 0.27 5.6
1960-01-01 2.31 5.2
pivot 메서드의 처음 두 값은 각각 행과 열 인덱스로 사용할 열 이름, 마지막 value는 DataFrame에 채워 넣을 값을 담은 열
한 번에 두 개의 열을 변형하는 데 사용하는 경우
In [94]: long_data.index.name = None
In [95]: long_data['value2'] = np.random.standard_normal(len(long_data))
In [96]: long_data[:10]
Out[96]:
date item value value2
0 1959-01-01 infl 0.00 -0.493828
1 1959-01-01 unemp 5.80 -0.447998
2 1959-04-01 infl 2.34 -0.178906
3 1959-04-01 unemp 5.10 2.024150
4 1959-07-01 infl 2.74 0.818945
5 1959-07-01 unemp 5.30 -0.854059
6 1959-10-01 infl 0.27 -0.333144
7 1959-10-01 unemp 5.60 0.863888
8 1960-01-01 infl 2.31 -0.386003
9 1960-01-01 unemp 5.20 -0.410906
# 마지막 인수를 생략해 계층적 열을 갖는 DataFrame을 얻을 수 있음
In [97]: pivoted = long_data.pivot(index='date', columns='item')
In [98]: pivoted.head()
Out[98]:
value value2
item infl unemp infl unemp
date
1959-01-01 0.00 5.8 -0.493828 -0.447998
1959-04-01 2.34 5.1 -0.178906 2.024150
1959-07-01 2.74 5.3 0.818945 -0.854059
1959-10-01 0.27 5.6 -0.333144 0.863888
1960-01-01 2.31 5.2 -0.386003 -0.410906
In [99]: pivoted['value'].head()
Out[99]:
item infl unemp
date
1959-01-01 0.00 5.8
1959-04-01 2.34 5.1
1959-07-01 2.74 5.3
1959-10-01 0.27 5.6
1960-01-01 2.31 5.2
# pivot 메서드는 set_index를 사용해 계층적 인덱스를 만들고 unstack 메서드로 형태를 변경하는 것과 동일
In [100]: unstacked = long_data.set_index(['date', 'item']).unstack(level='item')
In [101]: unstacked.head()
Out[101]:
value value2
item infl unemp infl unemp
date
1959-01-01 0.00 5.8 -0.493828 -0.447998
1959-04-01 2.34 5.1 -0.178906 2.024150
1959-07-01 2.74 5.3 0.818945 -0.854059
1959-10-01 0.27 5.6 -0.333144 0.863888
1960-01-01 2.31 5.2 -0.386003 -0.410906