18. 판다스 10분 완성 2부#

필수 라이브러리

import numpy as np
import pandas as pd

18.1. 합병과 결합: merge-join-concat#

18.1.1. 종/횡 결합: pd.concat() 함수#

pd.concat() 함수는 여러 개의 데이터프레임을 하나로 합친다.

  • axis=0: 종 결합. 즉 데이터프레임 여러 개의 위아래 결합.

df1 = pd.DataFrame(
    {
        "A": ["A0", "A1", "A2", "A3"],
        "B": ["B0", "B1", "B2", "B3"],
        "C": ["C0", "C1", "C2", "C3"],
        "D": ["D0", "D1", "D2", "D3"],
    },
    index=[0, 1, 2, 3],
)

df1
A B C D
0 A0 B0 C0 D0
1 A1 B1 C1 D1
2 A2 B2 C2 D2
3 A3 B3 C3 D3
df2 = pd.DataFrame(
    {
        "A": ["A4", "A5", "A6", "A7"],
        "B": ["B4", "B5", "B6", "B7"],
        "C": ["C4", "C5", "C6", "C7"],
        "D": ["D4", "D5", "D6", "D7"],
    },
    index=[4, 5, 6, 7],
)

df2
A B C D
4 A4 B4 C4 D4
5 A5 B5 C5 D5
6 A6 B6 C6 D6
7 A7 B7 C7 D7
df3 = pd.DataFrame(
    {
        "A": ["A8", "A9", "A10", "A11"],
        "B": ["B8", "B9", "B10", "B11"],
        "C": ["C8", "C9", "C10", "C11"],
        "D": ["D8", "D9", "D10", "D11"],
    },
    index=[8, 9, 10, 11],
)

df3
A B C D
8 A8 B8 C8 D8
9 A9 B9 C9 D9
10 A10 B10 C10 D10
11 A11 B11 C11 D11
pd.concat([df1, df2, df3]) # axis=0 이 기본값
A B C D
0 A0 B0 C0 D0
1 A1 B1 C1 D1
2 A2 B2 C2 D2
3 A3 B3 C3 D3
4 A4 B4 C4 D4
5 A5 B5 C5 D5
6 A6 B6 C6 D6
7 A7 B7 C7 D7
8 A8 B8 C8 D8
9 A9 B9 C9 D9
10 A10 B10 C10 D10
11 A11 B11 C11 D11
  • axis=1: 횡 결합. 즉 데이터프레임 여러 개의 좌우 결합.

df4 = pd.DataFrame(
    {
        "B": ["B2", "B3", "B6", "B7"],
        "D": ["D2", "D3", "D6", "D7"],
        "F": ["F2", "F3", "F6", "F7"],
    },
    index=[2, 3, 6, 7],
)

df4
B D F
2 B2 D2 F2
3 B3 D3 F3
6 B6 D6 F6
7 B7 D7 F7
pd.concat([df1, df4], axis=1)
A B C D B D F
0 A0 B0 C0 D0 NaN NaN NaN
1 A1 B1 C1 D1 NaN NaN NaN
2 A2 B2 C2 D2 B2 D2 F2
3 A3 B3 C3 D3 B3 D3 F3
6 NaN NaN NaN NaN B6 D6 F6
7 NaN NaN NaN NaN B7 D7 F7

인덱스를 기존의 데이터프레임과 통일시키기 위해 리인덱싱을 활용할 수도 있다.

df1.index
Int64Index([0, 1, 2, 3], dtype='int64')
pd.concat([df1, df4], axis=1).reindex(df1.index)
A B C D B D F
0 A0 B0 C0 D0 NaN NaN NaN
1 A1 B1 C1 D1 NaN NaN NaN
2 A2 B2 C2 D2 B2 D2 F2
3 A3 B3 C3 D3 B3 D3 F3
pd.concat([df1, df4.reindex(df1.index)], axis=1)
A B C D B D F
0 A0 B0 C0 D0 NaN NaN NaN
1 A1 B1 C1 D1 NaN NaN NaN
2 A2 B2 C2 D2 B2 D2 F2
3 A3 B3 C3 D3 B3 D3 F3

18.1.2. 합병: pd.merge() 함수#

pd.merge() 함수 는 SQL 방식으로 특정 열을 기준으로 두 개의 데이터프레임을 합친다. 다양한 옵션을 지원하는 매우 강력한 도구이다.

예제

실습을 위해 아래 두 데이터프레임을 이용한다.

left = pd.DataFrame({"key": ["foo", "foo"], "lval": [1, 2]})
right = pd.DataFrame({"key": ["foo", "foo"], "rval": [4, 5]})
left
key lval
0 foo 1
1 foo 2
right
key rval
0 foo 4
1 foo 5
  • on="key" 키워드 인자

    • key 열에 사용된 항목 각각에 대해 다른 열에서 해당 항목과 연관된 값들을 조합할 수 있는 모든 경우의 수를 다룬다.

    • foo 값에 대해 lval 열에서 2개의 값이, rval 열에서 2개의 값이 있기에 foo와 관련해서 총 4개의 경우가 생성된다.

    key

    left.lval

    right.rval

    경우의 수

    foo

    1, 2

    4, 5

    4

pd.merge(left, right, on="key")
key lval rval
0 foo 1 4
1 foo 1 5
2 foo 2 4
3 foo 2 5

예제

