Собесов

Сценарий: сессионизация пользовательских событий с таймаутом 30 минут

PythonАнализ событийСредняяMiddle

Условие

Дан лог events(user_id, ts, event_name). Нужно склеить события в сессии: новая сессия начинается, если с предыдущего события прошло больше 30 минут. Получить session_id, длительность сессии и число событий в сессии.

Решение

Идея

В рамках пользователя считаем разницу между соседними ts. Если она > 30 мин — это начало новой сессии. Кумулятивная сумма таких флагов даёт session_id внутри пользователя.

Реализация

import pandas as pd
 
events = events.sort_values(['user_id', 'ts']).copy()
 
# Разница со следующим (предыдущим) событием внутри пользователя
events['gap'] = events.groupby('user_id')['ts'].diff()
 
# Флаг новой сессии: первое событие пользователя или gap > 30 мин
events['new_session'] = (events['gap'].isna()) | (events['gap'] > pd.Timedelta('30min'))
 
# session_id уникальный по всему датасету
events['session_id'] = events['new_session'].cumsum()
 
# Агрегат по сессиям
sessions = (
    events.groupby(['user_id', 'session_id'], as_index=False)
          .agg(
              start=('ts', 'min'),
              end=('ts', 'max'),
              n_events=('event_name', 'size'),
          )
)
sessions['duration_min'] = (sessions['end'] - sessions['start']).dt.total_seconds() / 60

Вариант: глобальный session_id через cumsum на user-уровне

Можно cumsum внутри groupby — получите session-номер внутри пользователя:

events['session_num'] = events.groupby('user_id')['new_session'].cumsum()

Подводные камни

  1. Без sort_values(['user_id', 'ts']) diff() посчитает мусор.
  2. cumsum без groupby даёт глобально уникальный id (хорошо); внутри groupby — локальный (тоже бывает нужен).
  3. Первое событие у каждого пользователя должно начать новую сессию — отсюда gap.isna() | gap > 30min.
  4. Длительность одно-событийной сессии = 0 — это не баг, но часто фильтруется.
  5. Таймзоны: если разные ts в разных tz — гэп будет считаться неверно. Сначала привести к одной tz.

Эталонный ответ

gap = ts.diff() по пользователю → new_session = gap > 30minsession_id = new_session.cumsum().

Хочешь увидеть разбор?

Зарегистрируйся бесплатно — откроется развёрнутое решение этой задачи и ещё 4 на выбор.

Зарегистрироваться и увидеть разбор
Уже есть аккаунт? Войти