从数组中的下n个元素有效地创建数组

时间:2018-04-23 18:57:42

标签: python arrays performance pandas

简短版本:

我正在尝试有效地创建像x这样的数组:

input = [0, 1, 2, 3, 4, 5, 6]

x = [ [0,1,2], [1,2,3], [2,3,4], [3,4,5], [4,5,6] ]

我尝试过简单的for循环,但真正的用例需要很长时间。

长版:

(扩展简短版)

我有一个400k行的长数据帧,我需要从当前迭代的元素分割成下一个n元素的数组。目前我将其分组,如下面process_data函数中所示。

基于for的简单迭代需要永远在这里(我的硬件需要2.5分钟才能具体)。我搜索了itertoolspandas文档,尝试在此搜索,但找不到任何合适的解决方案。

我目前超级耗时的实施:

class ModelInputParsing(object):
    def __init__(self, data):
        self.parsed_dataframe = data.fillna(0)

    def process_data(self, lb=50):
        self.X, self.Y = [],[]
        for i in range(len(self.parsed_dataframe)-lb):
            self.X.append(self.parsed_dataframe.iloc[i:(i+lb),-2])
            self.Y.append(self.parsed_dataframe.iloc[(i+lb),-1])
        return (np.array(self.X), np.array(self.Y))

输入数据如下所示(其中Bid是提到的input):

    Bid     Changes     Expected
0   1.20102 NaN         0.000000
1   1.20102 0.000000    0.000000
2   1.20102 0.000000    0.000042
3   1.20102 0.000000    0.000017
4   1.20102 0.000000    0.000025
5   1.20102 0.000000    0.000025
6   1.20102 0.000000    0.000100
...

输出应如下所示:

array([[  0.00000000e+00,   0.00000000e+00,   0.00000000e+00, ...,
          8.34465027e-06,  -8.34465027e-06,   0.00000000e+00],
       [  0.00000000e+00,   0.00000000e+00,   0.00000000e+00, ...,
         -8.34465027e-06,   0.00000000e+00,   3.33786011e-05],
       [  0.00000000e+00,   0.00000000e+00,   0.00000000e+00, ...,
          0.00000000e+00,   3.33786011e-05,   0.00000000e+00],
       ..., 
       [  0.00000000e+00,   8.34465027e-06,   1.66893005e-05, ...,
         -8.34465027e-06,   0.00000000e+00,   0.00000000e+00],
       [  8.34465027e-06,   1.66893005e-05,  -8.34465027e-06, ...,
          0.00000000e+00,   0.00000000e+00,   0.00000000e+00],
       [  1.66893005e-05,  -8.34465027e-06,   0.00000000e+00, ...,
          0.00000000e+00,   0.00000000e+00,   1.66893005e-05]], dtype=float32)
len(x)
399950

下面我提交了x[0]x[1]。这里的关键是值如何在下一个数组中移回一个位置。例如,第一个非零值从7移动到6位置(基于0的位置)。

第一个元素:

x[0]
array([  0.00000000e+00,   0.00000000e+00,   0.00000000e+00,
         0.00000000e+00,   0.00000000e+00,   0.00000000e+00,
         0.00000000e+00,  -4.16040421e-05,   2.49147415e-05,
        -8.34465027e-06,   0.00000000e+00,  -7.49230385e-05,
         ...,
         2.50339508e-05,  -8.34465027e-06,   3.33786011e-05,
        -2.50339508e-05,  -8.34465027e-06,   8.34465027e-06,
        -8.34465027e-06,   0.00000000e+00], dtype=float32)
len(x[0])
50

第二个要素:

x[1]
array([  0.00000000e+00,   0.00000000e+00,   0.00000000e+00,
         0.00000000e+00,   0.00000000e+00,   0.00000000e+00,
        -4.16040421e-05,   2.49147415e-05,  -8.34465027e-06,
         0.00000000e+00,  -7.49230385e-05,  -1.58131123e-04,
         ....,
        -8.34465027e-06,   3.33786011e-05,  -2.50339508e-05,
        -8.34465027e-06,   8.34465027e-06,  -8.34465027e-06,
         0.00000000e+00,   3.33786011e-05], dtype=float32)
len(x[1])
50

我很好奇是否有办法更有效地完成这项工作,因为我很快就会计划解析+ 20米行的数据集。

5 个答案:

答案 0 :(得分:7)

zip()加上一些切片可以做到这一点:

>>> list(zip(input[0:], input[1:], input[2:]))
[(0, 1, 2), (1, 2, 3), (2, 3, 4), (3, 4, 5), (4, 5, 6)]

如果您需要将列表元素作为列表,请使用:

