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

　この記事はデータサイエンスを勉強しながら、データサイエンス協会が提供する__データサイエンス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)　|

+ `map()`
+ ダミー変数に変換　`pd.get_dummies()`
+ 標準化　`preprocessing.scale()`
+ 正規化　`preprocessing.minmax_scale()`
+ 常用対数化　`np.log10()`, `math.log10()`
+ 自然対数化　`np.log()`, `math.log()`


# P-052
>P-052: レシート明細データフレーム（df_receipt）の売上金額（amount）を顧客ID（customer_id）ごとに合計の上、売上金額合計に対して2000円以下を0、2000円超を1に2値化し、顧客ID、売上金額合計とともに10件表示せよ。ただし、顧客IDが"Z"から始まるのものは非会員を表すため、除外して計算すること。

```python:p052.py
df_amount_sum = df_receipt.query('not customer_id.str.startswith("Z")', engine='python').groupby('customer_id').amount.sum().reset_index()
df_amount_sum['amount_cd'] = df_amount_sum['amount'].apply(lambda x: 0 if x <= 2000 else 1)
df_amount_sum.head(10)
```
# P-053
>P-053: 顧客データフレーム（df_customer）の郵便番号（postal_cd）に対し、東京（先頭3桁が100〜209のもの）を1、それ以外のものを0に２値化せよ。さらにレシート明細データフレーム（df_receipt）と結合し、全期間において買い物実績のある顧客数を、作成した2値ごとにカウントせよ。

```python:p053.py
df_tmp = df_customer.copy()
df_tmp['postal_flg'] = df_tmp['postal_cd'].apply(lambda x: 1 if 100 <= int(x.split('-')[0]) <= 209 else 0)
pd.merge(df_receipt, df_tmp, how='inner', on='customer_id').groupby('postal_flg').agg({'customer_id':'nunique'})
```

　解答では`postal_flg`列を作るとき、
　`apply(lambda x: 1 if 100 <= int(x[0:3]) <= 209 else 0)`
　とコードを書いていました。
　解答のようにデータを__int型__に変換して先頭の3桁をif文で条件式にかけた方がコードも簡単になりますね。

# P-054 `map()`
>P-054: 顧客データデータフレーム（df_customer）の住所（address）は、埼玉県、千葉県、東京都、神奈川県のいずれかとなっている。都道府県毎にコード値を作成し、顧客ID、住所とともに抽出せよ。値は埼玉県を11、千葉県を12、東京都を13、神奈川県を14とすること。結果は10件表示させれば良い。

```python:p054.py
# map は replace でも同じ結果となる。
df_address_cd = df_customer['address'].str[0:3].map({'埼玉県':'11','千葉県':'12', '東京都':'13', '神奈川':'14'}).rename('address_cd')
pd.concat([df_customer[['customer_id', 'address']], df_address_cd], axis=1).head(10)
```

　`map()`の引数に辞書`dict（{key: value}）`を指定すると、keyと一致する要素がvalueに置き換えられる。
　要素の置換を行うメソッドには`replace()`があるが、すべての要素を別の値に置換するのであれば、`map()`のほうが処理速度は早い。

　また、`map()`と`relpace()`の違いは、辞書`dict（{key: value}）`が全ての要素を網羅していない場合、__map( )はNaN__、__replace( )はそのまま__となる。

# P-055
>P-055: レシート明細データフレーム（df_receipt）の売上金額（amount）を顧客ID（customer_id）ごとに合計し、その合計金額の四分位点を求めよ。その上で、顧客ごとの売上金額合計に対して以下の基準でカテゴリ値を作成し、顧客ID、売上金額と合計ともに表示せよ。カテゴリ値は上から順に1〜4とする。結果は10件表示させれば良い。
・　最小値以上第一四分位未満
・　第一四分位以上第二四分位未満
・　第二四分位以上第三四分位未満
・　第三四分位以上

```python:p055.py
df_amount_sum = df_receipt[['customer_id', 'amount']].groupby('customer_id').amount.sum().reset_index()
pct = df_amount_sum.quantile(q=[0, 0.25, 0.5, 0.75, 1])['amount']

# 売り上げ金額の合計を四分位ごとにカテゴリ値を返す関数
def pct_group(x):
    if x < pct[0.25]:
        return 1
    elif x < pct[0.5]:
        return 2
    elif x < pct[0.75]:
        return 3
    else:
        return 4

df_amount_sum['pct_group'] = df_amount_sum['amount'].apply(lambda x: pct_group(x))
df_amount_sum.head(10)
```

　`df_amount_sum.quantile(q=[0, 0.25, 0.5, 0.75, 1])`の結果はこちら
![スクリーンショット 2021-08-11 21.03.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1751395/f59b1644-0ede-6796-a98e-f7fd2b0dc32a.png)

# P-056
>P-056: 顧客データフレーム（df_customer）の年齢（age）をもとに10歳刻みで年代を算出し、顧客ID（customer_id）、生年月日（birth_day）とともに抽出せよ。ただし、60歳以上は全て60歳代とすること。年代を表すカテゴリ名は任意とする。先頭10件を表示させればよい。

