эффективный способ вычисления расстояния между комбинациями столбцов кадра pandas
задание
у меня есть фрейм данных pandas, где:
- столбцы-наименования документа
- строки являются словами в этих документах
- числа внутри ячеек фрейма являются мерой релевантности слова (количество слов, если вы хотите сохранить его простым)
мне нужно вычислить новую матрицу подобия doc1-doc, где:
- строки и столбцы являются именами документов
- в ячейки внутри рамки являются мерой сходства (1-косинусное расстояние) между двумя документами
расстояние Косинуса удобно обеспечено сценарий.пространственный.расстояние.Косинус.
В настоящее время я делаю это:
- используйте itertools для создания списка всех 2-комбинаций имен документов (имен столбцов фрейма данных)
- выполните цикл над ними и создайте обновление словаря {doc1: {doc2: similarity}}
- после цикла создайте новый фрейм с помощью 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))