21. 데이터 결합/합병#

import numpy as np
import pandas as pd
pd.options.display.max_rows = 20
pd.options.display.max_colwidth = 80
pd.options.display.max_columns = 20
np.random.seed(12345)
import matplotlib.pyplot as plt

plt.rc("figure", figsize=(10, 6))
np.set_printoptions(precision=4, suppress=True)

21.1. 데이터 결합#

pd.concat() 함수는 np.concatenate()와 유사하게 작동한다.

arr1 = np.arange(12).reshape((3, 4))
arr1
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
arr2 = np.arange(10, 22).reshape((3, 4))
arr2
array([[10, 11, 12, 13],
       [14, 15, 16, 17],
       [18, 19, 20, 21]])
np.concatenate([arr1, arr2], axis=1)
array([[ 0,  1,  2,  3, 10, 11, 12, 13],
       [ 4,  5,  6,  7, 14, 15, 16, 17],
       [ 8,  9, 10, 11, 18, 19, 20, 21]])

21.1.1. 시리즈 결합#

시리즈 종 결합

s1 = pd.Series([0, 1], index=["a", "b"], dtype="Int64")
s1
a    0
b    1
dtype: Int64
s2 = pd.Series([2, 3, 4], index=["c", "d", "e"], dtype="Int64")
s2
c    2
d    3
e    4
dtype: Int64
s3 = pd.Series([5, 6], index=["f", "g"], dtype="Int64")
s3
f    5
g    6
dtype: Int64
pd.concat([s1, s2, s3])
a    0
b    1
c    2
d    3
e    4
f    5
g    6
dtype: Int64

시리즈 횡 결합

