0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-08-04

 この記事はデータサイエンスを勉強しながら、データサイエンス協会が提供する__データサイエンス100本ノック(構造化データ加工編)__を解く過程を自分用にまとめたものです。

 チャプター   リンク   チャプター   リンク 
 P-001~P-016   part1   P-052~P-062   part6 
 P-017~P-022   part2   P-063~P-068   part7 
 P-023~P-031   part3   P-069~P-078   part8 
 P-032~P-039   part4   P-079~P-088   part9 
 P-040~P-051   part5  P-089~P-100  part10 
  • パーセンタイル quantile()
  • データフレームの結合 pd.merge()
  • 欠損値を埋める fillna()
  • 重複した行を検出 duplicated()

P-032 quantile()

P-032: レシート明細データフレーム(df_receipt)の売上金額(amount)について、25%刻みでパーセンタイル値を求めよ。

p032.py
df_receipt['amount'].quantile(q=[0, 0.25, 0.5, 0.75, 1])

 quantile():パーセンタイルを取得。(pandas.Series型)
 データを小さい順に数えて何%の値はどれくらいかを測定する。
 デフォルトでは1/2分位数・50パーセンタイル(中央値)を返す。

 第一引数で取得したい分位数を0.0~1.0で指定する。(複数指定の場合はリスト)
 解答ではnp.arange()を使って引数を q=np.arange(5)/4としている。

P-033

P-033: レシート明細データフレーム(df_receipt)に対し、店舗コード(store_cd)ごとに売上金額(amount)の平均を計算し、330以上のものを抽出せよ。

p033.py
df_receipt.groupby('store_cd').amount.mean().reset_index().query('amount >= 330')

 GroupByオブジェクトをmean()で平均を取った後、query()で条件を絞る。
 (今までの復習のような問題)

P-034

P-034: レシート明細データフレーム(df_receipt)に対し、顧客ID(customer_id)ごとに売上金額(amount)を合計して全顧客の平均を求めよ。ただし、顧客IDが"Z"から始まるのものは非会員を表すため、除外して計算すること。

p034.py
df_receipt.query('not(customer_id.str.startswith("Z"))', engine='python').groupby('customer_id').amount.sum().mean()

 まず、customer_idがZから始まるのをquery()で外し、その後グルーピング・統計。

P-035

P-035: レシート明細データフレーム(df_receipt)に対し、顧客ID(customer_id)ごとに売上金額(amount)を合計して全顧客の平均を求め、平均以上に買い物をしている顧客を抽出せよ。ただし、顧客IDが"Z"から始まるのものは非会員を表すため、除外して計算すること。なお、データは10件だけ表示させれば良い。

myp035.py
df_receipt.query('not customer_id.str.startswith("Z")', engine='python')
.groupby('customer_id').amount.sum().reset_index().query('amount >= amount.mean()').head(10)

 初めにこのように書いたが、これではcustomer_idがZから始まる会員を除外して計算はできているが、表示する時にもZから始まる会員を除外してしまう。
 解答では以下のようになっている。

p035.py
amount_mean = df_receipt.query('not customer_id.str.startswith("Z")', engine='python').groupby('customer_id').amount.sum().mean()
df_amount_sum = df_receipt.groupby('customer_id').amount.sum().reset_index()
df_amount_sum[df_amount_sum['amount'] >= amount_mean].head(10)

 ①customer_idがZから始まる会員を除外して、amountを合計して平均値を求める。
 ②customer_idでグルーピングし、amountの合計をとる。
 ③ ②のdfのamountが①で求めたamount_meanよりも大きいものだけを表示する。

P-036 pd.merge()

P-036: レシート明細データフレーム(df_receipt)と店舗データフレーム(df_store)を内部結合し、レシート明細データフレームの全項目と店舗データフレームの店舗名(store_name)を10件表示させよ。

p036.py
pd.merge(df_receipt, df_store[['store_cd', 'store_name']], how='inner', on='store_cd')

 pd.merge():データフレームを結合する。
 concat()との違いは、共通列名で結合するところ。

