Загружаем библиотеки pandas и numpy, а также display для отображения dataframe'ов
import pandas as pd
import numpy as np
from IPython.display import display
Загружаем лог
Структура данных:
event_df = pd.read_excel('event_log.xlsx')
display(event_df)
id | user_id | date_time | page | |
---|---|---|---|---|
0 | 1 | 36004921-2faf-45b6-bd35-7496474e6c87 | 2018-05-05 07:45:00 | /index |
1 | 2 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 07:46:00 | /index |
2 | 3 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 07:49:00 | /catalog |
3 | 4 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 07:50:00 | /catalog2 |
4 | 5 | 36004921-2faf-45b6-bd35-7496474e6c87 | 2018-05-05 07:51:00 | /contacts |
5 | 6 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 08:21:00 | /catalog |
6 | 7 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 09:22:00 | /index |
7 | 8 | 58774a77-5d8d-4459-b5f3-8cb539f4917c | 2018-05-05 09:25:00 | /index |
События сгенерированные разными пользователями идут в хронологическом порядке. Для удобства отсортируем их по user_id, тогда события каждого пользователя будут идти последовательно
event_df = event_df.sort_values('user_id')
display(event_df)
id | user_id | date_time | page | |
---|---|---|---|---|
0 | 1 | 36004921-2faf-45b6-bd35-7496474e6c87 | 2018-05-05 07:45:00 | /index |
4 | 5 | 36004921-2faf-45b6-bd35-7496474e6c87 | 2018-05-05 07:51:00 | /contacts |
7 | 8 | 58774a77-5d8d-4459-b5f3-8cb539f4917c | 2018-05-05 09:25:00 | /index |
1 | 2 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 07:46:00 | /index |
2 | 3 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 07:49:00 | /catalog |
3 | 4 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 07:50:00 | /catalog2 |
5 | 6 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 08:21:00 | /catalog |
6 | 7 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 09:22:00 | /index |
В колонке 'diff' для каждого события отдельного пользователя посчитаем разницу между временем посещения страницы и времененем посещения предыдущей страницы. Если страница была первой для пользователя, то значение в колонке 'diff' будет NaT, т.к. нет предыдущего значения
event_df['diff'] = event_df.groupby('user_id')['date_time'].diff(1)
display(event_df)
id | user_id | date_time | page | diff | |
---|---|---|---|---|---|
0 | 1 | 36004921-2faf-45b6-bd35-7496474e6c87 | 2018-05-05 07:45:00 | /index | NaT |
4 | 5 | 36004921-2faf-45b6-bd35-7496474e6c87 | 2018-05-05 07:51:00 | /contacts | 00:06:00 |
7 | 8 | 58774a77-5d8d-4459-b5f3-8cb539f4917c | 2018-05-05 09:25:00 | /index | NaT |
1 | 2 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 07:46:00 | /index | NaT |
2 | 3 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 07:49:00 | /catalog | 00:03:00 |
3 | 4 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 07:50:00 | /catalog2 | 00:01:00 |
5 | 6 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 08:21:00 | /catalog | 00:31:00 |
6 | 7 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 09:22:00 | /index | 01:01:00 |
Из основного dataframe 'event_df' создадим вспомогательный dataframe 'session_start_df'. Этот dataframe будет содержать события, которые будут считаться первыми событиями сессий. К таким событиям относятся все события, которые произошли спустя более чем 30 минут после предудыщего, либо события, которые были первыми для пользователя (NaT в колонке 'diff')
Также создадим во вспомогательном dataframe колонку 'session_id', которая будет содержать в себе id первого события сессии. Она пригодится, чтобы корректно отобразить идентификатор сессии, когда будем соединять данные из основного и вспомогательного dataframe
sessions_start_df = event_df[(event_df['diff'].isnull()) | (event_df['diff'] > '1800 seconds')]
sessions_start_df['session_id'] = sessions_start_df['id']
display(sessions_start_df)
/home/makarov/anaconda2/lib/python2.7/site-packages/ipykernel_launcher.py:2: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
id | user_id | date_time | page | diff | session_id | |
---|---|---|---|---|---|---|
0 | 1 | 36004921-2faf-45b6-bd35-7496474e6c87 | 2018-05-05 07:45:00 | /index | NaT | 1 |
7 | 8 | 58774a77-5d8d-4459-b5f3-8cb539f4917c | 2018-05-05 09:25:00 | /index | NaT | 8 |
1 | 2 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 07:46:00 | /index | NaT | 2 |
5 | 6 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 08:21:00 | /catalog | 00:31:00 | 6 |
6 | 7 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 09:22:00 | /index | 01:01:00 | 7 |
С помощью функции merge_asof объединим между собой данные основного и вспомогательного dataframe'ов. Эта функция позволяет объединить данные двух dataframe'ов схожим образом с левым join'ом, но не по точному соответствию ключей, а по ближайшему. Примеры и подробности в документации: https://pandas.pydata.org/pandas-docs/stable/generated/pandas.merge_asof.html
Для корректной работы этой функции оба dataframe должны быть отсортированы по ключу, на основе которого будет происходить merge_asof
event_df = event_df.sort_values('id')
sessions_start_df = sessions_start_df.sort_values('id')
event_df = pd.merge_asof(event_df,sessions_start_df[['id','user_id','session_id']],on='id',by='user_id')
После объединения отсортируем основной dataframe по user_id. И убедимся, что сессии корректно сопоставлены с событиями
event_df = event_df.sort_values(['user_id','date_time'])
display(event_df)
id | user_id | date_time | page | diff | session_id | |
---|---|---|---|---|---|---|
0 | 1 | 36004921-2faf-45b6-bd35-7496474e6c87 | 2018-05-05 07:45:00 | /index | NaT | 1 |
4 | 5 | 36004921-2faf-45b6-bd35-7496474e6c87 | 2018-05-05 07:51:00 | /contacts | 00:06:00 | 1 |
7 | 8 | 58774a77-5d8d-4459-b5f3-8cb539f4917c | 2018-05-05 09:25:00 | /index | NaT | 8 |
1 | 2 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 07:46:00 | /index | NaT | 2 |
2 | 3 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 07:49:00 | /catalog | 00:03:00 | 2 |
3 | 4 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 07:50:00 | /catalog2 | 00:01:00 | 2 |
5 | 6 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 08:21:00 | /catalog | 00:31:00 | 6 |
6 | 7 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 09:22:00 | /index | 01:01:00 | 7 |
/1. Можно найти события, которые были первыми в сессиях. Это будет полезно, если мы захотим определить страницы входа
Найти эти события предельно просто: их идентификаторы будут равны идентификаторам сессии
event_df['is_first_event_in_session'] = event_df['id'] == event_df['session_id']
display(event_df)
id | user_id | date_time | page | diff | session_id | is_first_event_in_session | |
---|---|---|---|---|---|---|---|
0 | 1 | 36004921-2faf-45b6-bd35-7496474e6c87 | 2018-05-05 07:45:00 | /index | NaT | 1 | True |
4 | 5 | 36004921-2faf-45b6-bd35-7496474e6c87 | 2018-05-05 07:51:00 | /contacts | 00:06:00 | 1 | False |
7 | 8 | 58774a77-5d8d-4459-b5f3-8cb539f4917c | 2018-05-05 09:25:00 | /index | NaT | 8 | True |
1 | 2 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 07:46:00 | /index | NaT | 2 | True |
2 | 3 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 07:49:00 | /catalog | 00:03:00 | 2 | False |
3 | 4 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 07:50:00 | /catalog2 | 00:01:00 | 2 | False |
5 | 6 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 08:21:00 | /catalog | 00:31:00 | 6 | True |
6 | 7 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 09:22:00 | /index | 01:01:00 | 7 | True |
/2. Можно вычислить время, проведенное на странице, руководствуясь временем посещения следующей страницы
Для этого сначала считаем разницу между предыдущей и следующей страницей внутри сессии
event_df['time_on_page'] = event_df.groupby(['session_id'])['date_time'].diff(1)
display(event_df)
id | user_id | date_time | page | diff | session_id | is_first_event_in_session | time_on_page | |
---|---|---|---|---|---|---|---|---|
0 | 1 | 36004921-2faf-45b6-bd35-7496474e6c87 | 2018-05-05 07:45:00 | /index | NaT | 1 | True | NaT |
4 | 5 | 36004921-2faf-45b6-bd35-7496474e6c87 | 2018-05-05 07:51:00 | /contacts | 00:06:00 | 1 | False | 00:06:00 |
7 | 8 | 58774a77-5d8d-4459-b5f3-8cb539f4917c | 2018-05-05 09:25:00 | /index | NaT | 8 | True | NaT |
1 | 2 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 07:46:00 | /index | NaT | 2 | True | NaT |
2 | 3 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 07:49:00 | /catalog | 00:03:00 | 2 | False | 00:03:00 |
3 | 4 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 07:50:00 | /catalog2 | 00:01:00 | 2 | False | 00:01:00 |
5 | 6 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 08:21:00 | /catalog | 00:31:00 | 6 | True | NaT |
6 | 7 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 09:22:00 | /index | 01:01:00 | 7 | True | NaT |
Затем смещаем посчитанную разницу на строку выше внутри сессии
event_df['time_on_page'] = event_df.groupby(['session_id'])['time_on_page'].shift(-1)
display(event_df)
id | user_id | date_time | page | diff | session_id | is_first_event_in_session | time_on_page | |
---|---|---|---|---|---|---|---|---|
0 | 1 | 36004921-2faf-45b6-bd35-7496474e6c87 | 2018-05-05 07:45:00 | /index | NaT | 1 | True | 00:06:00 |
4 | 5 | 36004921-2faf-45b6-bd35-7496474e6c87 | 2018-05-05 07:51:00 | /contacts | 00:06:00 | 1 | False | NaT |
7 | 8 | 58774a77-5d8d-4459-b5f3-8cb539f4917c | 2018-05-05 09:25:00 | /index | NaT | 8 | True | NaT |
1 | 2 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 07:46:00 | /index | NaT | 2 | True | 00:03:00 |
2 | 3 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 07:49:00 | /catalog | 00:03:00 | 2 | False | 00:01:00 |
3 | 4 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 07:50:00 | /catalog2 | 00:01:00 | 2 | False | NaT |
5 | 6 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 08:21:00 | /catalog | 00:31:00 | 6 | True | NaT |
6 | 7 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 09:22:00 | /index | 01:01:00 | 7 | True | NaT |
Для удобства дальнейших вычислений переведем 'time_on_page' в секунды
event_df['time_on_page'] = event_df['time_on_page'] / np.timedelta64(1, 's')
display(event_df)
id | user_id | date_time | page | diff | session_id | is_first_event_in_session | time_on_page | |
---|---|---|---|---|---|---|---|---|
0 | 1 | 36004921-2faf-45b6-bd35-7496474e6c87 | 2018-05-05 07:45:00 | /index | NaT | 1 | True | 360.0 |
4 | 5 | 36004921-2faf-45b6-bd35-7496474e6c87 | 2018-05-05 07:51:00 | /contacts | 00:06:00 | 1 | False | NaN |
7 | 8 | 58774a77-5d8d-4459-b5f3-8cb539f4917c | 2018-05-05 09:25:00 | /index | NaT | 8 | True | NaN |
1 | 2 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 07:46:00 | /index | NaT | 2 | True | 180.0 |
2 | 3 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 07:49:00 | /catalog | 00:03:00 | 2 | False | 60.0 |
3 | 4 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 07:50:00 | /catalog2 | 00:01:00 | 2 | False | NaN |
5 | 6 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 08:21:00 | /catalog | 00:31:00 | 6 | True | NaN |
6 | 7 | 61955afd-9718-49df-825c-1b21e352807f | 2018-05-05 09:22:00 | /index | 01:01:00 | 7 | True | NaN |
На основе полученных данных мы можем посчитать простейшие показатели. А можно придумать что-нибудь по-сложнее :)
print u'Количество пользователей: {0}'.format(event_df['user_id'].nunique())
print u'Количество сессий: {0}'.format(event_df['session_id'].nunique())
print u'Количество просмотров страниц: {0}'.format(event_df['id'].count())
print u'Среднее время просмотра страницы: {0}'.format(event_df['time_on_page'].mean())
Количество пользователей: 3 Количество сессий: 5 Количество просмотров страниц: 8 Среднее время просмотра страницы: 200.0