繁体   English   中英

基于另一个 dataframe 计算 dataframe 中列的值

[英]Computing values of a column in dataframe based on another dataframe

考虑以下两个数据帧(长度不等)。

df1 = pd.DataFrame({'date': ['2016-10-08', '2016-11-08','2016-12-08','2017-01-08'], 'qty': [1,8,2,4]})
df2 = pd.DataFrame({'date': ['2016-11-12', '2017-01-12'], 'factor': [2,3]})

>>> df1
         date  qty
0  2016-10-08    1
1  2016-11-08    8
2  2016-12-08    2
3  2017-01-08    4

>>> df2
         date  factor
0  2016-11-12       2
1  2017-01-12       3

我想在df1中计算一个名为factored_quantity的新列,如下所示。

选择df2中所有日期大于df1日期的因子,并将qty乘以相同的数量以到达factored_qty

所以我最终的 dataframe 看起来像

>>> df1
         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

解释:

  1. df1中的2016-10-08小于df22016-11-122017-01-12 所以乘以因子2*3*qty
  2. 但是, df1中的日期2016-12-08大于df22016-11-12并且小于2017-01-12 所以只乘以因子3*qty

我看过的大部分内容都与
1.合并两个dataframe。
2.比较两个等长的dataframe
3.比较两个不等长的dataframe

但是,当前问题与基于另一个 dataframe 的收集(因子相乘)值计算值有关 - 其中外键将不相等。

可能不是大型数据帧的最快解决方案,但它有效。 我们在满足条件的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 的解决方案。

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


较大数据帧的时间(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 中的随机因素,他们的产品可能会导致溢出,这里忽略了这一点。 仅当 df2 中的最后一个日期大于 df1 的最后一个日期时, method_dict才能正常工作,这对于计时也被忽略了。

确保您的date dtype 是有效的 datetime 否则使用pd.to_datetime 然后您可以使用pd.Series.to_numpy并在此处使用broadcasting来比较和构建 boolean 数组以用于 boolean 索引。 然后使用pd.Series.map ,使用np.prod得到整个阵列的产品。

mask = df1.date.to_numpy()[:,None] < df2.date.to_numpy() # `_.values` should be avoided instead use `_.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)
df1
        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

或者

mask = df1.date.to_numpy()[:,None] < df2.date.to_numpy()
l = [np.prod(df2.loc[idx,'factor']) for idx in mask]
     # df2.loc[idx,'factor'].prod()
df['factored_qty'] = df1.qty.mul(l)

时间分析:

# My answer
In [163]: %%timeit
     ...: mask = df1.date.to_numpy()[:,None] < df2.date.to_numpy() # `_.values` should be avoided instead use `_.to_num
     ...: py()`
     ...: it = iter(mask)
     ...: 
     ...: def mul(x):
     ...:     val = np.prod(df2.loc[next(it),'factor'])
     ...:     return x*val
     ...: df1['factored_qty'] = df1['qty'].map(mul)
     ...:
     ...:
1.31 ms ± 28.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
# Stef's answer.
In [164]: %%timeit
     ...: df1['factored_qty'] = df1.apply(lambda x: df2[df2.date>x.date].factor.cumprod().values[-1] * x.qty,axis=1)
     ...:
     ...:
3.65 ms ± 194 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

#sammy's answer
In [180]: %%timeit
     ...: d = defaultdict(list)
     ...: #iterate through data and append factors that meet criteria
     ...: 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()}
     ...: pd.DataFrame.from_dict(outcome, orient='index', columns=['qty','factored_qty']).reset_index()
     ...:
     ...:
1.49 ms ± 47.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

转换为日期时间:

df1['date'] = pd.to_datetime(df1['date'])
df2['date'] = pd.to_datetime(df2['date'])

将计算移至字典; 我想相信这样的计算在字典中会更快——这是一个假设; 希望有人进行测试并证明或揭穿这一点。

from itertools import product
from collections import defaultdict
d = defaultdict(list)
#iterate through data and append factors that meet criteria
for (date1, qty), (date2, factor) in product(df1.to_numpy(),df2.to_numpy()) : 
    if date1 <= date2 : 
        d[(date1, qty)].append(factor)
    else:
        d[(date1, qty)].append(1)

让我们看看d的内容:

print(d)

defaultdict(list,
            {(Timestamp('2016-10-08 00:00:00'), 1): [2, 3],
             (Timestamp('2016-11-08 00:00:00'), 8): [2, 3],
             (Timestamp('2016-12-08 00:00:00'), 2): [1, 3],
             (Timestamp('2017-01-08 00:00:00'), 4): [1, 3]})

得到过滤后的数据与数量的乘积:

outcome = {k:[s,np.prod((s,*v))] for (k,s),v in d.items()}

创建 dataframe:

pd.DataFrame.from_dict(outcome, orient='index', columns=['qty','factored_qty'])

           qty  factored_qty
2016-10-08  1       6
2016-11-08  8       48
2016-12-08  2       6
2017-01-08  4       12

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM