Pandas滚动应用使用多个列

jpfvwuh4  于 2023-09-29  发布在  其他
关注(0)|答案(6)|浏览(180)

我尝试在多个列上使用pandas.DataFrame.rolling.apply()滚动函数。Python版本是3.7,pandas是1.0.2。

import pandas as pd

#function to calculate
def masscenter(x):
    print(x); # for debug purposes
    return 0;

#simple DF creation routine
df = pd.DataFrame( [['02:59:47.000282', 87.60, 739],
                    ['03:00:01.042391', 87.51, 10],
                    ['03:00:01.630182', 87.51, 10],
                    ['03:00:01.635150', 88.00, 792],
                    ['03:00:01.914104', 88.00, 10]], 
                   columns=['stamp', 'price','nQty'])
df['stamp'] = pd.to_datetime(df2['stamp'], format='%H:%M:%S.%f')
df.set_index('stamp', inplace=True, drop=True)

'stamp'是单调且唯一的,'price'是double且不包含NaNs,'nQty'是整数且也不包含NaNs。
所以,我需要计算滚动的“质心”,即。sum(price*nQty)/sum(nQty)
到目前为止,我尝试的是:

df.apply(masscenter, axis = 1)

masscenter被调用5次,输出如下

price     87.6
nQty     739.0
Name: 1900-01-01 02:59:47.000282, dtype: float64

它是masscenter的理想输入,因为我可以使用x[0], x[1]轻松访问pricenQty。然而,我坚持使用rolling.apply()阅读文档DataFrame.rolling()rolling.apply(),我认为在rolling()中使用'axis'和在apply中使用'raw'可以实现类似的行为。天真的方法

rol = df.rolling(window=2)
rol.apply(masscenter)

逐行打印(增加行数以达到窗口大小)

stamp
1900-01-01 02:59:47.000282    87.60
1900-01-01 03:00:01.042391    87.51
dtype: float64

然后

stamp
1900-01-01 02:59:47.000282    739.0
1900-01-01 03:00:01.042391     10.0
dtype: float64

因此,列被单独传递给masscenter(预期)。
遗憾的是,在文档中几乎没有关于'axis'的任何信息。然而下一个变种显然是

rol = df.rolling(window=2, axis = 1)
rol.apply(masscenter)

从不调用masscenter并引发ValueError in rol.apply(..)

> Length of passed values is 1, index implies 5

我承认,由于缺乏文档,我不确定'axis'参数以及它是如何工作的。这是问题的第一部分:这是怎么回事?如何正确使用“轴”?它的设计目的是什么?**
当然,之前也有答案,那就是:
How-to-apply-a-function-to-two-columns-of-pandas-dataframe
它适用于整个DataFrame,而不是滚动。
How-to-invoke-pandas-rolling-apply-with-parameters-from-multiple-column
答案建议我自己写roll函数,但对我来说,罪魁祸首与评论中的问题相同:如果需要使用偏移窗口大小(例如,'1T')的非统一时间戳?
我不喜欢从头开始重新发明轮子的主意。另外,我想使用Pandas的一切,以防止不一致的集从Pandas和'自制辊'。这个问题还有另一个答案,需要单独填充框架并计算我需要的任何东西,但它不会起作用:存储数据的大小将是巨大的。同样的想法在这里提出:
Apply-rolling-function-on-pandas-dataframe-with-multiple-arguments
Q & A在这里发布
Pandas-using-rolling-on-multiple-columns
它很好,也最接近我的问题,但同样,没有可能使用偏移窗口大小(window = '1T')。
有些问题的答案是在pandas 1.0发布之前提出的,考虑到文档可以做得更好,我希望现在可以同时滚动多个列。
问题的第二部分是:是否有可能使用pandas 1.0.x和偏移窗口大小同时滚动多列?

rggaifut

rggaifut1#

不如这样吧:

import pandas as pd

def masscenter(ser: pd.Series, df: pd.DataFrame):
    df_roll = df.loc[ser.index]
    return your_actual_masscenter(df_roll)

masscenter_output = df['price'].rolling(window=3).apply(masscenter, args=(df,))

它使用滚动逻辑通过任意列获得子集。不使用任意列本身,只使用滚动索引。这依赖于默认值raw=False,它为这些子集提供索引值。applied函数使用这些索引值从原始数据框架中获取多列切片。

yhuiod9q

yhuiod9q2#

您可以使用numpy_ext模块中的rolling_apply函数:

import numpy as np
import pandas as pd
from numpy_ext import rolling_apply

def masscenter(price, nQty):
    return np.sum(price * nQty) / np.sum(nQty)

df = pd.DataFrame( [['02:59:47.000282', 87.60, 739],
                    ['03:00:01.042391', 87.51, 10],
                    ['03:00:01.630182', 87.51, 10],
                    ['03:00:01.635150', 88.00, 792],
                    ['03:00:01.914104', 88.00, 10]], 
                   columns=['stamp', 'price','nQty'])
df['stamp'] = pd.to_datetime(df['stamp'], format='%H:%M:%S.%f')
df.set_index('stamp', inplace=True, drop=True)

