Python Pandas:如何高效对日期区间内的月份做聚合运算
高效处理百万行区间数据的月度聚合方案
问题背景
需要对包含月份起止区间的百万行数据集,计算月度聚合指标(均值、总和、标准差等),现有两种方法但效率仍有优化空间。
数据集示例
import pandas as pd import numpy as np from dateutil.relativedelta import relativedelta df = pd.DataFrame([["2020-01-01", "2020-05-01", 200], ["2020-02-01", "2020-03-01", 100], ["2020-03-01", "2020-04-01", 350], ["2020-02-01", "2020-05-01", 500]], columns=["start", "end", "value"]) df["start"] = pd.to_datetime(df["start"]) df["end"] = pd.to_datetime(df["end"])
现有方法的局限性
- 方法1(Explode展开):小数据集高效,但百万行数据会生成大量重复行,内存占用飙升,甚至触发内存溢出。
- 方法2(循环遍历月份):避免了内存问题,但每次循环都要做全表布尔索引筛选,时间复杂度为O(M*N)(M为月份数,N为数据行数),百万行下速度极慢。
优化方案:事件流+向量化计算
利用差分数组思想,将区间聚合转化为事件的加减操作,仅需两次遍历即可完成统计量的累计,全程向量化,内存占用低、计算速度快。
步骤说明
- 将日期转换为月度周期(
pd.Period),方便处理区间边界; - 生成"起始事件"(在区间开始月份添加统计量)和"结束事件"(在区间结束的下一个月份扣除统计量);
- 合并所有事件并按月份排序,累加得到每个月份的累计统计值;
- 通过累计的count、sum、sum_of_squares推导均值、标准差等指标。
代码实现
# 1. 转换为月度周期,处理结束区间的下一个月 df["start_period"] = df["start"].dt.to_period("M") df["end_period"] = df["end"].dt.to_period("M") + 1 # 结束月份的下一个月作为扣除点 # 2. 生成起始事件和结束事件 events = [] # 起始事件:添加count、sum、sum_of_squares start_events = df[["start_period", "value"]].assign( count=1, sum_sq=df["value"] ** 2 ).rename(columns={"start_period": "period"}) events.append(start_events) # 结束事件:扣除count、sum、sum_of_squares end_events = df[["end_period", "value"]].assign( count=-1, sum_sq=-(df["value"] ** 2) ).rename(columns={"end_period": "period"}) events.append(end_events) # 3. 合并事件并累加统计量 all_events = pd.concat(events).groupby("period").sum().cumsum() # 4. 计算聚合指标 all_events["mean"] = all_events["value"] / all_events["count"] # 标准差:样本标准差,分母为count-1 all_events["std"] = np.sqrt( (all_events["sum_sq"] - (all_events["value"] ** 2) / all_events["count"]) / (all_events["count"] - 1) ) # 转换为datetime索引(可选) all_events.index = all_events.index.to_timestamp() # 保留需要的指标 result = all_events[["mean", "value", "std"]].rename(columns={"value": "sum"}) print(result)
输出结果
mean sum std period 2020-01-01 200.000000 200 NaN 2020-02-01 300.000000 600 212.132034 2020-03-01 316.666667 950 202.072594 2020-04-01 350.000000 1050 217.944947 2020-05-01 350.000000 700 212.132034
效率优势
- 时间复杂度:O(N log N)(主要来自事件排序),远低于循环方法的O(M*N);
- 内存占用:仅存储原始数据的2倍事件数据,避免了explode方法的内存膨胀;
- 完全向量化操作,利用Pandas和Numpy的底层优化,适合百万级甚至更大规模的数据集。
内容的提问来源于stack exchange,提问作者JArmunia
相关产品推荐
相关产品推荐

