为什么在Python中使用numpy.r_而不是concatenate?

31

在什么情况下使用像numpy.r_或numpy.c_这样的对象比使用函数(例如concatenate或vstack)更好(更高效、更适合)?

我正在尝试理解一段代码,其中程序员写了类似于以下内容:

return np.r_[0.0, 1d_array, 0.0] == 2

其中1d_array是一个值可以为0、1或2的数组。为什么不使用np.concatenate(例如)呢?像这样:

return np.concatenate([[0.0], 1d_array, [0.0]]) == 2

它更易读,而且显然做的是同样的事情。


只是一种符号上的便利。np.r_[1:5, 3:7]np.concatenate(np.arange(....)相同速度。最终都会变成一个concatenate调用。 - hpaulj
完整的代码在https://github.com/numpy/numpy/blob/master/numpy/lib/index_tricks.py中。`r_`是一个`AxisConcatenator`对象。值得一读。 - hpaulj
3个回答

56

np.r_实现在numpy/lib/index_tricks.py文件中。这是纯Python代码,没有任何特殊编译的东西。所以它不会比使用concatenatearangelinspace等等等效的方法更快。它只有在符合您的思维方式和需求时才有用。

在你的例子中,它只是节省了将标量转换为列表或数组的步骤:

In [452]: np.r_[0.0, np.array([1,2,3,4]), 0.0]
Out[452]: array([ 0.,  1.,  2.,  3.,  4.,  0.])

同样的参数导致错误:

In [453]: np.concatenate([0.0, np.array([1,2,3,4]), 0.0])
...
ValueError: zero-dimensional arrays cannot be concatenated

增加 [] 后正确

In [454]: np.concatenate([[0.0], np.array([1,2,3,4]), [0.0]])
Out[454]: array([ 0.,  1.,  2.,  3.,  4.,  0.])

hstack通过将所有参数传递给[atleast_1d(_m) for _m in tup] 来处理此问题:

In [455]: np.hstack([0.0, np.array([1,2,3,4]), 0.0])
Out[455]: array([ 0.,  1.,  2.,  3.,  4.,  0.])

所以至少在简单的情况下,它与hstack最相似。

但是r_的真正用处在于当您想使用范围时。

np.r_[0.0, 1:5, 0.0]
np.hstack([0.0, np.arange(1,5), 0.0])
np.r_[0.0, slice(1,5), 0.0]

r_让你可以使用索引中常用的:语法,这是因为它实际上是一个带有__getitem__方法的类的实例。index_tricks在多个地方使用了这个编程技巧。

此外,它们还添加了其他功能。

当使用一个imaginary步长时,使用np.linspace扩展切片而不是np.arange

np.r_[-1:1:6j, [0]*3, 5, 6]

产生:

array([-1. , -0.6, -0.2,  0.2,  0.6,  1. ,  0. ,  0. ,  0. ,  5. ,  6. ])

文档中有更多详细信息。

我在https://dev59.com/eJffa4cB1Zd3GeqP_rXR#37625115中对许多切片进行了一些时间测试。


20
我也对这个问题很感兴趣,比较了速度。
numpy.c_[a, a]
numpy.stack([a, a]).T
numpy.vstack([a, a]).T
numpy.column_stack([a, a])
numpy.concatenate([a[:,None], a[:,None]], axis=1)

这些函数针对任何输入向量a都具有相同的功能。我使用perfplot,发现以下结果:

enter image description here

对于较小的数字,numpy.concatenate是优胜者,而对于较大的数字,则是stack/vstack


绘制该图表的方法为

import numpy as np
import perfplot

b = perfplot.bench(
    setup=np.random.rand,
    kernels=[
        lambda a: np.c_[a, a],
        lambda a: np.stack([a, a]).T,
        lambda a: np.vstack([a, a]).T,
        lambda a: np.column_stack([a, a]),
        lambda a: np.concatenate([a[:, None], a[:, None]], axis=1),
    ],
    labels=["c_", "stack", "vstack", "column_stack", "concat"],
    n_range=[2**k for k in range(22)],
    xlabel="len(a)",
)
b.save("out.png")
b.show()

6
来了解np._r,留下来用perfplot :) - Shmil The Cat

5

您需要的所有说明:
我发现最相关的部分是: https://sourceforge.net/p/numpy/mailman/message/13869535/
"""
For r_ and c_ I'm summarizing, but effectively they seem to be doing
something like:

r_[args]:
    concatenate( map(atleast_1d,args),axis=0 )

c_[args]:
    concatenate( map(atleast_1d,args),axis=1 )

c_ behaves almost exactly like hstack -- with the addition of range
literals being allowed.

r_ is most like vstack, but a little different since it effectively
uses atleast_1d, instead of atleast_2d.  So you have
>>> numpy.vstack((1,2,3,4))
array([[1],
       [2],
       [3],
       [4]])
but
>>> numpy.r_[1,2,3,4]
array([1, 2, 3, 4])
"""

2
如果超链接失效,您至少应该描述该页面的内容。 - dodell
@dodell 好的,没问题。 - piRSquared
2
我认为将r_c_vstackhstack进行比较是具有误导性的,甚至是错误的。对于1,2,3,4这个例子,四个操作分别产生形状为(4,)、(1,4)、(4,1)、(4,)的数组。在这个简单的例子中,r_hstack产生的结果相同,而c_vstack则是彼此的转置。 - hpaulj

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接