データサイエンス 100 本ノックとは

The-Japan-DataScientist-Society/100knocks-preprocess: データサイエンス 100 本ノック(構造化データ加工編)

Google Colab で データサイエンス 100 本ノックをするにあたって

以下の ipynb ファイルを使用させていただきました。

noguhiro2002/100knocks-preprocess_ForColab-AzureNotebook: データサイエンス 100 本ノック(構造化データ加工編)For Azure_Notebook/Google_Colabo

実行ファイル

データサイエンス 100 本ノック by tomowarkar

071

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

tmp = pd.merge(
    df_receipt[["sales_ymd", "customer_id"]],
    df_customer[["customer_id", "application_date"]],
    how="left"
)
f = lambda x: pd.to_datetime(x, format='%Y%m%d').dt
# a (売り上げ日) >= b (申込日)
a, b = f(tmp.sales_ymd), f(tmp.application_date)
tmp["elapsed_months"] = a.month - b.month - (b.day > a.day) + 12 * (a.year - b.year)
tmp.head(10)
   sales_ymd     customer_id  application_date  elapsed_months
0   20181103  CS006214000001        20150201.0            45.0
1   20181118  CS008415000097        20150322.0            43.0
2   20170712  CS028414000014        20150711.0            24.0
3   20190205  ZZ000000000000               NaN             NaN
4   20180821  CS025415000050        20160131.0            30.0
5   20190605  CS003515000195        20150306.0            50.0
6   20181205  CS024514000042        20151010.0            37.0
7   20190922  CS040415000178        20150627.0            50.0
8   20170504  ZZ000000000000               NaN             NaN
9   20191010  CS027514000015        20151101.0            47.0

Tips

※ 一部dateutil.relativedelta.relativedeltaを使用する

%%timeit
tmp = pd.merge(
    df_receipt[["sales_ymd", "customer_id"]],
    df_customer[["customer_id", "application_date"]],
    how="left"
)
f = lambda x: pd.to_datetime(x.fillna("19700101"), format='%Y%m%d')
dict_sales_ymd = f(tmp.sales_ymd).to_dict()
dict_application_date = f(tmp.application_date).to_dict()

tmp.index.map(lambda x: relativedelta(dict_sales_ymd[x], dict_application_date[x]))
1 loop, best of 3: 5.16 s per loop
%%timeit
# 別解の別解(遅い)
tmp = pd.merge(
    df_receipt[["sales_ymd", "customer_id"]],
    df_customer[["customer_id", "application_date"]],
    how="left"
)
f = lambda x: pd.to_datetime(x.fillna("19700101"), format='%Y%m%d')
tmp.sales_ymd = f(tmp.sales_ymd)
tmp.application_date = f(tmp.application_date)

tmp.apply(lambda x: relativedelta(x.sales_ymd,x.application_date), axis=1)
1 loop, best of 3: 10.6 s per loop
%%timeit
tmp = pd.merge(
    df_receipt[["sales_ymd", "customer_id"]],
    df_customer[["customer_id", "application_date"]],
    how="left"
)
f = lambda x: pd.to_datetime(x, format='%Y%m%d').dt
# a (売り上げ日) >= b (申込日)
a, b = f(tmp.sales_ymd), f(tmp.application_date)

a.month - b.month - (b.day > a.day) + 12 * (a.year - b.year)
1 loop, best of 3: 213 ms per loop

以上のように全ての列にmapapplyを使ってアクセスするのは時間がかかるのでなるだけ配列演算をしたい

072

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

tmp = pd.merge(
    df_receipt[["sales_ymd", "customer_id"]],
    df_customer[["customer_id", "application_date"]],
    how="left"
)
f = lambda x: pd.to_datetime(x, format='%Y%m%d').dt
# a (売り上げ日) >= b (申込日)
a, b = f(tmp.sales_ymd), f(tmp.application_date)
tmp["elapsed_years"] = (a.year - b.year) - (a.month - b.month - (b.day > a.day)).map(lambda x: x<0)
tmp.head(10)
   sales_ymd     customer_id  application_date  elapsed_years
