Numpy: группировка / группирование значений на основе ассоциаций
Forgive me for a vague title. I honestly don't know which title will suit this question. If you have a better title, let's change it so that it will be apt for the problem at hand.
Эта проблема.
Скажем result
это 2D массив и values
является одномерным массивом values
содержит некоторые значения, связанные с каждым элементом в result
, Отображение элемента в values
в result
хранится в x_mapping
а также y_mapping
, Положение в result
могут быть связаны с разными значениями. Теперь я должен найти сумму значений, сгруппированных по ассоциациям.
Пример для лучшего разъяснения.
result
массив:
[[0, 0],
[0, 0],
[0, 0],
[0, 0]]
values
массив:
[ 1., 2., 3., 4., 5., 6., 7., 8.]
Примечание: здесь result
а также values
имеют одинаковое количество элементов. Но это может быть не так. Между размерами нет никакой связи.
x_mapping
а также y_mapping
есть отображения из 1D values
в 2D result
, Размеры x_mapping
, y_mapping
а также values
будет таким же.
x_mapping
- [0, 1, 0, 0, 0, 0, 0, 0]
y_mapping
- [0, 3, 2, 2, 0, 3, 2, 1]
Здесь 1-е значение (values[0]
) иметь х как 0 и у как 0(x_mapping[0]
а также y_mappping[0]
) и, следовательно, связано с result[0, 0]
, Если мы подсчитываем количество ассоциаций, то значение элемента в result[0,0]
будет 2 как 1-е значение, а 5-е значение связано с result[0, 0]
, Если мы берем сумму, то result[0, 0] = value[0] + value[4]
который 6
Текущее решение
# Initialisation. No connection with the solution.
result = np.zeros([4,2], dtype=np.int16)
values = np.linspace(start=1, stop=8, num=8)
y_mapping = np.random.randint(low=0, high=values.shape[0], size=values.shape[0])
x_mapping = np.random.randint(low=0, high=values.shape[1], size=values.shape[0])
# Summing the values associated with x,y (current solution.)
for i in range(values.size):
x = x_mapping[i]
y = y_mapping[i]
result[-y, x] = result[-y, x] + values[i]
result
,
[[6, 0],
[ 6, 2],
[14, 0],
[ 8, 0]]
Неудачное решение; Но почему?
test_result = np.zeros_like(result)
test_result[-y_mapping, x_mapping] = test_result[-y_mapping, x_mapping] + values # solution
К моему удивлению элементы перезаписываются в test_result
, Значения в test_result
,
[[5, 0],
[6, 2],
[7, 0],
[8, 0]]
Вопрос
1. Почему во втором решении каждый элемент перезаписывается?
Как отметил @Divakar в комментарии в своем ответе, NumPy не присваивает накопленные / суммированные значения, когда индексы повторяются в test_result[-y_mapping, x_mapping] =
, Он случайным образом присваивается из одного экземпляра.
2. Есть ли какой-нибудь способ Numpy сделать это? То есть без зацикливания? Я ищу некоторую оптимизацию скорости.
Подход № 2 в ответе @ Divakar дает мне хорошие результаты. Для 23315 ассоциаций, for
цикл занял 50 мс, а подход № 1 - 1,85 мс. Победив все это, подход № 2 занял 668 мкс.
Примечание
Я использую Numpy версии 1.14.3 с Python 3.5.2 на процессоре i7.
2 ответа
Подход № 1
Самый интуитивный был бы с np.add.at
для этих повторных индексов -
np.add.at(result, [-y_mapping, x_mapping], values)
Подход № 2
Нам нужно выполнить суммированные суммирования из-за возможной повторяющейся природы индексов x,y. Следовательно, другой способ может заключаться в использовании функции суммирования binum в NumPy: np.bincount
и иметь такую реализацию -
# Get linear index equivalents off the x and y indices into result array
m,n = result.shape
out_dtype = result.dtype
lidx = ((-y_mapping)%m)*n + x_mapping
# Get binned summations off values based on linear index as bins
binned_sums = np.bincount(lidx, values, minlength=m*n)
# Finally add into result array
result += binned_sums.astype(result.dtype).reshape(m,n)
Если вы всегда начинаете с массива нулей для result
, последний шаг может быть сделан более производительным с -
result = binned_sums.astype(out_dtype).reshape(m,n)
Я думаю, ты должен был написать
y_mapping = np.random.randint(low=0, high=result.shape[0], size=values.shape[0])
x_mapping = np.random.randint(low=0, high=result.shape[1], size=values.shape[0])
С этим исправлением код работает для меня, как и ожидалось.