パラメータ設定 詳細
how="inner"(デフォルト) 両方のテーブル共通してあるデータのみ残す(内部結合)
how="left" 左テーブル(第一引数)のデータは全て残す(左結合)
how="right" 右テーブル(第二引数)のデータは全て残す(右結合)
how="outer" 片方のテーブルにしかないデータも全て残す(外部結合)
パラメータ設定 詳細
on="列名" 指定列をキーとして結合。指定なしなら全ての共通列名がキーになる

 df_receiptdf_storeを結合して、__df_receiptの全項目__と__df_storeのstore_name列__を表示したいため、そこに結合のキー列(共通列)となる__store_cd__も合わせ、merge()の第二引数をdf_store[['store_cd', 'store_name']]とした。

P-037

P-037: 商品データフレーム(df_product)とカテゴリデータフレーム(df_category)を内部結合し、商品データフレームの全項目とカテゴリデータフレームの小区分名(category_small_name)を10件表示させよ。

p037.py
pd.merge(df_product
         , df_category[['category_small_cd','category_small_name']]
         , how='inner', on='category_small_cd').head(10)

 df_productdf_categoryの共通列を調べると
 category_major_cd, category_medium_cd, category_small_cdの3つがある。
 df_categoryの中身を見てみるとcategory_small_cdによってcategory_nameが決まっていることがわかる。
 なのでcategory_small_cdをキー列に結合している。

P-038 fillna()

P-038: 顧客データフレーム(df_customer)とレシート明細データフレーム(df_receipt)から、各顧客ごとの売上金額合計を求めよ。ただし、買い物の実績がない顧客については売上金額を0として表示させること。また、顧客は性別コード(gender_cd)が女性(1)であるものを対象とし、非会員(顧客IDが'Z'から始まるもの)は除外すること。なお、結果は10件だけ表示させれば良い。

p038.py
df_amount_sum = df_receipt.groupby('customer_id').amount.sum().reset_index()
df_tmp = df_customer.query('gender_cd == "1" & not(customer_id.str.startswith("Z"))', engine='python')
pd.merge(df_tmp['customer_id'], df_amount_sum, how='left', on='customer_id').fillna(0).head(10)

 fillna():欠損値を穴埋めする。
 引数に{'列名1':'値1', '列名2':'値2', }のように渡すと列ごとに違う値で欠損値を埋められる。
 また、df.fillna(df.mean())などと書くと平均値で埋めることもできる。(平均値以外でも可能)

P-039 duplicated()

P-039: レシート明細データフレーム(df_receipt)から売上日数の多い顧客の上位20件と、売上金額合計の多い顧客の上位20件を抽出し、完全外部結合せよ。ただし、非会員(顧客IDが'Z'から始まるもの)は除外すること。

p039.py
df_sales_ymd_rank = df_receipt[~df_receipt.duplicated(subset=['customer_id', 'sales_ymd'])]
df_sales_ymd_rank = df_sales_ymd_rank.query('not customer_id.str.startswith("Z")', engine='python').groupby('customer_id').sales_ymd.count().reset_index().sort_values('sales_ymd', ascending=False).head(20)
df_amount_rank = df_receipt.query('not customer_id.str.startswith("Z")', engine='python').groupby('customer_id').amount.sum().reset_index().sort_values('amount', ascending=False).head(20)
pd.merge(df_amount_rank, df_sales_ymd_rank, how='outer', on='customer_id')

 duplicated():データフレームから重複した要素を含む行を検出・抽出。(pd.Series型)
 subset='列名'で判定する列を指定できる。(重複した行がTrue、それ以外はFalse)
 複数行を指定する場合はリストで渡す。(完全一致のみTrue)

 df_receiptで一つのcustomer_idについて見てみると、__同じ日付のデータが複数ある__のが見られる。(下の写真はcustomer_id='CS017415000097'のデータをsales_idについてソートしたもの)
スクリーンショット 2021-08-04 23.23.47.png
 これは同じ日付に複数の商品を購入したからだと思われる。
 重複していない行(日数、sales_ymd)だけを計算したいため、否定~で反転してからカウントを取っている。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?