left = pd.DataFrame({"key": ["foo", "bar"], "lval": [1, 2]})
right = pd.DataFrame({"key": ["foo", "bar"], "rval": [4, 5]})
left
key lval
0 foo 1
1 bar 2
right
key rval
0 foo 4
1 bar 5
  • on="key" 키워드 인자

    • key 열에 사용된 항목별로 모든 경우의 수를 다룬다.

    • foo 값에 대해 lval 열에서 1개의 값이, rval 열에서 1개의 값이 있기에 foo와 관련해서 총 1개의 경우가 생성된다.

    • bar 값에 대해 lval 열에서 1개의 값이, rval 열에서 1개의 값이 있기에 foo와 관련해서 총 1개의 경우가 생성된다.

    key

    left.lval

    right.rval

    경우의 수

    foo

    1

    4

    1

    bar

    2

    5

    1

pd.merge(left, right, on="key")
key lval rval
0 foo 1 4
1 bar 2 5

예제

경우의 수는 지정된 열의 항목이 사용된 횟수를 기준으로 한다.

left = pd.DataFrame(
    {
        "key": ["K0", "K1", "K2", "K3"],
        "A": ["A0", "A1", "A2", "A3"],
        "B": ["B0", "B1", "B2", "B3"],
    }
)
left
key A B
0 K0 A0 B0
1 K1 A1 B1
2 K2 A2 B2
3 K3 A3 B3
right = pd.DataFrame(
    {
        "key": ["K0", "K1", "K2", "K3"],
        "C": ["C0", "C1", "C2", "C3"],
        "D": ["D0", "D1", "D2", "D3"],
    }
)
right
key C D
0 K0 C0 D0
1 K1 C1 D1
2 K2 C2 D2
3 K3 C3 D3

key

(left.A, left.B)

(right.C, right.D)

경우의 수

K0

(A0, B0)

(C0, D0)

1

K1

(A1, B1)

(C1, D1)

1

K2

(A2, B2)

(C2, D2)

1

K3

(A3, B3)

(C3, D3)

1

result = pd.merge(left, right, on="key")
result
key A B C D
0 K0 A0 B0 C0 D0
1 K1 A1 B1 C1 D1
2 K2 A2 B2 C2 D2
3 K3 A3 B3 C3 D3

다양한 키 활용

  • 두 개 이상의 키를 하나의 쌍으로 된 키를 사용하는 경우와 유사함.

left = pd.DataFrame(
    {
        "key1": ["K0", "K0", "K1", "K2"],
        "key2": ["K0", "K1", "K0", "K1"],
        "A": ["A0", "A1", "A2", "A3"],
        "B": ["B0", "B1", "B2", "B3"],
    }
)

left
key1 key2 A B
0 K0 K0 A0 B0
1 K0 K1 A1 B1
2 K1 K0 A2 B2
3 K2 K1 A3 B3
right = pd.DataFrame(
    {
        "key1": ["K0", "K1", "K1", "K2"],
        "key2": ["K0", "K0", "K0", "K0"],
        "C": ["C0", "C1", "C2", "C3"],
        "D": ["D0", "D1", "D2", "D3"],
    }
)

right
key1 key2 C D
0 K0 K0 C0 D0
1 K1 K0 C1 D1
2 K1 K0 C2 D2
3 K2 K0 C3 D3
  • how='inner': 지정된 키의 교집합 대상

result = pd.merge(left, right, on=["key1", "key2"]) # how='inner' 가 기본값
result
key1 key2 A B C D
0 K0 K0 A0 B0 C0 D0
1 K1 K0 A2 B2 C1 D1
2 K1 K0 A2 B2 C2 D2
result = pd.merge(left, right, how="inner", on=["key1", "key2"])
result
key1 key2 A B C D
0 K0 K0 A0 B0 C0 D0
1 K1 K0 A2 B2 C1 D1
2 K1 K0 A2 B2 C2 D2
  • how='outer': 지정된 키의 합집합 대상

result = pd.merge(left, right, how="outer", on=["key1", "key2"])
result
key1 key2 A B C D
0 K0 K0 A0 B0 C0 D0
1 K0 K1 A1 B1 NaN NaN
2 K1 K0 A2 B2 C1 D1
3 K1 K0 A2 B2 C2 D2
4 K2 K1 A3 B3 NaN NaN
5 K2 K0 NaN NaN C3 D3
  • how='left': 왼쪽 데이터프레임의 키에 포함된 항목만 대상

left
key1 key2 A B
0 K0 K0 A0 B0
1 K0 K1 A1 B1
2 K1 K0 A2 B2
3 K2 K1 A3 B3
result = pd.merge(left, right, how="left", on=["key1", "key2"])
result
key1 key2 A B C D
0 K0 K0 A0 B0 C0 D0
1 K0 K1 A1 B1 NaN NaN
2 K1 K0 A2 B2 C1 D1
3 K1 K0 A2 B2 C2 D2
4 K2 K1 A3 B3 NaN NaN
  • how='right': 오른쪽 데이터프레임의 키에 포함된 항목만 대상

right
key1 key2 C D
0 K0 K0 C0 D0
1 K1 K0 C1 D1
2 K1 K0 C2 D2
3 K2 K0 C3 D3
result = pd.merge(left, right, how="right", on=["key1", "key2"])
result
key1 key2 A B C D
0 K0 K0 A0 B0 C0 D0
1 K1 K0 A2 B2 C1 D1
2 K1 K0 A2 B2 C2 D2
3 K2 K0 NaN NaN C3 D3
  • how='cross': 모든 경우의 수 조합

