【问题标题】:Computing values of a column in dataframe based on another dataframe基于另一个数据帧计算数据帧中列的值
【发布时间】:2020-06-15 07:52:13
【问题描述】:

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

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

所以我的最终数据框看起来像

>>> 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 小于 df2 的 2016-11-12 和 2017-01-12。所以乘以因子2*3*qty
  2. 但是,df1 中的日期 2016-12-08 大于 2016-11-12 且小于 df2 的 2017-01-12。所以只乘以因子3*qty

我看过的大部分内容都与
有关 1.合并两个数据框。
2.比较两个长度相等的数据帧
3.比较两个不等长的数据帧

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

【问题讨论】:

  • 如果 df1 > df2 中的日期会发生什么?
  • 默认因子是 1。所以它会乘以 1

标签: python pandas dataframe


【解决方案1】:

确保您的date dtype 是有效的日期时间,否则使用pd.to_datetime。然后你可以使用pd.Series.to_numpy和这里使用broadcasting来比较和构建一个用于布尔索引的布尔数组。然后使用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)

【讨论】:

    【解决方案2】:

    对于大型数据帧,可能不是最快的解决方案,但它确实有效。我们在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 的最后一个日期时才能正常工作,这对于计时也被忽略了。

    【讨论】:

      【解决方案3】:

      转换为日期时间:

      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()}
      

      创建数据框:

      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
      

      【讨论】:

      • 添加了对您的答案的时间分析。它花了 1.49 毫秒,stef 的 3.65 毫秒和我的 1.31 毫秒。不错的答案虽然 +1。
      • 感谢您的时间...这是有用的信息。
      • 谢谢,我在最后添加了_.reset_index,以便公平比较。由于 OP 的输出也有 3 列。
      • 在一般情况下,此方法似乎存在错误,例如 n = 100 np.random.seed(1) 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)}) 此方法返回 194 而不是 200 行。
      • 此错误视实际数据而定:df2的最后日期小于df1的最后日期。
      猜你喜欢
      • 1970-01-01
      • 2018-06-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-09
      • 2020-09-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多