数据科学 100 道练习题 81~90

数据科学 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())
)
shape: (1, 6)
product_cdcategory_major_cdcategory_medium_cdcategory_small_cdunit_priceunit_cost
u32u32u32u32u32u32
000000

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())
)
shape: (1, 6)
product_cdcategory_major_cdcategory_medium_cdcategory_small_cdunit_priceunit_cost
u32u32u32u32u32u32
000000

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())
)
shape: (1, 6)
product_cdcategory_major_cdcategory_medium_cdcategory_small_cdunit_priceunit_cost
u32u32u32u32u32u32
000000

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()
)
shape: (5, 4)
customer_idsales_amount_allsales_amount_2019sales_rate
stri64i64f64
"CS031415000172"508829710.583923
"CS015414000103"31228740.279949
"CS011215000048"34442480.072009
"CS029415000023"516737670.72905
"CS035415000029"750458230.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()
shape: (5, 13)
customer_idcustomer_namegender_cdgenderbirth_dayagepostal_cdaddressapplication_store_cdapplication_datestatus_cdlongitudelatitude
strstrstrstrstri64strstrstri64strf64f64
"CS021313000114""大野 あや子""1""女性""1981-04-29"37"259-1113""神奈川県伊勢原市粟窪**********""S14021"20150905"0-00000000-0"139.3177935.41358
"CS037613000071""六角 雅彦""9""不明""1952-04-01"66"136-0076""東京都江東区南砂**********""S13037"20150414"0-00000000-0"139.8350235.67193
"CS031415000172""宇多田 貴美子""1""女性""1976-10-04"42"151-0053""東京都渋谷区代々木**********""S13031"20150529"D-20100325-C"139.6896535.67374
"CS028811000001""堀井 かおり""1""女性""1933-03-27"86"245-0016""神奈川県横浜市泉区和泉町**********""S14028"20160115"0-00000000-0"139.483635.39125
"CS001215000145""田崎 美紀""1""女性""1995-03-29"24"144-0055""東京都大田区仲六郷**********""S13001"20170605"6-20090929-2"139.7077535.54084

P-086#

对 085 创建的带经纬度的顾客数据,以会员申请门店代码(application_store_cd)为键与门店数据(df_store)连接。然后用申请门店的纬度(latitude)、经度(longitude)与顾客住址(address)的经纬度,求申请门店与顾客住址之间的距离(单位:km),并连同顾客 ID(customer_id)、顾客住址(address)、门店住址(address)一起显示。计算可用下面的简化公式,也可以使用其他精度更高的库。结果显示 10 条。

\[\begin{split} \mbox{纬度(弧度)}:\phi \\ \mbox{经度(弧度)}:\lambda \\ \mbox{距离}L = 6371 * \arccos(\sin \phi_1 * \sin \phi_2 + \cos \phi_1 * \cos \phi_2 * \cos(\lambda_1 − \lambda_2)) \end{split}\]
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()
)
shape: (5, 4)
customer_idcustomer_addressstore_addressdistance
strstrstrf64
"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()
shape: (5, 4)
customer_namepostal_cdcustomer_idamount
strstrstri64
"石丸 愛梨""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()
shape: (5, 12)
customer_idcustomer_namegender_cdgenderbirth_dayagepostal_cdaddressapplication_store_cdapplication_datestatus_cdintegration_id
strstrstrstrstri64strstrstri64strstr
"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         │
 └──────────┴────────────┴───────────┘]