データサイエンス100本ノック~初心者未満の戦いpart10

これはデータサイエンティストの卵がわけもわからないまま100本ノックを行っていく奮闘録である。
完走できるか謎。~~途中で消えてもQiitaにあげてないだけと思ってください。~~


[100本ノックの記事](https://codezine.jp/article/detail/12456)
[100本ノックのガイド](https://github.com/The-Japan-DataScientist-Society/100knocks-preprocess/blob/master/docker/doc/100knocks_guide.pdf)

**ネタバレも含みますのでやろうとされている方は注意**

統計学の問題がわからないんじゃぁああああ！(59～)

コレは見づらい！この書き方は危険！等ありましたら教えていただきたいです。~~心にダメージを負いながら~~糧とさせていただきます。

この解き方は間違っている！この解釈の仕方は違う！等もありましたらコメントください。

今回は５２～５６まで。
[[前回]](https://qiita.com/sola_wing529/items/785a33645f0749ecf5bc)４５～５１
[[目次付き初回]](https://qiita.com/sola_wing529/items/5fdbd97308dd88da28eb)

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

```mine52.py
df=df_receipt[df_receipt['customer_id'].str.contains('^[^Z]')].reset_index()
df=df.groupby('customer_id').agg({'amount':'sum'}).reset_index()
df['amount']=df['amount'].apply(lambda x : 0 if x<=2000 else 1)
df.head(10)

'''模範解答'''
df_sales_amount = df_receipt.query('not customer_id.str.startswith("Z")', engine='python')
df_sales_amount = df_sales_amount[['customer_id', 'amount']].groupby('customer_id').sum().reset_index()
df_sales_amount['sales_flg'] = df_sales_amount['amount'].apply(lambda x: 1 if x > 2000 else 0)
df_sales_amount.head(10)
```

`groupby`をしてから合計出し、それを比較しながら0と1に振り分ける。

どちらかといえば`lambda`を使いこなせるかの問題なので、久々のpython問題でした。

pythonってこの問題だけじゃないけど、型を意識しない文で型を意識して解かないといけないのがホントにめんどくさい。

この場合の`lambda x:～`の`x`にどの階層の型のデータが入っているのかを把握できるようになるのにすごく時間がかかりました。


#53本目
>P-053: 顧客データフレーム（df_customer）の郵便番号（postal_cd）に対し、東京（先頭3桁が100〜209のもの）を1、それ以外のものを0に２値化せよ。さらにレシート明細データフレーム（df_receipt）と結合し、全期間において買い物実績のある顧客数を、作成した2値ごとにカウントせよ。

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

'''模範解答'''
df_tmp = df_customer[['customer_id', 'postal_cd']].copy()
df_tmp['postal_flg'] = df_tmp['postal_cd'].apply(lambda x: 1 if 100 <= int(x[0:3]) <= 209 else 0)

pd.merge(df_tmp, df_receipt, how='inner', on='customer_id'). \
    groupby('postal_flg').agg({'customer_id':'nunique'})
```

`lambda`内のif文で　100<=x<=209 の形が書けるの便利すぎる。`else`に集中しすぎて`if`を書き忘れたのは内緒。

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

```mine54.py
df=df_customer.copy()
df['to_cd']=df['address'].apply(lambda x:11 if x.startswith('埼玉県')\
                                else 12 if x.startswith('千葉県')\
                                else 13 if x.startswith('東京都') \
                                else 14 if x.startswith('神奈川県') else 0)
df[['customer_id','address','to_cd']].head(10)

'''模範解答'''
pd.concat([df_customer[['customer_id', 'address']], df_customer['address'].str[0:3].map({'埼玉県': '11',
                                                                           '千葉県':'12', 
                                                                           '東京都':'13', 
                                                                           '神奈川':'14'})], axis=1).head(10)
```

いやぁ、`startwith()`便利ですねぇ(`contains`のスペルを忘れてた)
気を付けないといけないとは`bool`型で返ることですかね

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

>- 最小値以上第一四分位未満
- 第一四分位以上第二四分位未満
- 第二四分位以上第三四分位未満
- 第三四分位以上

```mine55.py
df=df_receipt.copy()
df=df.groupby('customer_id')['amount'].sum().reset_index()
si=df.quantile(q=[0.25,0.5,0.75]).T
#float(si[0.25])
df['sibun']=df['amount'].apply(lambda x:1 if x<float(si[0.25]) 
                              else 2 if x<float(si[0.5])
                              else 3 if x<float(si[0.75]) 
                              else 4)
df.head(10)

'''模範解答'''
# コード例1
df_sales_amount = df_receipt[['customer_id', 'amount']].groupby('customer_id').sum().reset_index()
pct25 = np.quantile(df_sales_amount['amount'], 0.25)
pct50 = np.quantile(df_sales_amount['amount'], 0.5)
pct75 = np.quantile(df_sales_amount['amount'], 0.75)

def pct_group(x):
    if x < pct25:
        return 1
    elif pct25 <= x < pct50:
        return 2
    elif pct50 <= x < pct75:
        return 3
    elif pct75 <= x:
        return 4

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

# コード例2
df_temp = df_receipt.groupby('customer_id')[['amount']].sum()
df_temp['quantile'], bins = pd.qcut(df_receipt.groupby('customer_id')['amount'].sum(), 4, retbins=True)
display(df_temp.head())
print('quantiles:', bins)
```

合計から四分位点を出して、以上未満で分けていく……
というやり方でやりましたが、コード例2では`pd.cut`の中にそのまま合計を入れて4分割指定してますね。

四分位点は32本目でやっている内容でしたが、案の定忘れていました。ﾌｸｼｭｳｼﾅｷｬ

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

```mine56.py
df=df_customer.copy()
df_bins=pd.cut(df.age,[10,20,30,40,50,60,150],right=False,labels=[10,20,30,40,50,60])

df=pd.concat([df[['customer_id','birth_day']],df_bins],axis=1)
df.head(10)

'''模範解答'''
# コード例1
df_customer_era = pd.concat([df_customer[['customer_id', 'birth_day']],
                             df_customer['age'].apply(lambda x: min(math.floor(x / 10) * 10, 60))],
                             axis=1)

df_customer_era.head(10)

# コード例2
df_customer['age_group'] = pd.cut(df_customer['age'], bins=[0, 10, 20, 30, 40, 50, 60, np.inf], right=False)
df_customer[['customer_id', 'birth_day', 'age_group']].head(10)
```

今度は43本目の復習ですね。
`np,inf`の存在を知らなかったので明らかにとりえない数字の150を入れました。また、`df.age.min()`で最低年齢を出して10～でいいことを確認してから0を抜きました。

`pd.cut`に`labels`という項目があるので入れるときれいな数字が出ます。
また、right=Falseで`10<=ｘ＜20`という範囲を取るようになります

[参考](https://note.nkmk.me/python-pandas-cut-qcut-binning/)

#今回はここまで