在之前的内容中,我们学习了Polars库的基础,今天来看看分组聚合与窗口分析。首先我们先模拟一下电商数据import polars as plimport numpy as npfrom datetime import datetime, timedeltanp.random.seed(42)n = 10000df = pl.DataFrame({ "order_id": range(1, n+1), "user_id": np.random.randint(1000, 2000, n), "region": np.random.choice(["华北", "华东", "华南", "华西"], n), "category": np.random.choice(["手机", "电脑", "配件", "服装"], n), "amount": np.round(np.random.lognormal(5, 1, n), 2), # 对数正态,更像真实价格 "quantity": np.random.randint(1, 5, n), "order_date": [ datetime(2024, 1, 1) + timedelta(days=int(x), hours=int(y)) for x, y in zip(np.random.randint(0, 90, n), np.random.randint(0, 24, n)) ]})print(f"数据就绪:{n} 条订单记录")print(df.head(3))
根据模拟数据,我们来进行分析,查看每个区域的销售情况,还要知道谁是区域的Top销售region_report = ( df .group_by("region") .agg([ # 基础指标 pl.count().alias("订单数"), pl.sum("amount").alias("总销售额"), pl.mean("amount").alias("客单价"), # 用户指标(去重计数) pl.col("user_id").n_unique().alias("购买人数"), # 分布指标(看出水不水) pl.median("amount").alias("中位数"), pl.std("amount").alias("标准差"), pl.max("amount").alias("最大单"), # 计算指标(表达式直接算) (pl.sum("amount") / pl.col("user_id").n_unique()).alias("人均贡献") ]) .sort("总销售额", descending=True))print("区域销售体检表:")print(region_report)
这时候如果要分组内再次筛选,例如统计大额订单(大于500元)的情况:conditional_report = ( df .group_by("region") .agg([ pl.count().alias("总订单数"), # 只统计大额订单 pl.col("amount").filter(pl.col("amount") > 500).count() .alias("大额订单数"), pl.col("amount").filter(pl.col("amount") > 500).sum() .alias("大额订单金额"), # 占比(注意表达式可以互相引用) (pl.col("amount").filter(pl.col("amount") > 500).sum() / pl.sum("amount") * 100) .round(2) .alias("大额贡献占比%") ]))print("大额订单分析:")print(conditional_report)
pl.col().filter()可以在聚合里做条件筛选,不用先filter再group_by那么绕。接下来我们要看的是窗口函数,如果我们要做每个区域的Top3 订单,最笨的方式是按区域分组,取最大,再合并.... ,而用窗口函数,很容易实现df_ranked = ( df .with_columns( # dense:相同金额同排名,不跳号(1,2,2,3) pl.col("amount").rank(method="dense", descending=True) .over("region") .alias("区域排名"), # 区域订单总数(用于计算百分位) pl.col("amount").count().over("region").alias("区域订单数") ) .with_columns( # 手动计算百分位排名 (0-1之间) # 使用 average 排名来处理并列值 ( (pl.col("amount").rank(method="average", descending=True) .over("region") - 1) / (pl.col("区域订单数") - 1) ).alias("区域百分位") ) .with_columns( # 直接标记 Top 3 pl.when(pl.col("区域排名") <= 3) .then(pl.lit("🏆 区域 Top 3")) .otherwise(pl.lit("其他")) .alias("荣誉标记") ) .drop("区域订单数") # 删除临时列)print(df_ranked)

# 按用户+时间排序,看消费累积user_growth = ( df .sort(["user_id", "order_date"]) # 必须先排序! .with_columns( # 这是用户第几单? pl.col("order_id").cum_count().over("user_id") .alias("第N单"), # 累计消费金额(LTV 雏形) pl.col("amount").cum_sum().over("user_id") .alias("累计消费"), # 距离上次购买多久?(复购周期) pl.col("order_date").diff().over("user_id").dt.total_days() .alias("距上次购买天数") ))print("用户 1001 的成长轨迹:")print( user_growth .filter(pl.col("user_id") == 1001) .select(["order_date", "amount", "第N单", "累计消费", "距上次购买天数"]))
第N单看出用户购买频次,累积消费就是用户LTV,距离上次购买天数可以识别流失风险。本次内容,就先到这里,在下次我们看看时间序列的分析。