result = pd.merge(left, right, how="cross")
result
key1_x key2_x A B key1_y key2_y C D
0 K0 K0 A0 B0 K0 K0 C0 D0
1 K0 K0 A0 B0 K1 K0 C1 D1
2 K0 K0 A0 B0 K1 K0 C2 D2
3 K0 K0 A0 B0 K2 K0 C3 D3
4 K0 K1 A1 B1 K0 K0 C0 D0
5 K0 K1 A1 B1 K1 K0 C1 D1
6 K0 K1 A1 B1 K1 K0 C2 D2
7 K0 K1 A1 B1 K2 K0 C3 D3
8 K1 K0 A2 B2 K0 K0 C0 D0
9 K1 K0 A2 B2 K1 K0 C1 D1
10 K1 K0 A2 B2 K1 K0 C2 D2
11 K1 K0 A2 B2 K2 K0 C3 D3
12 K2 K1 A3 B3 K0 K0 C0 D0
13 K2 K1 A3 B3 K1 K0 C1 D1
14 K2 K1 A3 B3 K1 K0 C2 D2
15 K2 K1 A3 B3 K2 K0 C3 D3

18.1.3. 합병: DataFrame.join() 메서드#

인덱스를 기준으로 두 개의 데이터프레임을 합병할 때 사용한다.

left = pd.DataFrame(
    {"A": ["A0", "A1", "A2"], "B": ["B0", "B1", "B2"]}, index=["K0", "K1", "K2"]
)

left
A B
K0 A0 B0
K1 A1 B1
K2 A2 B2
right = pd.DataFrame(
    {"C": ["C0", "C2", "C3"], "D": ["D0", "D2", "D3"]}, index=["K0", "K2", "K3"]
)

right
C D
K0 C0 D0
K2 C2 D2
K3 C3 D3
left.join(right)
A B C D
K0 A0 B0 C0 D0
K1 A1 B1 NaN NaN
K2 A2 B2 C2 D2

아래와 같이 pd.merge() 함수를 이용한 결과와 동일하다.

pd.merge(left, right, left_index=True, right_index=True, how='left')
A B C D
K0 A0 B0 C0 D0
K1 A1 B1 NaN NaN
K2 A2 B2 C2 D2

pd.merge() 함수의 키워드 인자를 동일하게 사용할 수 있다.

  • how='outer'

left.join(right, how="outer")
A B C D
K0 A0 B0 C0 D0
K1 A1 B1 NaN NaN
K2 A2 B2 C2 D2
K3 NaN NaN C3 D3

아래 코드가 동일한 결과를 낸다.

pd.merge(left, right, left_index=True, right_index=True, how='outer')
A B C D
K0 A0 B0 C0 D0
K1 A1 B1 NaN NaN
K2 A2 B2 C2 D2
K3 NaN NaN C3 D3
  • how='inner'

left.join(right, how="inner")
A B C D
K0 A0 B0 C0 D0
K2 A2 B2 C2 D2

아래 코드가 동일한 결과를 낸다.

pd.merge(left, right, left_index=True, right_index=True, how='inner')
A B C D
K0 A0 B0 C0 D0
K2 A2 B2 C2 D2

18.2. 다중 인덱스MultiIndex#

다중 인덱스를 이용하여 데이터를 보다 체계적으로 다를 수 있다. 또한 이어서 다룰 그룹 분류Group by, 모양 변환reshaping, 피벗 변환pivoting 등에서 유용하게 활용된다.

18.2.1. MultiIndex 객체#

다중 인덱스 객체는 보통 튜플을 이용한다. 예를 들어 아래 두 개의 리스트를 이용하여 튜플을 생성한 다음 다중 인덱스로 만들어보자.

arrays = [
    ["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"],
    ["one", "two", "one", "two", "one", "two", "one", "two"],
]
  • 튜플 생성: 항목 8개

tuples = list(zip(*arrays))
tuples
[('bar', 'one'),
 ('bar', 'two'),
 ('baz', 'one'),
 ('baz', 'two'),
 ('foo', 'one'),
 ('foo', 'two'),
 ('qux', 'one'),
 ('qux', 'two')]

다중 인덱스 객체 생성: from_tupes() 함수

튜플 리스트를 이용하여 다중 인덱스 객체를 생성할 수 있다.

index = pd.MultiIndex.from_tuples(tuples)
index
MultiIndex([('bar', 'one'),
            ('bar', 'two'),
            ('baz', 'one'),
            ('baz', 'two'),
            ('foo', 'one'),
            ('foo', 'two'),
            ('qux', 'one'),
            ('qux', 'two')],
           )
  • names 키워드 인자

    • 다중 인덱스의 각 레벨level의 이름 지정.

    • 지정되지 않으면 None으로 처리됨.

예를 들어 위 코드에서 사용된 각각의 레벨에 이름은 다음과 같다.

  • "first": 0-레벨 이름

  • "second": 1-레벨 이름

index = pd.MultiIndex.from_tuples(tuples, names=["first", "second"])
index
MultiIndex([('bar', 'one'),
            ('bar', 'two'),
            ('baz', 'one'),
            ('baz', 'two'),
            ('foo', 'one'),
            ('foo', 'two'),
            ('qux', 'one'),
            ('qux', 'two')],
           names=['first', 'second'])

다중 인덱스 객체 생성: from_arrays() 함수

