编写接受1-D和2-D numpy数组的函数?

时间:2011-11-27 16:28:29

标签: python numpy vectorization api-design

我的理解是numpy中的1-D数组可以解释为面向列的向量或面向行的向量。例如,形状为(8,)的1-D数组可视为形状(1,8)或形状(8,1)的2-D数组,具体取决于上下文。

我遇到的问题是我编写的用于操作数组的函数在2-D情况下倾向于概括以处理向量和矩阵,但在1-D情况下则不太好。

因此,我的函数最终会做这样的事情:

if arr.ndim == 1:
    # Do it this way
else:
    # Do it that way

甚至这个:

# Reshape the 1-D array to a 2-D array
if arr.ndim == 1:
    arr = arr.reshape((1, arr.shape[0]))

# ... Do it the 2-D way ...

也就是说,我发现我可以概括代码来处理二维案例(r,1)(1,c)(r,c),但不能处理没有分支或重新整形的一维案例。

当函数在多个数组上运行时,它会变得更加丑陋,因为我会检查并转换每个参数。

所以我的问题是:我错过了一些更好的成语吗?我上面描述的模式是否与numpy代码相同?

另外,作为API设计原则的相关问题,如果调用者将1-D数组传递给某个返回新数组的函数,并且返回值也是向量,那么通常的做法是重构2 D向量(r,1)(1,c)返回1-D数组或简单地记录该函数返回2-D数组?

由于

4 个答案:

答案 0 :(得分:6)

我认为一般来说,需要形状(r,c)数组的NumPy函数不需要特殊的1-D数组。相反,他们希望用户能够准确地传递形状(r,c)的数组,或者让用户传递broadcasts形状为(r,c)的1-D数组。

如果您将这样的函数传递给形状为(c,)的一维数组,它将广播到(1,c)形状,因为广播会在左侧添加新轴。它还可以广播(r,c)以形成任意r(取决于与其组合的其他数组)。

另一方面,如果您有一个形状为x的一维数组(r,),并且您需要将其广播到形状(r,c),那么NumPy需要用户传递一个形状(r,1)的数组,因为广播不会为你添加新的轴。

为此,用户必须通过x[:,np.newaxis]而不是x


关于返回值:我认为最好总是返回一个二维数组。如果用户知道输出的形状为(1,c),并且想要一维数组,那么让她自己切掉1-D数组x[0]

通过使返回值始终具有相同的形状,将更容易理解使用此函数的代码,因为并不总是立即明白输入的形状是什么。

此外,广播模糊了形状(c,)的1-D阵列和形状(r,c)的2-D阵列之间的区别。如果您的函数在输入1-D输入时返回1-D数组,而在输入2-D输入时返回2-D数组,则您的函数会使区分严格而不是模糊。从风格上来说,这让我想起了if isinstance(obj,type),这与鸭子打字有关。如果你不需要,不要这样做。

答案 1 :(得分:5)

unutbu的解释很好,但我不同意返回维度。

功能内部模式取决于功能的类型。

通常可以编写使用轴参数减少操作,以便维度数无关紧要。

Numpy还有一个atleast_2d(和atleast_1d)函数,如果你需要一个显式的2d数组,它也常用。在统计学中,我有时使用像atleast_2d_cols这样的函数,它将1d(r,)重新整形为2d(r,1),用于需要2d的代码,或者如果输入数组是1d,则解释和线性代数需要列向量。 (重塑很便宜所以这不是问题)

在第三种情况下,如果较低维度的情况可以比较高维度的情况更便宜或更简单,那么我可能会有不同的代码路径。 (例如:如果2d需要多个点积。)

返回维度

我认为不遵循带有返回维度的numpy约定对于一般函数的用户来说可能非常混乱。 (主题特定功能可以不同。) 例如,减少操作松散一维。

对于许多其他函数,输出维度与输入维度匹配。我认为1d输入应该有1d输出而不是额外的冗余维度。除了linalg中的函数,我不记得任何会返回冗余额外维度的函数。 (标量与单元素数组的情况并不总是一致的。)

在风格上,这让我想起了一个实例检查:

如果您允许使用numpy矩阵和蒙版数组,请尝试不使用它。您将获得不容易调试的有趣结果。虽然,对于大多数numpy和scipy函数,用户必须知道数组类型是否适用于它们,因为很少有实例检查,而asarray可能并不总是做正确的事。

作为一个用户,我总是知道我有什么样的“array_like”,列表,元组或者哪个数组子类,特别是当我使用乘法时。

np.array(np.eye(3).tolist()*3)
np.matrix(range(3)) * np.eye(3)
np.arange(3) * np.eye(3)

另一个例子:这是做什么的?

>>> x = np.array(tuple(range(3)), [('',int)]*3)
>>> x
array((0, 1, 2), 
      dtype=[('f0', '<i4'), ('f1', '<i4'), ('f2', '<i4')])
>>> x * np.eye(3)

答案 2 :(得分:2)

这对装饰器很有用

def atmost_2d(func):
  def wrapr(x):
    return func(np.atleast_2d(x)).squeeze()
  return wrapr

例如,此函数将选择其输入的最后一列。

@atmost_2d
def g(x):
  return x[:,-1]

但是:它适用于:

1D:

In [46]: b
Out[46]: array([0, 1, 2, 3, 4, 5])

In [47]: g(b)
Out[47]: array(5)

2D:

In [49]: A
Out[49]:
array([[0, 1],
       [2, 3],
       [4, 5]])

In [50]: g(A)
Out[50]: array([1, 3, 5])

0D:

In [51]: g(99)
Out[51]: array(99)

这个答案建立在前两个答案的基础上。

答案 3 :(得分:2)

这个问题已经有了很好的答案。在这里,我只想添加我通常所做的事情(以某种方式总结其他人的反应)当我想编写接受各种输入的函数时,我对它们的操作需要2d行或列向量。

  1. 如果我知道输入始终为1d(数组或列表):

    一个。如果我需要一行:x = np.asarray(x)[None,:]

    湾如果我需要专栏:x = np.asarray(x)[:,None]

  2. 如果输入可以是具有正确形状的2d(数组或列表)或1d(需要转换为2d行/列):

    一个。如果我需要一行:x = np.atleast_2d(x)

    湾如果我需要专栏:x = np.atleast_2d(np.asarray(x).T).Tx = np.reshape(x, (len(x),-1))(后者似乎更快)