2
3

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-08-15

 この記事はデータサイエンスを勉強しながら、データサイエンス協会が提供するデータサイエンス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 
  • 日付の加算・減算 relativedelta()
  • datetime型 → UNIX時間 timestamp()
  • 曜日をint型で返す weekday()
  • ランダムに抽出 sample()
  • 層化抽出 train_test_split(stratify=)

P-069

P-069: レシート明細データフレーム(df_receipt)と商品データフレーム(df_product)を結合し、顧客毎に全商品の売上金額合計と、カテゴリ大区分(category_major_cd)が"07"(瓶詰缶詰)の売上金額合計を計算の上、両者の比率を求めよ。抽出対象はカテゴリ大区分"07"(瓶詰缶詰)の購入実績がある顧客のみとし、結果は10件表示させればよい。

p069.py
df_tmp_1 = pd.merge(df_receipt, df_product, how='inner', on='product_cd').groupby('customer_id').amount.sum().rename('amount_sum').reset_index()
df_tmp_2 = pd.merge(df_receipt, df_product.query('category_major_cd == "07"'), 
                    how='inner', on='product_cd').groupby('customer_id').amount.sum().rename('07_amount_sum').reset_index()
df_tmp_3 = pd.merge(df_tmp_1, df_tmp_2, how='inner', on='customer_id')
df_tmp_3['07_rate'] = df_tmp_3['07_amount_sum'] / df_tmp_3['amount_sum']
df_tmp_3.head(10)

P-070 drop_duplicates()

P-070: レシート明細データフレーム(df_receipt)の売上日(sales_ymd)に対し、顧客データフレーム(df_customer)の会員申込日(application_date)からの経過日数を計算し、顧客ID(customer_id)、売上日、会員申込日とともに表示せよ。結果は10件表示させれば良い(なお、sales_ymdは数値、application_dateは文字列でデータを保持している点に注意)。

p070.py
df_tmp = pd.merge(df_receipt[['customer_id', 'sales_ymd']], df_customer[['customer_id', 'application_date']],
                  how='inner', on='customer_id')
df_tmp = df_tmp.drop_duplicates()
df_tmp['sales_ymd'] = pd.to_datetime(df_tmp['sales_ymd'].astype('str'))
df_tmp['application_date'] = pd.to_datetime(df_tmp['application_date'])
df_tmp['lapsed_date'] = df_tmp['sales_ymd'] - df_tmp['application_date']
df_tmp.head(10)

 drop_duplicates():重複した行を削除

 duplicated()を使うなら、論理演算子を用いて2行目を以下のように変更すればいい。

df_tmp = df_tmp[~df_tmp.duplicated()]

P-071 relativedelta()

P-071: レシート明細データフレーム(df_receipt)の売上日(sales_ymd)に対し、顧客データフレーム(df_customer)の会員申込日(application_date)からの経過月数を計算し、顧客ID(customer_id)、売上日、会員申込日とともに表示せよ。結果は10件表示させれば良い(なお、sales_ymdは数値、application_dateは文字列でデータを保持している点に注意)。1ヶ月未満は切り捨てること。

p071.py
df_tmp = pd.merge(df_receipt[['customer_id', 'sales_ymd']], df_customer[['customer_id', 'application_date']],
                  how='inner', on='customer_id')
df_tmp = df_tmp.drop_duplicates()

df_tmp['sales_ymd'] = pd.to_datetime(df_tmp['sales_ymd'].astype('str'))
df_tmp['application_date'] = pd.to_datetime(df_tmp['application_date'])

df_tmp['lapsed_month'] = df_tmp[['sales_ymd', 'application_date']].apply(lambda x: 
                                    relativedelta(x[0], x[1]).years * 12 + relativedelta(x[0], x[1]).months ,axis=1)
df_tmp.head(10)

 relativedelta(datetime1, datetime2):引数で渡したdatetime型の差分を計算してくれる。
 結果をyearsmonthsdaysで返してくれるため、yearsには12を掛けて月に変換している。

P-072

P-072: レシート明細データフレーム(df_receipt)の売上日(sales_ymd)に対し、顧客データフレーム(df_customer)の会員申込日(application_date)からの経過年数を計算し、顧客ID(customer_id)、売上日、会員申込日とともに表示せよ。結果は10件表示させれば良い。(なお、sales_ymdは数値、application_dateは文字列でデータを保持している点に注意)。1年未満は切り捨てること。

p072.py
df_tmp = pd.merge(df_receipt[['customer_id', 'sales_ymd']], df_customer[['customer_id', 'application_date']],
                 how='inner', on='customer_id')
df_tmp = df_tmp.drop_duplicates()

df_tmp['sales_ymd'] = pd.to_datetime(df_tmp['sales_ymd'].astype('str'))
df_tmp['application_date'] = pd.to_datetime(df_tmp['application_date'])

df_tmp['lapsed_years'] = df_tmp[['sales_ymd', 'application_date']].apply(lambda x:
                                 relativedelta(x[0], x[1]).years, axis=1)
df_tmp.head(10)

P-073 timestamp()

P-073: レシート明細データフレーム(df_receipt)の売上日(sales_ymd)に対し、顧客データフレーム(df_customer)の会員申込日(application_date)からのエポック秒による経過時間を計算し、顧客ID(customer_id)、売上日、会員申込日とともに表示せよ。結果は10件表示させれば良い(なお、sales_ymdは数値、application_dateは文字列でデータを保持している点に注意)。なお、時間情報は保有していないため各日付は0時0分0秒を表すものとする。