0   20181103  CS006214000001        20150201.0            3.0
1   20181118  CS008415000097        20150322.0            3.0
2   20170712  CS028414000014        20150711.0            2.0
3   20190205  ZZ000000000000               NaN            NaN
4   20180821  CS025415000050        20160131.0            2.0
5   20190605  CS003515000195        20150306.0            4.0
6   20181205  CS024514000042        20151010.0            3.0
7   20190922  CS040415000178        20150627.0            4.0
8   20170504  ZZ000000000000               NaN            NaN
9   20191010  CS027514000015        20151101.0            3.0

別解

tmp = pd.merge(
    df_receipt[["sales_ymd", "customer_id"]],
    df_customer[["customer_id", "application_date"]],
    how="left"
)
f = lambda x: pd.to_datetime(x.fillna("19700101"), format='%Y%m%d')
dict_sales_ymd = f(tmp.sales_ymd).to_dict()
dict_application_date = f(tmp.application_date).to_dict()
tmp["elapsed_years"] = pd.Series(tmp.index.map(lambda x: relativedelta(dict_sales_ymd[x], dict_application_date[x]).years))

tmp.head(10)

073

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

欠損値を UNIX 基準日に合わせる

tmp = pd.merge(
    df_receipt[["sales_ymd", "customer_id"]],
    df_customer[["customer_id", "application_date"]],
    how="left"
)
f = lambda x: pd.to_datetime(x.fillna("19700101"), format='%Y%m%d').map(lambda x: x.timestamp()).astype(int)
tmp["elapsed_epoch"] = f(tmp.sales_ymd)- f(tmp.application_date)
tmp.head(10)
   sales_ymd     customer_id  application_date  elapsed_epoch
0   20181103  CS006214000001        20150201.0      118454400
1   20181118  CS008415000097        20150322.0      115516800
2   20170712  CS028414000014        20150711.0       63244800
3   20190205  ZZ000000000000               NaN     1549324800
4   20180821  CS025415000050        20160131.0       80611200
5   20190605  CS003515000195        20150306.0      134092800
6   20181205  CS024514000042        20151010.0       99532800
7   20190922  CS040415000178        20150627.0      133747200
8   20170504  ZZ000000000000               NaN     1493856000
9   20191010  CS027514000015        20151101.0      124329600

074

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

pandas.tseries.offsetsを使う

import pandas.tseries.offsets as offsets

tmp = pd.to_datetime(df_receipt.sales_ymd, format='%Y%m%d')
print(pd.concat(
    [df_receipt[["sales_ymd"]],
     tmp.dt.weekday,
     tmp.map(lambda x: x + offsets.Day(-x.weekday()))],
    axis=1
).head(10))
  sales_ymd sales_ymd  sales_ymd
0  20181103         5 2018-10-29
1  20181118         6 2018-11-12
2  20170712         2 2017-07-10
3  20190205         1 2019-02-04
4  20180821         1 2018-08-20
5  20190605         2 2019-06-03
6  20181205         2 2018-12-03
7  20190922         6 2019-09-16
8  20170504         3 2017-05-01
9  20191010         3 2019-10-07

075

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

ランダムサンプリング

df_customer.sample(frac=0.01).head(10)
          customer_id customer_name  ...  application_date     status_cd
11557  CS011411000084          神戸 遥  ...          20150805  2-20100904-3
11803  CS001613000148        内野 れいな  ...          20160216  1-20091228-1
10038  CS003413000448         西脇 恭子  ...          20160322  0-00000000-0
21959  CS008513000185         長沢 路子  ...          20150228  0-00000000-0
15259  CS038412000016        柴田 はるみ  ...          20141226  7-20100214-B
12616  CS023515000118        吉原 ちえみ  ...          20150707  F-20100728-C
11091  CS005613000214        柴田 まさみ  ...          20170601  0-00000000-0
14468  CS005513000061        戸塚 菜々美  ...          20151030  A-20080826-8
18627  CS023613000074         浅川 奈々  ...          20141117  0-00000000-0
10967  CS038411000013         原 あや子  ...          20150525  9-20100306-A

