如何应用向每个numpy数组元素返回向量的函数(并获得更高维度的数组)

时间:2017-09-01 11:56:37

标签: python numpy

让我们直接在代码中编写

注意:我编辑了mapper(例如使用x - >(x,2 * x,3 * x))到通用blackbox函数,这会导致麻烦。

import numpy as np

def blackbox_fn(x): #I can't be changed!
    assert np.array(x).shape == (), "I'm a fussy little function!"
    return np.array([x, 2*x, 3*x])

# let's have 2d array
arr2d = np.array(list(range(4)), dtype=np.uint8).reshape(2, 2)

# each element should be mapped to vector
def mapper(x, blackbox_fn):
    # there is some 3rdparty non-trivial function, returning np.array
    # in examples returns np.array((x, 2 * x, 3 * x))
    # but still this 3rdparty function operates only on scalar values
    return vectorized_blackbox_fn(x) 

所以输入2d数组

array([[0, 1],
       [2, 3]], dtype=uint8)

我想获得3d数组

array([[[0, 0, 0],
        [1, 2, 3]],

       [[2, 4, 6],
        [3, 6, 9]]], dtype=uint8)

我可以使用for循环

编写朴素算法
# result should be 3d array, last dimension is same as mapper result size
arr3d = np.empty(arr2d.shape + (3,), dtype=np.uint8)
for y in range(arr2d.shape[1]):
    for x in xrange(arr2d.shape[0]):
        arr3d[x, y] = mapper(arr2d[x, y])

但对于大型阵列来说似乎相当慢。 我知道有np.vectorize,但使用

np.vectorize(mapper)(arr2d)

无法正常工作,因为

ValueError: setting an array element with a sequence.

(似乎vectorize不能改变维度) 是否有更好的(numpy惯用和更快)解决方案?

3 个答案:

答案 0 :(得分:1)

带有新签名选项的

np.vectorize可以处理此问题。它没有提高速度,但使维护簿记更容易。

In [159]: def blackbox_fn(x): #I can't be changed!
     ...:     assert np.array(x).shape == (), "I'm a fussy little function!"
     ...:     return np.array([x, 2*x, 3*x])
     ...: 

signature的文档有点神秘。我以前曾经使用它,所以做了一个很好的初步猜测:

In [161]: f = np.vectorize(blackbox_fn, signature='()->(n)')
In [162]: f(np.ones((2,2)))
Out[162]: 
array([[[ 1.,  2.,  3.],
        [ 1.,  2.,  3.]],

       [[ 1.,  2.,  3.],
        [ 1.,  2.,  3.]]])

使用你的阵列:

In [163]: arr2d = np.array(list(range(4)), dtype=np.uint8).reshape(2, 2)
In [164]: f(arr2d)
Out[164]: 
array([[[0, 0, 0],
        [1, 2, 3]],

       [[2, 4, 6],
        [3, 6, 9]]])
In [165]: _.dtype
Out[165]: dtype('int32')

dtype未保留,因为blackbox_fn并未保留vectorize。默认dtype使用第一个元素进行测试计算,并使用其otypes来确定结果的dtype。可以使用In [166]: f(np.arange(3)) Out[166]: array([[0, 0, 0], [1, 2, 3], [2, 4, 6]]) In [167]: f(3) Out[167]: array([3, 6, 9]) 参数指定return dtype。

它可以处理2d以外的数组:

signature

vectorize np.frompyfunc正在使用Python级迭代。如果没有签名,则使用blackbox_fn,性能会更好。但只要必须为输入元素调用np.frompyfunc,我们就无法提高速度(最多2倍)。

In [168]: fpy = np.frompyfunc(blackbox_fn, 1,1) In [169]: fpy(1) Out[169]: array([1, 2, 3]) In [170]: fpy(np.arange(3)) Out[170]: array([array([0, 0, 0]), array([1, 2, 3]), array([2, 4, 6])], dtype=object) In [171]: np.stack(_) Out[171]: array([[0, 0, 0], [1, 2, 3], [2, 4, 6]]) In [172]: fpy(arr2d) Out[172]: array([[array([0, 0, 0]), array([1, 2, 3])], [array([2, 4, 6]), array([3, 6, 9])]], dtype=object) 返回一个对象dtype数组:

stack
在这种情况下,

In [173]: np.stack(_) Out[173]: array([[array([0, 0, 0]), array([1, 2, 3])], [array([2, 4, 6]), array([3, 6, 9])]], dtype=object) 无法删除数组嵌套:

reshape

但是我们可以把它拉出来并叠加。它需要In [174]: np.stack(__.ravel()) Out[174]: array([[0, 0, 0], [1, 2, 3], [2, 4, 6], [3, 6, 9]])

In [175]: timeit f(np.arange(1000))
14.7 ms ± 322 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [176]: timeit fpy(np.arange(1000))
4.57 ms ± 161 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [177]: timeit np.stack(fpy(np.arange(1000).ravel()))
6.71 ms ± 207 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [178]: timeit np.array([blackbox_fn(i) for i in np.arange(1000)])
6.44 ms ± 235 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

速度测试:

def foo(x):
    return [x, 2*x, 3*x]

让你的函数返回一个列表而不是任何数组可能会使结果更容易重组,甚至可能更快

frompyfunc

或玩def foo(x): return x, 2*x, 3*x # return a tuple In [204]: np.stack(np.frompyfunc(foo, 1,3)(arr2d),2) Out[204]: array([[[0, 0, 0], [1, 2, 3]], [[2, 4, 6], [3, 6, 9]]], dtype=object) 参数;

In [212]: foo1 = np.frompyfunc(foo, 1,3)
In [213]: timeit np.stack(foo1(np.arange(1000)),1)
428 µs ± 17.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
加速10倍 - 我很惊讶:

MailItem.Send

答案 1 :(得分:0)

您可以为这类"外部产品使用基本的NumPy广播"

np.arange(3)[:, None] * np.arange(2)
# array([[0, 0],
#        [0, 1],
#        [0, 2]])

在你的情况下,它将是

def mapper(x):
    return (np.arange(3)[:, None, None] * x).transpose((1, 2, 0))

请注意,只有在您特别需要新轴的时候才需要.transpose()

它几乎是堆叠3次单独乘法的3倍:

def mapper(x):
    return (np.arange(3)[:, None, None] * x).transpose((1, 2, 0))


def mapper2(x):
    return np.stack((x, 2 * x, 3 * x), axis = -1)

a = np.arange(30000).reshape(-1, 30)

%timeit mapper(a)   # 48.2 µs ± 417 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit mapper2(a)  # 137 µs ± 3.57 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

答案 2 :(得分:0)

我可能会弄错,但理解能做到这一点:

a = np.array([[0, 1],
     [2, 3]])


np.array([[[j, j*2, j*3] for j in i] for i in a ])
#[[[0 0 0]
#  [1 2 3]]
#
# [[2 4 6]
#  [3 6 9]]]