有效地创建具有重复结构的NumPy数组

时间:2017-08-03 15:58:03

标签: python arrays numpy

我想创建一个具有某种重复结构的NumPy数组:一个特定的函数(这里,作为一个例子,shuffle()),取两个数字并返回一个数组(这里长度为8,可能更多虽然)。然后连接这些数组。

import numpy


def shuffle(a, b):
    return numpy.array([
        [+a, +b], [-a, +b], [+a, -b], [-a, -b],
        [+b, +a], [-b, +a], [+b, -a], [-b, -a],
        ])


pairs = [
    (0.1, 0.2),
    (3.14, 2.71), 
    # ... many, without a particular pattern ...
    (0.707, 0.577)
    ]
out = numpy.concatenate([shuffle(*pair) for pair in pairs])

我想这里发生的是所有长度为8的子数组都是在内存中独立创建的,只是要立即复制以形成更大的数组out。当有很多对(a, b)或当shuffle被返回更多数据的东西替换时,这会变得非常低效。

解决这个问题的一种方法是硬编码outàla

out = numpy.array([
    [+0.1, +0.2],
    [-0.1, +0.2],
    # ...
    [-0.2, -0.1],
    [+3.14, +2.71],
    # ...
    ])

但这显然也不可取。

在C中,我可能会使用由预处理器解析的宏。

有关如何安排上述代码的任何提示,以避免不必要的副本?

4 个答案:

答案 0 :(得分:1)

这:

   [
    [+a, +b], [-a, +b], [+a, -b], [-a, -b],
    [+b, +a], [-b, +a], [+b, -a], [-b, -a],
    ]

是列表清单。对数字进行硬编码几乎没有什么区别。

np.array(...)然后将列表转换为数组。

np.fromiterable往往更快,但仅适用于1d数据,因此需要重新整形。

这一步真的是那么大的消费者吗?

一些时间的探索:

In [245]: timeit shuffle(1,2)
9.29 µs ± 12.3 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
...
In [248]: out=np.concatenate([shuffle(1,2) for _ in range(100)])
In [249]: out.shape
Out[249]: (800, 2)
In [250]: timeit out=np.concatenate([shuffle(1,2) for _ in range(100)])
1.02 ms ± 4.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

生成相同大小的数组,但具有更简单的连接。如果生成正确的数字,这可能是可选的速度:

In [251]: np.stack([np.arange(800),np.arange(800)],1).shape
Out[251]: (800, 2)
In [252]: timeit np.stack([np.arange(800),np.arange(800)],1).shape
21.4 µs ± 902 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

我们可以探索替代方案,但在某种程度上,您希望优先考虑清晰度。生成所需数组最清晰的方法是什么?

让我们在没有中间array调用

的情况下尝试一下
def shuffle1(a, b):
    return [
        [+a, +b], [-a, +b], [+a, -b], [-a, -b],
        [+b, +a], [-b, +a], [+b, -a], [-b, -a],
        ]

In [259]: timeit np.array([shuffle1(1,2) for _ in range(100)]).reshape(-1,2)
765 µs ± 14.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

1ms v .75ms - 适度的速度提升。

在随机播放中使用fromiter代替np.array会缩短一半的时间:

def shuffle2(a, b):
    return np.fromiter(
        [+a, +b, -a, +b, +a, -b, -a, -b,
        +b, +a, -b, +a, +b, -a, -b, -a,
        ],int).reshape(-1,2)

In [279]: timeit out=np.concatenate([shuffle2(1,2) for _ in range(100)])
503 µs ± 4.56 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

答案 1 :(得分:1)

这是一种使用花式索引的方法。

pairs是您的示例输入,存储在numpy数组中:

In [7]: pairs
Out[7]: 
array([[ 0.1  ,  0.2  ],
       [ 3.14 ,  2.71 ],
       [ 0.707,  0.577]])

pairspm是一个数组,其行为[a, b, -a, -b]

In [8]: pairspm = np.hstack((pairs, -pairs))

indices中的值是与[a, b, -a, -b]中的8x2模式对应的shuffle(a, b)形式数组的索引:

In [9]: indices = np.array([[0, 1], [2, 1], [0, 3], [2, 3], [1, 0], [3, 0], [1, 2], [3, 2]])

out现在只是对pairspm进行了精确的索引,然后重新整形将pairspm[:, indices]的前两个维度合并为一个:

In [10]: out = pairspm[:, indices].reshape(-1, 2)

In [11]: out
Out[11]: 
array([[ 0.1  ,  0.2  ],
       [-0.1  ,  0.2  ],
       [ 0.1  , -0.2  ],
       [-0.1  , -0.2  ],
       [ 0.2  ,  0.1  ],
       [-0.2  ,  0.1  ],
       [ 0.2  , -0.1  ],
       [-0.2  , -0.1  ],
       [ 3.14 ,  2.71 ],
       [-3.14 ,  2.71 ],
       [ 3.14 , -2.71 ],
       [-3.14 , -2.71 ],
       [ 2.71 ,  3.14 ],
       [-2.71 ,  3.14 ],
       [ 2.71 , -3.14 ],
       [-2.71 , -3.14 ],
       [ 0.707,  0.577],
       [-0.707,  0.577],
       [ 0.707, -0.577],
       [-0.707, -0.577],
       [ 0.577,  0.707],
       [-0.577,  0.707],
       [ 0.577, -0.707],
       [-0.577, -0.707]])

(通过更多工作,您可以消除对pairspm的需求。)

答案 2 :(得分:0)

这是另一种在不堆叠单个数组的情况下构建整个输出结果的方法:

import numpy as np
# generate some data:
pairs = np.random.randint(1, 100, (1000, 2))
# create "sign" array:
u = np.array([[[1, 1], [-1, 1], [1, -1], [-1, -1]]])
# create full output array:
out = (pairs[:, None, :] * u).reshape((-1, 2))

定时:

%timeit (pairs[:, None, :] * u).reshape((-1, 2))
10000 loops, best of 3: 49 µs per loop

答案 3 :(得分:0)

如果您事先知道尺寸,则可以分配一个空数组然后填充它。假设您知道对的长度,从一开始就知道最终的数组大小,那么我们可以在16个块的“平面”视图中跨越数组并填充它。

def gen(pairs):
    out = np.empty((8 * len(pairs), 2), dtype=float)
    for n, (a, b) in enumerate(pairs):
        out.flat[16*n:16*(n+1)] = [
            +a, +b, -a, +b, +a, -b, -a, -b,
            +b, +a, -b, +a, +b, -a, -b, -a,
        ]
    return out