数据科学 100 道练习题 81~90#
import polars as pl
from helper.polars import load_100knocks_data
pl.Config.set_fmt_str_lengths(100)
df_customer, df_category, df_product, df_receipt, df_store, df_geocode = load_100knocks_data()
P-081#
对单价(unit_price)与成本价(unit_cost)的缺失值,分别用各自的平均值填补,创建新的商品数据。平均值需对不足 1 日元的部分取整(四舍五入或取偶舍入均可)。填补之后,请确认各项目已不产生缺失。
(
df_product
.with_columns(
pl.col.unit_price.fill_null(pl.col.unit_price.mean()),
pl.col.unit_cost.fill_null(pl.col.unit_cost.mean())
)
.select(pl.all().null_count())
)
| product_cd | category_major_cd | category_medium_cd | category_small_cd | unit_price | unit_cost |
|---|---|---|---|---|---|
| u32 | u32 | u32 | u32 | u32 | u32 |
| 0 | 0 | 0 | 0 | 0 | 0 |
P-082#
对单价(unit_price)与成本价(unit_cost)的缺失值,分别用各自的中位数填补,创建新的商品数据。中位数需对不足 1 日元的部分取整(四舍五入或取偶舍入均可)。填补之后,请确认各项目已不产生缺失。
(
df_product
.with_columns(
pl.col.unit_price.fill_null(pl.col.unit_price.median()),
pl.col.unit_cost.fill_null(pl.col.unit_cost.median())
)
.select(pl.all().null_count())
)
| product_cd | category_major_cd | category_medium_cd | category_small_cd | unit_price | unit_cost |
|---|---|---|---|---|---|
| u32 | u32 | u32 | u32 | u32 | u32 |
| 0 | 0 | 0 | 0 | 0 | 0 |
P-083#
对单价(unit_price)与成本价(unit_cost)的缺失值,用按商品小分类代码(category_small_cd)分组计算的中位数填补,创建新的商品数据。中位数需对不足 1 日元的部分取整(四舍五入或取偶舍入均可)。填补之后,请确认各项目已不产生缺失。
cols = pl.col('unit_price', 'unit_cost')
(
df_product
.with_columns(
pl.when(cols.is_null())
.then(cols.median())
.otherwise(cols)
.over('category_small_cd')
)
.select(pl.all().null_count())
)
| product_cd | category_major_cd | category_medium_cd | category_small_cd | unit_price | unit_cost |
|---|---|---|---|---|---|
| u32 | u32 | u32 | u32 | u32 | u32 |
| 0 | 0 | 0 | 0 | 0 | 0 |
P-084#
对顾客数据(df_customer)的全部顾客,计算 2019 年销售金额占全期间销售金额的比例,创建新的数据。没有销售实绩的按 0 处理。然后抽取比例大于 0 的记录,显示 10 条结果,并确认创建的数据中不存在缺失值。
(
df_customer
.join(
df_receipt
.group_by('customer_id')
.agg(sales_amount_all=pl.col.amount.sum()),
on='customer_id', how='left'
)
.join(
df_receipt
.filter((pl.col.sales_ymd // 10000) == 2019)
.group_by('customer_id')
.agg(sales_amount_2019=pl.col.amount.sum()),
on='customer_id', how='left'
)
.select(
'customer_id',
pl.col.sales_amount_all.fill_null(0),
pl.col.sales_amount_2019.fill_null(0)
)
.with_columns(
sales_rate=(pl.col.sales_amount_2019 / pl.col.sales_amount_all).fill_nan(0)
)
.filter(pl.col.sales_amount_2019 > 0)
.head()
)
| customer_id | sales_amount_all | sales_amount_2019 | sales_rate |
|---|---|---|---|
| str | i64 | i64 | f64 |
| "CS031415000172" | 5088 | 2971 | 0.583923 |
| "CS015414000103" | 3122 | 874 | 0.279949 |
| "CS011215000048" | 3444 | 248 | 0.072009 |
| "CS029415000023" | 5167 | 3767 | 0.72905 |
| "CS035415000029" | 7504 | 5823 | 0.775986 |
P-085#
对顾客数据(df_customer)的全部顾客,用邮政编码(postal_cd)关联地理编码数据(df_geocode),创建新的顾客数据。若一个邮政编码(postal_cd)对应多个经度(longitude)、纬度(latitude),则取经度、纬度的平均值使用。为确认创建结果,显示 10 条记录。
df_loc = (
df_geocode
.group_by(pl.col.postal_cd)
.agg(
pl.col.longitude.mean(),
pl.col.latitude.mean()
)
)
df_customer_loc = (
df_customer.join(df_loc, on='postal_cd', how='left')
)
df_customer_loc.head()
| customer_id | customer_name | gender_cd | gender | birth_day | age | postal_cd | address | application_store_cd | application_date | status_cd | longitude | latitude |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| str | str | str | str | str | i64 | str | str | str | i64 | str | f64 | f64 |
| "CS021313000114" | "大野 あや子" | "1" | "女性" | "1981-04-29" | 37 | "259-1113" | "神奈川県伊勢原市粟窪**********" | "S14021" | 20150905 | "0-00000000-0" | 139.31779 | 35.41358 |
| "CS037613000071" | "六角 雅彦" | "9" | "不明" | "1952-04-01" | 66 | "136-0076" | "東京都江東区南砂**********" | "S13037" | 20150414 | "0-00000000-0" | 139.83502 | 35.67193 |
| "CS031415000172" | "宇多田 貴美子" | "1" | "女性" | "1976-10-04" | 42 | "151-0053" | "東京都渋谷区代々木**********" | "S13031" | 20150529 | "D-20100325-C" | 139.68965 | 35.67374 |
| "CS028811000001" | "堀井 かおり" | "1" | "女性" | "1933-03-27" | 86 | "245-0016" | "神奈川県横浜市泉区和泉町**********" | "S14028" | 20160115 | "0-00000000-0" | 139.4836 | 35.39125 |
| "CS001215000145" | "田崎 美紀" | "1" | "女性" | "1995-03-29" | 24 | "144-0055" | "東京都大田区仲六郷**********" | "S13001" | 20170605 | "6-20090929-2" | 139.70775 | 35.54084 |
P-086#
对 085 创建的带经纬度的顾客数据,以会员申请门店代码(application_store_cd)为键与门店数据(df_store)连接。然后用申请门店的纬度(latitude)、经度(longitude)与顾客住址(address)的经纬度,求申请门店与顾客住址之间的距离(单位:km),并连同顾客 ID(customer_id)、顾客住址(address)、门店住址(address)一起显示。计算可用下面的简化公式,也可以使用其他精度更高的库。结果显示 10 条。
p1 = pl.col.latitude.radians()
p2 = pl.col.latitude_right.radians()
l1 = pl.col.longitude.radians()
l2 = pl.col.longitude.radians()
distance = (p1.sin() * p2.sin() + p1.cos() * p2.cos() * (l1 - l1).cos()).arccos() * 6371
(
df_customer_loc
.join(
df_store,
left_on='application_store_cd',
right_on='store_cd',
how='left'
)
.with_columns(distance=distance)
.select(
'customer_id',
pl.col.address.alias('customer_address'),
pl.col.address_right.alias('store_address'),
'distance'
)
.head()
)
| customer_id | customer_address | store_address | distance |
|---|---|---|---|
| str | str | str | f64 |
| "CS021313000114" | "神奈川県伊勢原市粟窪**********" | "神奈川県伊勢原市伊勢原四丁目" | 1.322108 |
| "CS037613000071" | "東京都江東区南砂**********" | "東京都江東区南砂一丁目" | 0.783924 |
| "CS031415000172" | "東京都渋谷区代々木**********" | "東京都渋谷区初台二丁目" | 0.269092 |
| "CS028811000001" | "神奈川県横浜市泉区和泉町**********" | "神奈川県横浜市瀬谷区二ツ橋町" | 7.982684 |
| "CS001215000145" | "東京都大田区仲六郷**********" | "東京都大田区仲六郷二丁目" | 1.168659 |
P-087#
顾客数据(df_customer)中,同一顾客会因在不同门店申请等原因被重复登记。将姓名(customer_name)与邮政编码(postal_cd)相同的顾客视为同一顾客,归并为一名顾客一条记录,创建归并后的顾客数据,并计算顾客数据的件数、归并后顾客数据的件数以及重复数。同一顾客保留销售金额合计最高的一条。销售金额合计相同或没有销售实绩的顾客,保留顾客 ID(customer_id)编号较小的一条。
df_receipt_sum = (
df_receipt
.group_by('customer_id')
.agg(pl.col.amount.sum())
)
df_unique_customer = (
df_customer
.join(df_receipt_sum, on='customer_id', how='left')
.with_columns(pl.col.amount.fill_null(0))
.group_by('customer_name', 'postal_cd')
.agg(
pl.col('customer_id', 'amount')
.sort_by('amount', 'customer_id', descending=[True, False])
.first()
)
)
df_unique_customer.head()
| customer_name | postal_cd | customer_id | amount |
|---|---|---|---|
| str | str | str | i64 |
| "石丸 愛梨" | "213-0022" | "CS021615000169" | 0 |
| "高山 恵梨香" | "276-0022" | "CS007412000105" | 0 |
| "馬場 兼" | "275-0021" | "CS013602000024" | 0 |
| "目黒 慶二" | "152-0035" | "CS009301000001" | 0 |
| "上杉 まみ" | "203-0052" | "CS018315000204" | 0 |
df_customer.shape[0], df_unique_customer.shape[0]
(21971, 21941)
P-088#
基于 087 创建的数据,为顾客数据添加统一归并 ID,创建新的数据。统一归并 ID 按以下规格赋予。
未重复的顾客:设置其顾客 ID(
customer_id)重复的顾客:设置前一题中抽取出的记录的顾客 ID
同时确认顾客 ID 的唯一件数与统一归并 ID 的唯一件数之差。
df_customer_integration = (
df_customer
.join(
df_unique_customer
.select(
'customer_name', 'postal_cd', pl.col.customer_id.alias('integration_id')
),
on=['customer_name', 'postal_cd'],
how='left',
)
)
df_customer_integration.head()
| customer_id | customer_name | gender_cd | gender | birth_day | age | postal_cd | address | application_store_cd | application_date | status_cd | integration_id |
|---|---|---|---|---|---|---|---|---|---|---|---|
| str | str | str | str | str | i64 | str | str | str | i64 | str | str |
| "CS021313000114" | "大野 あや子" | "1" | "女性" | "1981-04-29" | 37 | "259-1113" | "神奈川県伊勢原市粟窪**********" | "S14021" | 20150905 | "0-00000000-0" | "CS021313000114" |
| "CS037613000071" | "六角 雅彦" | "9" | "不明" | "1952-04-01" | 66 | "136-0076" | "東京都江東区南砂**********" | "S13037" | 20150414 | "0-00000000-0" | "CS037613000071" |
| "CS031415000172" | "宇多田 貴美子" | "1" | "女性" | "1976-10-04" | 42 | "151-0053" | "東京都渋谷区代々木**********" | "S13031" | 20150529 | "D-20100325-C" | "CS031415000172" |
| "CS028811000001" | "堀井 かおり" | "1" | "女性" | "1933-03-27" | 86 | "245-0016" | "神奈川県横浜市泉区和泉町**********" | "S14028" | 20160115 | "0-00000000-0" | "CS028811000001" |
| "CS001215000145" | "田崎 美紀" | "1" | "女性" | "1995-03-29" | 24 | "144-0055" | "東京都大田区仲六郷**********" | "S13001" | 20170605 | "6-20090929-2" | "CS001215000145" |
(
df_customer_integration
.filter(pl.col.customer_id != pl.col.integration_id)
.shape[0]
)
30
P-089#
希望将有销售实绩的顾客划分为训练用数据与测试用数据,用于构建预测模型。按 8:2 的比例随机划分数据。
df_train, df_test = (
df_receipt
.group_by('customer_id')
.agg(pl.col.amount.sum())
.join(df_customer, on='customer_id', how='left')
.with_columns(
split=pl.int_range(pl.len()).shuffle() < pl.len() * 0.2
)
.partition_by('split')
)
df_train.shape[0], df_test.shape[0]
(6645, 1662)
P-090#
收据明细数据(df_receipt)包含 2017 年 1 月 1 日至 2019 年 10 月 31 日的数据。将销售金额(amount)按月汇总,创建 3 组用于时序模型的数据,其中训练用 12 个月、测试用 6 个月。
df_tmp = (
df_receipt
.group_by(
(pl.col.sales_ymd // 100).alias('sales_ym')
)
.agg(sum_amount=pl.col.amount.sum())
.sort(by='sales_ym')
)
group = (
df_tmp
.with_row_index()
.group_by_dynamic(
pl.col.index.cast(pl.Int32), every='6i', period='18i', offset='0i'
)
)
dfs = [df for _, df in group][:3]
dfs = [
df
.with_columns(
test_flag=(pl.int_range(pl.len()) > 12).cast(pl.UInt8)
)
.select(pl.exclude('index'))
for df in dfs
]
dfs
[shape: (18, 3)
┌──────────┬────────────┬───────────┐
│ sales_ym ┆ sum_amount ┆ test_flag │
│ --- ┆ --- ┆ --- │
│ i64 ┆ i64 ┆ u8 │
╞══════════╪════════════╪═══════════╡
│ 201701 ┆ 902056 ┆ 0 │
│ 201702 ┆ 764413 ┆ 0 │
│ 201703 ┆ 962945 ┆ 0 │
│ 201704 ┆ 847566 ┆ 0 │
│ 201705 ┆ 884010 ┆ 0 │
│ … ┆ … ┆ … │
│ 201802 ┆ 864128 ┆ 1 │
│ 201803 ┆ 946588 ┆ 1 │
│ 201804 ┆ 937099 ┆ 1 │
│ 201805 ┆ 1004438 ┆ 1 │
│ 201806 ┆ 1012329 ┆ 1 │
└──────────┴────────────┴───────────┘,
shape: (18, 3)
┌──────────┬────────────┬───────────┐
│ sales_ym ┆ sum_amount ┆ test_flag │
│ --- ┆ --- ┆ --- │
│ i64 ┆ i64 ┆ u8 │
╞══════════╪════════════╪═══════════╡
│ 201707 ┆ 959205 ┆ 0 │
│ 201708 ┆ 954836 ┆ 0 │
│ 201709 ┆ 902037 ┆ 0 │
│ 201710 ┆ 905739 ┆ 0 │
│ 201711 ┆ 932157 ┆ 0 │
│ … ┆ … ┆ … │
│ 201808 ┆ 1045793 ┆ 1 │
│ 201809 ┆ 977114 ┆ 1 │
│ 201810 ┆ 1069939 ┆ 1 │
│ 201811 ┆ 967479 ┆ 1 │
│ 201812 ┆ 1016425 ┆ 1 │
└──────────┴────────────┴───────────┘,
shape: (18, 3)
┌──────────┬────────────┬───────────┐
│ sales_ym ┆ sum_amount ┆ test_flag │
│ --- ┆ --- ┆ --- │
│ i64 ┆ i64 ┆ u8 │
╞══════════╪════════════╪═══════════╡
│ 201801 ┆ 944509 ┆ 0 │
│ 201802 ┆ 864128 ┆ 0 │
│ 201803 ┆ 946588 ┆ 0 │
│ 201804 ┆ 937099 ┆ 0 │
│ 201805 ┆ 1004438 ┆ 0 │
│ … ┆ … ┆ … │
│ 201902 ┆ 959538 ┆ 1 │
│ 201903 ┆ 1093753 ┆ 1 │
│ 201904 ┆ 1044210 ┆ 1 │
│ 201905 ┆ 1111985 ┆ 1 │
│ 201906 ┆ 1089063 ┆ 1 │
└──────────┴────────────┴───────────┘]