2.4. 데이터프레임의 결합#

데이터를 이용하여 분석을 하는 경우 사용할 자료가 두 개 이상이 경우는 매우 흔한 일이다. 실제로 데이터 분석에서 하나의 자료만 가지고 수행하는 일은 매우 드물다. 이 절에서는 여러 개의 자료를 사용하는 경우 두 개의 자료를 서로 결합하여 새로운 자료를 만드는 것을 실습한다. 살펴 볼 내용은 다음과 같다.

  • 데이터의 결합

  • 식별자의 불일치

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

간단한 예제#

두 개의 데이터프레임 df1df2 가 다음과 같이 주어졌다고 하자.

df1 = pd.DataFrame({
    "name" : ["철수", "영이", "John"],
    "age"  : [23, 34, 19]
})
df1
name age
0 철수 23
1 영이 34
2 John 19
df2 = pd.DataFrame({
    "name" : ["철수", "영이", "John"],
    "sex"  : ["M", "F", "M"]
})
df2
name sex
0 철수 M
1 영이 F
2 John M

데이터프레임 df1df2 에는 같은 이름을 가지는 열 name 이 있다. 우리는 두 개의 데이터프레임을 합쳐서 나이(age)와 성(sex) 이 모두 포함된 새로운 데이터프레임을 만들려고 한다.

이렇게 공통으로 포함된 열의 정보를 이용하여 두 개의 데이터프레임을 결합하려면 pandas 라이브러리의 merge() 함수 를 이용한다.

  • merge() 함수의 첫 번째(왼쪽)와 두 번째 인자(오른쪽)에는 결합할 데이터프레임의 이름을 넣어준다.

  • 앞에 pd. 를 붙여서 pd.merge() 로 사용하는 것은 함수 merge() 가 pandas 라이브러리에 있다는 것을 알려준다.

  • 선택문 on= 에 두 데이터프레임에 결합의 기준이 되는 열이름을 문자열로 지정해준다. 결합의 기준으로 사용되는 열이름은 두 개의 데이터프레임에 모두 존재해야 한다.

  • 자료의 결합에 사용되는 공통으로 포함된 열의 내용을 식별자(key, identifier,..) 라고 부른다. 이 예제에서 식별자는 사람의 이름이다.

다음 코드의 결과를 먼저 보자.

pd.merge(df1, df2, on="name")
name age sex
0 철수 23 M
1 영이 34 F
2 John 19 M

두 개의 데이터프레임이 지정된 열 name 에 의하여 결합되어 나이(age)와 성(sex)이 같이 나타나게 된다.

식별자의 불일치#

만약 두 개의 데이터프레임에 있는 식별자에 포함된 자료가 다르면 어떻게 될까?

다음과 같이 John 의 자료가 빠져 있는 데이터프레임 df3df1 과 결합해 보자.

df3 = pd.DataFrame({
    "name" : ["철수", "영이"],
    "weight"  : [55, 44]
})
df3
name weight
0 철수 55
1 영이 44
pd.merge(df1, df3, on="name")
name age weight
0 철수 23 55
1 영이 34 44

위의 결과에서 John 의 자료가 사라져 버렸다. 물론 몸무게 값이 없는 John 의 자료가 필요없을 수도 있지만 많은 경우 자료를 유지해야 한다.

이렇게 식별자의 항목이 다른 경우, 결합의 기준이 되는 식별자를 데이터프레임의 위치로 지정할 수 있다. 위에서 함수 merge()를 설명할 때 사용되는 데이터프레임의 위치에 따라서 왼쪽 (첫 번째 인자) 과 오른쪽 (두 번째 인자)으로 나타냈다.

pd.merge(left_df, right_df, on="name", how="inner")
  • how='left' : 식별자는 왼쪽 데이터프레임에만 있는 것으로 선택

  • how='right' : 식별자는 오른쪽 데이터프레임에만 있는 것으로 선택

  • how='inner' : 식별자는 두 데이터프레임에 공통인 것으로 선택

  • how='outer' : 식별자는 두 데이터프레임에 나타난 모든 것으로 선택

식별자를 선택하는 선택명령문 how= 을 지정하지 않으면 자동으로 how='inner' 이 지정된다.

merge.png

이제 결합시 기준이 되는 식별자가 왼쪽에 있는 데이터프레임 df1 에 있다는 것을 선택명령문 how='left' 로 알려주자.

