Python

데이터 준비: 조인, 병합, 변형

haventmetyou 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