基于复杂条件为Pandas DataFrame添加列的高效实现问询
高效匹配df1与df2的F值方案
原始数据
df1的部分数据:
In [500]: df1.iloc[67:100] Out[500]: Expiry K Type close 107 2018-01-26 123.00 C 0.406250 108 2018-01-26 124.00 C 0.062500 109 2018-01-26 125.00 C 0.015625 112 2018-01-26 121.50 C 1.640625 121 2018-02-23 123.50 C 0.406250 124 2018-02-23 127.50 C 0.015625 127 2018-02-23 124.50 C 0.140625 130 2018-02-23 125.50 C 0.046875 144 2018-05-25 120.00 C 3.156250 145 2018-05-25 121.00 C 2.203125 146 2018-05-25 122.00 C 1.328125 147 2018-02-23 123.00 C 0.640625 148 2018-02-23 124.00 C 0.234375 152 2018-06-22 121.50 C 1.750000 156 2018-06-22 126.50 C 0.015625 158 2018-02-23 122.50 C 0.953125 160 2018-03-23 123.25 P 0.484375 161 2018-03-23 123.50 P 0.625000 162 2018-03-23 123.75 P 0.796875 163 2018-03-23 127.25 P 4.125000 164 2018-03-23 127.50 P 4.375000
df2数据:
In [501]: df2 Out[501]: F Symbol Expiry 2018-03-20 12:00:00 123.125000 ZN MAR 18 2018-06-20 12:00:00 122.734375 ZN JUN 18 2018-09-19 12:00:00 122.265625 ZN SEP 18
首先,你的需求是为df1每一行,根据Expiry在(Expiry+MonthEnd(1), Expiry+MonthEnd(4))区间内匹配df2的F值。当前用apply的方法虽然能实现,但当df1数据量较大时效率会极低——因为apply本质是逐行执行Python循环,每次都要对df2做切片查询,属于O(n*m)的时间复杂度。
下面给你两种更高效的向量化方案,基于Pandas/Numpy批量操作,彻底避免逐行循环:
方案一:Numpy广播匹配(推荐,适合df2行数少的场景)
你的df2只有3行数据,用广播可以一次性完成所有匹配,速度极快:
步骤1:预处理df2,将索引转为列
df2_reset = df2.reset_index().rename(columns={'index': 'df2_date'})
步骤2:计算df1每行的区间边界
from pandas.tseries.offsets import MonthEnd df1['lower'] = df1['Expiry'] + MonthEnd(1) df1['upper'] = df1['Expiry'] + MonthEnd(4)
步骤3:用Numpy广播实现批量匹配
import numpy as np # 转换数组形状,实现广播匹配 lower = df1['lower'].values[:, np.newaxis] upper = df1['upper'].values[:, np.newaxis] df2_dates = df2_reset['df2_date'].values[np.newaxis, :] df2_F_values = df2_reset['F'].values[np.newaxis, :] # 生成匹配掩码:每个df1行对应符合区间条件的df2日期 match_mask = (df2_dates > lower) & (df2_dates < upper) # 提取匹配到的F值并赋值 df1['F'] = df2_F_values[match_mask].reshape(-1) # 清理临时列 df1.drop(['lower', 'upper'], axis=1, inplace=True)
方案二:区间分箱匹配
如果df2行数较多,可通过区间分箱的方式,先给df2的每个日期定义对应的df1 Expiry区间,再匹配F值:
步骤1:预处理df2,计算对应的Expiry区间
df2_reset = df2.reset_index().rename(columns={'index': 'df2_date'}) df2_reset['expiry_low'] = df2_reset['df2_date'] - MonthEnd(4) df2_reset['expiry_high'] = df2_reset['df2_date'] - MonthEnd(1)
步骤2:用pd.cut给df1的Expiry分配对应的F值
# 创建区间索引(左开右开,匹配你的条件) interval_index = pd.IntervalIndex.from_arrays(df2_reset['expiry_low'], df2_reset['expiry_high'], closed='neither') # 匹配并转换为数值类型 df1['F'] = pd.cut(df1['Expiry'], bins=interval_index, labels=df2_reset['F']).astype(float)
效率对比
- 你的
apply方法:10万行df1需执行10万次df2查询,耗时秒级甚至更长; - 向量化方案:同样10万行数据,耗时仅几十毫秒,效率提升几十到上百倍。
两种方案都能得到你预期的输出结果。
内容的提问来源于stack exchange,提问作者steff
相关产品推荐
相关产品推荐

