DataFrameでreplaceされない要素を平均値で埋めたいけど冗長になってしまったあなたへ

# はじめに
データフレームはデータ分析をPythonでやるときには必須になります。
しかし、少し複雑な処理になると冗長なコードになりがちでもっとうまい処理の仕方はないかと思います。
私の勤める企業ではPythonを利用している人がかなり少ないため、そのような処理を書くときに`リファクタリング`について聞く当てがありません。(残念ながらエンジニアの知り合いも少ないです)

そんな状況でデータ分析をしているときに、「この処理うまく書けないか？」と思いました。

最近はITに関する質問をすることができる[teratail](https://teratail.com/)や[StackOverFlow](https://ja.stackoverflow.com/)などがあり、そこでは達人が多くの質問に早く回答してくれます。

私は処理を効率よくする(調べるだけではみつけずらい)方法を学ぶために上記のサービスを利用して学ぶようにしています。(terateilのほうが回答が返ってきやすいです)

そして今回もスマートにコードが書けないかとteratailを利用しましたので、まとめたいと思います。

# 問題

以下のようなデータフレームがあるとします。

|index| 都道府県 | 市区町村 |
|---|---|---|
| 1 | 東京 | 渋谷|
|2 | 東京 | 新宿 |
| 3 | 東京 | 池袋 |
| 4 | 埼玉 | 大宮 |
| 5 | 千葉 | 新浦安 |
|6 | 神奈川 | 横浜 |

そして、`市区町村`を辞書(dic)を利用して`replace`します。

```
# データフレームの準備
pref = ["東京", "東京", "東京", "埼玉", "千葉", "神奈川"]
value = ["渋谷", "新宿", "池袋", "大宮", "新浦安", "横浜"]
df = pd.DataFrame({'都道府県':pref, '市区町村': value})

# 辞書
dic = {"渋谷":100, "新宿":90, "大宮":50, "新浦安":45, "横浜":80}

# 辞書で市区町村列を置き換え
df["市区町村"] = df["市区町村"].replace(dic)
```

すると、市区町村の`池袋`がないために、置き換わらず以下のようなデータフレームになります。

|index| 都道府県 | 市区町村 |
|---|---|---|
| 0 | 東京 | 100|
|1 | 東京 | 90 |
| 2 | 東京 | 池袋 |
| 3 | 埼玉 | 50 |
| 4 | 千葉 | 45 |
|5 | 神奈川 | 80 |

ここで、`replace`できなかった池袋を「東京」の平均値で埋められる処理を考えます。

(100 + 90) / 2 = 95

池袋を95で埋めたいのですが、コードが冗長であったので質問をしました。

# 解決方法1

mapを利用することでうまく処理することができます。
`replace`だと辞書にないものはそのままになりますが、`map`を利用すると`NaN`となるため`float型`で扱いやすくなります。

またmapのほうが高速に動くそうです。

## 1.mapを利用して辞書で置換をする

```
import pandas as pd
# データフレームの準備
pref = ["東京", "東京", "東京", "埼玉", "千葉", "神奈川"]
value = ["渋谷", "新宿", "池袋", "大宮", "新浦安", "横浜"]
df = pd.DataFrame({'都道府県':pref, '市区町村': value})

# 辞書
dic = {"渋谷":100, "新宿":90, "大宮":50, "新浦安":45, "横浜":80}

# 置換
df["市区町村"] = df["市区町村"].map(dic)

```

するとdfは以下のようになります。

|index| 都道府県 | 市区町村 |
|---|---|---|
| 0 | 東京 | 100|
|1 | 東京 | 90 |
| 2 | 東京 | NaN |
| 3 | 埼玉 | 50 |
| 4 | 千葉 | 45 |
|5 | 神奈川 | 80 |

処理速度は以下のようになり、`map`の方が高速であることがわかります。

```
%timeit df["市区町村"].replace(dic)
# 1000 loops, best of 5: 865 µs per loop

%timeit df["市区町村"].map(dic)
# 1000 loops, best of 5: 478 µs per loop
```
## 2. 平均値で補完する

```
df['市区町村'] = df['市区町村'].groupby(df['都道府県']).transform(lambda s: s.fillna(s.mean()))
```

県ごとにまとめて、処理をしています。
`apply`と`transformer`は同じような使い方になりますが、少し違いがありますので以下を参照してください。

[Pandas の transform と apply の基本的な違い](https://qiita.com/greenteabiscuit/items/132e0f9b1479926e07e0)

そのあと`fillna()`を利用してNaNに該当するところを県の平均で補完します。

|index| 都道府県 | 市区町村 |
|---|---|---|
| 0 | 東京 | 100|
|1 | 東京 | 90 |
| 2 | 東京 | 95 |
| 3 | 埼玉 | 50 |
| 4 | 千葉 | 45 |
|5 | 神奈川 | 80 |

また1行でこの処理を書くことも可能です。

```
df['市区町村'] = df['市区町村'].map(dic).groupby(df['都道府県']).transform(lambda s: s.fillna(s.mean()))
```


# 解決方法2

手順を追ってやることも可能です。

## 1. 辞書を使って置換する
問題で行っていた手順をまず行います。


```
# データフレームの準備
pref = ["東京", "東京", "東京", "埼玉", "千葉", "神奈川"]
value = ["渋谷", "新宿", "池袋", "大宮", "新浦安", "横浜"]
df = pd.DataFrame({'都道府県':pref, '市区町村': value})

# 辞書
dic = {"渋谷":100, "新宿":90, "大宮":50, "新浦安":45, "横浜":80}

# 辞書で市区町村列を置き換え
df["市区町村"] = df["市区町村"].replace(dic)
```

|index| 都道府県 | 市区町村 |
|---|---|---|
| 0 | 東京 | 100|
|1 | 東京 | 90 |
| 2 | 東京 | 池袋 |
| 3 | 埼玉 | 50 |
| 4 | 千葉 | 45 |
|5 | 神奈川 | 80 |

## 2.市区町村ごとに平均値をもとめる

```
# 市区町村の要素を整数に変換する
tempdf["市区町村"] = tempdf["市区町村"].astype(int)

# 市区町村ごとに平均値をもとめる
mean_df = tempdf.groupby('都道府県').mean()
```
|index|  都道府県 | 市区町村 |
|---|---|---|
|0 |  東京  | 100 |
| 1  | 東京  |  90 |
| 2 | 東京  |  95 |
| 3  | 埼玉  |  50 |
| 4  | 千葉  |  45 |
| 5 | 神奈川  |  80 |

## 3. 平均値のデータフレームを辞書にする

```
mean_dict = mean_df.to_dict()['市区町村']
```

## 4.辞書でデータフレームを置換する
```
df['市区町村']  = df.apply(lambda row: row['市区町村'] if type(row['市区町村'])==int else mean_dict[row['都道府県']], axis=1)
```
|index| 都道府県 | 市区町村 |
|---|---|---|
| 0 | 東京 | 100|
|1 | 東京 | 90 |
| 2 | 東京 | 95 |
| 3 | 埼玉 | 50 |
| 4 | 千葉 | 45 |
|5 | 神奈川 | 80 |


# おわりに
replaceを2回利用して希望通りの結果になりました。
私は置換を2回行うところまでは同じでしたが、`apply`を利用したうまい処理ができず、困っていました。
詳しい人の技術を盗むことも成長するには必要ですね。



# 参考文献
[teratail](https://teratail.com/)
[StackOverFlow](https://ja.stackoverflow.com/)
[DataFrameのreplaceで置換されなかった要素の処理について](https://teratail.com/questions/340871)
[Pandas の transform と apply の基本的な違い](https://qiita.com/greenteabiscuit/items/132e0f9b1479926e07e0)