window = 2
df['y'] = rolling_apply(masscenter, window, df.price.values, df.nQty.values)
print(df)

                            price  nQty          y
stamp                                             
1900-01-01 02:59:47.000282  87.60   739        NaN
1900-01-01 03:00:01.042391  87.51    10  87.598798
1900-01-01 03:00:01.630182  87.51    10  87.510000
1900-01-01 03:00:01.635150  88.00   792  87.993890
1900-01-01 03:00:01.914104  88.00    10  88.000000
n3h0vuf2

n3h0vuf23#

参考@saninstein的精彩回答。
安装numpy_ext从:https://pypi.org/project/numpy-ext/

import numpy as np
import pandas as pd
from numpy_ext import rolling_apply as rolling_apply_ext

def box_sum(a,b):
    return np.sum(a) + np.sum(b)

df = pd.DataFrame({"x": [1,2,3,4], "y": [1,2,3,4]})

window = 2
df["sum"] = rolling_apply_ext(box_sum, window , df.x.values, df.y.values)

输出量:

print(df.to_string(index=False))
 x  y  sum
 1  1  NaN
 2  2  6.0
 3  3 10.0
 4  4 14.0

注意到

  • 滚动功能是时间序列友好的。它默认总是向后看,因此6是数组中当前值和过去值的总和。
  • 在上面的示例中,将rolling_apply导入为rolling_apply_ext,因此它不可能干扰对Pandas rolling_apply的任何现有调用(感谢@LudoSchmidt的评论)。

顺便说一句,我放弃了使用Pandas。它从根本上被打破了:它处理单列的aggreagate和apply几乎没有问题,但是当试图让它处理更多的两列或更多列时,它是一个过于复杂的Rube-Goldberg机器。

3pmvbmvn

3pmvbmvn4#

要执行滚动窗口操作并访问 Dataframe 的所有列,可以将mehtod='table'传递给rolling()。范例:

import pandas as pd
import numpy as np
from numba import jit

df = pd.DataFrame({'a': [1, 2, 3, 4, 5, 6], 'b': [1, 3, 5, 7, 9, 11]})

@jit
def f(w):
    # we have access to both columns of the dataframe here
    return np.max(w), np.min(w)

df.rolling(3, method='table').apply(f, raw=True, engine='numba')

需要注意的是,method='table'需要numba引擎(pip install numba)。示例中的@jit部分不是必需的,但有助于提高性能。以上示例代码的结果将是:
| 一|B|
| --|--|
| 楠|楠|
| 楠|楠|
| 5.0 |1.0|
| 7.0 |2.0|
| 9.0 |3.0|
| 11.0 |4.0|

4c8rllxm

4c8rllxm5#

所以我发现没有办法滚动两列,但是没有内置的pandas函数。代码如下所示。

# function to find an index corresponding
# to current value minus offset value
def prevInd(series, offset, date):
    offset = to_offset(offset)
    end_date = date - offset
    end = series.index.searchsorted(end_date, side="left")
    return end

# function to find an index corresponding
# to the first value greater than current
# it is useful when one has timeseries with non-unique
# but monotonically increasing values
def nextInd(series, date):
    end = series.index.searchsorted(date, side="right")
    return end

def twoColumnsRoll(dFrame, offset, usecols, fn, columnName = 'twoColRol'):
    # find all unique indices
    uniqueIndices = dFrame.index.unique()
    numOfPoints = len(uniqueIndices)
    # prepare an output array
    moving = np.zeros(numOfPoints)
    # nameholders
    price = dFrame[usecols[0]]
    qty   = dFrame[usecols[1]]

    # iterate over unique indices
    for ii in range(numOfPoints):
        # nameholder
        pp = uniqueIndices[ii]
        # right index - value greater than current
        rInd = afta.nextInd(dFrame,pp)
        # left index - the least value that 
        # is bigger or equal than (pp - offset)
        lInd = afta.prevInd(dFrame,offset,pp)
        # call the actual calcuating function over two arrays
        moving[ii] = fn(price[lInd:rInd], qty[lInd:rInd])
    # construct and return DataFrame
    return pd.DataFrame(data=moving,index=uniqueIndices,columns=[columnName])

这段代码可以工作,但相对较慢且效率较低。我认为可以使用How to invoke pandas.rolling.apply with parameters from multiple column?中的numpy.lib.stride_tricks来加速。然而,要么做大,要么回家--我结束了用C++写一个函数和它的 Package 器。
我不想把它作为答案贴出来,因为这是一个变通方案,我没有回答我的问题的任何一部分,但它太长了,不适合评论。

r55awzrz

r55awzrz6#

这个怎么样?

ggg = pd.DataFrame({"a":[1,2,3,4,5,6,7], "b":[7,6,5,4,3,2,1]})

def my_rolling_apply2(df, fun, window):
    prepend = [None] * (window - 1)
    end = len(df) - window
    mid = map(lambda start: fun(df[start:start + window]), np.arange(0,end))
    last =  fun(df[end:])
    return [*prepend, *mid, last]

my_rolling_apply2(ggg, lambda df: (df["a"].max(), df["b"].min()), 3)

结果是:

[None, None, (3, 5), (4, 4), (5, 3), (6, 2), (7, 1)]

相关问题