```python:p056.py
df_age_band = df_customer['age'].apply(lambda x: min(math.floor(x / 10) * 10, 60)).rename('age_band')
df_customer_age_band = pd.concat([df_customer[['customer_id', 'birth_day']],df_age_band],axis=1)
df_customer_age_band.head(10)
```

　`min(math.floor(x / 10) * 10, 60)`とすることで
　`math.floor(x / 10)`が60よりも大きい場合(70, 80歳代の場合)
　`60`(60歳代)が選択される。

# P-057
>P-057: 前問題の抽出結果と性別（gender）を組み合わせ、新たに性別×年代の組み合わせを表すカテゴリデータを作成せよ。組み合わせを表すカテゴリの値は任意とする。先頭10件を表示させればよい。

```python:p057.py
df_customer_age_band['age_band_gender'] = df_customer['gender_cd'] + df_customer_age_band['age_band'].astype('str')
df_customer_age_band.head(10)
```

　カテゴリ値を`性別コード + 年代`とした`age_band_gender`列を作成。
　`astype()`関数を使って年代を__str型__に変換してから計算しています。

# P-058 `pd.get_dummies()`
>P-058: 顧客データフレーム（df_customer）の性別コード（gender_cd）をダミー変数化し、顧客ID（customer_id）とともに抽出せよ。結果は10件表示させれば良い。

```python:p058.py
pd.get_dummies(df_customer[['customer_id', 'gender_cd']], columns=['gender_cd']).head(10)
```

　`pd.get_dummies()`：カテゴリ変数をダミー変数に変換。
　`columns`でダミー化したい列の列名を指定できる。

https://note.nkmk.me/python-pandas-get-dummies/

# P-059 `preprocessing.scale()`
>P-059: レシート明細データフレーム（df_receipt）の売上金額（amount）を顧客ID（customer_id）ごとに合計し、合計した売上金額を平均0、標準偏差1に標準化して顧客ID、売上金額合計とともに表示せよ。標準化に使用する標準偏差は、不偏標準偏差と標本標準偏差のどちらでも良いものとする。ただし、顧客IDが"Z"から始まるのものは非会員を表すため、除外して計算すること。結果は10件表示させれば良い。

```python:p059.py
from sklearn import preprocessing
df_amount_sum = df_receipt.query('not customer_id.str.startswith("Z")', engine='python').groupby('customer_id').amount.sum().reset_index()
df_amount_sum['amount_std'] = preprocessing.scale(df_amount_sum['amount'])
df_amount_sum.head(10)
```

　`preprocessing.scale()`：標準化を行うためのメソッド(平均を０、標準偏差を１)
　実際に`amount_std`列に`mean()`、`std()`を用いてみると、本当に標準化されているのが確認できる。

# P-060 `preprocessing.minmax_scale()`
>P-060: レシート明細データフレーム（df_receipt）の売上金額（amount）を顧客ID（customer_id）ごとに合計し、合計した売上金額を最小値0、最大値1に正規化して顧客ID、売上金額合計とともに表示せよ。ただし、顧客IDが"Z"から始まるのものは非会員を表すため、除外して計算すること。結果は10件表示させれば良い。

```python:p060.py
df_amount_sum = df_receipt.query('not customer_id.str.startswith("Z")', engine='python').groupby('customer_id').amount.sum().reset_index()
df_amount_sum['amount_normal'] = preprocessing.minmax_scale(df_amount_sum['amount'])
df_amount_sum.head(10)
```

　`preprocessing.minmax_scale()`：最小値0、最大値1に正規化

# P-061 `math.log10()`, `np.log10()`
>P-061: レシート明細データフレーム（df_receipt）の売上金額（amount）を顧客ID（customer_id）ごとに合計し、合計した売上金額を常用対数化（底=10）して顧客ID、売上金額合計とともに表示せよ。ただし、顧客IDが"Z"から始まるのものは非会員を表すため、除外して計算すること。結果は10件表示させれば良い。

```python:p061
df_amount_sum = df_receipt.query('not customer_id.str.startswith("Z")', engine='python').groupby('customer_id').amount.sum().reset_index()
df_amount_sum['amount_log10'] = df_amount_sum['amount'].apply(lambda x: math.log10(x))
df_amount_sum.head(10)
```
　　`lambda`を使って`amount`の全要素に`math.log10()`を用いて常用対数化を行っていますが、
　`np.log10`を使って引数に`df_amount_sum['amount']`を渡して書いた方がスッキリしそう。

# P-062 `np.log()`
>P-062: レシート明細データフレーム（df_receipt）の売上金額（amount）を顧客ID（customer_id）ごとに合計し、合計した売上金額を自然対数化(底=e）して顧客ID、売上金額合計とともに表示せよ。ただし、顧客IDが"Z"から始まるのものは非会員を表すため、除外して計算すること。結果は10件表示させれば良い。

```python:p062.py
df_amount_sum = df_receipt.query('not customer_id.str.startswith("Z")', engine='python').groupby('customer_id').amount.sum().reset_index()
df_amount_sum['amount_log'] = np.log(df_amount_sum['amount'])
df_amount_sum.head(10)
```