【问题标题】:How do pandas Rolling objects work?熊猫滚动物体如何工作?
【发布时间】:2017-12-28 11:31:35
【问题描述】:

编辑:我浓缩了这个问题,因为它可能太复杂而无法开始。问题的重点在下面以粗体显示。

想了解更多关于使用DataFrame.rollingSeries.rolling时实际创建的对象:

print(type(df.rolling))
<class 'pandas.core.window.Rolling'>

一些背景知识:考虑np.as_strided 的常用替代方案。这段代码 sn-p 本身并不重要,但它的结果是我提出这个问题的参考点。

def rwindows(a, window):
    if a.ndim == 1:
        a = a.reshape(-1, 1)
    shape = a.shape[0] - window + 1, window, a.shape[-1]
    strides = (a.strides[0],) + a.strides
    windows = np.lib.stride_tricks.as_strided(a, shape=shape, strides=strides)
    return np.squeeze(windows)

这里 rwindows 将采用 1d 或 2d ndarray 并构建等于指定窗口大小的滚动“块”(如下所示)。 .rolling 对象与下面的ndarray 输出相比如何? 它是一个迭代器,为每个块存储了某些属性吗?还是完全不同的东西?我尝试过使用属性/方法(例如__dict___get_index())在对象上使用制表符完成,但它们并没有告诉我太多。我还在 pandas 中看到了 _create_blocks 方法——它与 strided 方法有什么相似之处吗?

# as_strided version

a = np.arange(5)
print(rwindows(a, 3))           # 1d input
[[0 1 2]
 [1 2 3]
 [2 3 4]]

b = np.arange(10).reshape(5,2)
print(rwindows(b, 4))           # 2d input
[[[0 1]
  [2 3]
  [4 5]
  [6 7]]

 [[2 3]
  [4 5]
  [6 7]
  [8 9]]]

第 2 部分,额外学分

使用上面的 NumPy 方法(OLS 实现here)是必要的,因为func 必须包含在pandas.core.window.Rolling.apply

从 ndarray 输入中生成单个值 *args 和 **kwargs 是 传递给函数

所以参数不能是另一个滚动对象。即

def prod(a, b):
    return a * b
df.rolling(3).apply(prod, args=((df + 2).rolling(3),))
-----------------------------------------------------------------------
...
TypeError: unsupported operand type(s) for *: 'float' and 'Rolling'

所以这真的是我上面的问题的根源。为什么传递的函数必须使用 NumPy 数组并产生单个标量值,这与 .rolling 对象的布局有什么关系?

【问题讨论】:

  • 我认为这个问题太宽泛了,应该更具体一些,不要分成多个问题

标签: python pandas numpy dataframe cython


【解决方案1】:

我建议您查看源代码,以便深入了解滚动的作用。我特别建议您查看generic.pywindow.py 中的rolling 函数。从那里您可以查看Window class,如果您指定窗口类型或默认Rolling class,则使用该Window class。最后一个继承自_Rolling_and_Expanding,最终继承自_Rolling_Window

也就是说,我会给我两分钱:Pandas 的整个滚动机制依赖于 numpy 函数 apply_along_axis。特别是它在熊猫中使用here。它与windows.pyx cython 模块结合使用。在您的系列中,出现聚合滚动窗口。对于典型的聚合函数,它可以有效地为您处理它们,但对于自定义函数(使用apply()),它在windows.pyx 中使用roll_generic()

pandas 中的滚动功能独立地对 pandas 数据框列进行操作。它不是python iterator,并且是延迟加载的,这意味着在您对其应用聚合函数之前不会计算任何内容。实际应用数据滚动窗口的函数直到聚合完成之前才会使用。

混淆的来源可能是您将滚动对象视为数据框。 (您在上一个代码 sn-p 中将滚动对象命名为 df)。真的不是。它是一个对象,可以通过在它所包含的窗口逻辑上应用聚合来生成数据帧。

您提供的 lambda 应用于新数据框的每个单元格。它在旧数据框中向后(沿每一列)打开一个窗口,并将其聚合到新数据框中的一个单元格中。聚合可以是 summean、您自定义的东西等,在某个窗口大小(比如 3)上。以下是一些示例:

