pandasよりもdfplyの方が直感的にテーブル操作できるかもしれない件

## はじめに
構造化データの前処理は、基本pandasを使っているんですけど、
SQLだと簡単に書けるのに、pandasではどう書くんだっけと、時々忘れてしまうことが多々あり、、、
そんな悩みを解決してくれたライブラリがSQLっぽく直感的にテーブル操作できるdfply


## 使用データ概要

本記事で使用するデータは、[人流オープンデータ（国土交通省）](https://www.geospatial.jp/ckan/dataset/mlit-1km-fromto)よりダウンロードしました。（東京都と神奈川県のみ）

>・monthly_mdp_mesh1km_{pref_code}：1kmメッシュ別の滞在人口
>・prefcode_citycode_master：都道府県コード、市区町村コードのマスタデータ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354874/4650ead9-b794-4a4c-c74f-de874243bf6c.png)

### データ前処理

```sample.py
'''
Agoop/
└── data
│   └── {pref_code}_mesh1km
│   │   └── 2019/{mm}/monthly_mdp_mesh1km.csv.zip：2019年{mm}月の1kmメッシュ滞在人口
│   │   └── 2020/{mm}/monthly_mdp_mesh1km.csv.zip：2020年{mm}月の1kmメッシュ滞在人口
│   └── prefcode_citycode_master
│       └── prefcode_citycode_master_utf8_2019.csv.zip：都道府県・市区町村マスタ-_2019年ver
│       └── prefcode_citycode_master_utf8_2020.csv.zip：都道府県・市区町村マスタ-_2020年ver
└── src
   └── sample.py
'''
import pandas as pd
from dfply import *

prefcode_citycode_master = pd.DataFrame()
for year in [2019, 2020]:
    tmp_df = pd.read_csv(f'../data/prefcode_citycode_master/prefcode_citycode_master_utf8_{year}.csv.zip')
    tmp_df['year'] = year
    prefcode_citycode_master = pd.concat([prefcode_citycode_master, tmp_df])

dfs = []
for pref_code in [13, 14]: # 東京都(pref_code=13), 神奈川県(pref_code=14)
    for mm in range(12):
        mm = str(mm+1).zfill(2)
        dfs.append(pd.read_csv(f'../data/{pref_code}_mesh1km/2019/{mm}/monthly_mdp_mesh1km.csv.zip'))
        dfs.append(pd.read_csv(f'../data/{pref_code}_mesh1km/2020/{mm}/monthly_mdp_mesh1km.csv.zip'))

df = pd.concat(dfs).reset_index(drop=True)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354874/a31013c9-6a52-10dc-ec49-4b49b709ea8e.png)

## dfply　vs　pandas

基本的な使い方はデータに対する操作を`>>`演算子で連鎖させていく感じです。
SQLに慣れてる人なら、dfplyの方が使いやすいかも

### ①特定の列のみを参照したい

参照したい列名を`select`

```python
# dfply
df >> select(X.mesh1kmid, X.year, X.month, X.dayflag, X.timezone, X.population)

# pandas
df[['mesh1kmid', 'year', 'month', 'dayflag', 'timezone', 'population']]
```

### ②特定の列のみ削除したい

削除したい列に`~`をつける

```python
# dfply
df >> select(~X.prefcode, ~X.citycode)

# pandas
df.drop(['prefcode', 'citycode'], axis=1)
```

### ③特定の列で並び替えしたい

`arrange`を使う

```python
# dfply
df >> select(X.mesh1kmid, X.year, X.month, X.dayflag, X.timezone, X.population) >> arrange(X.year, X.population)

# pandas
df[['mesh1kmid', 'year', 'month', 'dayflag', 'timezone', 'population']].sort_values(by=['year', 'population'])
```

### ④列名を変更したい

**dfply**：rename(新しい名前=X.古い名前)と、代入する感覚
**pandas**：rename(columns={'古い名前':'新しい名前'}) or rename(columns={'新しい名前':'古い名前'})のどっちだっけってなる

```python
# dfply
df >> select(X.mesh1kmid, X.year, X.month, X.dayflag, X.timezone, X.population) >> rename(地域1kmメッシュコード=X.mesh1kmid, 年度=X.year, 月=X.month, 平日休日=X.dayflag, 時間帯=X.timezone, 滞在人口=X.population)