길이가 동일한 여러 개의 리스트로 구성된 어레이를 직접 이용할 수도 있다.

index = pd.MultiIndex.from_arrays(arrays, names=["first", "second"])
index
MultiIndex([('bar', 'one'),
            ('bar', 'two'),
            ('baz', 'one'),
            ('baz', 'two'),
            ('foo', 'one'),
            ('foo', 'two'),
            ('qux', 'one'),
            ('qux', 'two')],
           names=['first', 'second'])

18.2.2. 다중 인덱스 라벨label#

  • 시리즈 생성

아래 코드는 길이가 8인 어레이를 이용하여 시리즈를 생성한다. 인덱스의 라벨은 다중 인덱스가 사용된다. 각각의 레벨에서 라벨이 연속적으로 사용되는 경우는 보다 자연스러운 표현을 위해 생략되기도 한다.

s = pd.Series(np.random.randn(8), index=index)
s
first  second
bar    one      -0.102970
       two      -0.869112
baz    one       0.071613
       two      -0.481193
foo    one       0.848038
       two       0.024128
qux    one       0.494374
       two      -1.224866
dtype: float64
  • 데이터프레임 생성

아래 코드는 8개의 행으로 이뤄진 2차원 어레이를 이용하여 데이터프레임을 생성한다. index 또는 columns로 여러 개의 리스트로 구성된 어레이를 지정하면 자동으로 다중 인덱스 라벨이 지정된다.

df = pd.DataFrame(np.random.randn(8, 4), index=arrays)
df
0 1 2 3
bar one 1.011871 1.794615 -0.795156 -1.160517
two -1.659252 -0.473680 1.386762 -0.747361
baz one 2.200999 0.266427 -1.502801 -1.231025
two -0.854165 0.270410 -0.898856 0.066127
foo one 1.971631 0.143811 0.101862 -1.120642
two 1.236258 1.090704 -0.403456 1.793911
qux one 1.192296 -3.598376 0.913916 0.098270
two -1.917410 -1.085287 -2.223167 -0.450469

다중 인덱스를 열 라벨로도 활용할 수 있다. 아래 코드는 8개의 열로 이뤄진 2차원 어레이를 이용하여 데이터프레임을 생성한다.

df1 = pd.DataFrame(np.random.randn(3, 8), index=["A", "B", "C"], columns=index)
df1
first bar baz foo qux
second one two one two one two one two
A -0.023809 0.903218 0.402345 -1.132165 1.446495 -0.190539 0.303528 1.060082
B -0.588585 0.541489 0.235990 0.460059 1.419565 -0.191601 -0.504814 -1.384551
C -0.887831 -0.776880 1.080288 0.549915 0.825186 -0.539257 -1.068850 0.504218

인덱스 라벨과 열 라벨 모두 다중 인덱스를 이용할 수도 있다.

  • 동일한 길이의 리스트로 이루어진 리스트를 인덱스 또는 열의 라벨로 지정하면 다중 인덱스로 자동 지정된다.

arrays2 = [
    ["toto", "toto", "titi", "titi", "tata", "tata"],
    ["A", "B", "A", "B", "A", "B"],
]
pd.DataFrame(np.random.randn(6, 6), index=index[:6], columns=arrays2)
toto titi tata
A B A B A B
first second
bar one 0.656057 -1.181767 1.509824 0.691512 0.185092 -1.020352
two 1.638626 1.025058 0.233587 2.769262 0.474814 -1.395608
baz one -1.429578 0.686740 -0.490076 0.629835 1.158276 -0.642619
two -0.861363 -0.196854 -0.818325 -0.483153 0.069907 2.019037
foo one -1.111697 -0.706583 -2.259861 0.772279 1.417341 -1.998353
two -0.474314 1.684809 0.128608 -1.861478 0.636491 0.354697

주의사항

튜플을 라벨로 사용하는 것은 다중 인덱스와 아무 상관 없다. 단지 라벨이 튜플인 것 뿐이다.

tuples
[('bar', 'one'),
 ('bar', 'two'),
 ('baz', 'one'),
 ('baz', 'two'),
 ('foo', 'one'),
 ('foo', 'two'),
 ('qux', 'one'),
 ('qux', 'two')]
pd.Series(np.random.randn(8), index=tuples)
(bar, one)   -0.547467
(bar, two)    1.494266
(baz, one)    1.210512
(baz, two)   -2.214762
(foo, one)    1.610649
(foo, two)   -1.949249
(qux, one)   -0.476863
(qux, two)   -0.595795
dtype: float64

18.2.3. 다중 인덱스 레벨level#

다중 인덱스 객체의 get_level_values() 메서드를 이용하여 레벨별 인덱스 라벨을 확인할 수 있다.

  • 0-레블 라벨

index.get_level_values(0)
Index(['bar', 'bar', 'baz', 'baz', 'foo', 'foo', 'qux', 'qux'], dtype='object', name='first')

레벨 이름을 이용할 수도 있다.

index.get_level_values("first")
Index(['bar', 'bar', 'baz', 'baz', 'foo', 'foo', 'qux', 'qux'], dtype='object', name='first')
  • 1-레블 라벨

index.get_level_values(1)
Index(['one', 'two', 'one', 'two', 'one', 'two', 'one', 'two'], dtype='object', name='second')
index.get_level_values("second")
Index(['one', 'two', 'one', 'two', 'one', 'two', 'one', 'two'], dtype='object', name='second')

18.2.4. 다중 인덱스 인덱싱#