pd.merge(df1, df3, on="name", how='left')
name age weight
0 철수 23 55.0
1 영이 34 44.0
2 John 19 NaN

이제 John 의 자료가 나타나고 John 의 키는 결측값(NaN) 으로 표시된다.

만약 두 개의 식별자에 서로 다른 내용이 나타나면 어떻게 될까?

이제 새로운 사람 흥민의 자료를 가진 데이터프레임 df4df1 과 결합하는 예를 살펴보자. 아래 코드에서 선택명령문 how= 에 지정된 문자열에 따라서 결합의 결과가 어떻게 다른지 보자.

df4 = pd.DataFrame({
    "name" : ["철수", "영이", "흥민"],
    "height"  : [167, 175, 183]
})
df4
name height
0 철수 167
1 영이 175
2 흥민 183

위에서 본 바와 같이 선택명령문 how='left'이 결합시 기준이 데이터프레임 df1 에 있다는 것을 알려준다.

pd.merge(df1, df4, on="name", how='left')  
name age height
0 철수 23 167.0
1 영이 34 175.0
2 John 19 NaN

선택명령문 how='right'이 결합시 기준이 데이터프레임 df4 에 있다는 것을 알려준다.

pd.merge(df1, df4, on="name", how='right') 
name age height
0 철수 23.0 167
1 영이 34.0 175
2 흥민 NaN 183

선택명령문 how='inner'이 결합시 기준이 데이터프레임 df1& df4 에서 공통된 것임을 알려준다.

pd.merge(df1, df4, on="name", how='inner')
name age height
0 철수 23 167
1 영이 34 175

선택명령문 how='outer'이 결합시 기준이 데이터프레임 df1& df4의 모든 것임을 알려준다.

pd.merge(df1, df4, on="name", how='outer') 
name age height
0 철수 23.0 167.0
1 영이 34.0 175.0
2 John 19.0 NaN
3 흥민 NaN 183.0

전체 사업장 데이터와 음식 사업장 데이터의 결합#

이번에는 전체 사업장 데이터 (전체_사업장.csv)와 음식 사업장 데이터 (음식_사업장.csv) 을 결합하는 작업을 해보려고 한다. 전체 사업장 데이터에는 2010년부터 2021년까지 11년간의 7개 특별광역시(서울, 인천, 대전, 광주, 대구, 울산, 부산) 정보가 있다.

total = pd.read_csv("https://uos-bigdata.github.io/lab_data/docs/assets/data_lab_bokji/dat_total.csv", encoding = 'UTF8')
total
연도 시도 산재_평균근로자수 산재만_가입된사업장수 산재_가입된사업장수 고용_평균근로자수 고용만_가입된사업장수 고용_가입된사업장수
0 2010.0 광주 6.863187 52 1820 6.847419 1 1763
1 2010.0 대구 7.366627 82 2523 7.051240 7 2420
2 2010.0 대전 9.100746 59 1340 8.816680 3 1271
3 2010.0 부산 8.126126 121 3108 7.622692 11 2979
4 2010.0 서울 10.971553 343 11741 10.869985 30 11391
... ... ... ... ... ... ... ... ...
79 2021.0 대전 2.658709 434 7917 1.887973 369 8007
80 2021.0 부산 2.874647 688 17367 1.909389 660 17680
81 2021.0 서울 7.487265 2375 59009 2.020801 1410 58700
82 2021.0 울산 3.442591 273 5095 2.080108 262 5193
83 2021.0 인천 2.604997 703 15648 1.916362 444 15615

84 rows × 8 columns

음식 사업장 데이터에는 2017년부터 2021년까지 5년간의 7개 특별광역시(서울, 인천, 대전, 광주, 대구, 울산, 부산) 정보가 있다.