# pandas
df[['mesh1kmid', 'year', 'month', 'dayflag', 'timezone', 'population']].rename(columns={'mesh1kmid':'地域1kmメッシュコード', 'year':'年度', 'month':'月', 'dayflag':'平日休日', 'timezone':'時間帯', 'population':'滞在人口'})
```

### ⑤重複を削除したい

SQLと同様に`distinct`が使える

```python
# dfply
prefcode_citycode_master >> select(~X.year) >> distinct()

# pandas
prefcode_citycode_master.drop('year', axis=1).drop_duplicates()
```

### ⑥フィルタリングしたい

pandasは条件が増えると、( )が多くなるのが嫌だ

```python
# dfply
df >> mask(X.year==2019, X.month==12, X.population >= 10000, X.population <= 20000)

# pandas
df[(df['year']==2019) & (df['month']==12) & (df['population'] >= 10000) & (df['population'] <= 20000)]
```

### ⑦特定の列をキーにして、テーブル同士を結合したい

SQLと同様に`inner join`が使える


```python
# dfply
df >> inner_join(prefcode_citycode_master, by=['year', 'prefcode', 'citycode']) >> select(~X.prefcode, ~X.citycode)

# pandas
pd.merge(df, prefcode_citycode_master, on=['year', 'prefcode', 'citycode']).drop(['prefcode', 'citycode'], axis=1)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354874/d6aa4655-93ed-11a8-4b8e-a9652b273a19.png)



### ⑧複数列でグルーピング＆ランク付けしたい

年度別都道府県別に滞在人口にランク付けして、上位3つを表示することを考える。つまり、SQLの`partition by`に相当する操作。

**dfply**：一行で書こうとすると、ちょい長いが全然許容範囲。　※`mutate`は列名を追加する関数です
**pandas**：ちゃんとググらないとわからないので、諦めました（笑）

```python
# dfply
df >> inner_join(prefcode_citycode_master, by=['year', 'prefcode', 'citycode']) >> select(~X.prefcode, ~X.citycode, ~X.cityname) >> \
    group_by(X.prefname, X.year) >> mutate(population_rank=row_number(X.population, ascending=False)) >> mask(X.population_rank<=3) >> arrange(X.population_rank)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354874/c148af45-caa6-6333-c183-93442027e6ec.png)


### ⑨複数列でグルーピング＆前年比を集計したい

全日（dayflag=2）および終日（timezone=2）に絞って、1kmメッシュ別年平均滞在人口を算出し、前年比を集計する。

**dfply**：複数の関数を連鎖的に書けるのは便利！
**pandas**：ちゃんとググらないとわからないので、諦めました（笑）

```python
# dfply
import numpy as np
tmp_df = df  >> inner_join(prefcode_citycode_master, by=['year', 'prefcode', 'citycode']) >> mask(X.dayflag==2, X.timezone==2) >> \
    select(X.mesh1kmid, X.year, X.month, X.population, X.address) >> group_by(X.mesh1kmid, X.year) >> mutate(avg_population=np.mean(X.population)) >> \
    select(~X.month, ~X.population) >> distinct()

df_ratio = tmp_df >> group_by(X.mesh1kmid) >> mutate(lead_avg_population=lead(X.avg_population)) >> mutate(ratio=X.lead_avg_population/X.avg_population) >> mask(X.ratio.notnull())
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354874/afb4cc34-c977-d8b9-e3e8-5de6a97ec73f.png)

個人的には、pandasよりも直感的かつ覚えやすいなと！（もちろん、pandasでも便利な書き方はググれば色々あると思いますが、あくまで自分が知っている範囲で）

### 参考リンク

https://qiita.com/T_Shinomiya/items/c039451869382e0790f4

https://github.com/kieferk/dfply#spread