[10 rows x 11 columns]
df_customer.sample(frac=0.01).groupby("gender_cd").agg({"customer_id":"count"})
           customer_id
gender_cd
0                   27
1                  187
9                    6

076

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

層化抽出

idx = np.array([])
for _, v in df_customer.groupby("gender_cd").customer_id.groups.items():
  i = np.random.choice(v.values, size=len(v.values)//10, replace=False)
  idx = np.append(idx, i)

df_customer.loc[sorted(idx)]
            customer_id customer_name  ...  application_date     status_cd
2.0      CS031415000172       宇多田 貴美子  ...          20150529  D-20100325-C
10.0     CS007403000016          依田 満  ...          20150914  0-00000000-0
16.0     CS009315000023         皆川 文世  ...          20150319  5-20080322-1
29.0     CS028413000087          浅野 薫  ...          20150528  0-00000000-0
30.0     CS006415000279          末永 綾  ...          20150713  1-20100519-2
...                 ...           ...  ...               ...           ...
21879.0  CS001215000040         大原 奈央  ...          20151211  C-20081022-B
21901.0  CS028715000034        浅野 真悠子  ...          20151114  0-00000000-0
21918.0  CS038714000037        浜本 みゆき  ...          20160812  0-00000000-0
21926.0  CS045613000006        布施 あさみ  ...          20170513  8-20100809-9
21954.0  CS037701000008          米倉 優  ...          20151128  0-00000000-0

[2196 rows x 11 columns]
df_customer.loc[idx].groupby("gender_cd").agg({"customer_id":"count"})
           customer_id
gender_cd
0                  298
1                 1791
9                  107

別解

from sklearn.model_selection import train_test_split

_, tmp = train_test_split(df_customer, test_size=0.1, stratify=df_customer.gender)

077

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

pandas.Series.mean, pandas.Series.stdを使う

tmp = df_receipt[~df_receipt.customer_id.str.startswith("Z")].groupby("customer_id").amount.sum()
mean = tmp.mean()
std = tmp.std(ddof=0)
tmp[abs(mean-tmp) >= 3*std].reset_index().head(10)
      customer_id  amount
0  CS001605000009   18925
1  CS006415000147   12723
2  CS006515000023   18372
3  CS006515000125   12575
4  CS006515000209   11373
5  CS007115000006   11528
6  CS007514000056   13293
7  CS007514000094   15735
8  CS007515000107   11188
9  CS007615000026   11959

078

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

tmp = df_receipt[~df_receipt.customer_id.str.startswith("Z")].groupby("customer_id").amount.sum()
q1, q3 = tmp.quantile([.25, .75])
iqr = q3 - q1
tmp[(tmp<q1-1.5*iqr)|(tmp>q3+1.5*iqr)].reset_index().head(10)
      customer_id  amount
0  CS001414000048    8584
1  CS001605000009   18925
2  CS002415000594    9568
3  CS004414000181    9584
4  CS005415000137    8734
5  CS006414000001    9156
6  CS006414000029    9179
7  CS006415000105   10042
8  CS006415000147   12723
9  CS006415000157   10648

079

P-079: 商品データフレーム(df_product)の各項目に対し、欠損数を確認せよ。

# 列ベース
df_product.isna().sum()
product_cd            0
category_major_cd     0
category_medium_cd    0
category_small_cd     0
unit_price            7
unit_cost             7
dtype: int64
# 行ベース
df_product.isna().any(axis=1).sum() #> 7

080

P-080: 商品データフレーム(df_product)のいずれかの項目に欠損が発生しているレコードを全て削除した新たな df_product_1 を作成せよ。なお、削除前後の件数を表示させ、前設問で確認した件数だけ減少していることも確認すること。

df_product_1 = df_product.dropna()
print("before:", len(df_product), ", after:", len(df_product_1)) #> before: 10030 , after: 10023