Предположим, у нас есть фрейм данных из 100 000 строк и 3 столбцов, структурированный так:
| visitorId | timestamp | url |
1 | 1 | 11 | A |
2 | 1 | 12 | B |
3 | 2 | 21 | A |
4 | 3 | 31 | A |
5 | 3 | 32 | C |
.
.
n | z | Z1 | A |
Этот фрейм данных всегда сортируется и хранится в переменной с именем sortedData
.Сначала мы извлекаем все уникальные идентификаторы посетителей в список с именем visitors
и создаем переменную пути для хранения всех URL-адресов, которые посетил этот посетитель.
visitors = sortedData.visitorId.unique()
visitors = visitors.tolist()
paths = []
visitor_length = len(visitors)
Теперь я сделал для каждого посетителя цикл, чтобы найти и сохранить пройденный путь каждого посетителя, его временную метку и идентификатор и использовать его в качестве входных данных для вторичного алгоритма (не имеет значения).Я сделал это двумя способами:
A:
for visitor in visitors:
dataResult = sortedData.loc[sortedData['visitorId'] == visitor]
timestamps = dataResult.timestamp.tolist()
path = dataResult.pageUrl.tolist()
paths.append((visitor, timestamps, path))
sortedData = sortedData.iloc[len(dataResult):]
При этом используется встроенная функция pandas loc
, чтобы найти строки с совпадающими visitorId
и извлечь метки времени.и пути в списки, и, наконец, добавить их вместе.Затем он удаляет первые x строк (равных длине результата запроса, то есть числу совпадений), чтобы не пропустить их в будущем при выполнении аналогичных совпадений.
Процесс повторяетсядля каждого уникального visitor
в списке visitors
.
По времени я обнаружил, что для завершения понадобилось около 6.31
секунд.В моем случае это был файл размером 11,7 МБ, 100 000 строк.На 1,2 ГБ файле это масштабируется до 14 часов.Таким образом, я пробовал способ B, надеясь на ускорение.
Способ B использует логику, согласно которой данные всегда сортируются, так как такой посетитель 1 в visitors
всегда будет первым посетителем в sortedData
, посетитель2-й и т. Д. Таким образом, я мог бы использовать функцию pandas value_counts()
для подсчета вхождений x
текущего посетителя и просто извлечь данные из первых x
строк, используя head(x)
, так как они всегда будут совпадать.Таким образом, не нужно будет каждый раз перебирать и просматривать весь фрейм данных.Затем, как и раньше, я удаляю эти строки из кадра данных и повторяю цикл для следующего visitor
.
B:
for visitor in visitors:
x = sortedData.visitorId.value_counts()[visitor]
timestamps = sortedData.timestamp.head(x).tolist()
path = sortedData.pageUrl.head(x).tolist()
paths.append((visitor, timestamps, path))
sortedData = sortedData.iloc[x:]
К моему удивлению, это сделало его почти вдвое медленнымПри времени выполнения 10.89
секунд по сравнению с 6.31
от A.
Сроки loc
и value_counts()
оказалось, что последние были быстрее, однако при использовании в цикле верно обратное,
Учитывая, что в B мы знаем позиции посетителей, и нам нужно только перебрать первые x строк в кадре данных, а в A нам нужно каждый раз искать весь кадр данных, что вызывает эту разницу в производительности.?
В предыдущей оптимизации, которую я выполнял, с удалением уже пройденных строк, скорость была значительной, удваиваясь каждый раз, когда размер фрейма данных уменьшается вдвое, по сравнению с оставлением его целым.Это заставило меня заподозрить, что он каждый раз перебирает весь фрейм данных по пути A, за исключением случаев, когда я что-то упускаю?
Я использую MacBook Pro 2014, Python 3.6.4 (Anaconda), работающий на PyCharm 2018.