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

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

+ 差分・変化率 `diff()`
+ データをずらす `shift()`
+ 切り捨て `math.floor()`
+ `pd.pivot_table()`
+ インデックスの割り当て　`set_index()`
+ `stack()`
+ `pd.to_datetime()`
+ `strftime()`
+ `astype()`

# P-040
>P-040: 全ての店舗と全ての商品を組み合わせると何件のデータとなるか調査したい。店舗（df_store）と商品（df_product）を直積した件数を計算せよ。

```python:p040.py
df_store_tmp = df_store.copy()
df_product_tmp = df_product.copy()

df_store_tmp['key'] = 0
df_product_tmp['key'] = 0

len(pd.merge(df_store_tmp, df_product_tmp, how='outer', on='key'))
```
　直積を計算するために`df_store`と`df_product`を外部結合し要素数を取得する。
　共通列がないため新たに`key`という列を追加した。
　データフレームを直接いじるため、最初にコピーを取っておく。

　直積を計算するだけなので以下のコードではいけないのかな...

```python
len(df_store) * len(df_product)
```

# P-041 `diff()`, `shift()`
>P-041: レシート明細データフレーム（df_receipt）の売上金額（amount）を日付（sales_ymd）ごとに集計し、前日からの売上金額増減を計算せよ。なお、計算結果は10件表示すればよい。

```python:myp041.py
df_tmp = df_receipt.groupby('sales_ymd').amount.sum().reset_index()
df_tmp = pd.concat([df_tmp, df_tmp['amount'].diff()], axis=1)
df_tmp.columns = ['sales_ymd', 'amount', 'amount_diff']
df_tmp.head(10)
```
　`diff()`：行または列の差分・変化率を取得。
　第一引数で比較したい行を渡す。(デフォルトは1, 負数を渡すことも可能)
　`axis=1`で列をの差分を得る。

```python:p041.py
df_sales_amount_by_date = df_receipt[['sales_ymd', 'amount']].groupby('sales_ymd').sum().reset_index()
df_sales_amount_by_date = pd.concat([df_sales_amount_by_date, df_sales_amount_by_date.shift()], axis=1)
df_sales_amount_by_date.columns = ['sales_ymd','amount','lag_ymd','lag_amount']
df_sales_amount_by_date['diff_amount'] = df_sales_amount_by_date['amount'] - df_sales_amount_by_date['lag_amount']
df_sales_amount_by_date.head(10)
```
　解答はこの通り
　差分だけではなく元のデータをずらした値もデータフレームに結合した方がわかりやすい。

　`shift()`：データを行または列方向にずらす。
引数にずらし幅を指定できる。(デフォルトは1)
　列方向にずらす場合は引数に`axis=1`を指定。

# P-042
>P-042: レシート明細データフレーム（df_receipt）の売上金額（amount）を日付（sales_ymd）ごとに集計し、各日付のデータに対し、１日前、２日前、３日前のデータを結合せよ。結果は10件表示すればよい。

```python:p042.py
df_amount_sum = df_receipt.groupby('sales_ymd').amount.sum().reset_index()
df_1da = df_amount_sum.shift(1)
df_2da = df_amount_sum.shift(2)
df_3da = df_amount_sum.shift(3)

df_tmp = pd.concat([df_amount_sum, df_1da['amount'], df_2da['amount'], df_3da['amount']], axis=1)
df_tmp.columns = ['sales_ymd', 'amount', '1days_ago', '2days_ago', '3days_ago']
df_tmp.dropna().head(10)
```
　`df_receipt`の`sales_ymd`でグルーピングし、amountで和を取ったデータフレーム(df_amount_sum)を`shift()`を使って1,2,3日ずらしたデータフレームを`concat()`で横方向に結合している。(変数名の`df_1da`は__1 days ago__の意味)

# P-043 `math.floor()`, `pd.pivot_table()`
>P-043： レシート明細データフレーム（df_receipt）と顧客データフレーム（df_customer）を結合し、性別（gender）と年代（ageから計算）ごとに売上金額（amount）を合計した売上サマリデータフレーム（df_sales_summary）を作成せよ。性別は0が男性、1が女性、9が不明を表すものとする。
ただし、項目構成は年代、女性の売上金額、男性の売上金額、性別不明の売上金額の4項目とすること（縦に年代、横に性別のクロス集計）。また、年代は10歳ごとの階級とすること。

