データサイエンス100本ノック解説(P001~020)

#1. はじめに
データサイエンス協会から出ている[データサイエンス100本ノック](https://github.com/The-Japan-DataScientist-Society/100knocks-preprocess)の解説をします。

導入については[こちらの記事](https://qiita.com/karaage0703/items/1b18b1f4ab65d35afb5f)を参考に進めてください(※ MacでDockerを扱います)

基本的には解答の解説ですが別解についても記述しています。

#2. 解説編


>P-001: レシート明細のデータフレーム（df_receipt）から全項目の先頭10件を表示し、どのようなデータを保有しているか目視で確認せよ。

```python:P-001
# head()で先頭行を参照できる。()カッコ内に件数を入力する。空欄の場合は５行。
df_receipt.head(10)
```

>-002: レシート明細のデータフレーム（df_receipt）から売上日（sales_ymd）、顧客ID（customer_id）、商品コード（product_cd）、売上金額（amount）の順に列を指定し、10件表示させよ。

```python:P-002
# [[]](二重カッコ)となっているのはDataFrame型にするため
df_receipt[['sales_ymd', 'customer_id', 'product_cd', 'amount']].head(10)
```

>P-003: レシート明細のデータフレーム（df_receipt）から売上日（sales_ymd）、顧客ID（customer_id）、商品コード（product_cd）、売上金額（amount）の順に列を指定し、10件表示させよ。ただし、sales_ymdはsales_dateに項目名を変更しながら抽出すること。

```python:P-003
# [[]]二重カッコ内に'sales_ymd', 'customer_id', 'product_cd', 'amount'を入れることでカラムを選択。
# カラム名を変更するにはrename(columns={変更前のカラム名: 変更後のカラム名})
# head(10)で先頭１０行を選択
df_receipt[['sales_ymd', 'customer_id', 'product_cd', 'amount']].rename(columns={'sales_ymd': 'sales_date'}).head(10)
```

[参考：pandas.DataFrameの行名・列名の変更](https://note.nkmk.me/python-pandas-dataframe-rename/)

>P-004: レシート明細のデータフレーム（df_receipt）から売上日（sales_ymd）、顧客ID（customer_id）、商品コード（product_cd）、売上金額（amount）の順に列を指定し、以下の条件を満たすデータを抽出せよ。
・顧客ID（customer_id）が"CS018205000001"

```python:P-004
# 二重カッコ内に'sales_ymd', 'customer_id', 'product_cd', 'amount'を入れることでカラムを選択。
# query()メソッドでDataFrameの行を条件で抽出。
df_receipt[['sales_ymd', 'customer_id', 'product_cd', 'amount']].query('customer_id == "CS018205000001"')

# (別解)
df_receipt[df_receipt['customer_id'] == 'CS018205000001']
```

参考: https://note.nkmk.me/python-pandas-query/

>P-005: レシート明細のデータフレーム（df_receipt）から売上日（sales_ymd）、顧客ID（customer_id）、商品コード（product_cd）、売上金額（amount）の順に列を指定し、以下の条件を満たすデータを抽出せよ。
顧客ID（customer_id）が"CS018205000001"
売上金額（amount）が1,000以上

```python:P-005
# 前半部分は、P-004を参考。
# queryメソッドは複数条件の場合、"&"で記述する。
df_receipt[['sales_ymd', 'customer_id', 'product_cd', 'amount']].query('customer_id == "CS018205000001" & amount >= 1000')
```

参考: https://note.nkmk.me/python-pandas-query/

>P-006: レシート明細データフレーム「df_receipt」から売上日（sales_ymd）、顧客ID（customer_id）、商品コード（product_cd）、売上数量（quantity）、売上金額（amount）の順に列を指定し、以下の条件を満たすデータを抽出せよ。
顧客ID（customer_id）が"CS018205000001"
売上金額（amount）が1,000以上または売上数量（quantity）が5以上

```python:P-006
# 前半部分は、P-004を参考。
# queryメソッドは複数条件の場合、"&"(かつ)　"|"(もしくは)で記述
df_receipt[['sales_ymd', 'customer_id', 'product_cd', 'quantity', 'amount']] \
    .query('customer_id=="CS018205000001" & (amount>=1000 | quantity>=5)')
```

参考: https://note.nkmk.me/python-pandas-query/

>P-007: レシート明細のデータフレーム（df_receipt）から売上日（sales_ymd）、顧客ID（customer_id）、商品コード（product_cd）、売上金額（amount）の順に列を指定し、以下の条件を満たすデータを抽出せよ。
顧客ID（customer_id）が"CS018205000001"
売上金額（amount）が1,000以上2,000以下

```python:P-007
# P-006を参考
# 売上金額（amount）が1,000以上2,000以下は、1000<=amount<=2000と記述する。
df_receipt[['sales_ymd', 'customer_id', 'product_cd', 'amount']] \
    .query('customer_id=="CS018205000001" & 1000<=amount<=2000')
```

参考: https://note.nkmk.me/python-pandas-query/

>P-008: レシート明細のデータフレーム（df_receipt）から売上日（sales_ymd）、顧客ID（customer_id）、商品コード（product_cd）、売上金額（amount）の順に列を指定し、以下の条件を満たすデータを抽出せよ。
顧客ID（customer_id）が"CS018205000001"
商品コード（product_cd）が"P071401019"以外

```python:P-008
# P-006を参考
# 以外の表記は、"!=" で表記する。
df_receipt[['sales_ymd', 'customer_id', 'product_cd', 'amount']] \
    .query('customer_id=="CS018205000001" & product_cd!="P071401019"')
```

参考(queryメソッド): https://note.nkmk.me/python-pandas-query/
参考(比較演算): https://www.javadrive.jp/python/if/index4.html

>P-009: 以下の処理において、出力結果を変えずにORをANDに書き換えよ。
df_store.query('not(prefecture_cd == "13" | floor_area > 900)')

```python:P-009
# query()メソッドでDataFrameの行を条件で抽出。
df_store.query('prefecture_cd != "13" & floor_area <= 900')
```

分かりやすく図にすると以下のようになる。
![スクリーンショット 2020-08-19 17.11.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621150/b7720e98-0bf3-a23b-83b0-a106f39ab9e6.png)

>P-010: 店舗データフレーム（df_store）から、店舗コード（store_cd）が"S14"で始まるものだけ全項目抽出し、10件だけ表示せよ。

```python:P-010
# query()メソッドでDataFrameの行を条件で抽出。
# 前方一致はstartswithを用いる。文字列メソッドはengine='python'でないとquery()メソッドで使えない
df_store.query("store_cd.str.startswith('S14')", engine='python').head(10)

# (別解)
df_store[df_store['store_cd'].str.startswith('S14')].head(10)
```

参考(startswith): https://note.nkmk.me/python-str-compare/

>P-011: 顧客データフレーム（df_customer）から顧客ID（customer_id）の末尾が1のものだけ全項目抽出し、10件だけ表示せよ。

```python:P-011
# query()メソッドでDataFrameの行を条件で抽出。
# 後方一致はendswithを用いる。文字列メソッドはengine='python'でないとquery()メソッドで使えない
df_customer.query("customer_id.str.endswith('1')", engine='python').head(10)
```

参考(startswith): https://note.nkmk.me/python-str-compare/

>P-012: 店舗データフレーム（df_store）から横浜市の店舗だけ全項目表示せよ。

```python:P-012
# query()メソッドでDataFrameの行を条件で抽出。
# str.contains(): 特定の文字列を含む。
# 文字列メソッドはengine='python'でないとquery()メソッドで使えない
df_store.query("address.str.contains('横浜市')", engine='python')
```

参考(startswith): https://note.nkmk.me/python-str-compare/

>P-013: 顧客データフレーム（df_customer）から、ステータスコード（status_cd）の先頭がアルファベットのA〜Fで始まるデータを全項目抽出し、10件だけ表示せよ。

```python:P-013
# query()メソッドでDataFrameの行を条件で抽出。
# str.contains(): 特定の文字列を含む。
# ^(先頭から)、[A-F](A-Fまで)、regex=True(正規表現の場合に必要 ※現在は必要ない)
df_customer.query("status_cd.str.contains('^[A-F]', regex=True)", engine='python').head(10)
```

参考: https://qiita.com/luohao0404/items/7135b2b96f9b0b196bf3

>P-014: 顧客データフレーム（df_customer）から、ステータスコード（status_cd）の末尾が数字の1〜9で終わるデータを全項目抽出し、10件だけ表示せよ。

```python:P-014
# df_customer.query("status_cd.str.containsはP-013を参考
# [0-9](0-9まで)、$(文字列の末尾)
df_customer.query("status_cd.str.contains('[0-9]$', regex=True)", engine='python').head(10) 
```

参考: https://qiita.com/luohao0404/items/7135b2b96f9b0b196bf3

>P-015: 顧客データフレーム（df_customer）から、ステータスコード（status_cd）の先頭がアルファベットのA〜Fで始まり、末尾が数字の1〜9で終わるデータを全項目抽出し、10件だけ表示せよ。

```python:P-015
# df_customer.query("status_cd.str.containsはP-013を参考
# ^(先頭から)、[A-F](AからFまで）、　.(任意の１文字)、*(０回以上の繰り返し)、[0-9](0-9まで)、$(文字列の末尾)
df_customer.query("status_cd.str.contains('^[A-F].*[1-9]$', regex=True)", engine='python').head(10)

# (別解)
df_customer.query("(status_cd.str.contains('^[A-F]')) &　\
　　　　(status_cd.str.contains('[0-9]$'))", engine='python').head(10)
```

『　.*　』は、”任意の１文字を繰り返す”つまりはどんな文字が何個あっても良い。
参考: https://qiita.com/luohao0404/items/7135b2b96f9b0b196bf3

>P-016: 店舗データフレーム（df_store）から、電話番号（tel_no）が3桁-3桁-4桁のデータを全項目表示せよ。

```python:P-016
# # df_customer.query("tel_no.str.containsはP-013を参考
# ^(先頭から)、[0-9](0から９)、{3}(3回の繰り返し)
df_store.query("tel_no.str.contains('^[0-9]{3}-[0-9]{3}-[0-9]{4}', regex=True)", engine='python')
```

参考: https://qiita.com/luohao0404/items/7135b2b96f9b0b196bf3

>P-17: 顧客データフレーム（df_customer）を生年月日（birth_day）で高齢順にソートし、先頭10件を全項目表示せよ。

```python:P-017
# 要素でソートするsort_values()
# ソートしたい列のラベル（列名）を第一引数（by）に指定する。
# ascending=True: 昇順（高齢順→生誕日が早い（古い））
df_customer.sort_values('birth_day', ascending=True).head(10)
```

参考:　https://note.nkmk.me/python-pandas-sort-values-sort-index/

>P-18: 顧客データフレーム（df_customer）を生年月日（birth_day）で若い順にソートし、先頭10件を全項目表示せよ。

```python:P-018
# P-017を参考。
# 若い順なのでascending=False
df_customer.sort_values('birth_day', ascending=False).head(10)
```

参考:　https://note.nkmk.me/python-pandas-sort-values-sort-index/

>P-19: レシート明細データフレーム（df_receipt）に対し、1件あたりの売上金額（amount）が高い順にランクを付与し、先頭10件を抽出せよ。項目は顧客ID（customer_id）、売上金額（amount）、付与したランクを表示させること。なお、売上金額（amount）が等しい場合は同一順位を付与するものとする。

```python:P-019
# 'customer_id', 'amount'のデータフレームと'amount'をランク付けしたデータフレームを横方向に結合。
# rank()メソッドでランクをつける。method='min'で重複値の最小値の値。
df_tmp = pd.concat([df_receipt[['customer_id', 'amount']], df_receipt['amount'] \
                    .rank(method='min', ascending=False)], axis=1)

# カラム名が重複しているのでカラム名を再定義する。
df_tmp.columns = ['customer_id', 'amount', 'ranking']

# rankingカラムでソートする。
df_tmp.sort_values('ranking').head(10)
```

method='min'で１位、２位、３位、３位、5位といった最小値に合わせた値（テストやスポーツの順位）
参考: https://note.nkmk.me/python-pandas-rank/

>P-020: レシート明細データフレーム（df_receipt）に対し、1件あたりの売上金額（amount）が高い順にランクを付与し、先頭10件を抽出せよ。項目は顧客ID（customer_id）、売上金額（amount）、付与したランクを表示させること。なお、売上金額（amount）が等しい場合でも別順位を付与すること。

```python:P-020
# P-019を参考
# method='first'で同一値（重複値）は登場順に順位付けされる。
df_tmp = pd.concat([df_receipt[['customer_id', 'amount']], 
                    df_receipt['amount'].rank(method='first', ascending=False)], axis=1)
df_tmp.columns = ['customer_id', 'amount', 'ranking']
df_tmp.sort_values('ranking', ascending=True).head(10)
```

参考: https://note.nkmk.me/python-pandas-rank/

# 3. 参考文献
[データサイエンス100本ノック](https://github.com/The-Japan-DataScientist-Society/100knocks-preprocess)
[Macでデータサイエンス100本ノックを動かす方法](https://qiita.com/karaage0703/items/1b18b1f4ab65d35afb5f)