NumPy赋值中重复索引的处理

28

我正在设置一个二维数组中多个元素的值,但是我的数据有时包含给定索引的多个值。

似乎“后面”值总是被分配(如下面的示例所示),但这种行为是否得到保证,或者是否存在不一致的结果?我怎样知道可以以向量化的方式解释“后面”?

例如,在我的第一个示例中,a 中一定包含 4,而在第二个示例中,values[0] 是否会打印出来呢?

非常简单的示例:

import numpy as np
indices = np.zeros(5,dtype=np.int)
a[indices] = np.arange(5)
a # array([4])

另一个例子

import numpy as np

grid = np.zeros((1000, 800))

# generate indices and values
xs = np.random.randint(0, grid.shape[0], 100)
ys = np.random.randint(0, grid.shape[1], 100)
values = np.random.rand(100)

# make sure we have a duplicate index
print values[0], values[5]
xs[0] = xs[5]
ys[0] = ys[5]

grid[xs, ys] = values

print "output value is", grid[xs[0], ys[0]]
# always prints value of values[5]

1
为了理解Numpy数组的工作原理,我建议访问http://scipy-lectures.github.io/advanced/advanced_numpy/。 - tom10
4
好问题...这是其中一个你可能需要等待@seberg在场才能得到有意义的答案。 - Jaime
3
我觉得没有什么是百分之百保证的,但一些对异步阵列实验表明可以通过从左到右遍历索引数组来简单实现。 - Fred Foo
2
好问题!Numpy邮件列表确实是询问这个问题的最佳场所。一些核心开发人员偶尔会经常访问SO,(例如DavidCornapeau、rkern、seberg),但他们没有一个人经常这样做。需要有人对numpy C代码库相当熟悉才能确认当前版本是否总是会发生这种情况,并且需要其中一个核心开发人员说出它是否得到保证。(我认为@larsmans是正确的。) - Joe Kington
谢谢大家 - 如果这里没有成功,我会在下周通过其他途径跟进。 - YXD
5个回答

15
在NumPy 1.9及更高版本中,通常情况下这将无法定义清楚。当前实现同时使用单独的迭代器迭代所有(广播)花式索引(和赋值数组),并且这些迭代器都使用C顺序。换句话说,目前是可以的。如果您比较处理这些事情的NumPy中的mapping.c,您会发现它使用PyArray_ITER_NEXT,documented是按C顺序排列的。对于未来,我会用不同的方式描述情况。我认为将所有索引+赋值数组一起使用新的迭代器进行迭代会很好。如果这样做,那么顺序可以保持开放以使迭代器决定最快的方式。如果将其保持开放给迭代器,则很难说会发生什么,但您不能确定您的示例是否有效(可能仍然可以使用1-d情况,但...)。
因为据我所知,目前它可以工作,但未经记录(就我所知),所以如果您认为这应该得到保证,您需要进行游说,并最好编写一些测试以确保可以保证。因为至少我很想说:如果它可以使事情更快,那么没有理由保证C顺序,但当然也许有一个隐藏的好理由......
真正的问题是:你为什么要这样做呢? ;)

非常感谢您的回答。我将在接下来的几天内撰写一份关于为什么我提出这个问题/为什么这个问题出现的正当理由的证明。我不认为自己有足够的知识来判断是否有真正好的理由保留C顺序... - YXD
我看到了关于numpy 1.9的这个讨论,并想知道对于这个问题中呈现的二维情况有什么影响。动机来自一个视觉问题,我将一些三维数据投影到离散像素坐标上,并需要高效地跟踪每个像素的“最佳”数据,由于投影可能会在图像空间中发生碰撞。我的真实代码通过首先按成本/误差对数据进行排序,然后按照问题中所示的方式进行分配来实现这一点。 - YXD

8

我知道这个问题已经得到了令人满意的答复,但我想提一下,它被记录为“最后一个值”(可能是非正式的)在Tentative Numpy Tutorial使用索引数组进行索引中:

However, when the list of indices contains repetitions, the assignment is done several times, leaving behind the last value:

>>> a = arange(5)
>>> a[[0,0,2]]=[1,2,3]  
>>> a
array([2, 1, 3, 3, 4])  

This is reasonable enough, but watch out if you want to use Python's += construct, as it may not do what you expect:

>>> a = arange(5) 
>>> a[[0,0,2]]+=1  
>>> a
array([1, 1, 3, 3, 4])  

Even though 0 occurs twice in the list of indices, the 0th element is only incremented once. This is because Python requires a+=1 to be equivalent to a=a+1.


5
我发现了一种使用numpy进行此操作的方法,虽然不是最优的,但比使用Python循环(for loop)要快。
使用方法为:numpy.bincount。
size = 5
a = np.arange(size)
index = [0,0,2]
values = [1,2,3]
a[index] += values
a
[2 1 5 3 4]

这是错误的写法,应该使用:

size = 5
a = np.arange(size)
index = [0,0,2]
values = [1,2,3]
result = np.bincount(index, values, size)
a += result
a
[3 1 5 3 4]

wich is good !


5

我不会直接回答你的问题,但我想强调一点:即使你可以依赖这种行为是一致的,也最好不要这样做。

考虑以下情况:

a = np.zeros(4)
x = np.arange(4)
indices = np.zeros(4,dtype=np.int)
a[indices] += x

此时,可以合理地假设a.sum()a之前的总和加上x.sum()吗?
assert a.sum() == x.sum()
--> AssertionError 

a
= array([ 3.,  0.,  0.,  0.])

在您的情况下,使用重复索引分配数组时,结果是直观的:多次分配相同的索引,因此只有最后一次分配“生效”(覆盖之前的分配)。
但在这个例子中不是这种情况。它不再直观。如果是这样的话,就会进行多次原地加法,因为加法的性质是累积的。
因此,换句话说,您冒着落入这个陷阱的风险:
- 您开始使用重复索引。 - 您会发现一切正常,行为正如您所期望的那样。 - 您停止关注一个关键事实,即您的操作涉及重复索引。毕竟,这没有任何区别,对吗? - 您开始在不同的上下文中使用相同的索引,例如上面的例子。 - 深深的遗憾 :)
因此,引用@seberg的话:
“The real question here is: Why do you want that anyway? ;)”

1
这绝对是一个有趣的案例。是的,你列出的要点正是我提出问题的原因。在我的原始示例中,似乎更明显应该发生什么。我将在接下来的几天内发布一些背景/上下文信息。 - YXD

4

时间在变化,需要给出更新的答案。

size = 5
a = np.arange(size)
index = [0,0,2]
values = [1,2,3]
np.add.at(a,[0,0,2],values)
a

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