```python:p043.py
df_tmp = pd.merge(df_receipt, df_customer, how='inner', on='customer_id')
df_tmp['age_band'] = df_tmp['age'].apply(lambda x: math.floor(x/10)*10)
df_sales_summary = pd.pivot_table(df_tmp, index='age_band', columns='gender_cd', values='amount', aggfunc=sum).reset_index()
df_sales_summary.columns = ['age_band', 'male', 'female', 'anknown']
df_sales_summary
```
　`math.floor()`：小数点以下を切り捨て。(切り上げは`math.ceil()`)
　__年代の計算方法(age=22の場合)__
　math.floor(22/10)*10 = math.floor(2.2)*10 = 2.0(小数点以下を切り捨てた)*10 = 20(代)

　`pd.pivot_table()`：カテゴリ毎の統計量などを算出。pandas.DataFrame型を返す。
　引数は以下の通り。

+ `data`：参照にするデータフレーム
+ `index`：元データの列名。結果の行見出しとなる。
+ `columns`：元データの列名。結果の列見出しとなる。
+ `values`に元データの列名を指定すると、その列に対する結果のみ計算する。
+ `aggfunc`に関数を指定するといろんな値を計算できる。(デフォルトは平均値)

https://note.nkmk.me/python-pandas-pivot-table/

# P-044 `set_index()`, `stack()`
>P-044： 前設問で作成した売上サマリデータフレーム（df_sales_summary）は性別の売上を横持ちさせたものであった。このデータフレームから性別を縦持ちさせ、年代、性別コード、売上金額の3項目に変換せよ。ただし、性別コードは男性を'00'、女性を'01'、不明を'99'とする。

```mypython:p044.py
df_tmp = df_sales_summary.set_index('age_band').stack().reset_index()
df_tmp.columns = ['age_band', 'gender_cd', 'amount']
df_tmp.replace({'male':'00', 'female':'01', 'anknown':'99'})
```
　解答は以下のように一行でまとめている。

```python:p044.py
df_sales_summary = df_sales_summary.set_index('era'). \
        stack().reset_index().replace({'female':'01',
                                        'male':'00',
                                        'unknown':'99'}).rename(columns={'level_1':'gender_cd', 0: 'amount'})
```

　`set_index()`：既存の列をインデックスに割り当てる。

　`stack()`：列から行へピポット。(行方向に並び替え)
　pandas.Series型を返す。
　他にも行から列へのピポットやテーブルの再形成は以下を参照。

https://note.nkmk.me/python-pandas-stack-unstack-pivot/

# P-045 `pd.to_datetime()`, `strftime()`
>P-045: 顧客データフレーム（df_customer）の生年月日（birth_day）は日付型（Date）でデータを保有している。これをYYYYMMDD形式の文字列に変換し、顧客ID（customer_id）とともに抽出せよ。データは10件を抽出すれば良い。

```python:p045.py
pd.concat([df_customer['customer_id'], pd.to_datetime(df_customer['birth_day']).dt.strftime('%Y%m%d')], axis=1).head(10)
```
　`pd.to_datetime()`：文字列データや数値データを日付データである__datetime64型__に変換する。
　`df_customer['birth_day']`のデータ型は__object型__であるため、そのままでは`strftime()`メソッドを適用することができない。
　そのため、まず`to_datetime()`で__datetime64型__に変換する。

　`strftime()`：datetime.date型の日付を整形する。

　※_pd.to_datetime()メソッドを使わない場合(applyを使って)_

```python
pd.concat([df_customer['customer_id'], df_customer['birth_day'].apply(lambda x: x.strftime('%Y%m%d'))], axis=1).head(10)
```

# P-046
>P-046: 顧客データフレーム（df_customer）の申し込み日（application_date）はYYYYMMDD形式の文字列型でデータを保有している。これを日付型（dateやdatetime）に変換し、顧客ID（customer_id）とともに抽出せよ。データは10件を抽出すれば良い。

