Быстрое удаление дубликатов в numpy и python

есть ли быстрый способ получить уникальные элементы в numpy? У меня есть код, похожий на этот (последняя строка)

tab = numpy.arange(100000000)

indices1 = numpy.random.permutation(10000)
indices2 = indices1.copy()
indices3 = indices1.copy()
indices4 = indices1.copy()

result = numpy.unique(numpy.array([tab[indices1], tab[indices2], tab[indices3], tab[indices4]]))

Это просто пример и в моей ситуации indices1, indices2,...,indices4 содержит различный набор индексов и имеет различный размер. Последняя строка выполняется много раз и Inoticed, что на самом деле это узкое место в моем коде ({numpy.core.multiarray.arange} быть precesive). Кроме того, порядок не важен, а элемент в массиве индексов имеет значение int32 тип. Я думал об использовании хеш-таблицы со значением элемента в качестве ключа и попробовал:

seq = itertools.chain(tab[indices1].flatten(), tab[indices2].flatten(), tab[indices3].flatten(), tab[indices4].flatten())
myset = {}
map(myset.__setitem__, seq, [])
result = numpy.array(myset.keys())

но было еще хуже.

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

2 ответов


Извините, я не совсем понимаю ваш вопрос, но я сделаю все возможное, чтобы помочь.

кулак {numpy.ядро.multiarray.arange} является numpy.arange не причудливое индексирование, к сожалению, причудливое индексирование не отображается как отдельный элемент строки в профилировщике. Если вы звоните np.создать в петле вы должны увидеть, если вы можете переместить его наружу.

In [27]: prun tab[tab]
     2 function calls in 1.551 CPU seconds

Ordered by: internal time

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    1    1.551    1.551    1.551    1.551 <string>:1(<module>)
    1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler'    objects}

In [28]: prun numpy.arange(10000000)
     3 function calls in 0.051 CPU seconds

Ordered by: internal time

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    1    0.047    0.047    0.047    0.047 {numpy.core.multiarray.arange}
    1    0.003    0.003    0.051    0.051 <string>:1(<module>)
    1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

во-вторых, я предполагаю, что tab - это не np.arange(a, b) в коде, потому что если это не tab[index] == index + a, но я предполагаю, что это было просто для вашего примера.

третье, np.соединить примерно в 10 раз быстрее, чем НП.массив

In [47]: timeit numpy.array([tab[indices1], tab[indices2], tab[indices3], tab[indices4]])
100 loops, best of 3: 5.11 ms per loop

In [48]: timeit numpy.concatenate([tab[indices1], tab[indices2], tab[indices3],     tab[indices4]])
1000 loops, best of 3: 544 us per loop

(также np.конкатенат дает массив (4*n,) и np.массив дает массив (4, n), где n-длина, если индексы[1-4]. Последний будет работать только в том случае, если indices1-4 имеют одинаковую длину.)

и последнее, вы также можете сэкономить еще больше времени, если вы можете сделать следующее:

indices = np.unique(np.concatenate((indices1, indices2, indices3, indices4)))
result = tab[indices]

делать это в таком порядке быстрее, потому что вы уменьшаете количество индексы вам нужно посмотреть на вкладке, но это будет работать, только если вы знаете, что элементы вкладки уникальны (иначе вы можете получить повторы в результате, даже если индексы уникальны).

надеюсь, что это поможет


[то, что следует ниже, на самом деле частично неверно (см. PS):]

следующий способ получения уникальных элементов во всех суб-массивах очень быстрый:

seq = itertools.chain(tab[indices1].flat, tab[indices2].flat, tab[indices3].flat, tab[indices4].flat)
result = set(seq)

отметим, что flat (который возвращает итератор) используется вместо flatten() (который возвращает полный массив), и это set() можно вызвать напрямую (вместо использования map() и словарь, как и в вашем втором методе).

вот результаты синхронизации (полученные в IPython shell):

>>> %timeit result = numpy.unique(numpy.array([tab[indices1], tab[indices2], tab[indices3], tab[indices4]]))
100 loops, best of 3: 8.04 ms per loop
>>> seq = itertools.chain(tab[indices1].flat, tab[indices2].flat, tab[indices3].flat, tab[indices4].flat)
>>> %timeit set(seq)
1000000 loops, best of 3: 223 ns per loop

метод set / flat, таким образом, в 40 раз быстрее в этом примере.

PS: сроки set(seq) на самом деле не представитель. На самом деле, первый цикл синхронизации опустошает seq итератор и последующие set() оценки возвращают пустой набор! Правильный тест времени является следующим

>>> %timeit set(itertools.chain(tab[indices1].flat, tab[indices2].flat, tab[indices3].flat, tab[indices4].flat))
100 loops, best of 3: 9.12 ms per loop

что показывает, что метод set / flat на самом деле не быстрее.

PPS: вот (неудачное) исследование предложения mtrw; поиск уникальных индексов заранее может быть хорошей идеей, но я не могу найти способ реализовать его быстрее, чем приведенный выше подход:

>>> %timeit set(indices1).union(indices2).union(indices3).union(indices4)
100 loops, best of 3: 11.9 ms per loop
>>> %timeit set(itertools.chain(indices1.flat, indices2.flat, indices3.flat, indices4.flat))
100 loops, best of 3: 10.8 ms per loop

таким образом, поиск набора всех различных индексов сам по себе довольно медленный.

PPPS: numpy.unique(<concatenated array of indices>) на самом деле в 2-3 раза быстрее, чем set(<concatenated array of indices>). Это ключ к скорости, полученной в ответе Баго (unique(concatenate((…)))). Причина, вероятно, позволяя NumPy обрабатывать свои массивы самостоятельно, как правило, быстрее, чем интерфейс pure Python (set) с массивами NumPy.

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