pandasでデータを結合していく【concat、merge、joinの違い】

# はじめに
ググれば多量に出てくる話ではありますが、
pandasでデータを結合する際に出てくる、concat、merge、joinについて
正しく理解をするのは、pythonでデータを扱う第一歩です。

特にSQLは慣れているけれど、
Pyhton、pandasを使うのは初めて、という人からすると、
以下の感覚になるはずです。

+ concatって、文字列どうしを結合する話ではないの？
+ mergeって、あるテーブルから他のテーブルに、insertやupdateを行う話ではないの？
+ joinはまさしく、2つのテーブルを条件に合う形で結合する処理だよね

一方pandasだとそうではなく。それぞれを正しく理解していく必要があります。


# 結論
* concat：単にくっつけるイメージ、縦にも横にも結合可能、その結果細かい結合ができない
* merge：「データ」を軸に横に結合。何を軸に結合するかを意識しつつ利用。
* join：「インデックス」を軸に横に結合。結合軸がインデックスで固定のため、複数をまとめ結合できる



# concat
concatは、
「同じ構造のテーブルが、複数のCSVに分かれて保存されているけれど
　それを一つにくっつけて、扱いやすいようにしよう」
という目的で使うようなイメージが正しいです。

複数のデータフレームを縦方向または横方向に結合する手段です。
横にも縦にも結合できます。

後述するmergeやjoinと区別する意味合いでも、
concatは「単にくっつける」もの。と理解するといいでしょう。
複雑なくっつけ方を求める場合は他の手段を使う必要があります。


```python:Python
import pandas as pd

df1 = pd.DataFrame({"Name": ["田中", "鈴木", "柴田"], 
                    "Group": ["A", "A", "B"],
                    "Point": [1, 2, 2]}).set_index("Name")

df2 = pd.DataFrame({"Name": ["松井", "広末"], 
                    "Group": ["A", "C"],
                    "Point": [0, 3]}).set_index("Name")

df3 = pd.DataFrame({"Name": ["田中", "鈴木", "柴田"],  
                    "Address": ["東京", "大阪", "北海道"]}).set_index("Name")

df4 = pd.DataFrame({"Name": ["田中", "鈴木", "大井"],  
                    "Address": ["東京", "大阪", "福岡"]}).set_index("Name")

```

### シンプルに縦方向結合
```python:Python
pd.concat([df1, df2])
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3111552/7562b817-2463-05bc-0d66-60f4c3c85518.png)

### axis指定で横方向結合
```python:Python
pd.concat([df1, df3], axis=1)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3111552/ccc320d2-08ec-801b-4d74-2f1f5456a0ae.png)

### 異なるカラムを持つ要素同士を結合
```python:Python
pd.concat([df1, df4])
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3111552/bdb2260a-7549-18bc-4d94-f879ce27643b.png)

```python:Python
pd.concat([df1, df4], axis=1)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3111552/8def8fad-d893-2cb3-150d-be7e08e83683.png)

### 内部結合
一方でもデータがないものは削除する
```python:Python
pd.concat([df1, df4], axis=1, join='inner')
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3111552/9737d99e-e31c-4b12-c027-7f4e90e8e5cb.png)

:::note info
concatでは右結合、左結合はできません。
:::


### 結合軸（インデックス）以外は同名でも別物として扱う
```python:Python
pd.concat([df1, df2], axis=1)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3111552/441936fc-b379-ef50-6790-bbf701dd50dd.png)


# merge
mergeは、データ側（インデックスではない）を軸に横方向の結合を行います。

concatは単にくっつける形で、分割されていたデータを合体させていくイメージでした。
指定次第で横にも縦にも結合できました。
（但し、右結合や左結合ができない等、万能ではありません）

mergeは「データ」を軸に、横方向結合に特化しています。
concatでも横方向結合はできましたが、横方向結合だけでいえばmergeのほうができることが多くなります。

後述のjoinは「インデックス」を軸にした結合となる点が違いです。
mergeはデータが軸となるため、どこが結合の軸となるか？を意識しながら利用します。


```python:Python
import pandas as pd

df1 = pd.DataFrame({"Shop":["A", "A", "B", "C"],
                   "Item":["りんご", "みかん", "ぶどう", "りんご"]})

df2 = pd.DataFrame({"Item":["りんご", "みかん", "ぶどう"],
                    "price":[150, 300, 500]})

df3 = pd.DataFrame({"Shop":["A", "D", "E"],
                   "Item":["りんご", "りんご", "なし"]})

df4 = pd.DataFrame({"Shop":["A","B","D"],
                    "Location":["東京", "大阪", "北海道"]})
```

### 書き方1：df1に対しdf2をmergeし、新たなDFを作成する
```python:Python
df1.merge(df2)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3111552/a96bb408-b8e6-47d5-2ed0-5b6d41d812d6.png)

「共通する列名」をキーとして結合処理が行われる。

:::note info
この場合、df1を「上書き」するのではなく、
結果を新たなDFとして生成しています。
なのでこの直後にdf1を確認しても、元のデータのままです。

```python:Python
df1.head()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3111552/9235fafe-d43e-73ec-25e1-971a8c1a61e1.png)
:::