food = pd.read_csv("https://uos-bigdata.github.io/lab_data/docs/assets/data_lab_bokji/data_food.csv", encoding = 'UTF8')
food
시도 연도 산재_음식사업장수 고용_음식사업장수
0 광주 2017.0 796 801
1 광주 2018.0 1121 1136
2 광주 2019.0 1674 1679
3 광주 2020.0 1757 1763
4 광주 2021.0 1727 1734
5 대구 2017.0 1367 1375
6 대구 2018.0 1739 1749
7 대구 2019.0 2523 2531
8 대구 2020.0 2428 2434
9 대구 2021.0 2619 2624
10 대전 2017.0 933 927
11 대전 2018.0 1093 1131
12 대전 2019.0 1588 1606
13 대전 2020.0 1767 1781
14 대전 2021.0 1634 1652
15 부산 2017.0 1713 1720
16 부산 2018.0 2159 2168
17 부산 2019.0 3205 3215
18 부산 2020.0 3508 3508
19 부산 2021.0 3829 3836
20 서울 2017.0 6069 6117
21 서울 2018.0 7967 8000
22 서울 2019.0 10576 10596
23 서울 2020.0 10612 10631
24 서울 2021.0 10752 10796
25 울산 2017.0 541 541
26 울산 2018.0 651 654
27 울산 2019.0 996 998
28 울산 2020.0 1137 1138
29 울산 2021.0 1046 1048
30 인천 2017.0 1319 1318
31 인천 2018.0 1817 1835
32 인천 2019.0 2718 2731
33 인천 2020.0 2886 2892
34 인천 2021.0 2891 2898

연도,시도로 데이터를 결합해줄 것이기 때문에 먼저 사업장 데이터처럼 음식 사업장 데이터를 연도 기준으로 정렬해준다.

food = food.sort_values(by ="연도")
food
시도 연도 산재_음식사업장수 고용_음식사업장수
0 광주 2017.0 796 801
20 서울 2017.0 6069 6117
30 인천 2017.0 1319 1318
5 대구 2017.0 1367 1375
10 대전 2017.0 933 927
25 울산 2017.0 541 541
15 부산 2017.0 1713 1720
21 서울 2018.0 7967 8000
11 대전 2018.0 1093 1131
26 울산 2018.0 651 654
6 대구 2018.0 1739 1749
31 인천 2018.0 1817 1835
1 광주 2018.0 1121 1136
16 부산 2018.0 2159 2168
22 서울 2019.0 10576 10596
32 인천 2019.0 2718 2731
27 울산 2019.0 996 998
17 부산 2019.0 3205 3215
2 광주 2019.0 1674 1679
12 대전 2019.0 1588 1606
7 대구 2019.0 2523 2531
18 부산 2020.0 3508 3508
3 광주 2020.0 1757 1763
28 울산 2020.0 1137 1138
33 인천 2020.0 2886 2892
8 대구 2020.0 2428 2434
23 서울 2020.0 10612 10631
13 대전 2020.0 1767 1781
24 서울 2021.0 10752 10796
29 울산 2021.0 1046 1048
4 광주 2021.0 1727 1734
14 대전 2021.0 1634 1652
19 부산 2021.0 3829 3836
9 대구 2021.0 2619 2624
34 인천 2021.0 2891 2898

두 데이터를 결합하기에 앞서 전체 사업장 데이터(total)와 음식 사업장 데이터(food)를 가공해 줄 것이다.

  • total에서는 결합에 필요한 연도, 시도, 산재만_가입된사업장수, 고용_가입된사업장수 열을 추출하고 2015년~2018년 4년도의 데이터를 저장한다. 시도 열 중에서도 서울, 부산, 인천의 데이터만 사용할 것이다.

  • food에서는 2017~2019년 3년도의 데이터를 추출할 것이다. 시도 열 중에서도 서울, 부산, 인천의 데이터만 사용할 것이다.

total.columns =["연도","시도","산재_평균근로자수","산재만_가입된사업장수","산재_가입된사업장수","고용_평균근로자수", "고용만_가입된사업장수","고용_가입된사업장수"]
total_sub = total.loc[:,["연도", "시도","산재_가입된사업장수", "고용_가입된사업장수"]]
total_sub
연도 시도 산재_가입된사업장수 고용_가입된사업장수
0 2010.0 광주 1820 1763
1 2010.0 대구 2523 2420
2 2010.0 대전 1340 1271
3 2010.0 부산 3108 2979
4 2010.0 서울 11741 11391
... ... ... ... ...
79 2021.0 대전 7917 8007
80 2021.0 부산 17367 17680
81 2021.0 서울 59009 58700
82 2021.0 울산 5095 5193
83 2021.0 인천 15648 15615

84 rows × 4 columns