다중 인덱스를 라벨로 사용하는 시리즈와 데이터프레임의 인덱싱은 일반 인덱싱과 크게 다르지 않다.

  • 시리즈 인덱싱

s
first  second
bar    one      -0.102970
       two      -0.869112
baz    one       0.071613
       two      -0.481193
foo    one       0.848038
       two       0.024128
qux    one       0.494374
       two      -1.224866
dtype: float64
s["qux"]
second
one    0.494374
two   -1.224866
dtype: float64
  • 데이터프레임 인덱싱

df
0 1 2 3
bar one 1.011871 1.794615 -0.795156 -1.160517
two -1.659252 -0.473680 1.386762 -0.747361
baz one 2.200999 0.266427 -1.502801 -1.231025
two -0.854165 0.270410 -0.898856 0.066127
foo one 1.971631 0.143811 0.101862 -1.120642
two 1.236258 1.090704 -0.403456 1.793911
qux one 1.192296 -3.598376 0.913916 0.098270
two -1.917410 -1.085287 -2.223167 -0.450469
df.loc["bar"]
0 1 2 3
one 1.011871 1.794615 -0.795156 -1.160517
two -1.659252 -0.473680 1.386762 -0.747361

레벨별로 라벨을 지정할 수 있다. 각각의 라벨은 쉼표로 구분한다.

df.loc["bar", "one"]
0    1.011871
1    1.794615
2   -0.795156
3   -1.160517
Name: (bar, one), dtype: float64

아래와 같이 할 수도 있다.

df.loc["bar"].loc["one"]
0    1.011871
1    1.794615
2   -0.795156
3   -1.160517
Name: one, dtype: float64
  • 데이터프레임 인덱싱: 열 라벨이 다중 인덱스인 경우

df1
first bar baz foo qux
second one two one two one two one two
A -0.023809 0.903218 0.402345 -1.132165 1.446495 -0.190539 0.303528 1.060082
B -0.588585 0.541489 0.235990 0.460059 1.419565 -0.191601 -0.504814 -1.384551
C -0.887831 -0.776880 1.080288 0.549915 0.825186 -0.539257 -1.068850 0.504218
df1["bar"]
second one two
A -0.023809 0.903218
B -0.588585 0.541489
C -0.887831 -0.776880

레벨별로 라벨을 지정한다. 각각의 라벨은 쉼표로 구분한다.

df1["bar", "one"]
A   -0.023809
B   -0.588585
C   -0.887831
Name: (bar, one), dtype: float64

아래와 같이 할 수도 있다

df1["bar"]["one"]
A   -0.023809
B   -0.588585
C   -0.887831
Name: one, dtype: float64

18.2.5. 다중 인덱스 슬라이싱#

다중 인덱스를 라벨로 사용하는 시리즈와 데이터프레임의 인덱싱은 일반 슬라이싱과 크게 다르지 않다.

df
0 1 2 3
bar one 1.011871 1.794615 -0.795156 -1.160517
two -1.659252 -0.473680 1.386762 -0.747361
baz one 2.200999 0.266427 -1.502801 -1.231025
two -0.854165 0.270410 -0.898856 0.066127
foo one 1.971631 0.143811 0.101862 -1.120642
two 1.236258 1.090704 -0.403456 1.793911
qux one 1.192296 -3.598376 0.913916 0.098270
two -1.917410 -1.085287 -2.223167 -0.450469
  • 0-레벨 인덱싱

df.loc["baz":"foo"]
0 1 2 3
baz one 2.200999 0.266427 -1.502801 -1.231025
two -0.854165 0.270410 -0.898856 0.066127
foo one 1.971631 0.143811 0.101862 -1.120642
two 1.236258 1.090704 -0.403456 1.793911
  • (0, 1)-레벨 인덱싱

df.loc[("baz", "two"):("qux", "one")]
0 1 2 3
baz two -0.854165 0.270410 -0.898856 0.066127
foo one 1.971631 0.143811 0.101862 -1.120642
two 1.236258 1.090704 -0.403456 1.793911
qux one 1.192296 -3.598376 0.913916 0.098270

튜플들의 리스트를 지정하면 리인덱싱처럼 작동한다.

df.loc[[("bar", "two"), ("qux", "one")]]
0 1 2 3
bar two -1.659252 -0.473680 1.386762 -0.747361
qux one 1.192296 -3.598376 0.913916 0.098270

이외에 slice() 함수와 pd.IndexSlice 객체를 사용하는 방법도 있지만 여기서는 다루지 않는다.

18.3. 그룹 분류: pd.groupby() 함수#

pd.groupby() 함수는 다음 3 기능을 제공한다.

  • 쪼개기Splitting: 데이터를 조건에 따라 여러 그룹으로 쪼개기

  • 적용하기Applying: 그룹별로 함수 적용

  • 조합하기Combining: 그룹별 함수 적용 결과를 조합하여 새로운 데이터프레임/시리즈 생성

df = pd.DataFrame({'A': ['foo', 'bar', 'foo', 'bar',
                         'foo', 'bar', 'foo', 'bar'],
                   'B': ['one', 'one', 'two', 'three',
                         'two', 'two', 'one', 'three'],
                   'C': np.random.randn(8),
                   'D': np.random.randn(8)})

