如何为Pandas的df2新增列,值为df1指定年份区间的weight均值?
问题描述
我有两个形状不同的Pandas DataFrame:df1和df2。
df1的模拟数据:
year,weight 2023,0.392 2022,0.382 2021,0.858 2020,0.135
df2的模拟数据:
start_year,end_year 2022,2023 2021,2023 2020,2023 2020,2020 2021,2022 2022,2022
需求是给df2新增一列weight,值为df1中year落在对应行start_year和end_year区间内的weight的平均值。比如第一行结果是(0.382 + 0.392)/2=0.387,最终目标df2如下:
start_year,end_year,weight 2022,2023,0.387 2021,2023,0.544 2020,2023,0.44175 2020,2020,0.135 2021,2022,0.625 2022,2022,0.382
我尝试了以下代码:
df2["weight"] = df1[df1["year"].between(df2["start_year"], df2["end_year"], inclusive="both")]["weight"].mean()
但报错:
ValueError: Can only compare identically-labeled Series objects
我推测这是因为Pandas默认按行比较,而非笛卡尔积匹配。已知循环可以实现,但df1约110行,df2最多数万行,循环效率太低,希望用矢量化操作解决。
解决方案
方法1:利用Numpy广播创建布尔矩阵计算均值
这种方法通过广播将df1的year与df2的区间进行比较,生成布尔矩阵后计算每行的均值,完全矢量化,效率很高:
import pandas as pd import numpy as np # 构造示例数据 df1 = pd.DataFrame({'year': [2023,2022,2021,2020], 'weight': [0.392,0.382,0.858,0.135]}) df2 = pd.DataFrame({'start_year': [2022,2021,2020,2020,2021,2022], 'end_year': [2023,2023,2023,2020,2022,2022]}) # 广播生成布尔矩阵:df2的每一行对应df1所有year是否在区间内 mask = (df1['year'].values[:, None] >= df2['start_year'].values) & (df1['year'].values[:, None] <= df2['end_year'].values) # 计算每行的平均权重 df2['weight'] = (df1['weight'].values[:, None] * mask).sum(axis=0) / mask.sum(axis=0)
方法2:使用Pandas的merge+groupby(适合数据量适中的场景)
先做笛卡尔积合并,再筛选符合区间的行,最后分组求均值:
# 笛卡尔积合并 merged = df1.assign(key=1).merge(df2.assign(key=1), on='key').drop('key', axis=1) # 筛选year在区间内的行 filtered = merged[(merged['year'] >= merged['start_year']) & (merged['year'] <= merged['end_year'])] # 分组求均值并合并回df2 df2 = df2.merge(filtered.groupby(['start_year', 'end_year'])['weight'].mean().reset_index(), on=['start_year', 'end_year'], how='left')
方法3:使用apply结合矢量化判断(比纯循环高效,但不如前两种)
如果觉得广播的矩阵占用内存,可以用apply逐行处理,但内部用矢量化判断:
def get_avg_weight(row): mask = df1['year'].between(row['start_year'], row['end_year'], inclusive='both') return df1.loc[mask, 'weight'].mean() df2['weight'] = df2.apply(get_avg_weight, axis=1)
方法对比
- 方法1:纯矢量化,速度最快,适合df2行数多的场景(数万行完全没问题),内存占用仅为110*N的矩阵,压力极小。
- 方法2:笛卡尔积会生成大量中间数据,当df2是数万行时,中间表会有110*N行,内存占用较高,速度不如方法1。
- 方法3:比纯循环快,但本质还是逐行处理,速度不如方法1,适合内存紧张的情况。
内容的提问来源于stack exchange,提问作者jso_
相关产品推荐
相关产品推荐

