データサイエンス100本ノック~初心者未満の戦いpart6

これはデータサイエンティストの卵がわけもわからないまま100本ノックを行っていく奮闘録である。
完走できるか謎。~~途中で消えてもQiitaにあげてないだけと思ってください。~~


[100本ノックの記事](https://codezine.jp/article/detail/12456)
[100本ノックのガイド](https://github.com/The-Japan-DataScientist-Society/100knocks-preprocess/blob/master/docker/doc/100knocks_guide.pdf)

**ネタバレも含みますのでやろうとされている方は注意**


最初に参考にさせていただいたページのリンクは初回のみに変更しました。

コレは見づらい！この書き方は危険！等ありましたら教えていただきたいです。~~心にダメージを負いながら~~糧とさせていただきます。

この解き方は間違っている！この解釈の仕方は違う！等もありましたらコメントください。

今回は３３～３５まで。
[[前回]](https://qiita.com/sola_wing529/items/378c719b9a56dbd5a615)２９～３２
[[目次付き初回]](https://qiita.com/sola_wing529/items/5fdbd97308dd88da28eb)

#33本目
>P-033: レシート明細データフレーム（df_receipt）に対し、店舗コード（store_cd）ごとに売上金額（amount）の平均を計算し、330以上のものを抽出せよ。

```mine33.py
df=df_receipt
df=df.groupby('store_cd').agg({'amount':'mean'}).query("amount>=330")
df

'''模範解答'''
df_receipt.groupby('store_cd').amount.mean().reset_index().query('amount >= 330')
```

今までの復習をするような問題。
guroupbyで演算データの範囲指定をし、
aggで演算の行を追加し、
queryで条件指定をする。

だけだと思ったら模範解答では`.amount.mean()`
なるほど。ただ、列指定を`.`でつなげて書くとうまくいかない場合もあるらしいので気を付けたい。

~~queryに屈した~~こうやってまとめていると模範解答の便利な部分も使うことができていいと思う。

#34本目
>P-034: レシート明細データフレーム（df_receipt）に対し、顧客ID（customer_id）ごとに売上金額（amount）を合計して全顧客の平均を求めよ。ただし、顧客IDが"Z"から始まるのものは非会員を表すため、除外して計算すること。

```mine34.py
df=df_receipt
df=df[df['customer_id'].str.contains('^[^Z]')].reset_index()
df.groupby('customer_id').amount.sum().mean()

'''模範解答'''
# queryを使わない書き方
df_receipt[~df_receipt['customer_id'].str.startswith("Z")].groupby('customer_id').amount.sum().mean()

# queryを使う書き方
df_receipt.query('not customer_id.str.startswith("Z")', engine='python').groupby('customer_id').amount.sum().mean()
```


今度は模範解答が2パターンある問題でした。自分は`contains`を使って模範解答では`startwith`を使っている。正規表現にある程度慣れていれば`contains`のほうが書きやすいのですが、`startwith`のほうが前方しか見ないからレスポンス早かったりするのかな？

あと、自分は正規表現上で「先頭がＺから始まるもの以外」と指定していますが、模範解答は二つとも「先頭がＺから始まるもの」を定義して「それ以外」としています。正規表現って長くなると読み解くのに時間かかったりするから長くなりすぎる場合であればこういうのもありなのかな。

もしこの問題をSQLで書くとするならば

```py34.sql
SELECT SUM(amount)/COUNT(DISTINCT customer_id)
FROM receipt
WHERE NOT customer_id LIKE 'Z%'
```
って感じになると思う(一応動作確認済み)

#35本目
>P-035: レシート明細データフレーム（df_receipt）に対し、顧客ID（customer_id）ごとに売上金額（amount）を合計して全顧客の平均を求め、平均以上に買い物をしている顧客を抽出せよ。ただし、顧客IDが"Z"から始まるのものは非会員を表すため、除外して計算すること。なお、データは10件だけ表示させれば良い。

```mine35.py
df=df_receipt
df=df[df['customer_id'].str.contains('^[^Z]')].reset_index()
df=df.groupby('customer_id').amount.sum().reset_index()
df[df['amount'].mean() <= df['amount']].head(10)

'''模範解答'''
amount_mean = df_receipt[~df_receipt['customer_id'].str.startswith("Z")].groupby('customer_id').amount.sum().mean()
df_amount_sum = df_receipt.groupby('customer_id').amount.sum().reset_index()
df_amount_sum[df_amount_sum['amount'] >= amount_mean].head(10)
```
ついに模範解答さんもコードが長くなってきました。~~dfの2文字に略しているおかげだな~~

今回書くにあたってデータフィールド上でどの計算をしているかをかなり意識しました。
模範解答さんも`amount_mean`と`df_amount_sum`に分けていますし。

1. `df=df_receipt`
 - いつもの
2. `df=df[df['customer_id'].str.contains('^[^Z]')].reset_index()`
 - 先頭｢Z｣以外の`costomer_id`をまとめる
3. `df=df.groupby('customer_id').amount.sum().reset_index()`
 - `costomer_id`毎の`amount`合計を出す
4. `df[df['amount'].mean() <= df['amount']].head(10)`
 - 3の平均以上のデータに絞る

4の比較以外で平均を参照することが無かったのと、二つのdfを作るのが嫌だったので今回この書き方をしました。
もちろん、問題では先に平均を求めろと言っているわけですので他の処理で平均を使うようであればこの書き方は間違っているかもしれませんが。

どちらにせよ、「合計の平均」と「合計」を比較するので、4でその比較を行います。

~~`.reset_index()`の頻発はおまじないの意味もある~~

#今回はここまで
次回から表の結合が始まり中途半端だときりが悪いので少し短いですがここで区切ります。
~~そのために問題文と模範解答で少しでも尺稼ぎをした~~