a = np.arange(5)
df = pd.DataFrame(a, columns=['a'])
df.rolling(3).mean().dropna()

...也可以通过以下方式完成:

df.rolling(3).apply(np.mean).dropna()

... 并产生:

     a
2  3.0
3  6.0
4  9.0

(第一列是索引值,这里可以忽略,下一个例子。)

请注意我们如何提供现有的 numpy 聚合函数。这就是想法。我们应该能够提供我们想要的任何东西,只要它符合聚合函数的功能,即,获取一个值向量并从中产生一个值。这是我们创建自定义聚合函数的另一个例子,在本例中是窗口的 L2 范数:

df.rolling(3).apply(lambda x: np.sqrt(x.dot(x))).dropna()

如果您不熟悉 lambda 函数,这与以下内容相同:

def euclidean_dist(x):
    return np.sqrt(x.dot(x))

df.rolling(3).apply(euclidean_dist).dropna()

...屈服:

          a
2  2.236068
3  3.741657
4  5.385165

为了确保,我们可以手动检查np.sqrt(0**2 + 1**2 + 2**2) 确实是2.236068

[在您的原始编辑中] 最后一个代码 sn-p 中,您的代码可能比您预期的提前失败。在调用df.apply(...) 之前失败您正尝试将名为df 的滚动对象添加到数字2,然后再传递给df.apply(...)。滚动对象不是您对其进行操作的对象。您提供的聚合函数通常也不符合聚合函数。 a 是一个包含窗口值的列表,b 将是您传入的常量额外参数。如果您愿意,它可以是滚动对象,但通常不会是您想要做的事情.为了更清楚起见,这里有一些与您在原始编辑中所做的类似但有效的操作:

a = np.arange(8)
df = pd.DataFrame(a, columns=['a'])
n = 4
rol = df.rolling(n)

def prod(window_list, constant_rol):
    return window_list.dot(constant_rol.sum().dropna().head(n))

rol.apply(prod, args=(rol,)).dropna()

# [92.0, 140.0, 188.0, 236.0, 284.0]

这是一个人为的例子,但我展示它是为了说明你可以将任何你想要的东西作为常量传递,甚至是你自己使用的滚动对象。动态部分是第一个参数 a 在你的情况下或 window_list 在我的情况下。所有已定义的窗口,以单独列表的形式,都被一一传递给该函数。

根据您的后续 cmet,这可能是您正在寻找的:

import numpy as np
import pandas as pd

n = 3
a = np.arange(5)
df = pd.DataFrame(a, columns=['a'])

def keep(window, windows):
    windows.append(window.copy())
    return window[-1]

windows = list()
df['a'].rolling(n).apply(keep, args=(windows,))
df = df.tail(n)
df['a_window'] = windows

将数组/向量添加到每个滚动块,从而产生:

   a         a_window
2  2  [0.0, 1.0, 2.0]
3  3  [1.0, 2.0, 3.0]
4  4  [2.0, 3.0, 4.0]

请注意,它仅在您一次在列上执行时才有效。如果您想在将窗口存储到 keep 之前对窗口进行一些数学运算,那也可以。

也就是说,如果没有更多关于您想要实现的目标的详细信息,就很难构建一个适合您需求的示例。

如果您的最终目标是创建滞后变量的数据框,那么我会使用 shift() 来使用真实列:

import numpy as np
import pandas as pd

a = np.arange(5)

df = pd.DataFrame(a, columns=['a'])
for i in range(1,3):
    df['a-%s' % i] = df['a'].shift(i)

df.dropna()

...给予:

   a  a-1  a-2
2  2  1.0  0.0
3  3  2.0  1.0
4  4  3.0  2.0

(可能有一些更漂亮的方法,但它可以完成工作。)

关于第一个代码 sn-p 中的变量 b,请记住 pandas 中的 DataFrame 通常不会作为任意维度/对象的张量处理。您可能可以将任何您想要的东西塞入其中,但最终字符串、时间对象、整数和浮点数是预期的。这可能是 pandas 的设计者不愿意允许滚动聚合到非标量值的原因。似乎甚至不允许将简单字符串作为聚合函数的输出。

无论如何,我希望这能回答您的一些问题。如果不让我知道,我会尽力在 cmets 或更新中帮助您。


