按日期区间求和合并两个DataFrame:Excel SUMIFS等效实现
嘿,我懂你要实现的就是类似Excel里SUMIFS的区间求和功能,而且因为日期区间可能不规则,常规的merge、resample确实不好用。先给你修正下原始代码里的小错误(你写了len(df.Date),但df还没定义,应该用对应DataFrame的长度),然后给你三种纯Pandas的解决方案,从简单到高效都有:
先修正原始代码的小问题
你的代码里存在几处引用未定义变量的错误,修正后可直接运行:
import pandas as pd import datetime import numpy as np # 修正df1的错误:将df.Date改为df1的长度 df1 = pd.DataFrame() df1["StartDate"] = pd.date_range(start=('2016/01/01'), end=('2017/12/31'), freq="6D") df1["EndDate"] = df1["StartDate"] + datetime.timedelta(5) df1["Value_three"] = pd.Series(np.random.randn(len(df1)) + 2) # 修正df2的错误:将df.Date改为df2的长度 df2 = pd.DataFrame() df2["Date"] = pd.date_range(start=('2016/01/01'), end=('2017/12/31'), freq="D") df2["Value_one"] = pd.Series(np.random.randn(len(df2))) df2["Value_two"] = pd.Series(np.random.randn(len(df2)) + 1)
方法一:逐行apply(逻辑简单,适合小数据量)
这个方法最直观,和Excel的SUMIFS逻辑完全对应,逐行筛选df2中符合区间的日期并求和:
# 定义区间求和函数 def calculate_interval_sum(row): # 筛选df2中Date在当前行StartDate和EndDate之间的记录 mask = (df2["Date"] >= row["StartDate"]) & (df2["Date"] <= row["EndDate"]) # 返回Value_one和Value_two的求和结果 return pd.Series([df2.loc[mask, "Value_one"].sum(), df2.loc[mask, "Value_two"].sum()]) # 将结果赋值给df1的新列 df1[["Sum_one", "Sum_two"]] = df1.apply(calculate_interval_sum, axis=1)
优点:逻辑清晰,容易理解和调试;缺点:逐行循环效率较低,适合df1行数较少的场景(比如几百行以内)。
方法二:numpy广播向量化(高效,适合大数据量)
利用numpy的广播机制把区间比较转换成矩阵操作,直接通过矩阵乘法求和,效率比apply高一个数量级:
# 将df2的日期和数值转换成numpy数组,方便广播操作 dates = df2["Date"].to_numpy() values_one = df2["Value_one"].to_numpy() values_two = df2["Value_two"].to_numpy() # 提取df1的起始、结束日期数组 starts = df1["StartDate"].to_numpy() ends = df1["EndDate"].to_numpy() # 广播生成布尔矩阵:每一行对应df1的一个区间,每一列对应df2的一个日期 mask = (dates >= starts[:, np.newaxis]) & (dates <= ends[:, np.newaxis]) # 通过矩阵乘法直接求和(布尔值会被视为0/1,相乘后求和就是符合条件的数值总和) df1["Sum_one"] = mask @ values_one df1["Sum_two"] = mask @ values_two
优点:完全向量化操作,速度极快,适合大数据量场景;缺点:如果df1和df2的行数极大(比如百万级),布尔矩阵会占用较多内存,可考虑分块处理。
方法三:利用IntervalIndex分组(可读性好,效率均衡)
把df1的日期区间转换成Pandas的Interval类型,再给df2的每个日期匹配所属区间,最后分组求和:
# 给df1创建区间列(closed="both"表示包含区间两端的日期) df1["Interval"] = pd.IntervalIndex.from_arrays(df1["StartDate"], df1["EndDate"], closed="both") # 给df2的每个日期找到对应的df1区间索引 df2["interval_idx"] = df2["Date"].apply(lambda x: df1["Interval"].get_loc(x)) # 按区间索引分组求和,再合并回df1 sum_result = df2.groupby("interval_idx")[["Value_one", "Value_two"]].sum() sum_result = sum_result.rename(columns={"Value_one": "Sum_one", "Value_two": "Sum_two"}) df1 = df1.join(sum_result, how="left")
优点:代码可读性强,效率介于apply和广播之间;注意:如果df2存在不在任何df1区间的日期,get_loc会报错,可提前检查或添加异常处理(你的场景里区间是连续覆盖的,所以没问题)。
内容的提问来源于stack exchange,提问作者SJEL
相关产品推荐
相关产品推荐