df
A B C D
0 foo one 1.587711 -0.037118
1 bar one -0.197821 -0.119866
2 foo two 2.817105 -1.268842
3 bar three 2.692870 -0.819414
4 foo two -1.005778 0.544011
5 bar two 0.068631 1.578997
6 foo one 0.373363 0.778729
7 bar three 0.377979 1.125497
  • A 열에 사용된 항목 기준으로 그룹으로 분류한 후 그룹별로 CD 열의 모든 항목의 합 계산해서 새로운 데이터프레임 생성

    A

    경우의 수

    bar

    1

    foo

    1

df.groupby('A')[["C", "D"]].sum()
C D
A
bar 2.941660 1.765215
foo 3.772401 0.016780
  • A열의 항목과 B 열의 항목의 조합을 기준으로 그룹으로 그룹별로 CD 열의 모든 항목의 합 계산해서 새로운 데이터프레임 생성

    A

    B

    경우의 수

    bar

    one, three, two

    3

    foo

    one, two

    2

df.groupby(["A", "B"]).sum()
C D
A B
bar one -0.197821 -0.119866
three 3.070849 0.306083
two 0.068631 1.578997
foo one 1.961074 0.741611
two 1.811327 -0.724831

그룹 확인

  • for 반복문 활용

for name, group in df.groupby(["A", "B"]):
    print(name)
    print(group)
('bar', 'one')
     A    B         C         D
1  bar  one -0.197821 -0.119866
('bar', 'three')
     A      B         C         D
3  bar  three  2.692870 -0.819414
7  bar  three  0.377979  1.125497
('bar', 'two')
     A    B         C         D
5  bar  two  0.068631  1.578997
('foo', 'one')
     A    B         C         D
0  foo  one  1.587711 -0.037118
6  foo  one  0.373363  0.778729
('foo', 'two')
     A    B         C         D
2  foo  two  2.817105 -1.268842
4  foo  two -1.005778  0.544011
  • get_group() 메서드

df.groupby(["A", "B"]).get_group(('bar', 'one'))
A B C D
1 bar one -0.197821 -0.119866
df.groupby(["A", "B"]).get_group(('bar', 'three'))
A B C D
3 bar three 2.692870 -0.819414
7 bar three 0.377979 1.125497
  • groups 속성

df.groupby(["A", "B"]).groups
{('bar', 'one'): [1], ('bar', 'three'): [3, 7], ('bar', 'two'): [5], ('foo', 'one'): [0, 6], ('foo', 'two'): [2, 4]}
  • value_counts 속성

df.groupby(["A", "B"]).value_counts()
A    B      C          D        
bar  one    -0.197821  -0.119866    1
     three   0.377979   1.125497    1
             2.692870  -0.819414    1
     two     0.068631   1.578997    1
foo  one     0.373363   0.778729    1
             1.587711  -0.037118    1
     two    -1.005778   0.544011    1
             2.817105  -1.268842    1
dtype: int64
  • nunique 속성

df.groupby(["A", "B"]).nunique()
C D
A B
bar one 1 1
three 2 2
two 1 1
foo one 2 2
two 2 2
  • sort=True 키워드 인자

df.groupby(["A", "B"], sort=True).sum()
C D
A B
bar one -0.197821 -0.119866
three 3.070849 0.306083
two 0.068631 1.578997
foo one 1.961074 0.741611
two 1.811327 -0.724831
df.groupby(["A", "B"], sort=False).sum()
C D
A B
foo one 1.961074 0.741611
bar one -0.197821 -0.119866
foo two 1.811327 -0.724831
bar three 3.070849 0.306083
two 0.068631 1.578997
df.groupby(["A", "B"], sort=False).nunique()
C D
A B
foo one 2 2
bar one 1 1
foo two 2 2
bar three 2 2
two 1 1

그룹 연산 예제

  • max() 메서드

df.groupby('A')[["C", "D"]].max()
C D
A
bar 2.692870 1.578997
foo 2.817105 0.778729
df.groupby(["A", "B"]).max()
C D
A B
bar one -0.197821 -0.119866
three 2.692870 1.125497
two 0.068631 1.578997
foo one 1.587711 0.778729
two 2.817105 0.544011
  • mean() 메서드

df.groupby('A')[["C", "D"]].mean()
C D
A
bar 0.735415 0.441304
foo 0.943100 0.004195
df.groupby(["A", "B"]).mean()
C D
A B
bar one -0.197821 -0.119866
three 1.535425 0.153041
two 0.068631 1.578997
foo one 0.980537 0.370805
two 0.905664 -0.362415
  • size() 메서드

df.groupby('A')[["C", "D"]].size()
A
bar    4
foo    4
dtype: int64
df.groupby(["A", "B"]).size()
A    B    
bar  one      1
     three    2
     two      1
foo  one      2
     two      2
dtype: int64
  • describe() 메서드

