この記事はデータサイエンスを勉強しながら、データサイエンス協会が提供する__データサイエンス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件表示させればよい。
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は文字列でデータを保持している点に注意)。
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ヶ月未満は切り捨てること。
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型の差分を計算してくれる。
結果をyears
、months
、days
で返してくれるため、years
には12を掛けて月に変換している。
P-072
P-072: レシート明細データフレーム(df_receipt)の売上日(sales_ymd)に対し、顧客データフレーム(df_customer)の会員申込日(application_date)からの経過年数を計算し、顧客ID(customer_id)、売上日、会員申込日とともに表示せよ。結果は10件表示させれば良い。(なお、sales_ymdは数値、application_dateは文字列でデータを保持している点に注意)。1年未満は切り捨てること。
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秒を表すものとする。
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は数値でデータを保持している点に注意)。
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件データを抽出せよ。
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%のデータを層化抽出データし、性別ごとに件数を集計せよ。
_, 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_cd
が0
と9
のデータを逃さないために比率を固定している。
P-077
P-077: レシート明細データフレーム(df_receipt)の売上金額(amount)を顧客単位に合計し、合計した売上金額の外れ値を抽出せよ。ただし、顧客IDが"Z"から始まるのものは非会員を表すため、除外して計算すること。なお、ここでは外れ値を平均から3σ以上離れたものとする。結果は10件表示させれば良い。
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に標準化して行う場合
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件表示させれば良い。
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)