```python:p046.py
pd.concat([df_customer['customer_id'], pd.to_datetime(df_customer['application_date'])], axis=1).head(10)
```

# P-047 `astype()`
>P-047: レシート明細データフレーム（df_receipt）の売上日（sales_ymd）はYYYYMMDD形式の数値型でデータを保有している。これを日付型（dateやdatetime）に変換し、レシート番号(receipt_no)、レシートサブ番号（receipt_sub_no）とともに抽出せよ。データは10件を抽出すれば良い。

```python:p047.py
pd.concat([df_receipt[['receipt_no', 'receipt_sub_no']],
           pd.to_datetime(df_receipt['sales_ymd'].astype('str'))], axis=1).head(10)
```

　`astype()`：データ型の変換
　`df_receipt`の`'sales_ymd'`列のデータ型は__int64型__になっている。
　そのまま`to_datetime()`メソッドを使って変換しようとすると下図のようになる。

![スクリーンショット 2021-08-07 15.24.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1751395/f5b70ee2-708f-66e4-0d4d-2d93973b4afd.png)

　そのため、`astype()`メソッドを使って__str型__に変換した後、__datetime型__に変換している。

# P-048
>P-048: レシート明細データフレーム（df_receipt）の売上エポック秒（sales_epoch）は数値型のUNIX秒でデータを保有している。これを日付型（dateやdatetime）に変換し、レシート番号(receipt_no)、レシートサブ番号（receipt_sub_no）とともに抽出せよ。データは10件を抽出すれば良い。

```python:p048.py
pd.concat([df_receipt[['receipt_no', 'receipt_sub_no']],
          pd.to_datetime(df_receipt['sales_epoch'], unit='s')], axis=1).head(10)
```

　UNIX時間の表記は秒数なので、 `unit='s'`として表記を秒単位にします。
　(UNIX時間；コンピューター上での時刻表現方法の一つです。 「1970年1月1日午前0時(UTC)」からの経過秒数)

　※_datetimeモジュールを使った方法_

```python
import datetime
pd.concat([df_receipt[['receipt_no', 'receipt_sub_no']],
          df_receipt['sales_epoch'].apply(lambda x: datetime.datetime.fromtimestamp(x))], axis=1).head(10)
```

# P-049
>P-049: レシート明細データフレーム（df_receipt）の売上エポック秒（sales_epoch）を日付型（timestamp型）に変換し、"年"だけ取り出してレシート番号(receipt_no)、レシートサブ番号（receipt_sub_no）とともに抽出せよ。データは10件を抽出すれば良い。

```python:p049.py
pd.concat([df_receipt[['receipt_no', 'receipt_sub_no']],
          pd.to_datetime(df_receipt['sales_epoch'], unit='s').dt.year], axis=1)
```

# P-50
>P-050: レシート明細データフレーム（df_receipt）の売上エポック秒（sales_epoch）を日付型（timestamp型）に変換し、"月"だけ取り出してレシート番号(receipt_no)、レシートサブ番号（receipt_sub_no）とともに抽出せよ。なお、"月"は0埋め2桁で取り出すこと。データは10件を抽出すれば良い。

```python:p050.py
pd.concat([df_receipt[['receipt_no', 'receipt_sub_no']],
          pd.to_datetime(df_receipt['sales_epoch'], unit='s').dt.strftime('%m')], axis=1).head(10)
```

# P-51
>P-051: レシート明細データフレーム（df_receipt）の売上エポック秒（sales_epoch）を日付型（timestamp型）に変換し、"日"だけ取り出してレシート番号(receipt_no)、レシートサブ番号（receipt_sub_no）とともに抽出せよ。なお、"日"は0埋め2桁で取り出すこと。データは10件を抽出すれば良い。

```python:p051.py
pd.concat([df_receipt[['receipt_no', 'receipt_sub_no']],
          pd.to_datetime(df_receipt['sales_epoch'], unit='s').dt.strftime('%d')], axis=1).head(10)
```