关于滚动对象的_create_blocks() 函数的最后说明。

当您使用 rollingfreq 参数时,_create_blocks() 函数会处理重新索引和分箱。

如果您将频率与周数一起使用,例如 freq=W:

import pandas as pd

a = np.arange(50)
df = pd.DataFrame(a, columns=['a'])
df.index = pd.to_datetime('2016-01-01') + pd.to_timedelta(df['a'], 'D')
blocks, obj, index = df.rolling(4, freq='W')._create_blocks(how=None)
for b in blocks:
    print(b)

...然后我们逐周获得分箱(非滚动)原始数据:

               a
a               
2016-01-03   2.0
2016-01-10   9.0
2016-01-17  16.0
2016-01-24  23.0
2016-01-31  30.0
2016-02-07  37.0
2016-02-14  44.0
2016-02-21   NaN

请注意,这不是聚合滚动的输出。这只是它工作的新块。在这之后。我们像sum 这样进行聚合并得到:

                a
a                
2016-01-03    NaN
2016-01-10    NaN
2016-01-17    NaN
2016-01-24   50.0
2016-01-31   78.0
2016-02-07  106.0
2016-02-14  134.0
2016-02-21    NaN

...通过测试总和进行检查:50 = 2 + 9 + 16 + 23。

如果您不使用freq 作为参数,它只会返回原始数据结构:

import pandas as pd
a = np.arange(5)
df = pd.DataFrame(a, columns=['a'])
blocks, obj, index = df.rolling(3)._create_blocks(how=None)

for b in blocks:
    print(b)

...产生...

            a
a            
2016-01-01  0
2016-01-02  1
2016-01-03  2
2016-01-04  3
2016-01-05  4

...用于滚动窗口聚合。

【讨论】:

  • 稍后当您有时间查看并有一些问题时,我会进行更新。关于您所做的更新的注释:它不再提前失败,这很好,但现在它在聚合函数中失败了。您现在将 (df + 2).rolling(3)(它是一个滚动对象)作为常量传递给聚合函数,然后尝试在窗口数据 (float64 ndarray) 和新滚动对象之间进行算术运算 (*)。滚动对象只是辅助对象,除了聚合之外,您不应该对它们进行操作。这是一个 df 对象工厂,如果有意义的话。
  • 有点不清楚你到底想做什么。您能否给出输入和预期输出的数据示例。给定[0, 1, 2, 3, 4]:你想产生类似[[0, 1, 2] * 2, [1, 2, 3] * 3, [2, 3, 4] * 4] 的东西吗?如果是这样,您可以通过def prod(a): return myagg(a * a[-1]) = 窗口乘以窗口的最后一个元素来实现。但正如我所说,您仍然必须在窗口上进行聚合,即将myagg 替换为 sum、mean 或其他内容。滚动的输出始终是单个值。使用sum 而不是myagg 你会得到[sum(0,2,4), sum(3,6,9), sum(8,12,16)] = [6, 18, 36]
  • 您关于在args=(rol,) 中传递rolling 对象的部分很有帮助,但不幸的是,您的大部分回答都重申了我已经知道的事情。但我最终想要得到的是为每个滚动块生成一个列向量(然后转置它)或一个行向量,而不是一个单一的标量。但看起来没有办法直接解决这个问题。
  • @BradSolomon 向量“每个滚动块”,又名。窗口,是 a 聚合函数中的参数 prod(a, b)。问问自己下一步是什么。你想用滚动产生的内部向量做什么?通常这会以某种方式聚合它。如果您只是想保留它们,那么您可以使用我的shift(...) 示例。在那里,您可以将窗口/向量保持为行,在示例中包含列 aa-1a-2
  • 是的。它确实有一个欺骗性的名字。除了在使用onon 参数时选择聚合列之外,它所做的只是在使用rolling() 的已弃用freq 参数时处理重新索引和分箱。我会在答案末尾添加注释。
猜你喜欢
  • 2017-09-19
  • 2018-07-30
  • 2021-10-17
  • 1970-01-01
  • 1970-01-01
  • 2018-06-26
  • 2017-03-30
  • 2019-04-11
  • 1970-01-01
相关资源
最近更新 更多