df.groupby('A')[["C", "D"]].describe()
C D
count mean std min 25% 50% 75% max count mean std min 25% 50% 75% max
A
bar 4.0 0.735415 1.326011 -0.197821 0.002018 0.223305 0.956702 2.692870 4.0 0.441304 1.105560 -0.819414 -0.294753 0.502815 1.238872 1.578997
foo 4.0 0.943100 1.638103 -1.005778 0.028578 0.980537 1.895059 2.817105 4.0 0.004195 0.915357 -1.268842 -0.345049 0.253447 0.602691 0.778729
df.groupby(["A", "B"]).describe()
C D
count mean std min 25% 50% 75% max count mean std min 25% 50% 75% max
A B
bar one 1.0 -0.197821 NaN -0.197821 -0.197821 -0.197821 -0.197821 -0.197821 1.0 -0.119866 NaN -0.119866 -0.119866 -0.119866 -0.119866 -0.119866
three 2.0 1.535425 1.636875 0.377979 0.956702 1.535425 2.114147 2.692870 2.0 0.153041 1.375259 -0.819414 -0.333186 0.153041 0.639269 1.125497
two 1.0 0.068631 NaN 0.068631 0.068631 0.068631 0.068631 0.068631 1.0 1.578997 NaN 1.578997 1.578997 1.578997 1.578997 1.578997
foo one 2.0 0.980537 0.858673 0.373363 0.676950 0.980537 1.284124 1.587711 2.0 0.370805 0.576891 -0.037118 0.166844 0.370805 0.574767 0.778729
two 2.0 0.905664 2.703187 -1.005778 -0.050057 0.905664 1.861384 2.817105 2.0 -0.362415 1.281881 -1.268842 -0.815629 -0.362415 0.090798 0.544011

18.4. 모양 변환Reshaping#

18.4.1. 항목 재배열#

스택

열 인덱스의 레벨을 하나 줄일 때 사용한다. 없어진 레벨은 행 인덱스의 마지막 레벨로 추가된다.

index
MultiIndex([('bar', 'one'),
            ('bar', 'two'),
            ('baz', 'one'),
            ('baz', 'two'),
            ('foo', 'one'),
            ('foo', 'two'),
            ('qux', 'one'),
            ('qux', 'two')],
           names=['first', 'second'])
df = pd.DataFrame(np.random.randn(8, 2), index=index, columns=["A", "B"])
df
A B
first second
bar one 1.532293 0.053124
two -1.328301 -1.943130
baz one 1.713431 -2.052346
two -0.148125 2.166299
foo one 0.251638 0.299314
two -0.834706 0.250370
qux one 0.889168 -0.181520
two -0.139113 -0.984504
df2 = df[:4]
df2
A B
first second
bar one 1.532293 0.053124
two -1.328301 -1.943130
baz one 1.713431 -2.052346
two -0.148125 2.166299
  • stack() 메서드: 열이 한 개의 레벨로 구성되어 있기에 stack() 메서드를 적용하면 결국 모든 열이 없어지고, 열의 라벨은 인덱스의 마지막 레벨의 라벨로 변환된다. 여기서는 결국 3중 인덱스를 사용하는 시리즈를 생성한다.

stacked = df2.stack()
stacked
first  second   
bar    one     A    1.532293
               B    0.053124
       two     A   -1.328301
               B   -1.943130
baz    one     A    1.713431
               B   -2.052346
       two     A   -0.148125
               B    2.166299
dtype: float64

언스택

행 인덱스의 지정된 레벨을 열의 마지막 레벨로 변환한다. 인자를 지정하지 않으면 마지막 레벨을 변환한다.

  • unstack() 메서드

stacked.unstack()
A B
first second
bar one 1.532293 0.053124
two -1.328301 -1.943130
baz one 1.713431 -2.052346
two -0.148125 2.166299
stacked.unstack().unstack()
A B
second one two one two
first
bar 1.532293 -1.328301 0.053124 -1.943130
baz 1.713431 -0.148125 -2.052346 2.166299

인자를 지정하면 해당 레벨을 열의 마지막 레벨로 변환한다.

stacked.unstack(0)
first bar baz
second
one A 1.532293 1.713431
B 0.053124 -2.052346
two A -1.328301 -0.148125
B -1.943130 2.166299
stacked.unstack(1)
second one two
first
bar A 1.532293 -1.328301
B 0.053124 -1.943130
baz A 1.713431 -0.148125
B -2.052346 2.166299

18.4.2. 피버팅#

pd.pivot_table() 함수

예제

import datetime

df = pd.DataFrame(
    {
        "A": ["one", "one", "two", "three"] * 6,
        "B": ["A", "B", "C"] * 8,
        "C": ["foo", "foo", "foo", "bar", "bar", "bar"] * 4,
        "D": np.random.randn(24),
        "E": np.random.randn(24),
    }
)

df
A B C D E
0 one A foo -2.878726 -2.064460
1 one B foo 1.450555 0.130506
2 two C foo -1.023284 -0.070181
3 three A bar 0.360537 1.091334
4 one B bar 2.076769 -0.766590
5 one C bar -0.226304 0.575288
6 two A foo 0.875535 -2.104198
7 three B foo -0.981223 0.452921
8 one C foo -0.254173 -0.573222
9 one A bar 1.322995 0.695488
10 two B bar 0.006313 -0.070619
11 three C bar 0.263893 0.670962
12 one A foo -1.089184 0.158075
13 one B foo 1.034373 -0.508421
14 two C foo 0.484234 -0.955311
15 three A bar 0.693513 1.219480
16 one B bar -1.147675 -0.729457
17 one C bar -0.772348 0.525151
18 two A foo 0.906707 -1.395497
19 three B foo 1.194608 0.374196
20 one C foo -1.908083 -1.584192
21 one A bar 0.074390 1.095922
22 two B bar -0.443055 -2.268940
23 three C bar -1.182320 0.442309
pd.pivot_table(df, values="D", index=["A", "B"], columns=["C"]) # aggfunc=np.mean 이 기본값
C bar foo
A B
one A 0.698693 -1.983955
B 0.464547 1.242464
C -0.499326 -1.081128
three A 0.527025 NaN
B NaN 0.106693
C -0.459214 NaN
two A NaN 0.891121
B -0.218371 NaN
C NaN -0.269525
pd.pivot_table(df, values="D", index=["A", "B"], columns=["C"], aggfunc=np.sum)
C bar foo
A B
one A 1.397385 -3.967910
B 0.929094 2.484928
C -0.998652 -2.162256
three A 1.054050 NaN
B NaN 0.213385
C -0.918427 NaN
two A NaN 1.782242
B -0.436741 NaN
C NaN -0.539050

