эффективный способ вычисления расстояния между комбинациями столбцов кадра pandas

задание

у меня есть фрейм данных pandas, где:

  • столбцы-наименования документа
  • строки являются словами в этих документах
  • числа внутри ячеек фрейма являются мерой релевантности слова (количество слов, если вы хотите сохранить его простым)

мне нужно вычислить новую матрицу подобия doc1-doc, где:

  • строки и столбцы являются именами документов
  • в ячейки внутри рамки являются мерой сходства (1-косинусное расстояние) между двумя документами

расстояние Косинуса удобно обеспечено сценарий.пространственный.расстояние.Косинус.

В настоящее время я делаю это:

  1. используйте itertools для создания списка всех 2-комбинаций имен документов (имен столбцов фрейма данных)
  2. выполните цикл над ними и создайте обновление словаря {doc1: {doc2: similarity}}
  3. после цикла создайте новый фрейм с помощью pandas.DataFrame (dict)

2 ответов


Numba будет хорошим решением для этого. Как я думаю, вы знаете, он не поддерживает фреймы данных Pandas, но он построен вокруг массивов NumPy. Это не проблема-вы можете легко и быстро преобразовать свой фрейм данных в 2D-массив и передать его в функцию Numba (которая будет в значительной степени кодом, который у вас уже есть, просто украшен @njit вверху).

также обратите внимание, что вместо dict-of-dicts для результатов вы можете использовать один треугольник квадрата матрица для их хранения:

     doc1 doc2 doc3
doc1  NAN  NAN  NAN
doc2  ...  NAN  NAN
doc3  ...  ...  NAN

Edit: теперь вы реализовали его с помощью Numba, но получили только ускорение 2.5 x. Я провел несколько экспериментов и нашел большую победу:

In [66]: x = np.random.random((1000,1000))

In [67]: y = np.array(x, order='F')

In [68]: %timeit similarity_jit(x)
1 loop, best of 3: 13.7 s per loop

In [69]: %timeit similarity_jit(y)
1 loop, best of 3: 433 ms per loop

то есть ваш алгоритм будет намного, намного быстрее, если вы будете работать с непрерывными кусками данных из-за кэширования. Поскольку ядро вашего алгоритма -numpy.dot(m[:,i], m[:,j]) и m[:,i] принимает один столбец, вам лучше ориентировать свои данные в "Фортран заказ" (столбцам) первый, так что m[:,i] дает один непрерывный массив (потому что массив выложен "транспонированным" в памяти).


Это примерно так же эффективно, как я могу сделать алгоритм, не переходя в многопроцессорную обработку (bleh). Функция использует массивы numpy для всех расчетов.

def cos_sim(data_frame):
    # create a numpy array from the data frame
    a = data_frame.values
    # get the number of documents
    n = a.shape[-1]
    # create an array of size docs x docs to populate
    out = np.ravel(np.zeros(shape=(n, n)))

    for i in range(n):
        # roll the array one step at a time, calculating the cosine similarity each time
        r = np.roll(a, -i, axis=1)
        cs = np.sum(a[:,:n-i]*r[:,:n-i], axis=0) / (
                np.sqrt(np.sum(a[:,:n-i]*a[:,:n-i], axis=0))
                *np.sqrt(np.sum(r[:,:n-i]*r[:,:n-i], axis=0)))

        # push the cosine similarity to the output array's i-th off-diagonal
        out[i:n*n-i*n:n+1] = cs

    return out.reshape((n,n))