-
데이터 준비: 조인, 병합, 변형Python 2023. 11. 27. 17:12
계층적 색인
계층적인 색인(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
'Python' 카테고리의 다른 글
주피터 노트북 설정 (0) 2023.12.07 그래프와 시각화 (1) 2023.11.28 데이터 정제 및 준비 (0) 2023.11.21 데이터 로딩과 저장, 파일 형식 (0) 2023.11.20 판다스 (0) 2023.11.14