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

将Python序列(时间序列/数组)拆分为具有重叠的子序列,python,performance,numpy,pandas,time-series,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,

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

>>> 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)
迭代序列的简单方法当然代价高昂,例如:

>>> 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)
我找到了一个更好的方法,复制序列,将其移动一个不同的值,直到窗口被覆盖,然后用
重塑
分割不同的序列。性能大约提高了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-answer,对于这个特定的案例,有一个稍微简单的实现,让我在这里写下来,并解释它在做什么:

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开始,我们无法创建包含三个元素的窗口。因此,行数是大小减去窗口加一。列的数量只是窗口的一部分

接下来,棘手的部分是如何用我们刚刚定义的形状填充生成的数组

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

考虑一个不同的示例,其中我们要实现
np.reformate
函数,从一个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])]
因此,要重塑,我们的两个维度的步骤是
(1,3)
。对于我们的情况,如果存在重叠,它实际上更简单。当我们向右移动以填充结果数组时,我们从一维数组中的下一个位置开始,当我们向右移动时,我们再次得到一维数组中的下一个元素,即1步。因此,步骤应该是
(1,1)

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

np.lib.stride\u技巧。as\u stride
函数使用所描述的方法执行填充,而不复制数据,这使得它非常高效


最后,请注意,此处发布的函数假定为1-D输入数组(这与以1个元素作为行或列的2-D数组不同)。查看输入数组的shape方法,应该得到类似
(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]])

值得称赞的是。

值得注意的是,在处理转换后的阵列时,跨步技巧可能会产生意外的后果。它是有效的,因为它修改内存指针而不创建原始数组的副本。如果更新返回数组中的任何值,则会更改原始数组中的值,反之亦然

l = np.asarray([1,2,3,4,5,6,7,8,9])
_ = rolling_window(l, 3)
print(_)
array([[1, 2, 3],
   [2, 3, 4],
   [3, 4, 5],
   [4, 5, 6],
   [5, 6, 7],
   [6, 7, 8],
   [7, 8, 9]])

_[0,1] = 1000
print(_)
array([[   1, 1000,    3],
   [1000,    3,    4],
   [   3,    4,    5],
   [   4,    5,    6],
   [   5,    6,    7],
   [   6,    7,    8],
   [   7,    8,    9]])

# create new matrix from original array
xx = pd.DataFrame(rolling_window(l, 3))
# the updated values are still updated
print(xx)
      0     1  2
0     1  1000  3
1  1000     3  4
2     3     4  5
3     4     5  6
4     5     6  7
5     6     7  8
6     7     8  9

# change values in xx changes values in _ and l
xx.loc[0,1] = 100
print(_)
print(l)
[[  1 100   3]
 [100   3   4]
 [  3   4   5]
 [  4   5   6]
 [  5   6   7]
 [  6   7   8]
 [  7   8   9]]
[  1 100   3   4   5   6   7   8   9]

# make a dataframe copy to avoid unintended side effects
new = xx.copy()
# changing values in new won't affect l, _, or xx
xx
\u
l
中更改的任何值都会显示在其他变量中,因为它们在内存中都是相同的对象

有关更多详细信息,请参见numpy文档:

我想指出的是,它为这个问题提供了一个单一的函数,在使用Torch张量时,它的内存效率与当前的最佳解决方案一样高,但更为简单和通用(即,在使用多维度时):


主要是
展开
功能,有关详细说明,请参阅。如果您可以直接使用PyTorch张量,那么可能不需要将其转换回numpy——在这种情况下,解决方案同样具有内存效率。在我的用例中,我发现首先使用Torch张量创建子序列(以及进行其他预处理)更容易,并在需要时在这些张量上使用
.numpy()
转换为numpy。

当你说naive方法很昂贵时,我假设你已经分析了你的程序,这确实是一个瓶颈?是的,因为我需要迭代整个序列,所以计算中没有优化,而且速度很慢。对于4719个元素的序列和5个窗口,大约需要700毫秒。第二种方法,对于相同的数据,大约需要8毫秒。问题是pandas(或numpy)是否可以做到这一点而不需要迭代,这应该更快。你可能会在codereview.stackexchange.com获得更好的运气,我也会把你的时间信息放在问题中,谢谢大家!在我的机器上,您的解决方案也更快,但看起来大部分收益都是因为计算是在numpy中执行的,而不是熊猫。如果在您的解决方案中,我将返回的numpy数组转换为pandas数据帧,则增益约为10%,即