프로그래밍 Programming

판다 데이터프레임 합치기 concat(), merge(), merge_asof() Joining DataFrames in Pandas

문장전달자 2020. 8. 8. 22:11

대부분의 경우 데이터는 다수의 소스나 파일로부터 나오므로, 데이터프레임을 합치는 작업은 아주 중요한 작업 중 하나로, 여기서는 Pandas 라이브러리를 사용하여 복수의 데이터프레임을 병합하는 방법에 대해 살펴보자. 

 

Concatenate DataFrames

먼저 pandas 라이브러리를 임포트한다.

import pandas as pd

파이썬 딕셔너리로 샘플로 사용할 데이터프레임을 다음과 같이 만든다.

dummy_data1 = { 
    'id': ['1', '2', '3', '4', '5'], 
    'Feature1': ['A', 'C', 'E', 'G', 'I'], 
    'Feature2': ['B', 'D', 'F', 'H', 'J']}

딕셔너리 dummy_data1 의 키는 컬럼명이고 리스트의 값들은 각각의 행에 대응하는 데이터이다. 이를 pandas 데이터프레임으로 변환하기 위해 DataFrame() 함수를 사용한다. columns 인수를 사용하여 컬럼의 이름을 지정한다.

df1 = pd.DataFrame(dummy_data1, columns = ['id', 'Feature1', 'Feature2'])
df1

보시다시피 id, Feature1, 그리고 Feature2 의 3개의 컬럼을 가진 데이터프레임이 완성되었다. 기본적으로 생성되는 이름 없는 컬럼인 row 라벨도 있다. 위와 마찬가지로 데이터프레임 df2, df3 를 만들자. 

dummy_data2 = {
        'id': ['1', '2', '6', '7', '8'],
        'Feature1': ['K', 'M', 'O', 'Q', 'S'],
        'Feature2': ['L', 'N', 'P', 'R', 'T']}
df2 = pd.DataFrame(dummy_data2, columns = ['id', 'Feature1', 'Feature2'])
df2

dummy_data3 = {
        'id': ['1', '2', '3', '4', '5', '7', '8', '9', '10', '11'],
        'Feature3': [12, 13, 14, 15, 16, 17, 15, 12, 13, 23]}
df3 = pd.DataFrame(dummy_data3, columns = ['id', 'Feature3'])
df3

pandas의 concat() 함수를 이용하여 행을 따라 데이터프레임을 합칠 수 있다. concat() 함수의 인수로 데이터프레임의 이름을 넣어준다.

df_row = pd.concat([df1, df2])
df_row

이제 데이터프레임 df1, df2 가 df_row 라는 하나의 데이터프레임으로 합쳐진 것을 볼 수 있다. 하지만 row 라벨이 적절해보이지 않는다. ignore_index 인수를 True 로 설정하여 기존의 row 라벨을 무시하고 새롭게 번호 매김할 수 있도록 하자. 

df_row_reindex = pd.concat([df1, df2], ignore_index=True)
df_row_reindex

pandas 는 데이터프레임을 합친 후 데이터프레임에 라벨링할 수 있는 옵션이 있는데 keys 인수를 이용하여 해당 데이터가 어떤 데이터프레임에서 왔는지 알아볼 수 있게 라벨링하는 옵션을 제공한다. 아래와 같이 라벨 이름을 리스트 형식으로 넣어 keys 인수를 이용하여 합치면 된다.

frames = [df1,df2]
df_keys = pd.concat(frames, keys=['x', 'y'])
df_keys

loc 메소드를 이용하여 라벨 y fmf rkwls 데이터프레임 df2 의 데이터만 따로 뽑아낼 수 있다.

df_keys.loc['y']

concat() 함수에 딕셔너리 형식으로 전달하면, 딕셔너리 키가 keys 인수처럼 작동한다. 

pieces = {'x': df1, 'y': df2}
df_piece = pd.concat(pieces)
df_piece

concat() 는 데이터 전체 복사본도 만들 수 있다. list comprehension 을 이용하여 다수의 데이터셋에 일괄 작업도 가능하다. 

frames = [ process_your_file(f) for f in files ]
result = pd.concat(frames)

