逐元素添加1D和2D numpy数组

时间:2017-02-01 22:21:27

标签: python numpy

情况

我的对象具有由numpy数组表示的属性:

>> obj = numpy.array([1, 2, 3])

其中123是属性'值。

我即将编写一些应该在 单个对象和一组对象上同等工作的方法。一组对象由2D numpy数组表示:

>>> group = numpy.array([[11, 21, 31],
...                      [12, 22, 32],
...                      [13, 23, 33]])

其中第一个数字表示对象,第二个数字表示属性。即12是对象1的属性2,21是对象2的属性1。

为什么这样而不转置?因为我希望数组索引对应于属性。那就是object_or_group[0]应该将第一个属性作为单个数字或numpy数组产生,因此它可以用于进一步的计算。

好吧,所以当我想要计算点积时,这个开箱即用:

>>> obj = numpy.array([1, 2, 3])
>>> obj.dot(object_or_group)

什么不起作用是元素添加。

输入:

>>> group
array([[1, 2, 3],
       [4, 5, 6]])
>>> obj
array([10, 20])

结果数组应该是groupobj的第一个元素的总和,对于第二个元素是相似的:

>>> result = numpy.array([group[0] + obj[0],
...                       group[1] + obj[1]])
>>> result
array([[11, 12, 13],
       [24, 25, 26]])

然而:

>>> group + obj
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: operands could not be broadcast together with shapes (2,3) (2,)

考虑到numpy&#39; broadcasting rules,这是有道理的。

似乎没有numpy函数沿指定轴执行添加(或等效广播)。虽然我可以使用

>>> (group.T + obj).T
array([[11, 12, 13],
       [24, 25, 26]])

这感觉非常麻烦(如果我认为单个物体确实是错误的,而不是一个群体)。特别是因为numpy涵盖了每个角落的使用情况,我觉得我可能在这里遇到了一些概念错误。

总结一下

相似
>>> obj1
array([1, 2])
>>> obj2
array([10, 20])
>>> obj1 + obj2
array([11, 22])

(执行元素 - 或属性 - 添加)我想对对象组执行相同的操作:

>>> group
array([[1, 2, 3],
       [4, 5, 6]])

虽然这样的2D组数组的布局必须使得单个对象沿第二轴(axis=1)列出,以便能够通过正常索引请求某个属性(或许多): obj[0]group[0]都应该产生第一个属性。

2 个答案:

答案 0 :(得分:2)

您似乎对矩阵的哪个维度是一个对象以及哪个是一个属性感到困惑,正如示例中对象大小的变化所证明的那样。实际上,事实上你正在交换尺寸以匹配那些让你失望的大小变化。您还使用了3x3组的不幸示例作为您的点积,这进一步说明了您的解释。

在下面的示例中,对象将是三元素向量,即它们各自具有三个属性。示例组将始终包含两行,其中包含两个对象和三列,因为对象具有三个属性。

该组的第一行group[0],a.k.a。group[0, :]将成为该组中的第一个对象。第一列group[:, 0]将是第一个属性

以下是几个示例对象和组,以说明以下几点:

>>> obj1 = np.array([1, 2, 3])
>>> obj2 = np.array([4, 5, 6])
>>> group1 = np.array([[7, 8, 9],
                      [0, 1, 2]])
>>> group2 = np.array([[3, 4, 5]])

由于现在的广播,增加将开箱即用:

>>> obj1 + obj2
array([5, 7, 9])
>>> group1 + obj1
array([[ 8, 10, 12],
       [ 1,  3,  5]])

正如您所看到的,相应的属性正在添加得很好。您甚至可以将组添加到一起,但前提是它们的大小相同或者其中一个只包含一个对象:

>>> group1 + group2
array([[10, 12, 14],
       [ 3,  5,  7]])
>>> group1 + group1
array([[14, 16, 18],
       [ 0,  2,  4]])

所有二元元素运算符都是如此:*-/np.bitwise_and等。

唯一剩下的问题是,如果点阵产品在矩阵或矢量上运行,如何使它们无关紧要。事实上,点产品并不关心。您的公共维度始终是属性的数量,因此需要转置第二个操作数(乘数),以便列数成为行数。无论np.dot(x1, x2.T)x1.dot(x2.T)是群组还是对象,x1或等效x2都能正常运作:

>>> obj1.dot(obj2.T)
32
>>> obj1.dot(group1.T)
array([50,  8])
>>> group1.dot(obj1.T)
array([50,  8])

您可以使用np.atleast_1dnp.atleast_2d始终将结果强制转换为特定形状,这样您就不会得到像obj1.dot(obj2.T)这样的标量。我建议使用后者,因此无论输入如何,您始终都有一致的维数:

>>> np.atleast_2d(obj1.dot(obj2.T))
array([[32]])
>>> np.atleast_2d(obj1.dot(group1.T))
array([[50,  8]])

请记住,点积的维数将是第一个操作数中的对象数量与第二个操作数中的对象数量(所有内容都将被视为一个组)。属性将相乘并相加。是否具有适用于您的目的的有效解释完全由您决定。

<强>更新

此时唯一剩下的问题是属性访问。如上所述,obj1[0]group1[0]意味着截然不同的事情。有三种方法可以协调这种差异,按照我个人喜欢的顺序列出,1是最优选的:

  1. 使用Ellipsis索引对象获取最后一个索引而不是第一个索引

    >>> obj1[..., 0]
    array([1])
    >>> group1[..., 0]
    array([7, 0])
    

    这是最有效的方法,因为它不会制作任何副本,只是对原始数组进行正常索引。如您所见,单个对象(1D数组)的结果与仅包含一个对象的组(2D数组)之间没有区别。

  2. 将所有对象设为2D。正如您自己指出的那样,这可以通过装饰器和/或使用np.atleast_2d来完成。就个人而言,我更倾向于使用1D数组作为单个对象,而不必将它们包装在2D中。

  3. 始终通过转置访问属性:

    >>> obj1.T[0]
    1
    >>> group1.T[0]
    array([7, 0])
    

    虽然这在功能上等同于#1,但相比之下,除了做一些非常不同的内幕之外,它相当笨拙而且难看。这种方法至少会创建底层数组的新视图,并且如果组数组布局不正确,则可能会在某些情况下冒不必要的副本。即使它确实解决了统一访问的问题,我也不推荐这种方法。

答案 1 :(得分:1)

您想要执行的操作似乎可以通过此简单的代码完成!!

>>> m
array([[1, 2, 3],
       [4, 5, 6]])
>>> g = np.array([10,20])
>>> m + g[ : , None]
array([[11, 12, 13],
       [24, 25, 26]])