### 書き方2：mergeの引数にd1,d1を含め、新たなDFを作成する
```python:Python
pd.merge(df1, df2)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3111552/4fafc0c3-5e09-1021-114a-5367b20f975e.png)

書き方1と結果は同様



### 複数列の名称が同じ場合のmerge
```python:Python
df1.merge(df3)
``` 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3111552/db23a3f7-9ef9-c8a8-78bf-44814782faa4.png)

複数列が同じ場合、同じ名称の列すべてを対象に、同一の列をマージする。
そのため、今回の例では　A-りんご　のみ残る。

### 結合列の明示的な指定
```python:Python
df1.merge(df3, on='Shop')
``` 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3111552/b8abbdea-20bb-03db-feb2-6092b165a926.png)

on で結合時の列を指定できる。
同一名の列が1つしかない場合は、onを指定してもしなくても結果は同じ。
同一名の列が2つ以上ある場合結果が変わる。
この時、他の同一名列に関しては、名称が例のように補完される。

### 結合方法

1. 内部結合：how='inner'　（※何もつけない時と同じ）
    ```python:Python
    pd.merge(df1, df4, how='inner')
    ``` 
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3111552/95ab1964-5ae9-958a-6358-f5557238299d.png)

1. 外部結合：how='outer'
    ```python:Python
    pd.merge(df1, df4, how='outer')
    ``` 
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3111552/30f46925-c59a-ecf6-e361-1466838f27f6.png)

1. 左結合：how='left'
    ```python:Python
    pd.merge(df1, df4, how='left')
    ``` 
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3111552/9bcd6d98-6354-9348-f429-6de659e91642.png)

1. 左結合：how='right'
    ```python:Python
    pd.merge(df1, df4, how='right')
    ``` 
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3111552/7f993d8d-e60d-64a1-e4f9-f441e75f74cc.png)


:::note info
mergeの重要な特徴として、1度に結合できるのは2つまでという点です。
concatは3つ以上の結合が可能です
　例）pd.concat([df1, df2, df3])
ですが、mergeではこのような記載はできません。
:::


# join
joinは、インデックス（データではない）を軸に、横方向の結合を行います。

joinは「インデックス」を軸にした結合です。
つまり同一のインデックスでないと結合できません。
（※厳密には、左側のデータはインデックス以外を指定することも可能ですが、右側はインデックスが軸になります）
mergeはデータを軸とするためある意味では汎用的ですが、一度に3つ以上の結合を行うことはできませんでした。
joinはインデックスが軸となるため、一度に3つ以上の結合が可能となります。

concatとの違いはmergeと同様、
concatは単に結合するイメージ、縦にも横にも結合可能でしたが、その結果細かい結合ができませんでした。
joinは横方向特化のため、concatより細かい結合が可能です。


```python:Python
import pandas as pd

df1 = pd.DataFrame({"Shop":["A", "A", "B", "C"],
                   "Fruit":["りんご", "みかん", "ぶどう", "りんご"]}).set_index("Shop")

df2 = pd.DataFrame({"Shop":["A", "B", "D"],
                    "Meat":["牛", "豚", "牛"]}).set_index('Shop')

df3 = pd.DataFrame({"ID":["1", "2", "3", "4", "5"],
                    "Shop":["A", "B", "C", "D", "E"],
                    "Location":["東京", "大阪", "北海道", "愛知", "福岡"]}).set_index('ID')

df4 = pd.DataFrame({"Shop":["B", "D"],
                    "Drink":["お茶", "コーラ"]}).set_index('Shop')
```


### 書き方：df1に対しdf2をjoinし、新たなDFを作成する
```python:Python
df1.join(df2)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3111552/d90ec641-2ef1-17ec-ca6f-f5eef7dcb864.png)


:::note info
mergeの時にできた以下のような記述は、joinではできません。

```python:Python
#mergeでできた以下のような記載はできない
pd.join(df1, df2)
```
```css:
AttributeError: module 'pandas' has no attribute 'join'
```
:::


### 結合列の明示的な指定（※左側のみ）
```python:Python
# 左側であるdf3について、結合列をonで指定することが可能
# 右側であるdf1は指定できず、インデックスが利用される
df3.join(df1, on="Shop")
``` 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3111552/3a200901-4d5d-8ecf-7009-593106716021.png)


### 3つ以上の一括結合
```python:Python
df1.join([df2, df4])
``` 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3111552/2798bc07-f1b8-5761-26ba-4ba8bc6de31e.png)


### 結合方法　→　mergeとデフォルト値が異なる

1. 内部結合：how='inner'　
    ```python:Python
    df1.join(df2, how='inner')
    ``` 
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3111552/b4a3da24-2454-0604-ba34-9549f55670bb.png)


1. 外部結合：how='outer'
    ```python:Python
    df1.join(df2, how='outer')
    ``` 
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3111552/07391ff1-43cf-0651-7dc1-e6efd2dd3d55.png)


1. 左結合：how='left'　（※何もつけない時と同じ）
    ```python:Python
    df1.join(df2, how='left')
    ``` 
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3111552/326145c0-fcd4-2578-ec2d-67eb2ee45fcf.png)


1. 左結合：how='right'
    ```python:Python
    df1.join(df2, how='right')
    ``` 
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3111552/ea9da51a-c53d-acc3-c690-9c94f6ee7b79.png)




# まとめ
* concat：単にくっつけるイメージ、縦にも横にも結合可能、その結果細かい結合ができない
* merge：「データ」を軸に横に結合。何を軸に結合するかを意識しつつ利用。
* join：「インデックス」を軸に横に結合。結合軸がインデックスで固定のため、複数をまとめ結合できる