データサイエンス100本ノック（構造化データ加工編）をやってみた part3(P-023~P-031)

　この記事はデータサイエンスを勉強しながら、データサイエンス協会が提供する__データサイエンス100本ノック（構造化データ加工編）__を解く過程を自分用にまとめたものです。

|　チャプター　|　リンク　|　チャプター　|　リンク　|
|:------:|:----:|:------:|:---:|
|　P-001~P-016　|　[part1](https://qiita.com/pyon/items/c208e585e0cdfabc58bd)　|　P-052~P-062　|　[part6](https://qiita.com/pyon/items/5c5926241b8e6da978f3)　|
|　P-017~P-022　|　[part2](https://qiita.com/pyon/items/73bf9e8fe75f807aeea8)　|　P-063~P-068　|　[part7](https://qiita.com/pyon/items/ac8c7fb9110dbba061f5)　|
|　P-023~P-031　|　[part3](https://qiita.com/pyon/items/948ca309c7985fa2e855)　|　P-069~P-078　|　[part8](https://qiita.com/pyon/items/60daed73e5ff670e209d)　|
|　P-032~P-039　|　[part4](https://qiita.com/pyon/items/e7146bbb728979430814)　|　P-079~P-088　|　[part9](https://qiita.com/pyon/items/911e3d236fad8df13f44)　|
|　P-040~P-051　|　[part5](https://qiita.com/pyon/items/dac17fb162c99769978b)　| P-089~P-100 |　[part10](https://qiita.com/pyon/items/70c1e222914c658a480c)　|

+ `groupby()`
+ `agg()`
+ 最大値　`max()`
+ 最小値　`min()`
+ 平均値　`mean()`
+ 中央値 `median()`
+ 最頻値 `mode()`
+ `apply()`
+ 分散 `var()`
+ 標準偏差 `std()`

# P-023 `groupby()`, `agg()`
>P-023: レシート明細データフレーム（df_receipt）に対し、店舗コード（store_cd）ごとに売上金額（amount）と売上数量（quantity）を合計せよ。

```python:p023.py
df_receipt.groupby('store_cd').agg({'amount':'sum', 'quantity':'sum'}).reset_index()
```
　`groupby('列名')`：引数に列名を指定するとその列の値ごとにグルーピングされる。
　返されるのはGroupByオブジェクトでそれ自体をprint()で出力しても中身は表示されない。
　グルーピングしたオブジェクトの処理に関しては以下のページを参照。

https://note.nkmk.me/python-pandas-groupby-statistics/

# P-024 `max()`
>P-024: レシート明細データフレーム（df_receipt）に対し、顧客ID（customer_id）ごとに最も新しい売上日（sales_ymd）を求め、10件表示せよ。

```python:p024.py
df_receipt.groupby('customer_id').sales_ymd.max().reset_index().head(10)
```

# P-025 `min()`
>P-025: レシート明細データフレーム（df_receipt）に対し、顧客ID（customer_id）ごとに最も古い売上日（sales_ymd）を求め、10件表示せよ。

```python:p025.py
df_receipt.groupby('customer_id').sales_ymd.min().reset_index().head(10)
```

# P-026
>P-026: レシート明細データフレーム（df_receipt）に対し、顧客ID（customer_id）ごとに最も新しい売上日（sales_ymd）と古い売上日を求め、両者が異なるデータを10件表示せよ。

```python:myp026.py
df_max = df_receipt.groupby('customer_id').sales_ymd.max().reset_index()
df_min = df_receipt.groupby('customer_id').sales_ymd.min().reset_index()
df_max_min = pd.concat([df_max, df_min['sales_ymd']], axis=1)
df_max_min.columns = ['customer_id', 'sales_ymd_max', 'sales_ymd_min']
df_max_min.query('sales_ymd_max != sales_ymd_min').head(10)
```
　最も新しい売り上げ日(`df_max`)と最も古い売り上げ日(`df_min`)のデータフレームを作り、それを横方向に結合した`df_max_min`を作成。
　その後、カラム名を変更し、条件に合うものを出力した。

```python:p026.py
df_tmp = df_receipt.groupby('customer_id').agg({'sales_ymd':['max','min']}).reset_index()
df_tmp.columns = ["_".join(pair) for pair in df_tmp.columns]
#df_tmp.columns = ['customer_id', 'sales_ymd_max', 'sales_ymd_min'] でも可
df_tmp.query('sales_ymd_max != sales_ymd_min').head(10)
```
　解答では`agg()`メソッドを使って上のように書いている。
　2行目でリスト内包表記を使いカラム名を変更している。

# P-027 `mean()`
>P-027: レシート明細データフレーム（df_receipt）に対し、店舗コード（store_cd）ごとに売上金額（amount）の平均を計算し、降順でTOP5を表示せよ。

```python:p027.py
df_receipt.groupby('store_cd').amount.mean().reset_index().sort_values('amount', ascending=False).head(5)
```
　`agg()`メソッドを使うなら

```python:p027.py
df_receipt.groupby('store_cd').agg({'amount':'mean'}).reset_index().sort_values('amount', ascending=False).head(5)
```

# P-028 `median()`
>P-028: レシート明細データフレーム（df_receipt）に対し、店舗コード（store_cd）ごとに売上金額（amount）の中央値を計算し、降順でTOP5を表示せよ。

```python:p028.py
df_receipt.groupby('store_cd').amount.median().reset_index().sort_values('amount', ascending=False).head(5)
```
　これもP-027と同様に`amount.median()`を`agg({'amount':'median'})`と書いても同様の結果を得られる。

# P-029 `mode()`, `apply()`
>P-029: レシート明細データフレーム（df_receipt）に対し、店舗コード（store_cd）ごとに商品コード（product_cd）の最頻値を求めよ。

```python:p029.py
df_receipt.groupby('store_cd').product_cd.apply(lambda x: x.mode()).reset_index()
```

　GroupByオブジェクトの最頻値は他の関数`sum`,`mean`,`median`等と違い、そのまま使うことができない。
　apply()やvalue_counts()を組み合わせる必要がある。

　`apply()`：引数に一次元配列に適用可能な関数を渡す。
　デフォルトで各列、`axis=1`で各行に適用される。
　解答では無名関数( `lambda` )を使っている。

# P-030 `var()`
>P-030: レシート明細データフレーム（df_receipt）に対し、店舗コード（store_cd）ごとに売上金額（amount）の標本分散を計算し、降順でTOP5を表示せよ。

```python:p030.py
df_receipt.groupby('store_cd').amount.var(ddof=0).reset_index().sort_values('amount', ascending=False).head(5)
```
　__分散の式__
$$　s^2 = \frac{1}{n} \sum_{i=1}^{n} (x_i - \bar{x})^2 $$

　解答にも書いてあるんですがNumpyとPandasでは分散`var()`と標準偏差`std()`のデフォルト値が変わってきます。
　これはNumpyはデフォルト値で標本分散が求まり、Pandasでは不偏分散の値が求まるようになっているからだそうです。

　`var()`で分散を計算する際、平均との偏差の2乗の和を`n - ddof`で割ります。
　Pandasではデフォルトが`ddof=1`であるため、引数に`ddof=0`を渡します。

# P-031 `std()`
>P-031: レシート明細データフレーム（df_receipt）に対し、店舗コード（store_cd）ごとに売上金額（amount）の標本標準偏差を計算し、降順でTOP5を表示せよ。

```python:p031.py
df_receipt.groupby('store_cd').amount.std(ddof=0).reset_index().sort_values('amount', ascending=False).head(5)
```
　P-030と同じように`std()`の引数に`ddof=0`を渡します。