Как изменить массив numpy с произвольными индексами векторизованным способом?

Упрощенная история

Предположим, у меня есть массив arr и индексы idx. Для каждого i, входящего в idx, я хочу увеличить arr[i] на единицу.

Невекторизованный подход будет таким:

import numpy as np

arr = np.zeros(5)
idx = [0, 1, 1, 2, 0]

for i in idx:
    arr[i] += 1

Есть ли способ векторизовать это?

Обратите внимание, что arr[idx] += 1 недействителен из-за повторяющихся индексов.

arr = np.zeros(1)
idx = [0, 0]
arr[idx] += 1  # arr becomes array([1]), not array([2])

Конечно, использование np.unique() может достичь той же цели в этом примере с одномерным массивом. Но на самом деле я пытаюсь иметь дело с 2D-массивом и сомневаюсь, что подсчет элементов будет лучшим решением.

Редактировать

np.unique действительно работает, но кажется есть лишнее замедление. Я хотел бы более быстрый подход (если существует).

Вот пример 2D-индексов для 10 000 точек без дубликатов.

arr = np.zeros((10000, 10000))
idx = np.stack([np.arange(10000), np.arange(10000)])

%timeit np.unique(idx, axis=1, return_counts=True)  # takes 1.93 ms

%timeit arr[idx[0], idx[1]] += 1  # takes 235 μs

Судя по всему, итерация по индексации примерно в 10 раз быстрее.

Редактировать2

Ответ @PaulS был быстрее, чем np.unique.

%timeit np.add.at(arr, (idx[0], idx[1]), 1) # takes 925 μs

Редактировать3

Вот пример со случайным индексом для проверки повторяющихся индексов.

arr = np.zeros((10000, 10000))
ran = (np.random.rand(10000)*10).astype(int)
idx = np.stack([ran, ran])

%timeit np.unique(idx, axis=1, return_counts=True)  # takes 3.24 ms

%timeit np.add.at(arr, (idx[0], idx[1]), 1) # takes 859 μs

(изменить: опечатка)

Подробная история

Я пытаюсь реализовать алгоритм преобразования линии Хафа, используя NumPy. (Причина, по которой я не использую cv2.HoughLines(), заключается в том, что я хочу получить результат непосредственно из координат точек, а не из двоичного массива).

Получить кривые в плоскости (r, θ) было легко, но у меня возникли проблемы с реализацией аккумулятора векторизованным способом. В настоящее время я полагаюсь на преобразование 2D-данных в 1D. Есть ли более приятный и быстрый способ выполнить накопление?

Заранее спасибо!

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
0
68
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Используйте numpy.unique, чтобы получить уникальные индексы и их количество:

idx2, cnt = np.unique(idx, return_counts=True)

arr[idx2] += cnt

Обновления arr:

array([2, 2, 1, 0, 0])

с nd-массивами (пример в 2D):

arr = np.zeros([3, 4], dtype=int)
idx = [[0, 0, 2, 0],
       [1, 1, 3, 1]]

idx2, cnt = np.unique(idx, axis=1, return_counts=True)
arr[*idx2] = cnt

Выход:

array([[0, 3, 0, 0],
       [0, 0, 0, 0],
       [0, 0, 0, 1]])

Если индексы транспонированы:

arr = np.zeros([3, 4], dtype=int)
idx = idx = [[0, 1], [0, 1], [2, 3], [0, 1]]

idx2, cnt = np.unique(idx, axis=0, return_counts=True)
arr[*idx2.T] = cnt

Спасибо за ответ! Но, как я уже говорил в своем вопросе, есть ли другой способ?

JS S 03.04.2023 16:36

@JSS, почему? это очень хорошо работает с массивом 2D (ND) (см. обновленный пример)

mozway 03.04.2023 16:39

Кроме того, прямое использование arr[idx] было бы неоднозначным, если бы вы использовали умножение и имели дублирующуюся координату, хотели бы вы умножить 2 раза на 1? или на 2?

mozway 03.04.2023 16:43

Потому что np.unique кажется немного медленным. Пожалуйста, смотрите мой обновленный вопрос.

JS S 03.04.2023 17:47
Ответ принят как подходящий

1D массивы

Другое возможное решение:

np.add.at(arr, idx, 1)

Выход:

[2. 2. 1. 0. 0.]

2D массивы

(Спасибо, @mozway, за ваш пример, который я сейчас использую здесь.)

arr = np.zeros([3, 4], dtype=int)
idx = [[0, 0, 2, 0],
       [1, 1, 3, 1]]

np.add.at(arr, (idx[0], idx[1]), 1)

Выход:

array([[0, 3, 0, 0],
       [0, 0, 0, 0],
       [0, 0, 0, 1]])

Спасибо! Это работает как шарм без потери скорости.

JS S 03.04.2023 17:53

Другие вопросы по теме