python - 将 Python 序列(时间序列/数组)拆分为重叠的子序列

标签 python performance numpy pandas time-series

我需要提取给定窗口的时间序列/数组的所有子序列。例如:

>>> ts = pd.Series([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> window = 3
>>> subsequences(ts, window)
array([[0, 1, 2],
       [1, 2, 3],
       [2, 3, 4],
       [3, 4, 5],
       [4, 5, 6],
       [5, 6, 7],
       [5, 7, 8],
       [6, 8, 9]])

遍历序列的朴素方法当然是昂贵的,例如:

def subsequences(ts, window):
    res = []
    for i in range(ts.size - window + 1):
        subts = ts[i:i+window]
        subts.reset_index(drop=True, inplace=True)
        subts.name = None
        res.append(subts)
    return pd.DataFrame(res)

我找到了一种更好的方法,即复制序列,将其移动一个不同的值直到窗口被覆盖,然后使用 reshape 拆分不同的序列。性能提高了大约 100 倍,因为 for 循环遍历窗口大小,而不是序列大小:

def subsequences(ts, window):
    res = []
    for i in range(window):
        subts = ts.shift(-i)[:-(ts.size%window)].reshape((ts.size // window, window))
        res.append(subts)
    return pd.DataFrame(np.concatenate(res, axis=0))

我看到 pandas 在 pandas.stats.moment 模块中包含了几个滚动函数,我猜它们所做的在某种程度上类似于子序列问题。该模块中的任何地方或 pandas 中的其他任何地方都可以提高效率吗?

谢谢!

更新(解决方案):

根据@elyase 的回答,对于这个特定的案例,有一个稍微简单的实现,让我在这里写下来,并解释它在做什么:

def subsequences(ts, window):
    shape = (ts.size - window + 1, window)
    strides = ts.strides * 2
    return np.lib.stride_tricks.as_strided(ts, shape=shape, strides=strides)

给定一维 numpy 数组,我们首先计算结果数组的形状。我们将从数组的每个位置开始一行,只有最后几个元素除外,从它们开始,旁边没有足够的元素来完成窗口。

参见本说明中的第一个示例,我们如何从 6 开始的最后一个数字,因为从 7 开始,我们无法创建包含三个元素的窗口。因此,行数是大小减去窗口加一。列数就是窗口。

接下来,棘手的部分是告诉如何用我们刚刚定义的形状填充结果数组。

我们认为第一个元素将是第一个。然后我们需要指定两个值(在两个整数的元组中作为参数 strides 的参数)。这些值指定了我们需要在原始数组(一维数组)中执行的步骤以填充第二个数组(二维数组)。

考虑一个不同的例子,我们想要实现 np.reshape 函数,从 9 元素的一维数组到 3x3 数组。第一个元素填充第一个位置,然后它右边的元素将成为一维数组中的下一个元素,因此我们移动 1 步。然后,棘手的部分,要填充第二行的第一个元素,我们应该执行 3 个步骤,从 0 到 4,请参见:

>>> original = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8])
>>> new = array([[0, 1, 2],
                 [3, 4, 5],
                 [6, 7, 8])]

因此,要 reshape ,我们对两个维度的步骤是(1, 3)。对于我们的例子,它存在重叠,实际上更简单。当我们向右移动以填充结果数组时,我们从一维数组中的下一个位置开始,当我们向右移动时,我们再次获得一维数组中的下一个元素,即 1 步。因此,步骤将是 (1, 1)

只有最后一件事需要注意。 strides 参数不接受我们使用的“步数”,而是内存中的字节数。要了解它们,我们可以使用 numpy 数组的 strides 方法。它返回一个包含步幅(以字节为单位的步长)的元组,每个维度都有一个元素。在我们的例子中,我们得到一个 1 元素的元组,我们需要它两次,所以我们有 * 2

np.lib.stride_tricks.as_strided 函数使用描述的方法执行填充, 无需复制数据,这使其非常高效。

最后,请注意,此处发布的函数采用一维输入数组(不同于以 1 个元素作为行或列的二维数组)。查看输入数组的形状方法,您应该得到类似于 (N, ) 而不是 (N, 1) 的内容。这种方法在后者上会失败。请注意,@elyase 发布的方法处理二维输入数组(这就是此版本稍微简单的原因)。

最佳答案

这比我机器上的快速版本快 34 倍:

def rolling_window(a, window):
    shape = a.shape[:-1] + (a.shape[-1] - window + 1, window)
    strides = a.strides + (a.strides[-1],)
    return np.lib.stride_tricks.as_strided(a, shape=shape, strides=strides)

>>> rolling_window(ts.values, 3)
array([[0, 1, 2],
      [1, 2, 3],
      [2, 3, 4],
      [3, 4, 5],
      [4, 5, 6],
      [5, 6, 7],
      [6, 7, 8],
      [7, 8, 9]])

归功于 Erik Rigtorp .

关于python - 将 Python 序列(时间序列/数组)拆分为重叠的子序列,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27852343/

相关文章:

python - 使用原始字符串运行子进程

c++ - 在一个大文件上进行许多小的盲写的最快方法(在 C++ 中)?

python - Conda 3.9 不能在 macOS ("Reason: Image not found"上使用 numpy )?

python - 如何在 Python 中查找相交索引和值?

python - python3.5中无法导入Kicad的Pcbnew

python - 类型错误 : 'instancemethod' object has no attribute '__getitem__' in Celery

python - 使用 plt.savefig 保存图像,但图像都是白色的

python - 什么时候更快的 python ?

javascript - Safari 和 Firefox 上的视差/translate3d 性能问题?

python - 比较 Pandas Dataframe 的列名