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

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

+ アンダーサンプリング　`RandomUnderSampler()`
+ csvファイルの書き出し　`to_csv()`
+ csv、tsvファイルの読み込み　`pd.read_csv()`、`pd.read_table()`

# P-089
>P-089: 売上実績のある顧客に対し、予測モデル構築のため学習用データとテスト用データに分割したい。それぞれ8:2の割合でランダムにデータを分割せよ。

```python:p089.py
df_tmp = df_receipt.groupby('customer_id').amount.sum().reset_index()
# how='inner'とすることで買い物実績のあるcustomer_idだけを残す
df_tmp = pd.merge(df_customer, df_tmp['customer_id'], how='inner', on='customer_id')
df_train, df_test = train_test_split(df_tmp, test_size=0.2)
```

# P-090
>P-090: レシート明細データフレーム（df_receipt）は2017年1月1日〜2019年10月31日までのデータを有している。売上金額（amount）を月次で集計し、学習用に12ヶ月、テスト用に6ヶ月のモデル構築用データを3セット作成せよ。

```python:p090.py
df_tmp = df_receipt[['sales_ymd', 'amount']].copy()
# sales_ymdから年と月のみを取り出した列を新たに作成
df_tmp['sales_ym'] = df_tmp['sales_ymd'].astype('str').str[:6]
df_tmp = df_tmp.groupby('sales_ym').amount.sum().reset_index()

# データセットを3組作成
df_train_1, df_test_1 = df_tmp[:12], df_tmp[12:18]
df_train_2, df_test_2 = df_tmp[1:13], df_tmp[13:19]
df_train_3, df_test_3 = df_tmp[2:14], df_tmp[14:20]
```

　スライスでデータセットを用意したが、解答のように用意する数を増やしたり、サイズを変えたりしたい時のために関数を用意したほうがいいと思う。

```python
def split_data(df, train_size, test_size, slide_window, start_point):
    train_start = start_point * slide_window
    test_start = train_start + train_size
    return df[train_start : test_start], df[test_start : test_start + test_size]

df_train_1, df_test_1 = split_data(df_tmp, train_size=12, test_size=6, slide_window=6, start_point=0)
df_train_2, df_test_2 = split_data(df_tmp, train_size=12, test_size=6, slide_window=6, start_point=1)
df_train_3, df_test_3 = split_data(df_tmp, train_size=12, test_size=6, slide_window=6, start_point=2)
```

# P-091 `RandomUnderSampler()`
>P-091: 顧客データフレーム（df_customer）の各顧客に対し、売上実績のある顧客数と売上実績のない顧客数が1:1となるようにアンダーサンプリングで抽出せよ。

```python:p091.py
df_tmp = df_receipt.groupby('customer_id').amount.sum().reset_index()
df_tmp = pd.merge(df_customer, df_tmp, how='left', on='customer_id').fillna(0)
# 売り上げ実績がある顧客は'1'、ない顧客は'0'をもつ新しい列を作成
df_tmp['sales_performance'] = df_tmp['amount'].apply(lambda x: 0 if x == 0 else 1)

# imblearn.under_samplingライブラリののRandomUnderSamplerを使用
rus = RandomUnderSampler(sampling_strategy = 'majority')
df_resample, _ = rus.fit_resample(df_tmp, df_tmp.sales_performance)
```

　アンダーサンプリング：頻度が高い値を含むデータを削減する方法
　`df_tmp`では実績があるデータが`8306`件、実績がないデータが`13665`個あるのが確認できる。
　つまり実績がないデータを削減して１：１になるように調節する。

　抽出できているかは以下のコードで確認(出力結果はどちらも8306)

```python
print(len(df_resample.query('sales_performance == 0')))
print(len(df_resample.query('sales_performance == 1')))
```

# P-092
>P-092: 顧客データフレーム（df_customer）では、性別に関する情報が非正規化の状態で保持されている。これを第三正規化せよ。

```python:092.py
df_gender = df_customer[['gender_cd', 'gender']].drop_duplicates().reset_index(drop=True)
df_customer_1 = df_customer.drop('gender', axis=1)
```

　第三正規化の意味がわからなかったので以下を参考にしました。

