この記事はデータサイエンスを勉強しながら、データサイエンス協会が提供する__データサイエンス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%刻みでパーセンタイル値を求めよ。
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以上のものを抽出せよ。
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"から始まるのものは非会員を表すため、除外して計算すること。
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件だけ表示させれば良い。
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から始まる会員を除外してしまう。
解答では以下のようになっている。
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件表示させよ。
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_receipt
とdf_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件表示させよ。
pd.merge(df_product
, df_category[['category_small_cd','category_small_name']]
, how='inner', on='category_small_cd').head(10)
df_product
とdf_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件だけ表示させれば良い。
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'から始まるもの)は除外すること。
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についてソートしたもの)
これは同じ日付に複数の商品を購入したからだと思われる。
重複していない行(日数、sales_ymd)だけを計算したいため、否定~
で反転してからカウントを取っている。