total_sub = total_sub.loc[(total_sub["연도"]>=2015)&(total_sub["연도"]<=2018)]
total_sub= total_sub.loc[(total_sub["시도"]=="서울")|(total_sub["시도"]=="부산")|(total_sub["시도"]=="인천")]
total_sub = total_sub.reset_index(drop=True)
total_sub
연도 시도 산재_가입된사업장수 고용_가입된사업장수
0 2015.0 부산 7423 7289
1 2015.0 서울 26025 25641
2 2015.0 인천 5850 5702
3 2016.0 부산 8507 8275
4 2016.0 서울 30761 30038
5 2016.0 인천 6755 6602
6 2017.0 부산 9748 9543
7 2017.0 서울 34718 34114
8 2017.0 인천 8013 7800
9 2018.0 부산 12433 11640
10 2018.0 서울 45766 43654
11 2018.0 인천 11109 10323
food_sub = food.loc[(food["연도"]>=2017)&(food["연도"]<=2019)]
food_sub = food.loc[(food["시도"]=="서울")|(food["시도"]=="부산")|(food["시도"]=="인천")]
food_sub = food_sub.reset_index(drop=True)
food_sub
시도 연도 산재_음식사업장수 고용_음식사업장수
0 서울 2017.0 6069 6117
1 인천 2017.0 1319 1318
2 부산 2017.0 1713 1720
3 서울 2018.0 7967 8000
4 인천 2018.0 1817 1835
5 부산 2018.0 2159 2168
6 서울 2019.0 10576 10596
7 인천 2019.0 2718 2731
8 부산 2019.0 3205 3215
9 부산 2020.0 3508 3508
10 인천 2020.0 2886 2892
11 서울 2020.0 10612 10631
12 서울 2021.0 10752 10796
13 부산 2021.0 3829 3836
14 인천 2021.0 2891 2898

데이터프레임 total_subfood_sub 에는 같은 이름을 가지는 열 연도시도가 있다. 우리는 두 개의 데이터프레임을 전체 사업장과 음식 사업장 모두를 한 번에 볼 수 있는 새로운 데이터프레임을 만들 것이다.

아래의 결과를 보면 두 개의 데이터프레임이 지정된 열 연도시도 에 의하여 결합되어 나타나게 된다. 결과에서 2015년,2016년,2019년 의 자료가 사라져 버린 것을 확인할 수 있다. how='를 따로 설정해주지 않았기 때문에 기본값인how='inner'로 결과가 나타난다. 물론 2015년,2016년,2019년 자료가 필요없을 수도 있지만 많은 경우 자료를 유지해야 한다.

pd.merge(total_sub, food_sub, on=["연도","시도"])
연도 시도 산재_가입된사업장수 고용_가입된사업장수 산재_음식사업장수 고용_음식사업장수
0 2017.0 부산 9748 9543 1713 1720
1 2017.0 서울 34718 34114 6069 6117
2 2017.0 인천 8013 7800 1319 1318
3 2018.0 부산 12433 11640 2159 2168
4 2018.0 서울 45766 43654 7967 8000
5 2018.0 인천 11109 10323 1817 1835
pd.merge(total_sub, food_sub, on=["연도","시도"], how="inner")
연도 시도 산재_가입된사업장수 고용_가입된사업장수 산재_음식사업장수 고용_음식사업장수
0 2017.0 부산 9748 9543 1713 1720
1 2017.0 서울 34718 34114 6069 6117
2 2017.0 인천 8013 7800 1319 1318
3 2018.0 부산 12433 11640 2159 2168
4 2018.0 서울 45766 43654 7967 8000
5 2018.0 인천 11109 10323 1817 1835

이제 how 옵션을 바꾸어 결합 시 기준이 되는 식별자에 조건을 바꾸어보자.

먼저 결합시 기준이 되는 식별자가 왼쪽에 있는 데이터프레임 total_sub 에 있다는 것을 선택명령문 how='left' 로 알려주자. 아래의 결과를 확인해보면 2015년도 의 자료가 나타나지만 산재_음식사업장수고용_음식사업장수에서 결측값(NaN) 으로 표시된다.

pd.merge(total_sub, food_sub, on=["연도","시도"], how='left')
연도 시도 산재_가입된사업장수 고용_가입된사업장수 산재_음식사업장수 고용_음식사업장수
0 2015.0 부산 7423 7289 NaN NaN
1 2015.0 서울 26025 25641 NaN NaN
2 2015.0 인천 5850 5702 NaN NaN
3 2016.0 부산 8507 8275 NaN NaN
4 2016.0 서울 30761 30038 NaN NaN
5 2016.0 인천 6755 6602 NaN NaN
6 2017.0 부산 9748 9543 1713.0 1720.0
7 2017.0 서울 34718 34114 6069.0 6117.0
8 2017.0 인천 8013 7800 1319.0 1318.0
9 2018.0 부산 12433 11640 2159.0 2168.0
10 2018.0 서울 45766 43654 7967.0 8000.0
11 2018.0 인천 11109 10323 1817.0 1835.0

