Kaggleから学ぶPandasのテクニック

KaggleのNotebookで見つけたPandasのテクニックのメモ

元データ：[NFL_Predictions](https://www.kaggle.com/code/codyjanderson/nfl-predictions)

# フィルタリング
eventがball_snap, pass_forward, だったら抜き出す、みたいなことを一行でやっている。
``` Python
plays_pass = plays_pass.loc[plays_pass['event'].isin(['ball_snap','pass_forward','lateral','fumble','fumble_offense_recovered','qb_sack','qb_strip_sack','run'])]
```

# 集計
キーでグループ化した中からmin値を求める。aggは使ったことがなかったが、これは便利。あと、集計したあとreset_index()でインデックスを初期化するのも大事なテクニックだと思う。
``` Python
keys2 = ['gameId','playId']
non_bs3 = non_bs2.groupby(keys2).agg({'action_end_frame': 'min'}).reset_index()
```

# カラムのリネーム
こういう風にいくつものカラムを一気にリネームできるのはやったことがなかった。
``` Python
pos_qb.rename(columns = {'x': 'qb_x', 'y': 'qb_y'}, inplace = True)
```

# データフレームのマージ
一旦左結合して、数が同じだったら結合する。左結合の場合は、keysが共通でないものはNanになる。そうでない結合はkeysで共通なものだけ結合される。
``` Python
keys = ['gameId', 'playId', 'nflId']
    b = a.merge(pff_df, on=keys, how='left')
    
    if a.shape[0] != b.shape[0]: # test left join quality, if passes continue with inner
        print('error')
        break
    else:
        b = a.merge(pff_df, on=keys)
```

<details><summary>左結合（left join）とは？</summary>
### キーの指定と結合の操作
```python
keys = ['gameId', 'playId', 'nflId']
b = a.merge(pff_df, on=keys, how='left')
```

このコード行では、`a`データフレームと`pff_df`データフレームを、`keys`で指定された列（`gameId`、`playId`、`nflId`）に基づいて左結合（left join）しています。

### 左結合（left join）とは？
左結合は、`a`データフレームのすべての行を保持し、`pff_df`から一致するキーを持つ行を追加します。一致する行が`pff_df`に存在しない場合、`pff_df`の列には`NaN`（欠損値）が入ります。

### 例
仮に、以下のようなデータがあったとします。

#### `a`データフレーム
| gameId | playId | nflId | other_columns_a |
|--------|--------|-------|-----------------|
| 1      | 101    | 1001  | ...             |
| 2      | 102    | 1002  | ...             |
| 1      | 103    | 1003  | ...             |

#### `pff_df`データフレーム
| gameId | playId | nflId | officialPosition | pff_positionLinedUp | completion_pct | interception_pct | sack_pct | scramble_pct | playResult |
|--------|--------|-------|------------------|----------------------|----------------|------------------|----------|--------------|------------|
| 1      | 101    | 1001  | ...              | ...                  | ...            | ...              | ...      | ...          | ...        |
| 1      | 103    | 1003  | ...              | ...                  | ...            | ...              | ...      | ...          | ...        |

### 左結合の結果
左結合を行うと、以下のような結果が得られます。

#### `b`データフレーム
| gameId | playId | nflId | other_columns_a | officialPosition | pff_positionLinedUp | completion_pct | interception_pct | sack_pct | scramble_pct | playResult |
|--------|--------|-------|-----------------|------------------|----------------------|----------------|------------------|----------|--------------|------------|
| 1      | 101    | 1001  | ...             | ...              | ...                  | ...            | ...              | ...      | ...          | ...        |
| 2      | 102    | 1002  | ...             | NaN              | NaN                  | NaN            | NaN              | NaN      | NaN          | NaN        |
| 1      | 103    | 1003  | ...             | ...              | ...                  | ...            | ...              | ...      | ...          | ...        |

このように、`a`データフレームのすべての行が保持され、`pff_df`に一致するキーを持つ行のデータが追加されています。`pff_df`に一致する行がない場合、その列には`NaN`が入ります。

### その後の処理
```python
if a.shape[0] != b.shape[0]: # test left join quality, if passes continue with inner
    print('error')
    break
else:
    b = a.merge(pff_df, on=keys)
```

ここで、左結合後の行数が一致しない場合はエラーを表示して処理を中断します。行数が一致する場合は、再度`inner join`（内部結合）を行います。内部結合では、両方のデータフレームに共通するキーを持つ行のみが結合されます。

最終的に、結合されたデータフレームを`data`に追加しています。
```python
data = pd.concat([data, b], ignore_index=True)
```

このようにして、各週のデータを読み込み、必要な情報を結合し、最終的なデータセットを構築しています。
</details>

# 重複削除
重複行を削除する。確かにデータを加工していると重複することがあるのかも。
``` Python
keys2 = ['gameId','playId']
facts = ['completion_pct','interception_pct','playResult']
bs2 = plays_pass.loc[plays_pass['event'] == 'ball_snap'][keys2 + ['frameId'] + facts].drop_duplicates()
```

# ランク付け
例えば距離そのものではなく、相対的に「近いか遠いか」というようなランク付けができる。
```python
# データの用意
dist_qb2 = dist_qb1.merge(pos_qb1, on=['gameId', 'playId', 'frameId'])
dist_qb2['dist_qb'] = ((dist_qb2['qb_x'] - dist_qb2['x'])**2 + (dist_qb2['qb_y'] - dist_qb2['y'])**2)**0.5

# プレイヤーごとの最小距離を計算
dist_qb_plyr_min = dist_qb2.groupby(['gameId', 'playId', 'nflId']).agg({'dist_qb': 'min'}).reset_index()

# ランクの計算
dist_qb_plyr_min['prox_rank'] = dist_qb_plyr_min.groupby(['gameId', 'playId'])['dist_qb'].rank()

# 最も近いプレイヤーを特定
prox1 = dist_qb_plyr_min.loc[dist_qb_plyr_min['prox_rank'] == 1][['gameId', 'playId', 'dist_qb']].copy()
prox1.rename(columns={'dist_qb': 'prox1'}, inplace=True)
```