【问题标题】:Pandas how to vectorize a calculation that relies on previous rowsPandas 如何对依赖于前一行的计算进行矢量化
【发布时间】:2022-09-27 09:49:21
【问题描述】:

我是 pandas 的新手,正在尝试将指标从 pine 脚本迁移到 python。我有一个计算依赖于动态计算的前一行值来获取当前行的值。我只能使用 for 循环来做到这一点,并且还没有找到使用 numpy 或 dataframe.apply 来做到这一点的好方法。问题是这个计算运行得非常慢,太慢了,无法用于我的目的。仅 21951 行 14 秒。

有谁知道如何在熊猫中以更有效的方式做到这一点?当我构建其他指标时,弄清楚这一点肯定会对我有所帮助,因为大多数指标都依赖于先前的行值。

数据框如下所示:


\"\"\"
//
// @author LazyBear 
// List of all my indicators: 
// https://docs.google.com/document/d/15AGCufJZ8CIUvwFJ9W-IKns88gkWOKBCvByMEvm5MLo/edit?usp=sharing
// 
study(title=\"Coral Trend Indicator [LazyBear]\", shorttitle=\"CTI_LB\", overlay=true)
src=close
sm =input(21, title=\"Smoothing Period\")
cd = input(0.4, title=\"Constant D\")
ebc=input(false, title=\"Color Bars\")
ribm=input(false, title=\"Ribbon Mode\")
\"\"\"

# @jit(nopython=True) -- Tried this but was getting an error ==> argument 0: Cannot determine Numba type of <class \'pandas.core.frame.DataFrame\'>
def coral_trend_filter(df, sm = 21, cd = 0.4):
  new_df = df.copy()

  di = (sm - 1.0) / 2.0 + 1.0
  c1 = 2 / (di + 1.0)
  c2 = 1 - c1
  c3 = 3.0 * (cd * cd + cd * cd * cd)
  c4 = -3.0 * (2.0 * cd * cd + cd + cd * cd * cd)
  c5 = 3.0 * cd + 1.0 + cd * cd * cd + 3.0 * cd * cd

  new_df[\'i1\'] = 0
  new_df[\'i2\'] = 0
  new_df[\'i3\'] = 0
  new_df[\'i4\'] = 0
  new_df[\'i5\'] = 0
  new_df[\'i6\'] = 0

  for i in range(1, len(new_df)):
    new_df.loc[i, \'i1\'] = c1*new_df.loc[i, \'close\'] + c2*new_df.loc[i - 1, \'i1\']
    new_df.loc[i, \'i2\'] = c1*new_df.loc[i, \'i1\'] + c2*new_df.loc[i - 1, \'i2\']
    new_df.loc[i, \'i3\'] = c1*new_df.loc[i, \'i2\'] + c2*new_df.loc[i - 1, \'i3\']
    new_df.loc[i, \'i4\'] = c1*new_df.loc[i, \'i3\'] + c2*new_df.loc[i - 1, \'i4\']
    new_df.loc[i, \'i5\'] = c1*new_df.loc[i, \'i4\'] + c2*new_df.loc[i - 1, \'i5\']
    new_df.loc[i, \'i6\'] = c1*new_df.loc[i, \'i5\'] + c2*new_df.loc[i - 1, \'i6\']

  new_df[\'cif\'] = -cd*cd*cd*new_df[\'i6\'] + c3*new_df[\'i5\'] + c4*new_df[\'i4\'] + c5*new_df[\'i3\']
  new_df.dropna(inplace=True)
  
  # trend direction
  new_df[\'cifd\'] = 0

  # trend direction color
  new_df[\'cifd\'] = \'blue\'
  
  new_df[\'cifd\'] = np.where(new_df[\'cif\'] < new_df[\'cif\'].shift(-1), 1, -1)
  new_df[\'cifc\'] = np.where(new_df[\'cifd\'] == 1, \'green\', \'red\')


  new_df.drop(columns=[\'i1\', \'i2\', \'i3\', \'i4\', \'i5\', \'i6\'], inplace=True)

  return new_df

df = coral_trend_filter(data_frame)

评论回复: 一个建议是使用 shift。由于在每次迭代中都会更新每行计算,因此这不起作用。移位存储初始值并且不更新移位的列,因此计算值是错误的。请参阅此屏幕截图,该屏幕截图与 cif 列中的原始屏幕不匹配。另请注意,我留在 shift_i1 以显示列保持为 0,这对于计算是不正确的。

更新: 通过更改为使用.at 而不是.loc,我获得了明显更好的性能。我的问题可能是我在这种类型的处理中使用了错误的访问器。

    标签: python pandas dataframe numpy


    【解决方案1】:

    编辑:由于问题的连续性,看起来这种方法不起作用。留给后人。

    像使用 for 循环一样遍历 dataframe 从来都不是一件好事。 Pandas 最终只是Numpy 的包装,所以最好弄清楚如何进行向量化数组操作。基本上总有办法。

    对于您的情况,我会考虑使用 pd.DataFrame.shift 在同一行中获取您的 i - 1 值,然后将 apply (或不使用 - 可能实际上不是)与该新值一起使用。

    像这样的东西(对于你的前几点):

    new_df["shifted_i1"] = new_df["i1"].shift(periods=1)
    new_df["i1"] = c1 * new_df["close"] + c2 * new_df["shifted_i1"]
    
    new_df["shifted_i2"] = new_df["i2"].shift(periods=1)
    new_df["i2"] = c1 * new_df["i1"] + c2 * new_df["shifted_i2"])
    
    new_df["shifted_i3"] = new_df["i3"].shift(periods=1)
    new_df["i3"] = c1 * new_df["i2"] + c2 * new_df["shifted_i3"])
    
    ...
    

    完成此操作后,您可以从数据框中删除移位的列:new_df.drop(columns=["shifted_i1", "shifted_i2", "shifted_i3"], inplace=True)

    【讨论】:

    • 是的,我认为每一行都依赖于更新的前一行,因此您可以提前移动值。所以具体来说,我不认为这些是等效的python new_df.loc[i, 'i1'] = c1*new_df.loc[i, 'close'] + c2*new_df.loc[i - 1, 'i1'] ===== new_df["shifted_i1"] = new_df["i1"].shift(periods=1) new_df["i1"] = c1 * new_df["close"] + c2 * new_df["shifted_i1"] (抱歉,格式化无法让新行在该代码 sn-p 中工作)
    • 这些不一样,因为如果不对整个数据帧进行计算,您就无法提前知道 i1 。因此,如果您最初将 i1 转移到该新列中,它只会在整个数据表中具有初始值,而不是在处理行时进行更新。如果您有一个有助于理解您建议的方法的工作示例,我可能会弄错。谢谢!
    • 只需在上一次计算完成后进行每个班次,而不是在顶部进行所有班次。我将编辑我的答案以显示这种方式。
    • 你也可能根本不需要申请。
    • numpy/pandas 中的“向量化”本质上是一个并行操作,一次对所有行执行相同的操作(是的,编译代码中有一个循环,但您不在乎)。但是你有一个串行操作;顺序很重要。通过处理数组版本,您可能会获得更快的速度 - 前提是不需要数据帧索引。
    【解决方案2】:

    看起来矢量化仅在可以根据@hpaulj 的评论拆分和并行处理计算时才有用。我通过转换为数组并对数组执行循环,然后将结果保存回 DataFrame 解决了速度问题。这是代码,希望它可以帮助其他人

    """
    //
    // @author LazyBear 
    // List of all my indicators: 
    // https://docs.google.com/document/d/15AGCufJZ8CIUvwFJ9W-IKns88gkWOKBCvByMEvm5MLo/edit?usp=sharing
    // 
    study(title="Coral Trend Indicator [LazyBear]", shorttitle="CTI_LB", overlay=true)
    src=close
    sm =input(21, title="Smoothing Period")
    cd = input(0.4, title="Constant D")
    ebc=input(false, title="Color Bars")
    ribm=input(false, title="Ribbon Mode")
    """
    def coral_trend_filter(df, sm = 25, cd = 0.4):
      new_df = df.copy()
    
      di = (sm - 1.0) / 2.0 + 1.0
      c1 = 2 / (di + 1.0)
      c2 = 1 - c1
      c3 = 3.0 * (cd * cd + cd * cd * cd)
      c4 = -3.0 * (2.0 * cd * cd + cd + cd * cd * cd)
      c5 = 3.0 * cd + 1.0 + cd * cd * cd + 3.0 * cd * cd
    
      new_df['i1'] = 0
      new_df['i2'] = 0
      new_df['i3'] = 0
      new_df['i4'] = 0
      new_df['i5'] = 0
      new_df['i6'] = 0
    
      close = new_df['close'].to_numpy()
      i1 = new_df['i1'].to_numpy()
      i2 = new_df['i2'].to_numpy()
      i3 = new_df['i3'].to_numpy()
      i4 = new_df['i4'].to_numpy()
      i5 = new_df['i5'].to_numpy()
      i6 = new_df['i6'].to_numpy()
    
      for i in range(1, len(close)):
        i1[i] = c1*close[i] + c2*i1[i-1]
        i2[i] = c1*i1[i] + c2*i2[i-1]
        i3[i] = c1*i2[i] + c2*i3[i-1]
        i4[i] = c1*i3[i] + c2*i4[i-1]
        i5[i] = c1*i4[i] + c2*i5[i-1]
        i6[i] = c1*i5[i] + c2*i6[i-1]
    
      new_df['i1'] = i1
      new_df['i2'] = i2
      new_df['i3'] = i3
      new_df['i4'] = i4
      new_df['i5'] = i5
      new_df['i6'] = i6
    
      new_df['cif'] = -cd*cd*cd*new_df['i6'] + c3*new_df['i5'] + c4*new_df['i4'] + c5*new_df['i3']
      new_df.dropna(inplace=True)
      
      new_df['cifd'] = 0
      new_df['cifd'] = np.where(new_df['cif'] < new_df['cif'].shift(), 1, -1)
      new_df['cifc'] = np.where(new_df['cifd'] == 1, 'green', 'red')
    
      new_df.drop(columns=['i1', 'i2', 'i3', 'i4', 'i5', 'i6'], inplace=True)
    
      return new_df
    

    【讨论】:

      【解决方案3】:

      您可以尝试使用以下内容来替换对数据框行的迭代:

      import pandas as pd
      import numpy as np
      
      # sample dataframe
      rng = np.random.default_rng(0)
      new_df = pd.DataFrame({'close': rng.integers(1, 10, 10)})
      new_df['i1'] = 0
      new_df['i2'] = 0
      
      c1 = 3
      c2 = 2
      N = len(new_df)
      
      new_df['i1'].iloc[1:] = np.convolve(c1 * new_df['close'].iloc[1:], c2**np.r_[:N - 1], mode='full')[:N - 1]
      new_df['i2'].iloc[1:] = np.convolve(c1 * new_df['i1'].iloc[1:], c2**np.r_[:N - 1], mode='full')[:N - 1]
      

      您可以通过使用新列名重复最后一行来计算列 'i3''i4' 等的值。

      【讨论】:

        猜你喜欢
        • 2020-12-25
        • 2021-11-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-05-23
        • 1970-01-01
        • 2019-11-24
        相关资源
        最近更新 更多