axis 파라메터 값을 1로 지정하여 컬럼으 따라 데이터프레임을 합칠 수도 있다.

df_col = pd.concat([df1,df2], axis=1) 
df_col

 

Merge DataFrames

2개의 데이터프레임이 동일한 개체에 대해서 서로 다른 종류의 정보를 가지고 있고, 특정 컬럼에 의해 링크되어 있는 경우를 생각해보자. 이러한 데이터프레임을 합치기 위해 concat(), merge() , join() 등의 함수를 사용할 수 있는데, 이번 섹션에서는 이 중 merge() 함수에 대해 살펴본다. 

(df1, df2 를 합쳐서 생성된) 데이터프레임 df_row 와 df3 를 공통되는 컬럼 (또는 key)인 id 를 기반으로 합쳐보자. 이를 위해서 데이터프레임의 이름과 여기서는 id 인 공통되는 컬럼명을 추가적인 인수로 merge() 함수로 넘긴다. 

df_merge_col = pd.merge(df_row, df3, on='id')
df_merge_col

2개의 데이터프레임에 공통되는 컬럼인 id 를 기반으로 합쳐져 단일 데이터프레임이 된 것을 볼 수 있다. 예를 들어 id 값 1 은 데이터프레임 df_row 의 A, B 그리고 K, L 에 존재하고 있었다. 따라서 id 1 은 최종 데이터프레임 df_merge_col 에 반복해서 등장하고 데이터프레임 df3 에서 온 Feature3 컬럼의 값인 12 역시 반복해서 나옴을 알 수 있다. 

위의 예와는 달리 합칠 데이터프레임이 서로 다른 컬럼 이름을 가지고 있는 경우에는 좌측 데이터프레임 이름을 left_on 인수로, 우측 데이터프레임의 이름을 right_on 인수에 지정해서 다음과 같이 합칠 수 있다.

df_merge_difkey = pd.merge(df_row, df3, left_on='id', right_on='id')
df_merge_difkey

다음과 같이 append() 함수를 이용하여 행을 데이터프레임에 추가하여 새로운 데이터프레임을 만들 수 있다. 

add_row = pd.Series(['10', 'X1', 'X2', 'X3'],
                    index=['id','Feature1', 'Feature2', 'Feature3'])
df_add_row = df_merge_col.append(add_row, ignore_index=True)
df_add_row

 

Join DataFrames

pandas 데이터프레임을 합칠 때 유용한 다양한 join 로직에 대해 살펴보자.

Full Outer Join

FULL OUTER JOIN 은 left outer join 과 right outer join 의 결과를 합친 것이다. 합쳐진 데이터프레임은 양쪽 데이터프레임의 모든 레코드를 가지게 된다. 그리고 없는 값에 대해서는 NaN 을 채워넣는다. how 인수에 outer 라고 명시하여 실행하면 된다. 

df_outer = pd.merge(df1, df2, on='id', how='outer')
df_outer

해당 컬럼이 어떤 데이터프레임에서 왔는지 보여주기 위해 컬럼명에 기본 접미사 x, y 가 붙은 것을 볼 수 있다. merge() 함수의 suffixes 인수를 다음과 같이 지정할 수도 있다.

df_suffix = pd.merge(df1, df2, left_on='id',right_on='id',how='outer',suffixes=('_left','_right'))
df_suffix

 

Inner Join

INNER JOIN 는 양쪽 데이터프레임에 공통되는 레코드 셋트만 생성해낸다. 

df_inner = pd.merge(df1, df2, on='id', how='inner')
df_inner

Right Join

RIGHT JOIN 는 데이터프레임 B (우측 데이터프레임) 의 전체 레코드 셋트와 이와 매칭되는 데이터프레임 A 를 보여준다. 만약 양쪽에 매칭되는 부분이 없다면 우측 사이드는 null 을 포함한다. 

df_right = pd.merge(df1, df2, on='id', how='right')
df_right

Left Join

LEFT JOIN 은 마찬가지로 데이터프레임 DataFrame A (좌측 DataFrame) 의 모든 레코드 셋과 이와 매칭되는 데이터프레임 B 의 레코드를 가져온다. 매칭되는 부분이 없으며, 좌측 사이드는 null 을 포함하게 된다. 