　参考：[関係データベースの正規化](https://breezegroup.co.jp/202005/database-normalization/)

　つまり、`df_customer`の`gender`は`gender_cd`と紐づいているため
　`gender_cd`をキーにして`df_gender`と`df_customer_1`の二つのデータフレームに分離している。

# P-093
>P-093: 商品データフレーム（df_product）では各カテゴリのコード値だけを保有し、カテゴリ名は保有していない。カテゴリデータフレーム（df_category）と組み合わせて非正規化し、カテゴリ名を保有した新たな商品データフレームを作成せよ。

```python:p093.py
df_product_1 = pd.merge(df_product, df_category, how='left', on=['category_major_cd', 'category_medium_cd', 'category_small_cd'])
df_product_1
```

　`pd.merge()`による結合のキー列を３つ指定したが、`category_small_cd`によって全てのカテゴリ名が決まるので、キー列は`category_small_cd`だけでいい。
　その場合、重複する列以外を指定して以下のように書ける。

```python:p093.py
df_product_1 = pd.merge(df_product,
                        df_category[['category_major_name', 'category_medium_name', 'category_small_name', 'category_small_cd']],
                        how='left', on='category_small_cd')
df_product_1
```

# P-094 `to_csv()`
>P-094: 先に作成したカテゴリ名付き商品データを以下の仕様でファイル出力せよ。なお、出力先のパスはdata配下とする。
・ファイル形式はCSV（カンマ区切り）
・ヘッダ有り
・文字コードはUTF-8

```python:p094.py
df_product_1.to_csv('./data/df_product_1_header_utf8.csv',header=True, index=False, encoding='UTF-8')
```

　`to_csv()`：データフレームをcsvファイルとして保存。
　`header`、`index`で列名と行名を保存するか指定（デフォルトで`True`なので`header=True`は書かなくても良い）
　以降の問題と区別するため、ファイル名にヘッダと文字コードと追記している。

参考：[pandasでcsvファイルの書き出し・追記（to_csv）](https://note.nkmk.me/python-pandas-to-csv/)

# P-095
>P-095: 先に作成したカテゴリ名付き商品データを以下の仕様でファイル出力せよ。なお、出力先のパスはdata配下とする。
・ファイル形式はCSV（カンマ区切り）
・ヘッダ有り
・文字コードはCP932

```python:p095.py
df_product_1.to_csv('./data/df_product_1_header_cp932.csv', index=False, encoding='CP932')
```

# P-096
>P-096: 先に作成したカテゴリ名付き商品データを以下の仕様でファイル出力せよ。なお、出力先のパスはdata配下とする。
・ファイル形式はCSV（カンマ区切り）
・ヘッダ無し
・文字コードはUTF-8

```python:p096.py
df_product_1.to_csv('./data/df_product_1_utf8.csv', header=False, index=False, encoding='UTF-8')
```

# P-097 `pd.read_csv()`
>P-097: 先に作成した以下形式のファイルを読み込み、データフレームを作成せよ。また、先頭10件を表示させ、正しくとりまれていることを確認せよ。
・ファイル形式はCSV（カンマ区切り）
・ヘッダ有り
・文字コードはUTF-8

```python:p097.py
df = pd.read_csv('./data/df_product_1_header_utf8.csv')
df.head(10)
```

　`pd.read_csv()`：csvファイルの読み込み
　デフォルトで`encoding='UTF-8'`であるため省略している。

参考：[pandasでcsv/tsvファイル読み込み（read_csv, read_table）](https://note.nkmk.me/python-pandas-read-csv-tsv/)

# P-098
>P-098: 先に作成した以下形式のファイルを読み込み、データフレームを作成せよ。また、先頭10件を表示させ、正しくとりまれていることを確認せよ。
・ファイル形式はCSV（カンマ区切り）
・ヘッダ無し
・文字コードはUTF-8

```python:p098.py
df = pd.read_csv('./data/df_product_1_utf8.csv', header=None)
df.head(10)
```

　ヘッダがないデータなのでそのまま読み込むと１行目がカラムになってしまう。
　`header=None`とすることで、０からの整数がカラムに割り当てられる。

# P-099
>P-099: 先に作成したカテゴリ名付き商品データを以下の仕様でファイル出力せよ。なお、出力先のパスはdata配下とする。
・ファイル形式はTSV（タブ区切り）
・ヘッダ有り
・文字コードはUTF-8

```python:p099.py
df_product_1.to_csv('./data/df_product_1_header_utf8.tsv', sep='\t', index=None, encoding='UTF-8')
```

# P-100 `pd.read_table()`
>P-100: 先に作成した以下形式のファイルを読み込み、データフレームを作成せよ。また、先頭10件を表示させ、正しくとりまれていることを確認せよ。
・ファイル形式はTSV（タブ区切り）
・ヘッダ有り
・文字コードはUTF-8

```python:p100.py
df = pd.read_table('./data/df_product_1_header_utf8.tsv')
df.head(10)
```

　タブ区切りのデータであるため、`read_csv()`ではなく、`read_table()`を使う。