DataFrame.pivot() 메서드

df
A B C D E
0 one A foo -2.878726 -2.064460
1 one B foo 1.450555 0.130506
2 two C foo -1.023284 -0.070181
3 three A bar 0.360537 1.091334
4 one B bar 2.076769 -0.766590
5 one C bar -0.226304 0.575288
6 two A foo 0.875535 -2.104198
7 three B foo -0.981223 0.452921
8 one C foo -0.254173 -0.573222
9 one A bar 1.322995 0.695488
10 two B bar 0.006313 -0.070619
11 three C bar 0.263893 0.670962
12 one A foo -1.089184 0.158075
13 one B foo 1.034373 -0.508421
14 two C foo 0.484234 -0.955311
15 three A bar 0.693513 1.219480
16 one B bar -1.147675 -0.729457
17 one C bar -0.772348 0.525151
18 two A foo 0.906707 -1.395497
19 three B foo 1.194608 0.374196
20 one C foo -1.908083 -1.584192
21 one A bar 0.074390 1.095922
22 two B bar -0.443055 -2.268940
23 three C bar -1.182320 0.442309
df1 = df.groupby(['A', 'B', 'C']).sum().reset_index()
df1
A B C D E
0 one A bar 1.397385 1.791411
1 one A foo -3.967910 -1.906385
2 one B bar 0.929094 -1.496047
3 one B foo 2.484928 -0.377915
4 one C bar -0.998652 1.100439
5 one C foo -2.162256 -2.157414
6 three A bar 1.054050 2.310814
7 three B foo 0.213385 0.827117
8 three C bar -0.918427 1.113271
9 two A foo 1.782242 -3.499695
10 two B bar -0.436741 -2.339559
11 two C foo -0.539050 -1.025492
df1.pivot(index=['A', 'B'], columns='C', values="D")
C bar foo
A B
one A 1.397385 -3.967910
B 0.929094 2.484928
C -0.998652 -2.162256
three A 1.054050 NaN
B NaN 0.213385
C -0.918427 NaN
two A NaN 1.782242
B -0.436741 NaN
C NaN -0.539050

18.5. 데이터셋 불러오기와 저장하기#

csv 파일 불러오기

df.to_csv("foo.csv")

csv 파일로 저장하기

pd.read_csv("foo.csv")
Unnamed: 0 A B C D E
0 0 one A foo -2.878726 -2.064460
1 1 one B foo 1.450555 0.130506
2 2 two C foo -1.023284 -0.070181
3 3 three A bar 0.360537 1.091334
4 4 one B bar 2.076769 -0.766590
5 5 one C bar -0.226304 0.575288
6 6 two A foo 0.875535 -2.104198
7 7 three B foo -0.981223 0.452921
8 8 one C foo -0.254173 -0.573222
9 9 one A bar 1.322995 0.695488
10 10 two B bar 0.006313 -0.070619
11 11 three C bar 0.263893 0.670962
12 12 one A foo -1.089184 0.158075
13 13 one B foo 1.034373 -0.508421
14 14 two C foo 0.484234 -0.955311
15 15 three A bar 0.693513 1.219480
16 16 one B bar -1.147675 -0.729457
17 17 one C bar -0.772348 0.525151
18 18 two A foo 0.906707 -1.395497
19 19 three B foo 1.194608 0.374196
20 20 one C foo -1.908083 -1.584192
21 21 one A bar 0.074390 1.095922
22 22 two B bar -0.443055 -2.268940
23 23 three C bar -1.182320 0.442309

엑셀 파일 불러오기

df.to_excel("foo.xlsx", sheet_name="Sheet1")

엑셀 파일로 저장하기

pd.read_excel("foo.xlsx", "Sheet1", index_col=None, na_values=["NA"])
Unnamed: 0 A B C D E
0 0 one A foo -2.878726 -2.064460
1 1 one B foo 1.450555 0.130506
2 2 two C foo -1.023284 -0.070181
3 3 three A bar 0.360537 1.091334
4 4 one B bar 2.076769 -0.766590
5 5 one C bar -0.226304 0.575288
6 6 two A foo 0.875535 -2.104198
7 7 three B foo -0.981223 0.452921
8 8 one C foo -0.254173 -0.573222
9 9 one A bar 1.322995 0.695488
10 10 two B bar 0.006313 -0.070619
11 11 three C bar 0.263893 0.670962
12 12 one A foo -1.089184 0.158075
13 13 one B foo 1.034373 -0.508421
14 14 two C foo 0.484234 -0.955311
15 15 three A bar 0.693513 1.219480
16 16 one B bar -1.147675 -0.729457
17 17 one C bar -0.772348 0.525151
18 18 two A foo 0.906707 -1.395497
19 19 three B foo 1.194608 0.374196
20 20 one C foo -1.908083 -1.584192
21 21 one A bar 0.074390 1.095922
22 22 two B bar -0.443055 -2.268940
23 23 three C bar -1.182320 0.442309