df_left = pd.merge(df1, df2, on='id', how='left')
df_left

Joining on index

인덱스 또는 row 라벨에 기반하여 조인하는 경우로, right_indexleft_indexTrue 로 지정하면 된다.

df_index = pd.merge(df1, df2, right_index=True, left_index=True)
df_index

 

Time-series friendly merging

Pandas 는 시계열 데이터프레임을 합칠 수 있는 특별한 함수를 제공한다. 이 중 가장 많이 쓰는 함수로 merge_asof() 가 있다. merge_asof() 는 완전히 일치하는 키 대신에 가장 근접한 키를 기반으로 한다는 점을 제외하면 left-join 과 유사하다. 좌측 데이터프레임의 각각의 행에 대해 좌측프레임의 키보다 적은 우측 데이터프레임에서 최종 행을 선택한다. 양쪽 데이터프레임은 key 기반으로 정렬되어야 한다. 

선택적으로 asof 병합은 그룹 기반 병합을 수행한다. 예를 들어, trades 및 quotes 데이터를 가지고 asof 병합을 하고 싶다고 하자. 여기서 좌측 데이터프레임은 trades 이고 우측 데이터프레임은 quotes 이다. 키 타임 기반으로 asof 병합되었고, 티커 심볼로 그룹 기반 병합되었다. 

trades = pd.DataFrame({
    'time': pd.to_datetime(['20160525 13:30:00.023',
                            '20160525 13:30:00.038',
                            '20160525 13:30:00.048',
                            '20160525 13:30:00.048',
                            '20160525 13:30:00.048']),
    'ticker': ['MSFT', 'MSFT','GOOG', 'GOOG', 'AAPL'],
    'price': [51.95, 51.95,720.77, 720.92, 98.00],
    'quantity': [75, 155,100, 100, 100]},
    columns=['time', 'ticker', 'price', 'quantity'])

quotes = pd.DataFrame({
    'time': pd.to_datetime(['20160525 13:30:00.023',
                            '20160525 13:30:00.023',
                            '20160525 13:30:00.030',
                            '20160525 13:30:00.041',
                            '20160525 13:30:00.048',
                            '20160525 13:30:00.049',
                            '20160525 13:30:00.072',
                            '20160525 13:30:00.075']),
    'ticker': ['GOOG', 'MSFT', 'MSFT','MSFT', 'GOOG', 'AAPL', 'GOOG','MSFT'],
    'bid': [720.50, 51.95, 51.97, 51.99,720.50, 97.99, 720.50, 52.01],
    'ask': [720.93, 51.96, 51.98, 52.00,720.93, 98.01, 720.88, 52.03]},
    columns=['time', 'ticker', 'bid', 'ask'])
trades

quotes

df_merge_asof = pd.merge_asof(trades, quotes,
              on='time',
              by='ticker')

df_merge_asof

자세히 살펴보면 AAPL 티커 행에 NaN 이 등장했음을 알 수 있다. 이는 우측 데이터프레임 quotes 가 AAPL 티커에 대해서 (좌측 테이블의 시간인) 13:30:00.048 보다 적은 값을 가진 게 없기 때문에 bidask 컬럼에 등장하게 된 것이다.

만약 quote time 과 trade time 이 2ms 이내인 경우만 asof 병합을 하고자 한다면, tolerance 인수를 다음과 같이 지정해주면 된다.

df_merge_asof_tolerance = pd.merge_asof(trades, quotes,
              on='time',
              by='ticker',
              tolerance=pd.Timedelta('2ms'))

df_merge_asof_tolerance

앞선 결과와 비교해보면 2ms 범위내에 있지 않은 레코드는 제외되었음을 알 수 있다. 

 

더욱 자세한 내용은 아래 링크에서 살펴볼 수 있다.

https://pandas.pydata.org/pandas-docs/stable/user_guide/merging.html

 

Merge, join, concatenate and compare — pandas 1.1.0 documentation

pandas has full-featured, high performance in-memory join operations idiomatically very similar to relational databases like SQL. These methods perform significantly better (in some cases well over an order of magnitude better) than other open source imple

pandas.pydata.org