-
Chapter 05 데이터 연결하기Do it! 판다스 입문 2022. 6. 21. 12:09
분석하기 좋은 데이터란?
분석하기 좋은 데이터란 데이터 집합을 분석하기 좋은 상태로 만들어 놓은 것을 말합니다. 데이터 분석 단계에서 데이터 정리는 아주 중요합니다. 실제로 데이터 분석 작업의 70% 이상을 차지하고 있는 작업이 데이터 정리 작업이죠. 분석하기 좋은 데이터는 다음 조건을 만족해야 하며 이 조건을 만족하는 데이터를 특별히 깔끔한 데이터(Tidy Data)라고 부릅니다.
깔끔한 데이터의 조건
- 데이터 분석 목적에 맞는 데이터를 모아 새로운 표(Table)를 만들어야 합니다.
- 측정한 값은 행(row)을 구성해야 합니다.
- 변수는 열(column)로 구성해야 합니다.
아직은 깔끔한 데이터가 왜 중요한지 알 수 없겠지만 실습을 하나씩 진행하다 보면 깔끔한 데이터의 중요성에 대해 자연스럽게 이해할 수 있을 것입니다.
깔끔한 데이터는 데이터 연결부터
예를 들어 주식 데이터를 분석하는 과정에서 '기업 정보'가 있는 데이터 집합과 '주식 가격'이 있는 데이터 집합이 있을 때 '첨단 산업 기업의 주식 가격에 대한 데이터'를 보려면 어떻게 해야 할까요? 일단 '기업 정보'에서 첨단 기술을 가진 기업을 찾아야 합니다. 그리고 이 기업들의 '주식 가격'을 찾아야겠죠. 그런 다음 찾아낸 2개의 데이터를 연결하면 됩니다. 이렇게 데이터 집합은 연관성이 깊은 값끼리 모여 있기 때문에 데이터 연결을 통해 필요한 데이터를 만드는 과정이 반드시 필요합니다. 그러면 다음 실습을 통해 데이터 연결을 어떻게 하는지 알아보겠습니다.
데이터 연결하기
1. concat 메서드로 데이터 연결하기
데이터를 연결하려면 concat 메서드를 사용하면 됩니다. 다음 예제를 통해 concat 메서드의 사용법을 익혀보겠습니다. 준비된 CSV 파일을 읽어 들여 변수 df1, 2, 3에 저장합니다.
import pandas as pd
df1 = pd.read_csv('../data/concat_1.csv')
df2 = pd.read_csv('../data/concat_2.csv')
df3 = pd.read_csv('../data/concat_3.csv')2. concat 메서드에 연결하려는 데이터프레임을 리스트에 담아 전달하면 연결한 데이터프레임을 반환합니다. concat 메서드는 데이터프레임을 연결할 때 위에서 아래 방향으로 연결합니다. 그리고 df1, 2, 3은 열의 이름이 모두 A, B, C, D로 같습니다. 그래서 데이터프레임을 연결한 다음에도 열이 그대로 유지됩니다.
row_concat = pd.concat([df1, df2, df3])
print(row_concat)
A B C D
0 a0 b0 c0 d0
1 a1 b1 c1 d1
2 a2 b2 c2 d2
...
2 a10 b10 c10 d10
3 a11 b11 c11 d113. 연결한 데이터프레임에서 행 데이터를 추출해 볼까요? concat 메서드는 전달받은 리스트의 요소 순서대로 데이터를 연결합니다. 그래서 기존 데이터프레임에 있던 인덱스도 그대로 유지됩니다. 다음은 데이터프레임에서 네 번째 행을 추출한 것입니다.
print(row_concat.iloc[3, ])
A a3
B b3
C c3
D d3
Name: 3, dtype: object4. 데이터프레임에 시리즈 연결하기
이번에는 데이터프레임에 시리즈를 추가해 보겠습니다. 먼저 리스트를 시리즈로 변환합니다.
new_row_series = pd.Series(['n1', 'n2', 'n3', 'n4']) 5. concat 메서드로 데이터프레임과 시리즈를 연결해 볼까요? 시리즈가 새로운 행으로 추가될 것 같죠? 하지만 행이 아니라 새로운 열로 추가됩니다. 그래서 NaN이라는 값도 많이 생겼습니다. 앞으로 NaN을 누락값이라고 부르겠습니다.
print(pd.concat([df1, new_row_series]))
A B C D 0
0 a0 b0 c0 d0 NaN
1 a1 b1 c1 d1 NaN
2 a2 b2 c2 d2 NaN
3 a3 b3 c3 d3 NaN
0 NaN NaN NaN NaN n1
1 NaN NaN NaN NaN n2
2 NaN NaN NaN NaN n3
3 NaN NaN NaN NaN n4행이 1개라도 반드시 데이터프레임에 담아 연결해야 합니다
시리즈를 데이터프레임의 새로운 행으로 연결하려고 하면 제대로 되지 않습니다. 왜 그럴까요? 시리즈에는 열 이름이 없기 때문입니다. 그래서 시리즈를 새로운 열로 간주하여 0이라는 이름의 열로 추가한 것이죠.
행 1개로 구성된 데이터프레임 생성하여 연결하기
1. 시리즈는 행이 1개인 데이터프레임이라고 생각해도 됩니다. 다음은 1개의 행을 가지는 데이터프레임을 생성하여 df1에 연결한 것입니다.
new_row_df = pd.DataFrame([['n1', 'n2', 'n3', 'n4']], columns=['A', 'B', 'C', 'D'])
print(new_row_df)
A B C D
0 n1 n2 n3 n4
print(pd.concat([df1, new_row_df]))
A B C D
0 a0 b0 c0 d0
1 a1 b1 c1 d1
2 a2 b2 c2 d2
3 a3 b3 c3 d3
0 n1 n2 n3 n42. concat 메서드는 한 번에 2개 이상의 데이터프레임을 연결할 수 있는 메서드입니다. 만약 연결할 데이터프레임이 1개라면 append 메서드를 사용해도 됩니다.
print(df1.append(new_row_df))
A B C D
0 a0 b0 c0 d0
1 a1 b1 c1 d1
2 a2 b2 c2 d2
3 a3 b3 c3 d3
0 n1 n2 n3 n43. append 메서드와 딕셔너리를 사용하면 더욱 간편하게 행을 연결할 수 있습니다. 이때 ignore_index를 True로 설정하면 데이터를 연결한 다음 데이터프레임의 인덱스를 0부터 다시 지정합니다.
data_dict = {'A': 'n1', 'B': 'n2', 'C': 'n3', 'D': 'n4'}
print(df1.append(data_dict, ignore_index=True))
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 n1 n2 n3 n4다양한 방법으로 데이터 연결하기
판다스는 데이터를 연결하는 다양한 방법을 제공합니다. 다음 예제를 통해 데이터를 연결하는 다양한 방법에 대해 알아보겠습니다.
다양한 방법으로 데이터 연결하기
1. ignore_index 인자 사용하기
바로 앞에서 실습했던 내용이죠? ignore_index를 True로 지정하면 데이터를 연결한 다음 데이터프레임의 인덱스를 0부터 다시 지정합니다.
row_concat_i = pd.concat([df1, df2, df3], ignore_index=True)
print(row_concat_i)
A B C D
0 a0 b0 c0 d0
1 a1 b1 c1 d1
2 a2 b2 c2 d2
...
10 a10 b10 c10 d10
11 a11 b11 c11 d112. 열 방향으로 데이터 연결하기
만약 행 방향이 아니라 열 방향으로 데이터를 연결하려면 어떻게 해야 할까요? concat 메서드의 axis 인자를 1로 지정하면 됩니다. 다음은 df1, df2, df3을 열 방향으로 연결한 것입니다.
col_concat = pd.concat([df1, df2, df3], axis=1)
print(col_concat)
A B C D A B C D A B C D
0 a0 b0 c0 d0 a4 b4 c4 d4 a8 b8 c8 d8
1 a1 b1 c1 d1 a5 b5 c5 d5 a9 b9 c9 d9
2 a2 b2 c2 d2 a6 b6 c6 d6 a10 b10 c10 d10
3 a3 b3 c3 d3 a7 b7 c7 d7 a11 b11 c11 d113. 만약 같은 열 이름이 있는 데이터프레임에서 열 이름으로 데이터를 추출하면 해당 열 이름의 데이터를 모두 추출합니다.
print(col_concat['A'])
A A A
0 a0 a4 a8
1 a1 a5 a9
2 a2 a6 a10
3 a3 a7 a114. 다음과 같이 입력하면 간편하게 새로운 열을 추가할 수도 있습니다.
col_concat['new_col_list'] = ['n1', 'n2', 'n3', 'n4']
print(col_concat)
A B C D A B C D A B C D new_col_list
0 a0 b0 c0 d0 a4 b4 c4 d4 a8 b8 c8 d8 n1
1 a1 b1 c1 d1 a5 b5 c5 d5 a9 b9 c9 d9 n2
2 a2 b2 c2 d2 a6 b6 c6 d6 a10 b10 c10 d10 n3
3 a3 b3 c3 d3 a7 b7 c7 d7 a11 b11 c11 d11 n45. 과정 2에서는 데이터프레임의 열 이름을 유지한 채 연결했기 때문에 열 이름이 중복되었습니다. 다음은 ignore_index를 True로 지정하여 열 이름을 다시 지정한 것입니다.
print(pd.concat([df1, df2, df3], axis=1, ignore_index=True))
0 1 2 3 4 5 6 7 8 9 10 11
0 a0 b0 c0 d0 a4 b4 c4 d4 a8 b8 c8 d8
1 a1 b1 c1 d1 a5 b5 c5 d5 a9 b9 c9 d9
2 a2 b2 c2 d2 a6 b6 c6 d6 a10 b10 c10 d10
3 a3 b3 c3 d3 a7 b7 c7 d7 a11 b11 c11 d116. 공통 열과 공통 인덱스만 연결하기
만약 열 이름의 일부가 서로 다른 데이터프레임을 연결하면 어떻게 될까요? 앞에서 사용한 df1, df2, df3의 열 이름을 다시 지정하겠습니다.
df1.columns = ['A', 'B', 'C', 'D']
df2.columns = ['E', 'F', 'G', 'H']
df3.columns = ['A', 'C', 'F', 'H']
print(df1)
print(type(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
<class 'pandas.core.frame.DataFrame'>
print(df2)
print(type(df2))
E F G H
0 a4 b4 c4 d4
1 a5 b5 c5 d5
2 a6 b6 c6 d6
3 a7 b7 c7 d7
<class 'pandas.core.frame.DataFrame'>
print(df3)
print(type(df3))
A C F H
0 a8 b8 c8 d8
1 a9 b9 c9 d9
2 a10 b10 c10 d10
3 a11 b11 c11 d11
<class 'pandas.core.frame.DataFrame'>7. 새롭게 열 이름을 부여한 데이터프레임 3개를 concat 메서드로 연결해 보겠습니다. 어떻게 되었나요? 열 이름이 정렬되며 연결되었습니다. 그리고 데이터프레임에 없는 열 이름의 데이터는 누락값으로 처리되었습니다. 누락값 없이 데이터를 연결하는 방법은 없을까요?
row_concat = pd.concat([df1, df2, df3])
print(row_concat)
A B C D E F G H
0 a0 b0 c0 d0 NaN NaN NaN NaN
1 a1 b1 c1 d1 NaN NaN NaN NaN
2 a2 b2 c2 d2 NaN NaN NaN NaN
3 a3 b3 c3 d3 NaN NaN NaN NaN
0 NaN NaN NaN NaN a4 b4 c4 d4
1 NaN NaN NaN NaN a5 b5 c5 d5
2 NaN NaN NaN NaN a6 b6 c6 d6
3 NaN NaN NaN NaN a7 b7 c7 d7
0 a8 NaN b8 NaN NaN c8 NaN d8
1 a9 NaN b9 NaN NaN c9 NaN d9
2 a10 NaN b10 NaN NaN c10 NaN d10
3 a11 NaN b11 NaN NaN c11 NaN d118. 데이터프레임의 공통 열만 골라 연결하면 누락값이 생기지 않을 것입니다. 공통 열만 골라서 연결하려면 join 인자를 inner로 지정해야 합니다. 아쉽게도 df1, df2, df3은 공통 열이 없습니다. 따라서 새 데이터프레임의 공통 열을 연결한 결괏값으로 Empty DataFrame이 출력됩니다.
print(pd.concat([df1, df2, df3], join='inner'))
Empty DataFrame
Columns: [ ]
Index: [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3]9. df1, df3의 공통 열만 골라 연결해 볼까요? 그러면 공통 열인 A와 C만 연결됩니다.
print(pd.concat([df1, df3], ignore_index=False, join='inner'))
A C
0 a0 c0
1 a1 c1
2 a2 c2
3 a3 c3
0 a8 b8
1 a9 b9
2 a10 b10
3 a11 b1110. 이번에는 데이터프레임을 행 방향으로 연결해 볼까요? df1, df2, df3의 인덱스를 다시 지정해 보겠습니다.
df1.index = [0, 1, 2, 3]
df2.index = [4, 5, 6, 7]
df3.index = [0, 2, 5, 7]
print(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
print(df2)
E F G H
4 a4 b4 c4 d4
5 a5 b5 c5 d5
6 a6 b6 c6 d6
7 a7 b7 c7 d7
print(df3)
A C F H
0 a8 b8 c8 d8
2 a9 b9 c9 d9
5 a10 b10 c10 d10
7 a11 b11 c11 d1111. concat 메서드로 df1, df2, df3을 행 방향으로 연결하면 과정 2와 비슷한 결과가 출력됩니다.
col_concat = pd.concat([df1, df2, df3], axis=1)
print(col_concat)
A B C D E F G H A C F H
0 a0 b0 c0 d0 NaN NaN NaN NaN a8 b8 c8 d8
1 a1 b1 c1 d1 NaN NaN NaN NaN NaN NaN NaN NaN
2 a2 b2 c2 d2 NaN NaN NaN NaN a9 b9 c9 d9
3 a3 b3 c3 d3 NaN NaN NaN NaN NaN NaN NaN NaN
4 NaN NaN NaN NaN a4 b4 c4 d4 NaN NaN NaN NaN
5 NaN NaN NaN NaN a5 b5 c5 d5 a10 b10 c10 d10
6 NaN NaN NaN NaN a6 b6 c6 d6 NaN NaN NaN NaN
7 NaN NaN NaN NaN a7 b7 c7 d7 a11 b11 c11 d1112. 과정 9와 비슷한 방법으로 df1, df3의 공통 행만 골라 연결해 볼까요? 그러면 공통 행인 0과 2만 출력됩니다.
print(pd.concat([df1, df3], axis=1, join='inner'))
A B C D A C F H
0 a0 b0 c0 d0 a8 b8 c8 d8
2 a2 b2 c2 d2 a9 b9 c9 d9* 외부 조인과 내부 조인
앞의 실습 예제는 데이터베이스의 주요 개념 중 하나인 내부 조인(Inner Join)과 외부 조인(Outer Join)을 실습한 것입니다. 만약 두 개념이 잘 이해되지 않는다면 아래에 정리한 내용을 읽어 보세요.
내부 조인
둘 이상의 데이터프레임에서 조건에 맞는 행을 연결하는 것입니다.
외부 조인
외부 조인은 두 데이터프레임 중 어떤 데이터프레임을 기준으로 할 것인지에 따라 왼쪽 외부 조인(Left Outer Join)과 오른쪽 오른쪽 외부 조인(Right Outer Join), 완전 외부 조인(Full Outer Join)으로 나눌 수 있습니다. 왼쪽 외부 조인은 데이터프레임을 연결할 때 왼쪽 데이터프레임을 모두 포함하여 연결하는 것이고 오른쪽 외부 조인은 데이터프레임을 연결할 때 오른쪽 데이터프레임을 모두 포함하여 연결하는 것입니다. 완전 외부 조인은 왼쪽과 오른쪽 데이터프레임을 모두 포함하여 연결합니다.
데이터 연결 마무리
판다스는 데이터 연결 전용 메서드인 merge를 제공합니다. merge 메서드의 사용 방법을 실습하면서 데이터 연결을 마무리하겠습니다.
merge 메서드 사용하기
1. 다음은 특정 위치의 날씨 정보에 필요한 데이터 집합을 모두 불러온 것입니다. person은 관측한 사람의 이름, site는 관측 위치, visited는 관측 날짜, survey는 날씨 정보입니다.
person = pd.read_csv('../data/survey_person.csv')
site = pd.read_csv('../data/survey_site.csv')
survey = pd.read_csv('../data/survey_survey.csv')
visited = pd.read_csv('../data/survey_visited.csv')
print(person)
ident personal family
0 dyer William Dyer
1 pb Frank Pabodie
2 lake Anderson Lake
3 roe Valentina Roerich
4 danforth Frank Danforth
print(site)
name lat long
0 DR-1 -49.85 -128.57
1 DR-3 -47.15 -126.72
2 MSK-4 -48.87 -123.40
print(visited)
ident site dated
0 619 DR-1 1927-02-08
1 622 DR-1 1927-02-10
2 734 DR-3 1939-01-07
3 735 DR-3 1930-01-12
4 751 DR-3 1930-02-26
5 752 DR-3 NaN
6 837 MSK-4 1932-01-14
7 844 DR-1 1932-03-22
print(survey)
taken person quant reading
0 619 dyer rad 9.82
1 619 dyer sal 0.13
2 622 dyer rad 7.80
...
19 837 roe sal 22.50
20 844 roe rad 11.252. visited 데이터프레임의 일부 데이터만 떼어 실습에 사용하겠습니다.
visited_subset = visited.loc[[0, 2, 6],] 3. merge 메서드는 기본적으로 내부 조인을 실행하며 메서드를 사용한 데이터프레임(site)을 왼쪽으로 지정하고 첫 번째 인잣값으로 지정한 데이터프레임(visited_subset)을 오른쪽으로 지정합니다. left_on, right_on 인자는 값이 일치해야 할 왼쪽과 오른쪽 데이터프레임의 열을 지정합니다. 즉, 왼쪽 데이터프레임(site)의 열(name)과 오른쪽 데이터프레임(visited)의 열(site)의 값이 일치하면 왼쪽 데이터프레임을 기준으로 연결합니다.
o2o_merge = site.merge(visited_subset, left_on='name', right_on='site')
print(o2o_merge)
name lat long ident site dated
0 DR-1 -49.85 -128.57 619 DR-1 1927-02-08
1 DR-3 -47.15 -126.72 734 DR-3 1939-01-07
2 MSK-4 -48.87 -123.40 837 MSK-4 1932-01-144. 다음은 site, visited 데이터프레임을 이용하여 데이터를 연결한 것입니다.
m2o_merge = site.merge(visited, left_on='name', right_on='site')
print(m2o_merge)
name lat long ident site dated
0 DR-1 -49.85 -128.57 619 DR-1 1927-02-08
1 DR-1 -49.85 -128.57 622 DR-1 1927-02-10
2 DR-1 -49.85 -128.57 844 DR-1 1932-03-22
3 DR-3 -47.15 -126.72 734 DR-3 1939-01-07
4 DR-3 -47.15 -126.72 735 DR-3 1930-01-12
5 DR-3 -47.15 -126.72 751 DR-3 1930-02-26
6 DR-3 -47.15 -126.72 752 DR-3 NaN
7 MSK-4 -48.87 -123.40 837 MSK-4 1932-01-145. 다른 데이터프레임도 연결해 볼까요? 다음은 person, survey 데이터프레임과 visited, survey 데이터프레임을 merge 메서드로 연결한 것입니다.
ps = person.merge(survey, left_on='ident', right_on='person')
vs = visited.merge(survey, left_on='ident', right_on='taken')
print(ps)
ident personal family taken person quant reading
0 dyer William Dyer 619 dyer rad 9.82
1 dyer William Dyer 619 dyer sal 0.13
...
17 roe Valentina Roerich 837 roe sal 22.50
18 roe Valentina Roerich 844 roe rad 11.25
print(vs)
ident site dated taken person quant reading
0 619 DR-1 1927-02-08 619 dyer rad 9.82
1 619 DR-1 1927-02-08 619 dyer sal 0.13
2 622 DR-1 1927-02-10 622 dyer rad 7.80
...
19 837 MSK-4 1932-01-14 837 roe sal 22.50
20 844 DR-1 1932-03-22 844 roe rad 11.256. left_on, right_on에 전달하는 값은 여러 개라도 상관이 없습니다. 다음과 같이 여러 개의 열 이름을 리스트에 담아 전달해도 됩니다. 다음은 ps 데이터프레임의 ident, taken, quant, reading 열의 값과 vs 데이터프레임의 person, ident, quant, reading 열의 값을 이용하여 ps와 vs 데이터프레임을 서로 연결한 것입니다.
ps_vs = ps.merge(vs, left_on=['ident', 'taken', 'quant', 'reading'], right_on=['person', 'ident', 'quant', 'reading']) 7. 과정 6에서 연결한 ps_vs 데이터프레임의 첫 번째 행을 살펴보면 양쪽 데이터프레임에 있었던 중복된 열 이름(ident, taken, person)에 접미사 _x, _y가 추가되어 있는 것을 알 수 있습니다. _x는 왼쪽 데이터프레임의 열을 의미하고 _y는 오른쪽 데이터프레임의 열을 의미합니다.
print(ps_vs.loc[0, 1])
ident_x dyer
personal William
family Dyer
taken_x 619
person_x dyer
quant rad
reading 9.82
ident_y 619
site DR-1
dated 1927-02-08
taken_y 619
person_y dyer
Name: 0, dtype: object마무리하며
이 장에서는 데이터를 연결하는 다양한 방법을 알아보았습니다. 특히 누락값과 중복값을 해결하기 위한 여러 가지 방법에 대해 알아보았습니다. 누락값과 중복값은 데이터 분석을 방해하는 요소가 될 수 있기 때문이죠. 05장을 시작하며 소개한 깔끔한 데이터의 조건 중 하나인 '데이터 분석 목적에 맞는 데이터를 모아 새로운 표(Table)를 만들어야 합니다'는 바로 누락값이나 중복값이 없는 상태로 데이터가 잘 연결되어 있어야 한다는 말입니다.
출처 : "판다스 입문"
'Do it! 판다스 입문' 카테고리의 다른 글
Chapter 06 누락값 처리하기_2 (0) 2022.06.21 Chapter 06 누락값 처리하기_1 (0) 2022.06.21 Chapter 04-4~04-5 데이터프레임과 시리즈로 그래프 그리기, seaborn 라이브러리로 그래프 스타일 설정하기 (0) 2022.06.20 Chapter 04-3 seaborn 라이브러리 자유자재로 사용하기 (0) 2022.06.20 Chapter 04-2 matplotlib 라이브러리 자유자재로 사용하기 (0) 2022.06.20