Самый быстрый способ найти ближайшее ненулевое значение в массиве из столбцов в фрейме данных pandas

#pandas #numpy #optimization

Вопрос:

Я ищу ближайшую ненулевую ячейку в трехмерном массиве numpy на основе координат i,j,k, хранящихся во фрейме данных pandas. Мое решение ниже работает, но оно медленнее, чем мне бы хотелось. Я знаю, что мне не хватает навыков оптимизации, поэтому я надеюсь, что кто-нибудь сможет помочь мне найти более быстрый вариант.

Требуется 2 секунды, чтобы найти ближайшее ненулевое значение для двоичного массива 100 х 100 х 100, а у меня есть сотни файлов, поэтому любые улучшения скорости были бы очень признательны!

 a=np.random.randint(0,2,size=(100,100,100))
# df with i,j,k of interest
df=pd.DataFrame(np.random.randint(100,size=(100,3)).tolist(),
                columns=['i','j','k'])

def find_nearest(a,df):
    import numpy as np
    import pandas as pd
    import time
    t0=time.time()
    nzi = np.nonzero(a)
    for i,r in df.iterrows():
        dist = ((r['k'] - nzi[0])**2   
              (r['i'] - nzi[1])**2   
              (r['j'] - nzi[2])**2)
        nidx = dist.argmin()
        df.loc[i,['nk','ni','nj']]=(nzi[0][nidx],
                                    nzi[1][nidx],
                                    nzi[2][nidx])
    print(time.time()-t0)
    return(df)
 

Ответ №1:

Проблема, которую вы пытаетесь решить, выглядит как поиск ближайшего соседа.

Наихудшая сложность текущего кода O(n m) связана с n количеством точек для поиска и m количеством соседних кандидатов. С n = 100 и m = 100**3 = 1,000,000 это означает около сотен миллионов итераций. Чтобы эффективно решить эту проблему, можно использовать более совершенный алгоритм.

Распространенный способ решения такого рода проблем состоит в размещении всех элементов в структуре данных BSP-дерева (например, Квадродерево или Октрево. Такая структура данных помогает вам находить ближайшие элементы в определенном месте за определенный O(log(m)) промежуток времени. В результате общая сложность этого метода составляет O(n log(m)) ! SciPy уже реализует KD-деревья.

Векторизация, как правило, также помогает ускорить вычисления.

 def find_nearest_fast(a,df):
    from scipy.spatial import KDTree
    import numpy as np
    import pandas as pd
    import time
    t0=time.time()
    candidates = np.array(np.nonzero(a)).transpose().copy()
    tree = KDTree(candidates, leafsize=1024, compact_nodes=False)
    searched = np.array([df['k'], df['i'], df['j']]).transpose()
    distances, indices = tree.query(searched)
    nearestPoints = candidates[indices,:]
    df[['nk', 'ni', 'nj']] = nearestPoints
    print(time.time()-t0)
    return df
 

Эта реализация в 16 раз быстрее на моей машине. Обратите внимание, что результаты немного отличаются, так как для данной входной точки существует несколько ближайших точек (с одинаковым расстоянием).