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"
가 기본값이기 때문.
|
|
|
경우의 수 |
---|---|---|---|
|
|
|
3 |
|
|
|
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"
키워드 인자: 양쪽 데이터프레임에 사용된 모든 라벨을 기준값으로 사용
|
|
|
경우의 수 |
---|---|---|---|
|
|
|
3 |
|
|
|
3 |
|
|
|
1 |
|
|
|
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_on
과right_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"
키워드 인자: 왼쪽 데이터프레임의 라벨만 사용
|
|
|
경우의 수 |
---|---|---|---|
|
|
|
6 |
|
|
|
4 |
|
|
|
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"
키워드 인자: 양쪽 데이터프레임에 공동으로 사용된 라벨만 사용
|
|
|
경우의 수 |
---|---|---|---|
|
|
|
6 |
|
|
|
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"]
키워드 인자: 양쪽 데이트프레임이 공동으로 사용된 두 개의 키를 기준으로 사용
|
|
|
경우의 수 |
---|---|---|---|
|
|
|
2 |
|
|
|
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
키워드 인자: 양쪽 데이트프레임에 포함된 모든 키를 기준으로 사용
|
|
|
경우의 수 |
---|---|---|---|
|
|
|
2 |
|
|
|
1 |
|
|
|
1 |
|
|
|
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
접미사 사용.
|
|
|
경우의 수 |
---|---|---|---|
|
|
|
4 |
|
|
|
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
는 포함되지 않음에 주의.
|
|
|
경우의 수 |
---|---|---|---|
|
|
|
3 |
|
|
|
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
값도 포함
|
|
|
경우의 수 |
---|---|---|---|
|
|
|
3 |
|
|
|
2 |
|
|
|
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 |