Ваш метод крайне неэффективен. Неэффективность в основном обусловлена двумя причинами:
- Цикл O (n ^ 2) в
itertools.combinations(..)
- Использование тяжелых панд. Несмотря на простоту использования, панды немного неэффективны из-за большого количества счетов.
Мы решаем их по
- Использование scipy distance.cdist (источник написан на c) для расчета расстояний между всеми парами.
- Используйте numpy вместо панд
- Jit скомпилирует функцию расстояния jaccard, так как она вызывается большое количество раз.
Итак, код:
from __future__ import division
import time
import pandas as pd
import numpy as np
from scipy.spatial import distance
import numba as nb
def _make_2D_array(lis):
n = len(lis)
lengths = np.array([len(x) for x in lis])
max_len = max(lengths)
arr = np.zeros((n, max_len))
for i in range(n):
arr[i, :lengths[i]] = lis[i]
return arr, lengths
@nb.jit(nopython=True, cache=True)
def compute_jaccard(session1, session2, arr, lengths):
"""Jited funciton to calculate jaccard distance
"""
session1, session2 = session1[0], session2[0]
intersection, union = 0, 0
if(lengths[session2] > lengths[session1]):
session1, session2 = session2, session1
marked = np.zeros((lengths[session2],))
for x in arr[session1][:lengths[session1]]:
x_in_2 = arr[session2][:lengths[session2]] == x
marked[x_in_2] = 1
if(np.any(x_in_2)):
intersection+=1
union+=1
else:
union+=1
union+=np.sum(marked==0)
jaccard = intersection/union
return jaccard
def calculate_sim_between(stack_dataframe):
# get integer encodings for session ids and pages
session_encode, sessions = pd.factorize(stack_dataframe["Session ID"])
page_encode, pages = pd.factorize(stack_dataframe["Page"])
# take unique pages in each session
pages_in_sessions = [np.unique(page_encode[session_encode==x]) for x in range(len(sessions))]
# convert the list of lists to numpy array
arr, lengths = _make_2D_array(pages_in_sessions)
# make a dummy array like [[0], [1], [2] ...] to get the distances between every pair of sessions
_sessions = np.arange(len(sessions))[:, np.newaxis]
# get the distances
distances = distance.cdist(_sessions, _sessions, compute_jaccard, arr=arr, lengths=lengths)
sim_df = pd.DataFrame(distances, columns=sessions, index=sessions)
return sim_df
Обратите внимание, что мы скомпилировали функцию compute_jaccard, чтобы вывести событие из цикла на один уровень. Если вы не хотите устанавливать numba, просто закомментируйте декоратор.
Тайминги:
На этом примере данных:
from faker import Faker
fake = Faker()
stack_dataframe = pd.DataFrame({"Session ID":[fake.name() for i in range(200)], "Page":[fake.name() for i in range(200)]})
сроки
Ваш метод: 69,465 с
без джита: 0,374 с
С JIT (при втором запуске, чтобы сделать скидку на время компиляции): 0,147 с
P.S .: Поскольку мы используем фальшивые данные для запуска на большой выборке для наблюдения за ускорением, на ваших реальных данных профиль синхронизации может немного отличаться.