p073.py
df_tmp = pd.merge(df_receipt[['customer_id', 'sales_ymd']], df_customer[['customer_id', 'application_date']],
                  how='inner', on='customer_id')
df_tmp = df_tmp.drop_duplicates()

df_tmp['sales_ymd'] = pd.to_datetime(df_tmp['sales_ymd'].astype('str'))
df_tmp['application_date'] = pd.to_datetime(df_tmp['application_date'])

df_tmp['lapsed_date_unix'] = df_tmp['sales_ymd'].apply(lambda x: x.timestamp()) - df_tmp['application_date'].apply(lambda x: x.timestamp())
df_tmp.head(10)

 timestamp():datetimeオブジェクトをUNIX時間に変換

P-074 weekday()

P-074: レシート明細データフレーム(df_receipt)の売上日(sales_ymd)に対し、当該週の月曜日からの経過日数を計算し、売上日、当該週の月曜日付とともに表示せよ。結果は10件表示させれば良い(なお、sales_ymdは数値でデータを保持している点に注意)。

074.py
df_tmp = df_receipt[['customer_id', 'sales_ymd']]
df_tmp = df_tmp.drop_duplicates()
df_tmp['sales_ymd'] = pd.to_datetime(df_receipt['sales_ymd'].astype('str'))

df_tmp['this_monday'] = df_tmp['sales_ymd'].apply(lambda x: x - relativedelta(days=x.weekday()))

df_tmp['lapsed_date'] = df_tmp['sales_ymd'] - df_tmp['this_monday']
df_tmp.head(10)

 weekday():datetimeオブジェクトの曜日を整数値(int型)で返す。(月曜日は0、日曜日は6)

 ※weekday()と似たような関数

  • isoweekday():月曜日が1、日曜日が7の整数値を返す
  • day_name():曜日を文字列で返す。('Monday', 'Tuesday',...)

P-075 sample()

P-075: 顧客データフレーム(df_customer)からランダムに1%のデータを抽出し、先頭から10件データを抽出せよ。

p075.py
df_customer.sample(frac=0.01).head(10)

 sample():行または列をランダムに抽出する。

引数 説明
n 抽出する数を指定できる。
frac 抽出する割合を指定(1なら100%、0.01なら1%)
random_state 乱数シードを固定
replace Trueで重複を許可(デフォルトはFalse
axis 1で列をランダムに取得(デフォルトは0、列)

P-076 train_test_split()

P-076: 顧客データフレーム(df_customer)から性別(gender_cd)の割合に基づきランダムに10%のデータを層化抽出データし、性別ごとに件数を集計せよ。

p076.py
_, df_tmp = train_test_split(df_customer, test_size=0.1, stratify=df_customer['gender_cd'])
df_tmp.groupby('gender_cd').customer_id.count().reset_index()

 train_test_split()scikit-learnの関数で配列やリストを2分割できる。
 主に、機械学習においてデータを訓練用とテスト用に分割してホールドアウト検証を行う際に用いることが多い。

引数stratifyに均等に分割させたいデータを指定すると、そのデータの値の比率が元のデータと一致するように分割される。
 問題のケースではサンプル数が少ないgender_cd09のデータを逃さないために比率を固定している。

P-077

P-077: レシート明細データフレーム(df_receipt)の売上金額(amount)を顧客単位に合計し、合計した売上金額の外れ値を抽出せよ。ただし、顧客IDが"Z"から始まるのものは非会員を表すため、除外して計算すること。なお、ここでは外れ値を平均から3σ以上離れたものとする。結果は10件表示させれば良い。

p077.py
df_tmp = df_receipt.query('not customer_id.str.startswith("Z")', engine='python').groupby('customer_id').amount.sum().reset_index()
amount_mean = df_tmp['amount'].mean()
amount_std = np.std(df_tmp['amount'])
df_tmp[df_tmp['amount'] >= amount_mean + amount_std*3].head(10)

 preprocessing.scale()を使って平均を0、標準偏差を1に標準化して行う場合

p077.py
df_tmp = df_receipt.query('not customer_id.str.startswith("Z")', engine='python').groupby('customer_id').amount.sum().reset_index()
df_tmp['amount_ss'] = preprocessing.scale(df_tmp['amount'])
df_tmp.query('abs(amount_ss) >= 3').head(10)

P-078

P-078: レシート明細データフレーム(df_receipt)の売上金額(amount)を顧客単位に合計し、合計した売上金額の外れ値を抽出せよ。ただし、顧客IDが"Z"から始まるのものは非会員を表すため、除外して計算すること。なお、ここでは外れ値を第一四分位と第三四分位の差であるIQRを用いて、「第一四分位数-1.5×IQR」よりも下回るもの、または「第三四分位数+1.5×IQR」を超えるものとする。結果は10件表示させれば良い。

p078.py
df_tmp = df_receipt.query('not customer_id.str.startswith("Z")', engine='python').groupby('customer_id').amount.sum().reset_index()
amount_25 = df_tmp['amount'].quantile(0.25)
amount_75 = df_tmp['amount'].quantile(0.75)
iqr = amount_75 - amount_25

lower_lim = amount_25 - 1.5 * iqr
upper_lim = amount_75 + 1.5 * iqr

df_tmp[(lower_lim > df_tmp['amount']) | (upper_lim < df_tmp['amount'])].head(10)

 上のように書いたが、最後の行はquery()を使って書いた方がスッキリする。

df_tmp.query('amount < @lower_lim or @upper_lim < amount').head(10)
2
3
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
2
3