>>> list(map(list, zip(input[0:], input[1:], input[2:])))
[[0, 1, 2], [1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6]]

一般来说,如果你需要n元组而不是三元组,你可以这样做:

>>> list(zip(*(input[i:] for i in range(3))))
[(0, 1, 2), (1, 2, 3), (2, 3, 4), (3, 4, 5), (4, 5, 6)]

>>> list(map(list, zip(*(input[i:] for i in range(3)))))
[[0, 1, 2], [1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6]]

另一种方法:

>>> [input[i:i+3] for i in range(len(input)-3+1)]
[[0, 1, 2], [1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6]]

一些基准:

设定:

import timeit

def ff1(input):
    return list(map(list, zip(input[0:], input[1:], input[2:])))

def ff2(input):
    return list(map(list, zip(*(input[i:] for i in range(3)))))

def ff3(input):
    return [input[i:i+3] for i in range(len(input)-3+1)]

def jg(input):
    for i in range(0, len(input) - 2):
        yield input[i:i+3]

def jg1(input):
    return list(jg(input))

import itertools

def n(input, n=3):
    i = list(itertoopls.tee(input, n))
    for p, it in enumerate(i):
        next(itertools.slice(it, p, p), None)
    return zip(*i)

def n1(input, _n=3):
    return list(map(list, n(input, _n)))

from numpy.lib.stride_tricks import as_strided

def strided_groupby(n, l=3):
    s = n.strides[0]
    return as_strided(n, shape=(n.size-l+1,l), strides=(s,s))

结果:

>>> input = list(range(10000))
>>> timeit.timeit(stmt='ff1(input)', globals=globals(), number=1000)
1.4750333260162733
>>> timeit.timeit(stmt='ff2(input)', globals=globals(), number=1000)
1.486136345018167
>>> timeit.timeit(stmt='ff3(input)', globals=globals(), number=1000)
1.6864491199958138
>>> timeit.timeit(stmt='jg1(input)', globals=globals(), number=1000)
2.300399674975779
>>> timeit.timeit(stmt='n1(input)', globals=globals(), number=1000)
2.2269885840360075
>>> input_arr = np.array(input)
>>> timeit.timeit(stmt='strided_groupby(input_arr)', globals=globals(), number=1000)
0.01855822204379365

请注意,内部列表转换会浪费大量的CPU周期。如果你能负担得到元组而不是列表,那么最里面的序列(即(0,1,2),(1,2,3),...)将会表现得更好。

为了公平比较,我将相同的列表转换应用于所有算法。

答案 1 :(得分:3)

如果您正在使用numpy或pandas,那么您可以使用@miradulo建议的步幅。但是在使用它们时需要非常小心。当对它们使用矢量化操作时,它们可能会产生非常意外的结果,但miradulo的正确之处在于它应该非常快。

这是一个示例实现:

def strided_groupby(n, l):
    s = n.strides[0]
    return as_strided(n, shape=(n.size-l+1,l), strides=(s,s))

改编自scipy-strides

的文档

输出如下:

[[0 1 2]
 [1 2 3]
 [2 3 4]
 [3 4 5]
 [4 5 6]]

在我的机器上编辑我得到了以下结果:

>>> timeit.timeit(stmt='ff1(n)', globals=globals(), number=1000)
0.2299177199965925

>>> timeit.timeit(stmt='strided_groupby(n, 3)', globals=globals(), number=1000)
0.012110635001590708

这实际上是一个非常显着的差异。

答案 2 :(得分:1)

这就是你所谓的低效率吗?

def answer(data): return [[data[k], data[k+1], data[k+2]] for k in range(len(data)-2)]

答案 3 :(得分:0)

我有另一个天真的解决方案,但是我不熟悉Python,所以我无法判断它与zip相比有多快:

def chunks(l):
    for i in range(0, len(l) - 2):
        yield l[i:i + 3]

if __name__ == '__main__':
    input = [0, 1, 2, 3, 4, 5, 6]

    print(list(chunks(input)))

输出:

[[0, 1, 2], [1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6]]

注意:这假设您的输入列表的长度至少为3。

答案 4 :(得分:0)

您可以基于itertools创建一个函数。这不会消耗迭代所需的更多元素。

import itertools

def groupwithnext(iterable, n=2):
    iterators = list(itertools.tee(iterable, n))
    for pos, iterator in enumerate(iterators):
        # advance each iterator by the correct number of elements
        next(itertools.islice(iterator, pos, pos), None) 
    return zip(*iterators)

测试:

data = [0, 1, 2, 3, 4, 5, 6]

for g in groupwithnext(data, 3):
    print(g)

将打印

(0, 1, 2)
(1, 2, 3)
(2, 3, 4)
(3, 4, 5)
(4, 5, 6)`