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

これはデータサイエンティストの卵がわけもわからないまま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)

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

模範解答を書き方が違いすぎて怖い

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

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

今回は４１～４４まで。
[[前回]](https://qiita.com/sola_wing529/items/7b0a55b174da433debef)３６～４０
[[目次付き初回]](https://qiita.com/sola_wing529/items/5fdbd97308dd88da28eb)

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

```mine41.py
df_day=df_receipt.groupby('sales_ymd').agg({'amount':'sum'}).reset_index()
df_Yday=pd.concat([df_day['sales_ymd']+1,df_day['amount']],axis=1).rename(columns={'amount': 'Y_amount'})
df=pd.merge(df_day.head(10),df_Yday,on='sales_ymd',how='outer')
df['sa']=df['amount']-df['Y_amount']
df.head(10)

'''模範解答'''
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)
```

1日ずらしの集計です
自分の手法としては

1. `df_Y`という[(1日後の)日付 、 集計]の形の表を作成
2. 日付を合わせてマージ、こうすることで[日付、集計、(1日前の)集計]というデータができる
3. 集計の差の列を追加する

として作りました。このやり方が間違ってるとは思わないが、「1日」ではなくテーブルの日付から引っ張ったほうがいいなとは思った。

- 例えば、土日休みのお店がある場合、日付が飛ぶから

模範解答を見てから[`.shift()`の使い方を調べたら](https://note.nkmk.me/python-pandas-shift/)すごく楽だと思ったので次の問題から活用することにした

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

```mine42.py
df_day=df_receipt.groupby('sales_ymd').agg({'amount':'sum'})
df=df_day
for i in range(1,4):
    df=pd.merge( df,df_day.shift(i).reset_index(),how='outer',on='sales_ymd')

df.columns=['sales_ymd','amount_sum_0','amount_sum_1','amount_sum_2','amount_sum_3']
df.head(10)

'''模範解答'''
# コード例2:横持ちケース
df_sales_amount_by_date = df_receipt[['sales_ymd', 'amount']].groupby('sales_ymd').sum().reset_index()
for i in range(1, 4):
    if i == 1:
        df_lag = pd.concat([df_sales_amount_by_date, df_sales_amount_by_date.shift(i)],axis=1)
    else:
        df_lag = pd.concat([df_lag, df_sales_amount_by_date.shift(i)],axis=1)
df_lag.columns = ['sales_ymd', 'amount', 'lag_ymd_1', 'lag_amount_1', 'lag_ymd_2', 'lag_amount_2', 'lag_ymd_3', 'lag_amount_3']
df_lag.dropna().sort_values(['sales_ymd']).head(10)
```

最初はfor文で回さずに4つの表を一気に結合しようと思いましたが、どうやら`pd.merge()`は二つの結合しかできないらしい
[参考](https://teratail.com/questions/175005)

なので1つ元を作ってfor文でまわしてツッコミました。

個人的に最近で一番楽しかったデータの操作でした
ちなみに、`.shift(0)`で変わらないデータでるっぽいです


`df=pd.merge( df_day.shift(0).reset_index()
           , df_day.shift(1).reset_index()
           , df_day.shift(2).reset_index()
           , df_day.shift(3).reset_index()
           ,how='outer',on='sales_ymd')`
こんな感じで書きたかった

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

次の問題は

1. 「年代」列を作成
2. `.groupby`で2項目指定する
3. クロス集計の形にする

という段階を踏みました

```mine43.py
df=pd.merge(df_receipt,df_customer,how='inner',on='customer_id')
df_bins=pd.cut(df.age,range(0,100,10))
df=pd.concat([df[['gender_cd','amount']],df_bins],axis=1)
df=df.groupby(['age','gender_cd']).agg({'amount':'sum'}).reset_index()

df_cross=pd.merge( df.query("gender_cd=='0'")[['age','amount']]
                  ,df.query("gender_cd=='1'")[['age','amount']]
                  ,how='outer',on='age')
df_cross=pd.merge( df_cross
                  ,df.query("gender_cd=='9'")[['age','amount']]
                  ,how='outer',on='age')
df_cross.columns=['age','male','female','unkown']
df_cross

'''模範解答'''
df_tmp = pd.merge(df_receipt, df_customer, how ='inner', on="customer_id")
df_tmp['era'] = df_tmp['age'].apply(lambda x: math.floor(x / 10) * 10)
df_sales_summary = pd.pivot_table(df_tmp, index='era', columns='gender_cd', values='amount', aggfunc='sum').reset_index()
df_sales_summary.columns = ['era', 'male', 'female', 'unknown']
df_sales_summary
```
と、思って模範解答見たらbinに分けることなく
`.apply(lambda x: math.floor(x / 10) * 10)`
10で割って(整数型にして)10でかけてるのかな？
`lambda`の使い方がイマイチ分かっていない。
そして、ピボットテーブル化……なるほど。そういう使い方もできるのか……

[ピボットテーブル勉強してきます（](https://note.nkmk.me/python-pandas-stack-unstack-pivot/)

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

今回は連続問題なので前回のまとめたdfをそのまま流用
縦持ちに変えるということなので、dfを3つに分けて縦に並べました。
というか前回縦を横に変えたので逆のが楽だった……

```mine44.py
df_cross_M=df_cross[['age','male']].rename(columns={'male':'sum'})
df_cross_M['gender']='00'
df_cross_F=df_cross[['age','female']].rename(columns={'female':'sum'})
df_cross_F['gender']='01'
df_cross_U=df_cross[['age','unkown']].rename(columns={'unkown':'sum'})
df_cross_U['gender']='99'

df=pd.concat([df_cross_M,df_cross_F,df_cross_U])
df
'''模範解答'''
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'})
```

超長い1文で模範解答さん終わらせています。
`setindex()`で年代列をインデックスに待避させてすべての列を一本化させたのかな？
`.replace()`で一気に名前を変えられるのはすごいと思う。ぜひとも使いこなしたい


#今回はここまで
いずれにしても、自分のやり方とは違うやり方が増えてきたのでとても勉強になります
##この場を借りて
いつも自分のスタックしてくださる方、ありがとうございます。
もし個別記事を登録するのではなく、自分の進捗を見ていただけている方がいるのであれば、part1の目次を更新するたびに更新通知を送らせていただいております。
その為、すべての記事をストックすることなくpart1をストックすれば自分の進捗を見ることができますので、お手数かけずに済むかと思います。（思ってるだけです）

いつも見ていただけてる方、今回が初の方、すべての方に感謝します。閲覧ありがとうございます。