对于大型数据帧,可能不是最快的解决方案,但它确实有效。我们在df2的所有满足条件的行上使用prod。
df1['factored_qty'] = df1.apply(lambda x: df2[df2.date>x.date].factor.prod() * x.qty,axis=1)
结果:
date qty factored_qty
0 2016-10-08 1 6
1 2016-11-08 8 48
2 2016-12-08 2 6
3 2017-01-08 4 12
更新
对于更大的数据帧,我们可以使用
merge_asof。我们计算相反的
cumprod,即从最后一行到第一行。不幸的是,如果 df2 中的最后一个日期小于 df1 中的最后一个日期,它会变得有点复杂,因为在这种情况下,我们必须向 df2 添加一个标记(因子 1 的 df1 的最大日期)。
这种方法明显快于 Ch3steR 和 sammywemmy 的解决方案。
df3 = pd.merge_asof(df1.assign(date=pd.to_datetime(df1.date)),
df2.assign(date=pd.to_datetime(df2.date), factor=df2.factor.iloc[::-1].cumprod().iloc[::-1]) if(df1.date.max()<df2.date.max())
else df2.assign(date=pd.to_datetime(df2.date), factor=df2.factor.iloc[::-1].cumprod().iloc[::-1]).append({'date': pd.to_datetime(df1.date.max()), 'factor': 1}, ignore_index=True),
'date',
direction='forward')
df3.factor *= df3.qty
df3.rename(columns={'factor': 'factored_qty'}, inplace=True)
较大数据帧的时间(df1 200 行,df2 100 行
import pandas as pd
import numpy as np
n = 100
np.random.seed(0)
df1_ = pd.DataFrame({'date': [(pd.Timestamp('2020-06-01') - pd.Timedelta(x,'D')).strftime('%Y-%m-%d') for x in np.sort(np.random.choice(200*n, 2*n, False))[::-1]],
'qty': np.random.randint(1, 20, 2*n)})
df2_ = pd.DataFrame({'date': [(pd.Timestamp('2020-06-01') - pd.Timedelta(x,'D')).strftime('%Y-%m-%d') for x in np.sort(np.random.choice(100*n, n, False))[::-1]],
'factor': np.random.randint(1, 10, n)})
def setup():
global df1, df2
df1 = df1_.copy(True)
df2 = df2_.copy(True)
def method_apply():
df1['factored_qty'] = df1.apply(lambda x: df2[df2.date>x.date].factor.prod() * x.qty,axis=1)
return df1
def method_merge():
df3 = pd.merge_asof(df1.assign(date=pd.to_datetime(df1.date)),
df2.assign(date=pd.to_datetime(df2.date), factor=df2.factor.iloc[::-1].cumprod().iloc[::-1]) if(df1.date.max()<df2.date.max())
else df2.assign(date=pd.to_datetime(df2.date), factor=df2.factor.iloc[::-1].cumprod().iloc[::-1]).append({'date': pd.to_datetime(df1.date.max()), 'factor': 1}, ignore_index=True),
'date',
direction='forward')
df3.factor *= df3.qty
df3.rename(columns={'factor': 'factored_qty'}, inplace=True)
return df3
from itertools import product
from collections import defaultdict
def method_dict():
d = defaultdict(list)
df1['date'] = pd.to_datetime(df1['date'])
df2['date'] = pd.to_datetime(df2['date'])
for (date1, qty), (date2, factor) in product(df1.to_numpy(),df2.to_numpy()) :
if date1 < date2 :
d[(date1, qty)].append(factor)
outcome = {k:[s,np.prod((s,*v))] for (k,s),v in d.items()}
return pd.DataFrame.from_dict(outcome, orient='index', columns=['qty','factored_qty']).reset_index()
def method_numpy():
mask = df1.date.to_numpy()[:,None] < df2.date.to_numpy()
it = iter(mask)
def mul(x):
val = np.prod(df2.loc[next(it),'factor'])
return x*val
df1['factored_qty'] = df1['qty'].map(mul)
return df1
结果:
method_apply 220 ms ± 5.99 ms per loop
method_numpy 86.7 ms ± 2.51 ms per loop
method_dict 80.7 ms ± 436 µs per loop
method_merge 8.87 ms ± 68.1 µs per loop
根据 df2 中的随机因素,他们的产品可能会导致溢出,这里忽略了这一点。 method_dict 仅在 df2 中的最后一个日期大于 df1 的最后一个日期时才能正常工作,这对于计时也被忽略了。