Обработка дублированных индексов в назначениях NumPy

Я устанавливаю значения нескольких элементов в двумерном массиве, однако мои данные иногда содержат несколько значений для данного индекса.

Кажется, что "позднее" значение всегда присваивается (см. Примеры ниже), но гарантируется ли такое поведение или есть вероятность, что я получу противоречивые результаты? Откуда мне знать, что я могу интерпретировать "позже" так, как мне хотелось бы в векторизованном назначении?

т.е. в моем первом примере будет 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]

5 ответов

Решение

В NumPy 1.9 и более поздних версиях это, как правило, не будет четко определено.

Текущая реализация выполняет итерации по всем (транслируемым) причудливым индексам (и массиву назначений) одновременно, используя отдельные итераторы, и все эти итераторы используют C-порядок. Другими словами, в настоящее время да, вы можете. Поскольку вы, возможно, хотите знать это более точно. Если вы сравните mapping.c в NumPy, который обрабатывает эти вещи, вы увидите, что он использует PyArray_ITER_NEXT, который задокументирован в C-порядке.

На будущее я бы нарисовал картину по-другому. Я думаю, что было бы хорошо итерировать все индексы + массив присвоений вместе, используя более новый итератор. Если это сделано, то итератор может оставить порядок открытым, чтобы выбрать наиболее быстрый путь. Если вы держите его открытым для итератора, трудно сказать, что произойдет, но вы не можете быть уверены, что ваш пример работает (вероятно, 1-й случай все еще может, но...).

Итак, насколько я могу судить, он работает в настоящее время, но он недокументирован (насколько я знаю), поэтому, если вы действительно думаете, что это должно быть обеспечено, вам нужно будет лоббировать это и лучше написать несколько тестов, чтобы убедиться, что он может быть гарантированным Потому что, по крайней мере, я испытываю желание сказать: если это делает вещи быстрее, нет причин для обеспечения C-порядка, но, конечно, может быть, есть веская причина, скрытая где-то...

Настоящий вопрос здесь таков: зачем ты этого хочешь?;)

Я знаю, что на этот вопрос был получен удовлетворительный ответ, но я хотел бы упомянуть, что он задокументирован как " последнее значение " (возможно, неофициально) в Предварительном обучающем руководстве по индексированию с массивами индексов:

Однако, когда список индексов содержит повторы, присваивание выполняется несколько раз, оставляя после себя последнее значение:

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

Это достаточно разумно, но будьте осторожны, если вы хотите использовать конструкцию Python +=, так как она может не выполнить то, что вы ожидаете:

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

Даже если в списке индексов 0 встречается дважды, 0-й элемент увеличивается только один раз. Это потому, что Python требует a+=1 быть эквивалентным a=a+1,

Я нашел способ с помощью numpy сделать эту операцию, это явно не оптимально, но это быстрее, чем зацикливание (с python for loop)

with: 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]

что хорошо!

Я не отвечаю на ваш вопрос напрямую, я просто хотел бы подчеркнуть, что даже если вы можете полагаться на то, что такое поведение является последовательным, вам лучше этого не делать.

Рассматривать:

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:

Настоящий вопрос здесь таков: зачем ты этого хочешь?;)

Время меняется. Должен быть дан обновленный ответ.

      size = 5
a = np.arange(size)
index = [0,0,2]
values = [1,2,3]
np.add.at(a,[0,0,2],values)
a
Другие вопросы по тегам