Я работаю с 3D Pointcloud Лидар. Точки задаются массивом numpy, который выглядит следующим образом:
points = np.array([[61651921, 416326074, 39805], [61605255, 416360555, 41124], [61664810, 416313743, 39900], [61664837, 416313749, 39910], [61674456, 416316663, 39503], [61651933, 416326074, 39802], [61679969, 416318049, 39500], [61674494, 416316677, 39508], [61651908, 416326079, 39800], [61651908, 416326087, 39802], [61664845, 416313738, 39913], [61674480, 416316668, 39503], [61679996, 416318047, 39510], [61605290, 416360572, 41118], [61605270, 416360565, 41122], [61683939, 416313004, 41052], [61683936, 416313033, 41060], [61679976, 416318044, 39509], [61605279, 416360555, 41109], [61664837, 416313739, 39915], [61674487, 416316666, 39505], [61679961, 416318035, 39503], [61683943, 416313004, 41054], [61683930, 416313042, 41059]])
Я хотел бы, чтобы мои данные были сгруппированы в кубы по размеру, 50*50*50
чтобы каждый куб сохранял свой хешируемый индекс и целые индексы моего, который points
он содержит . Чтобы разделить, я назначаю, cubes = points \\ 50
какие выходы:
cubes = np.array([[1233038, 8326521, 796], [1232105, 8327211, 822], [1233296, 8326274, 798], [1233296, 8326274, 798], [1233489, 8326333, 790], [1233038, 8326521, 796], [1233599, 8326360, 790], [1233489, 8326333, 790], [1233038, 8326521, 796], [1233038, 8326521, 796], [1233296, 8326274, 798], [1233489, 8326333, 790], [1233599, 8326360, 790], [1232105, 8327211, 822], [1232105, 8327211, 822], [1233678, 8326260, 821], [1233678, 8326260, 821], [1233599, 8326360, 790], [1232105, 8327211, 822], [1233296, 8326274, 798], [1233489, 8326333, 790], [1233599, 8326360, 790], [1233678, 8326260, 821], [1233678, 8326260, 821]])
Мой желаемый результат выглядит так:
{(1232105, 8327211, 822): [1, 13, 14, 18]),
(1233038, 8326521, 796): [0, 5, 8, 9],
(1233296, 8326274, 798): [2, 3, 10, 19],
(1233489, 8326333, 790): [4, 7, 11, 20],
(1233599, 8326360, 790): [6, 12, 17, 21],
(1233678, 8326260, 821): [15, 16, 22, 23]}
Мое настоящее pointcloud содержит до нескольких сотен миллионов 3D-точек. Какой самый быстрый способ сделать такую группировку?
Я пробовал большинство различных решений. Вот сравнение времени, предполагающего, что размер точек составляет около 20 миллионов, а размер отдельных кубов - около 1 миллиона:
Панды [tuple (elem) -> np.array (dtype = int64)]
import pandas as pd
print(pd.DataFrame(cubes).groupby([0,1,2]).indices)
#takes 9sec
По умолчанию [elem.tobytes () или кортеж -> список]
#thanks @abc:
result = defaultdict(list)
for idx, elem in enumerate(cubes):
result[elem.tobytes()].append(idx) # takes 20.5sec
# result[elem[0], elem[1], elem[2]].append(idx) #takes 27sec
# result[tuple(elem)].append(idx) # takes 50sec
numpy_indexed [int -> np.array]
# thanks @Eelco Hoogendoorn for his library
values = npi.group_by(cubes).split(np.arange(len(cubes)))
result = dict(enumerate(values))
# takes 9.8sec
Панды + уменьшение размерности [int -> np.array (dtype = int64)]
# thanks @Divakar for showing numexpr library:
import numexpr as ne
def dimensionality_reduction(cubes):
#cubes = cubes - np.min(cubes, axis=0) #in case some coords are negative
cubes = cubes.astype(np.int64)
s0, s1 = cubes[:,0].max()+1, cubes[:,1].max()+1
d = {'s0':s0,'s1':s1,'c0':cubes[:,0],'c1':cubes[:,1],'c2':cubes[:,2]}
c1D = ne.evaluate('c0+c1*s0+c2*s0*s1',d)
return c1D
cubes = dimensionality_reduction(cubes)
result = pd.DataFrame(cubes).groupby([0]).indices
# takes 2.5 seconds
Скачать cubes.npz
файл можно здесь и использовать команду
cubes = np.load('cubes.npz')['array']
проверить время выполнения.
numpy_indexed
только подходит. Я думаю, это правильно. Я используюpandas
для моих процессов классификации в настоящее время.Ответы:
Постоянное количество показателей на группу
Подход № 1
Мы можем выполнить
dimensionality-reduction
сокращениеcubes
до одномерного массива. Это основано на отображении данных данных кубов в n-dim сетку для вычисления эквивалентов линейного индекса, подробно обсуждаемыхhere
. Затем, основываясь на уникальности этих линейных индексов, мы можем выделить уникальные группы и соответствующие им индексы. Следовательно, следуя этим стратегиям, у нас будет одно решение, например,Альтернатива № 1: Если целочисленные значения в
cubes
слишком велики, мы могли бы сделатьdimensionality-reduction
так, чтобы измерения с более коротким экстентом были выбраны в качестве основных осей. Следовательно, для этих случаев мы можем изменить шаг сокращения, чтобы получитьc1D
, например, так -Подход № 2
Далее, мы можем использовать
Cython-powered kd-tree
для быстрого поиска ближайших соседей, чтобы получить ближайшие соседние индексы и, следовательно, решить наш случай следующим образом:Общий случай: переменное количество индексов на группу
Мы расширим метод, основанный на argsort, с некоторым разбиением, чтобы получить желаемый результат, например:
Использование 1D версий групп в
cubes
качестве ключейМы расширим ранее перечисленный метод группами
cubes
ключей в качестве ключей, чтобы упростить процесс создания словаря и сделать его более эффективным, например:Далее, мы будем использовать
numba
пакет для итерации и получения окончательного вывода в словарь. Следуя этому принципу, было бы два решения - одно, которое получает ключи и значения по отдельности, используя,numba
и основной вызов будет zip и преобразован в dict, в то время как другое создастnumba-supported
тип dict, и, следовательно, основная вызывающая функция не требует дополнительной работы. ,Таким образом, у нас будет первое
numba
решение:И второе
numba
решение как:Сроки с
cubes.npz
данными -Альтернатива № 1: мы можем добиться дальнейшего ускорения
numexpr
вычислений для больших массивовc1D
, например:Это будет применимо во всех местах, которые требуют
c1D
.источник
dtypes
int32
иint64
number of indices per group would be a constant number
что я собрал комментарии. Будет ли это безопасным предположением? Кроме того, вы тестируетеcubes.npz
на выход915791
?cubes.npz
только и это было983234
для других подходов, которые я предложил.Approach #3
этот общий случай переменного числа индексов.Вы можете просто повторить и добавить индекс каждого элемента в соответствующий список.
Время выполнения может быть дополнительно улучшено путем использования tobytes () вместо преобразования ключа в кортеж.
источник
res[tuple(elem)].append(idx)
заняло 50 секунд против его издания,res[elem[0], elem[1], elem[2]].append(idx)
которое заняло 30 секунд.Вы можете использовать Cython:
но это не сделает вас быстрее, чем то, что делает Pandas, хотя после этого оно будет самым быстрым (и, возможно,
numpy_index
основанным на решении) и не приведет к потере памяти. Коллекция того, что было предложено до сих пор, находится здесь .В машине OP это должно быть близко к ~ 12 секунд времени выполнения.
источник