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

Joe*_*way 15 python api-design numpy vectorization

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

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

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

if arr.ndim == 1:
    # Do it this way
else:
    # Do it that way
Run Code Online (Sandbox Code Playgroud)

甚至这个:

# 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 ...
Run Code Online (Sandbox Code Playgroud)

也就是说,我发现我可以概括代码来处理2-d的情况下(r,1),(1,c),(r,c),但也不是没有分支或重塑1-d的情况.

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

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

此外,作为API设计原则的相关问题,如果调用者将1-D数组传递给返回新数组的某个函数,并且返回值也是向量,则通常的做法是重新形成2-D向量(r,1)(1,c)回到1-D数组或简单地说明该函数返回2-D数组而不管?

谢谢

unu*_*tbu 6

我认为通常NumPy函数需要一个形状阵列,(r,c)不需要特殊的1-D数组.相反,他们希望用户(r,c)准确地传递一个形状数组,或者让用户传递一个广播形状的一维数组(r,c).

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

另一方面,如果你有一个单一的阵列x形状(r,),你需要它来广播形状(r,c),那么NumPy希望用户传递一个形状数组,(r,1)因为广播不会在右边添加新的轴您.

要做到这一点,用户必须通过x[:,np.newaxis]而不是仅仅通过x.


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

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

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


Jos*_*sef 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)
Run Code Online (Sandbox Code Playgroud)

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

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