6 minutes
データサイエンス100本ノックをPythonでやってみた [71-80]
データサイエンス 100 本ノックとは
The-Japan-DataScientist-Society/100knocks-preprocess: データサイエンス 100 本ノック(構造化データ加工編)
Google Colab で データサイエンス 100 本ノックをするにあたって
以下の ipynb ファイルを使用させていただきました。
実行ファイル
データサイエンス 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
以上のように全ての列にmap
やapply
を使ってアクセスするのは時間がかかるのでなるだけ配列演算をしたい
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