pd.concat([s1, s2, s3], axis="columns")
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
  • axis="columns", keys=["one", "two", "three": 횡으로 결합하는 경우 keys로 지정된 값은 열의 라벨로 사용.

pd.concat([s1, s2, s3], axis="columns", keys=["one", "two", "three"])
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
  • join="inner": 공동 인덱스 라벨만 사용

s4 = pd.concat([s1, s3])
s4
a    0
b    1
f    5
g    6
dtype: Int64
pd.concat([s1, s4], axis="columns", join="inner")
0 1
a 0 0
b 1 1

21.1.2. 데이터프레임 결합#

시리즈의 경우와 유사하게 작동한다.

df1 = pd.DataFrame(np.arange(6).reshape(3, 2), index=["a", "b", "c"],
                   columns=["one", "two"])
df1
one two
a 0 1
b 2 3
c 4 5
df2 = pd.DataFrame(5 + np.arange(4).reshape(2, 2), index=["a", "c"],
                   columns=["three", "four"])
df2
three four
a 5 6
c 7 8
pd.concat([df1, df2], axis="columns")
one two three four
a 0 1 5.0 6.0
b 2 3 NaN NaN
c 4 5 7.0 8.0

행 인덱스 무시하기

행의 인덱스가 중요하지 않다면 결합 후에 정수 인덱스로 초기화할 수 있다.

df1 = pd.DataFrame(np.random.standard_normal((3, 4)),
                   columns=["a", "b", "c", "d"])
df1
a b c d
0 -0.204708 0.478943 -0.519439 -0.555730
1 1.965781 1.393406 0.092908 0.281746
2 0.769023 1.246435 1.007189 -1.296221
df2 = pd.DataFrame(np.random.standard_normal((2, 3)),
                   columns=["b", "d", "a"])
df2
b d a
0 0.274992 0.228913 1.352917
1 0.886429 -2.001637 -0.371843
  • ignore_index=True: 결합 후 인덱스 초기화

pd.concat([df1, df2], ignore_index=True)
a b c d
0 -0.204708 0.478943 -0.519439 -0.555730
1 1.965781 1.393406 0.092908 0.281746
2 0.769023 1.246435 1.007189 -1.296221
3 1.352917 0.274992 NaN 0.228913
4 -0.371843 0.886429 NaN -2.001637
  • ignore_index=False 기본값을 사용하면 기존 인덱스 그대로 사용

pd.concat([df1, df2]) # ignore_index=False
a b c d
0 -0.204708 0.478943 -0.519439 -0.555730
1 1.965781 1.393406 0.092908 0.281746
2 0.769023 1.246435 1.007189 -1.296221
0 1.352917 0.274992 NaN 0.228913
1 -0.371843 0.886429 NaN -2.001637

21.2. 데이터 합병#

두 데이터프레임의 특정 키 또는 행 인덱스를 기준으로 합병하는 다양한 방식을 살펴 본다.

21.2.1. 단일키 합병#

공통 열 라벨이 있는 경우

df1 = pd.DataFrame({"key": ["b", "b", "a", "c", "a", "a", "b"],
                    "data1": pd.Series(range(7), dtype="Int64")})

df1
key data1
0 b 0
1 b 1
2 a 2
3 c 3
4 a 4
5 a 5
6 b 6
df2 = pd.DataFrame({"key": ["a", "b", "d"],
                    "data2": pd.Series(range(3), dtype="Int64")})

df2
key data2
0 a 0
1 b 1
2 d 2
  • on="key": 열 라벨 "key"를 기준으로 합병. 두 데이터프레임에 모두 사용된 라벨만 대상으로 함. 이유는 how="inner"가 기본값이기 때문.

key

data1

data2

경우의 수

b

0, 1, 6

1

3

a

2, 4, 5

0

3

pd.merge(df1, df2, on="key") # how="inner"
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
  • on=None 기본값: 공동으로 사용된 열 라벨을 기준으로 합병. 따라서 여기서는 위 결과와 동일.

pd.merge(df1, df2) # on=None
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

참고: 합병 기준값으로 사용된 라벨의 순서는 특정되지 않는다.

  • how="outer" 키워드 인자: 양쪽 데이터프레임에 사용된 모든 라벨을 기준값으로 사용

key

data1

data2

경우의 수

b

0, 1, 6

1

3

a

2, 4, 5

0

3

c

3

NA

1

d

NA

2

1

pd.merge(df1, df2, how="outer")
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

공통 열 라벨이 없는 경우

df3 = pd.DataFrame({"lkey": ["b", "b", "a", "c", "a", "a", "b"],
                    "data1": pd.Series(range(7), dtype="Int64")})

df3
lkey data1
0 b 0
1 b 1
2 a 2
3 c 3
4 a 4
5 a 5
6 b 6
df4 = pd.DataFrame({"rkey": ["a", "b", "d"],
                    "data2": pd.Series(range(3), dtype="Int64")})

df4
rkey data2
0 a 0
1 b 1
2 d 2
  • left_onright_on: 합병 기준으로 사용될 열을 따로따로 지정

pd.merge(df3, df4, left_on="lkey", right_on="rkey")
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
pd.merge(df3, df4, left_on="lkey", right_on="rkey", how="outer")
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

다대다 합병

합병 과정에서 다뤄야 하는 경우의 수가 데카르트 곱Cartesian product 방식으로 정해지는 경우를 다룬다.

df1 = pd.DataFrame({"key": ["b", "b", "a", "c", "a", "b"],
                    "data1": pd.Series(range(6), dtype="Int64")})

df1
key data1
0 b 0
1 b 1
2 a 2
3 c 3
4 a 4
5 b 5
df2 = pd.DataFrame({"key": ["a", "b", "a", "b", "d"],
                    "data2": pd.Series(range(5), dtype="Int64")})

df2
key data2
0 a 0
1 b 1
2 a 2
3 b 3
4 d 4
  • how="left" 키워드 인자: 왼쪽 데이터프레임의 라벨만 사용

key

data1

data2

경우의 수

b

0, 1, 5

1, 3

6

a

2, 4

0, 2

4

c

3

NA

1

pd.merge(df1, df2, on="key", how="left")
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
  • how="inner" 키워드 인자: 양쪽 데이터프레임에 공동으로 사용된 라벨만 사용

key

data1

data2

경우의 수

b

0, 1, 5

1, 3

6

a

2, 4

0, 2

4

pd.merge(df1, df2, how="inner")
key data1 data2
0 b 0 1
1 b 0 3
2 b 1 1
3 b 1 3
4 b 5 1
5 b 5 3
6 a 2 0
7 a 2 2
8 a 4 0
9 a 4 2

21.2.2. 다중키 합병#

left = pd.DataFrame({"key1": ["foo", "foo", "bar"],
                     "key2": ["one", "two", "one"],
                     "lval": pd.Series([1, 2, 3], dtype='Int64')})
left
key1 key2 lval
0 foo one 1
1 foo two 2
2 bar one 3
right = pd.DataFrame({"key1": ["foo", "foo", "bar", "bar"],
                      "key2": ["one", "one", "one", "two"],
                      "rval": pd.Series([4, 5, 6, 7], dtype='Int64')})
right
key1 key2 rval
0 foo one 4
1 foo one 5
2 bar one 6
3 bar two 7
  • on=["key1", "key2"] 키워드 인자: 양쪽 데이트프레임이 공동으로 사용된 두 개의 키를 기준으로 사용

(key1, key2)

lval

rval

경우의 수

(foo, one)

1

4, 5

2

(bar, one)

3

6

1

pd.merge(left, right, on=["key1", "key2"]) # how="inner"
key1 key2 lval rval
0 foo one 1 4
1 foo one 1 5
2 bar one 3 6
  • on=None 기본값: 공동 키만 대상. 여기서는 위 결과와 동일.

pd.merge(left, right) # on=None
key1 key2 lval rval
0 foo one 1 4
1 foo one 1 5
2 bar one 3 6
  • how=outer 키워드 인자: 양쪽 데이트프레임에 포함된 모든 키를 기준으로 사용

(key1, key2)

lval

rval

경우의 수

(foo, one)

1

4, 5

2

(foo, two)

2

NA

1

(bar, one)

3

6

1

(bar, two)

NA

7

1

pd.merge(left, right, on=["key1", "key2"], how="outer")
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
  • on=key1 키워드 인자: key1 라벨 기분으로 합병. 공동으로 사용된 키는 구분을 위해 자동으로 _x, _y 접미사 사용.

key1

(key2_x, lval)

(key2_y, rval)

경우의 수

foo

(one, 1), (two, 2)

(one, 4), (one, 5)

4

bar

(one, 3)

(one, 6), (one, 7)

2

pd.merge(left, right, on="key1")
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

접미사를 지정할 수도 있다.

pd.merge(left, right, on="key1", suffixes=("_left", "_right"))
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

21.2.3. 인덱스 기준 합병#

left1 = pd.DataFrame({"key": ["a", "b", "a", "a", "b", "c"],
                      "value": pd.Series(range(6), dtype="Int64")})

left1
key value
0 a 0
1 b 1
2 a 2
3 a 3
4 b 4
5 c 5
right1 = pd.DataFrame({"group_val": [3.5, 7]}, index=["a", "b"])

right1
group_val
a 3.5
b 7.0
  • left_on="key", right_index=True: 왼쪽 데이터 프레임의 "key" 열과 오른쪽 프레임의 인덱스를 기준으로 합병 c는 포함되지 않음에 주의.

key(인덱스)

value

group_val

경우의 수

a

0, 2, 4

3.5

3

b

1, 4

7.0

2

pd.merge(left1, right1, left_on="key", right_index=True)
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

아래와 같이 해도 된다.

pd.merge(right1, left1, right_on="key", left_index=True)
group_val key value
0 3.5 a 0
2 3.5 a 2
3 3.5 a 3
1 7.0 b 1
4 7.0 b 4

주의사항: 이전과는 달리 left1의 인덱스가 그대로 사용된다. 단지 순서가 합병 방식에 따라 다르게 지정될 뿐이다.

  • how="outer": c 값도 포함

key(인덱스)

value

group_val

경우의 수

a

0, 2, 4

3.5

3

b

1, 4

7.0

2

c

5

NA

1

pd.merge(left1, right1, left_on="key", right_index=True, how="outer")
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

인덱스만 활용해서 합병하기

동일한 하나의 키를 이용하는 것과 동일하게 작동한다.

left2 = pd.DataFrame([[1., 2.], [3., 4.], [5., 6.]],
                     index=["a", "c", "e"],
                     columns=["Ohio", "Nevada"]).astype("Int64")

left2
Ohio Nevada
a 1 2
c 3 4
e 5 6
right2 = pd.DataFrame([[7., 8.], [9., 10.], [11., 12.], [13, 14]],
                      index=["b", "c", "d", "e"],
                      columns=["Missouri", "Alabama"]).astype("Int64")

right2
Missouri Alabama
b 7 8
c 9 10
d 11 12
e 13 14
pd.merge(left2, right2, how="outer", left_index=True, right_index=True)
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

21.2.4. join() 메서드#

인덱스 기준으로 합병을 지원한다.

left2
Ohio Nevada
a 1 2
c 3 4
e 5 6
right2
Missouri Alabama
b 7 8
c 9 10
d 11 12
e 13 14
left2.join(right2) # how="left" 기본값
Ohio Nevada Missouri Alabama
a 1 2 <NA> <NA>
c 3 4 9 10
e 5 6 13 14
left2.join(right2, how="outer")
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

왼쪽 데이터프레임의 키와 오른쪽 데이터프레임의 인덱스를 기준으로 합병할 수 있다.

  • on=키 키워드 인자 활용

left1
key value
0 a 0
1 b 1
2 a 2
3 a 3
4 b 4
5 c 5
right1
group_val
a 3.5
b 7.0
  • left1의 “key” 열과 left2의 인덱스 기준 합병

left1.join(right1, on="key")
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

데이터프레임으로 구성된 인자를 join() 메서드의 인자로 사용하면 pd.concat(axis=1) 함수처럼 작동한다.

another = pd.DataFrame([[7., 8.], [9., 10.], [11., 12.], [16., 17.]],
                       index=["a", "c", "e", "f"],
                       columns=["New York", "Oregon"])
another
New York Oregon
a 7.0 8.0
c 9.0 10.0
e 11.0 12.0
f 16.0 17.0
left2
Ohio Nevada
a 1 2
c 3 4
e 5 6
right2
Missouri Alabama
b 7 8
c 9 10
d 11 12
e 13 14
left2.join([right2, another])
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
left2.join([right2, another], how="outer")
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