【python】pandasのmerge備忘録

## 1. はじめに
`pandas`の`merge`の挙動についてよく忘れて何回も調べるはめになるのでまとめておく．（`pandas`に限った話ではないが）

## 2.環境
- python 3.6.0
- pandas 0.23.4

## 3.merge
実際にどう処理されるかは別として，以下のように考えるとわかりやすい．

1. `on`で指定した列からインデックス集合を作る．
2. インデックス集合の要素を一つとり，`on`で指定した列がその値をとる行を抽出．
3. `on`で指定した列以外の列の直積集合がmerge後のdatadrameの行として追加される．
4. 2,3をインデックス集合の全ての要素に対して繰り返す．

1のインデックス集合の作り方によって，`inner`, `outer`, `left`, `right`がある．
それ以降についてはどれも共通．

### 例となるdataframeを準備

```python
import pandas as pd

left_df = pd.DataFrame({"idx": [1,1,1], "left_val": [100,101,102]})
right_df = pd.DataFrame({"idx": [1,1,1], "right_val": [103,104,105]})

# In [9]: left_df
# Out[9]:
#    idx  left_val
# 0    1       100
# 1    1       101
# 2    1       102

# In [10]: right_df
# Out[10]:
#    idx  right_val
# 0    1        103
# 1    1        104
# 2    1        105
```

### とりあえず結果の確認

```python
how = "inner"
merged_df = left_df.merge(right_df, on="idx", how=how)

# In [11]: merged_df
# Out[11]:
#    idx  left_val  right_val
# 0    1       100        103
# 1    1       100        104
# 2    1       100        105
# 3    1       101        103
# 4    1       101        104
# 5    1       101        105
# 6    1       102        103
# 7    1       102        104
# 8    1       102        105

```


### 1.`on`で指定した列からインデックス集合を作る．
`on="idx"`とした時，インデックス集合は

```python
set(left_df["idx"]) | set(right_df["idx"])
```

の部分集合となるように作る．

`how`オプションごとに以下の評価式の右辺のようにインデックス集合を作る．
以下の評価は，全て`True`となる．

```python
# inner
set(merged_df["idx"]) == set(left_df["idx"]) & set(right_df["idx"])

# outer
set(merged_df["idx"]) == set(left_df["idx"]) | set(right_df["idx"])

# left
set(merged_df["idx"]) == set(left_df["idx"])

# right
set(merged_df["idx"]) == set(right_df["idx"])
```




### 2.インデックス集合の要素を一つとり，`on`で指定した列がその値をとる行を抽出．

```python
idxset = set(merged_df["idx"])
# In [20]: idxset
# Out[20]: {1}

idx = idxset.pop()
# idx = 1

left_df_idx = left_df.query(f"idx == {idx}")
right_df_idx = right_df.query(f"idx == {idx}")

# In [12]: left_df_idx
# Out[12]:
#    idx  left_val
# 0    1       100
# 1    1       101
# 2    1       102

# In [13]: right_df_idx
# Out[13]:
#    idx  right_val
# 0    1        103
# 1    1        104
# 2    1        105
```

ここで，`left_df`または`right_df`に対応する行がない時，`NaN`となる．
`inner`の時は常に`len(left_df_idx) > 0`かつ`len(right_df_idx) > 0`なので問題ないが，それ以外にした時は`len(left_df_idx) >= 0`かつ`len(right_df_idx) >= 0`となるのであり得る．

### 3.`on`で指定した列以外の列の直積集合がmerge後のdatadrameの行として追加される．

```python
merged_df_idx = merged_df.query(f"idx == {idx}")

# In [23]: merged_df_idx
# Out[23]:
#    idx  left_val  right_val
# 0    1       100        103
# 1    1       100        104
# 2    1       100        105
# 3    1       101        103
# 4    1       101        104
# 5    1       101        105
# 6    1       102        103
# 7    1       102        104
# 8    1       102        105
```

直積集合なので以下の関係が成り立つ．

```python
len(merged_df_idx) == len(left_df_idx) * len(right_df_idx)
# 9 = 3*3
```

この例の場合，

```
{100, 101, 102} × {103, 104, 105} = {(100, 103), (100, 104), (100, 105),
                                     (101, 103), (101, 104), (101, 105),
                                     (102, 103), (102, 104), (102, 105)}
```

となっている．

### 4. 2,3をインデックス集合の全ての要素に対して繰り返す．
これを全てのインデックス集合に対して繰り返したdataframeが，merge後のdataframeになる．（この例だと1回で終了してるが）