다음 결합시 기준이 되는 식별자가 오른쪽에 있는 데이터프레임 total_sub 에 있다는 것을 선택명령문 how='right' 로 알려주자. 2019년도 의 자료가 나타나지만 산재_가입된사업장수고용_가입된사업장수에서 결측값(NaN) 으로 표시되는 것을 확인할 수 있다.

pd.merge(total_sub, food_sub, on=["연도","시도"], how='right')
연도 시도 산재_가입된사업장수 고용_가입된사업장수 산재_음식사업장수 고용_음식사업장수
0 2017.0 서울 34718.0 34114.0 6069 6117
1 2017.0 인천 8013.0 7800.0 1319 1318
2 2017.0 부산 9748.0 9543.0 1713 1720
3 2018.0 서울 45766.0 43654.0 7967 8000
4 2018.0 인천 11109.0 10323.0 1817 1835
5 2018.0 부산 12433.0 11640.0 2159 2168
6 2019.0 서울 NaN NaN 10576 10596
7 2019.0 인천 NaN NaN 2718 2731
8 2019.0 부산 NaN NaN 3205 3215
9 2020.0 부산 NaN NaN 3508 3508
10 2020.0 인천 NaN NaN 2886 2892
11 2020.0 서울 NaN NaN 10612 10631
12 2021.0 서울 NaN NaN 10752 10796
13 2021.0 부산 NaN NaN 3829 3836
14 2021.0 인천 NaN NaN 2891 2898

다음 결합시 기준이 되는 식별자가 오른쪽에 있는 데이터프레임 total_sub 에 있다는 것을 선택명령문 how='outer' 로 알려주자. 이제2015년도 2019년도 의 자료가 나타나지만 ‘ 산재_음식사업장수고용_음식사업장수에서 2015년도 자료가 결측값(NaN) 으로 표시되고 산재_가입된사업장수고용_가입된사업장수에서 2019년도 지료가 결측값(NaN) 으로 표시되는 것을 확인할 수 있다.

pd.merge(total_sub, food_sub, on=["연도","시도"], how='outer') 
연도 시도 산재_가입된사업장수 고용_가입된사업장수 산재_음식사업장수 고용_음식사업장수
0 2015.0 부산 7423.0 7289.0 NaN NaN
1 2015.0 서울 26025.0 25641.0 NaN NaN
2 2015.0 인천 5850.0 5702.0 NaN NaN
3 2016.0 부산 8507.0 8275.0 NaN NaN
4 2016.0 서울 30761.0 30038.0 NaN NaN
5 2016.0 인천 6755.0 6602.0 NaN NaN
6 2017.0 부산 9748.0 9543.0 1713.0 1720.0
7 2017.0 서울 34718.0 34114.0 6069.0 6117.0
8 2017.0 인천 8013.0 7800.0 1319.0 1318.0
9 2018.0 부산 12433.0 11640.0 2159.0 2168.0
10 2018.0 서울 45766.0 43654.0 7967.0 8000.0
11 2018.0 인천 11109.0 10323.0 1817.0 1835.0
12 2019.0 서울 NaN NaN 10576.0 10596.0
13 2019.0 인천 NaN NaN 2718.0 2731.0
14 2019.0 부산 NaN NaN 3205.0 3215.0
15 2020.0 부산 NaN NaN 3508.0 3508.0
16 2020.0 인천 NaN NaN 2886.0 2892.0
17 2020.0 서울 NaN NaN 10612.0 10631.0
18 2021.0 서울 NaN NaN 10752.0 10796.0
19 2021.0 부산 NaN NaN 3829.0 3836.0
20 2021.0 인천 NaN NaN 2891.0 2898.0

요약#

두 개의 데이터프레임을 결합하는 경우 pandas 라이브러리의 함수 merge() 를 사용하며 식별자가 포함된 공통의 열은 on= 으로 지정한다. 결합의 방향을 how=로 지정한다.