Воронка с подтверждённым результатом
В первых шестнадцати уроках аналитики мы получили договор событий и воспроизводимый снимок данных. Теперь рассмотрим конверсию: переход от подходящего посещения к заранее определённому результату. Чтобы вычислить такой показатель, недостаточно сложить клики разных страниц.
Продолжение использует conversion-lab и вымышленную запись на обновления курса. Материалы markdown и xml сохраняют учебные ID. Новые события и сессии специально подготовлены для расчёта; они не являются экспортом настоящего ProfessorWeb или предложением передавать собственные session_id внешнему счётчику.
Результат и знаменатель
Целевым действием выберем подтверждённую запись. Нажатие кнопки отправки ещё не означает этот результат: запрос мог не пройти проверку, письмо могло не прийти, а посетитель мог не подтвердить адрес. Каждая ступень помогает найти разрыв, но не заменяет окончательное определение.
Знаменателем станут шесть учебных сессий, которым показано предложение. Это не все открытия библиотеки и не все люди. Одна реальная личность может иметь несколько посещений; наши искусственные session_id только позволяют объяснить порядок на маленьком известном наборе.
Заранее зададим окно: следующий этап должен произойти в течение 30 минут после показа внутри одной учебной сессии. Это выбранный договор примера. Для настоящего подтверждения подписки могут понадобиться более длинный срок и другая связь событий. Тогда изменится метод, а не только подпись таблицы.
Четыре этапа
Воронка состоит из offer_view, form_start, form_submit и subscription_confirmed. Сначала посетитель видит предложение, затем начинает форму, отправляет её и получает подтверждённый результат.
| Этап | Что наблюдаем | Чего он не доказывает |
|---|---|---|
| offer_view | Предложение показано | Посетитель внимательно его прочитал |
| form_start | Началось взаимодействие | Данные корректны |
| form_submit | Отправлена попытка | Сервер принял запись |
| subscription_confirmed | Подтверждение связано с записью | Человек будет читать каждую рассылку |
В маленьком снимке шесть сессий проходят первый этап, четыре — второй, три — третий, две — последний. У каждой события имеют относительные секунды. Скачайте архив conversion-lab: он содержит исходные данные, программы расчёта и README. После распаковки используйте файлы из папки conversion-lab.
Эти числа выбраны для объяснения и не изображают реальную эффективность подписки. Важен способ получения: этапы относятся к одному набору и заданному порядку.
Упорядоченный расчёт
Ниже полный алгоритм funnel.py для массива events.json. Каждая запись содержит event_id, session_id, document_id, event и second. Дубликаты ID с разным содержанием считаются ошибкой данных.
import json
from collections import defaultdict
from pathlib import Path
STAGES = ["offer_view", "form_start", "form_submit", "subscription_confirmed"]
def funnel(events, window=1800):
unique = {}
for event in events:
previous = unique.get(event["event_id"])
if previous is not None and previous != event:
raise ValueError("Конфликт event_id")
unique[event["event_id"]] = event
grouped = defaultdict(list)
for event in unique.values():
grouped[event["session_id"]].append(event)
counts = [0] * len(STAGES)
for rows in grouped.values():
rows.sort(key=lambda row: (row["second"], row["event_id"]))
stage = 0
started = None
document = None
for row in rows:
if row["event"] != STAGES[stage]:
continue
if stage == 0:
started = row["second"]
document = row["document_id"]
elif row["document_id"] != document or row["second"] > started + window:
continue
counts[stage] += 1
stage += 1
if stage == len(STAGES):
break
return counts
if __name__ == "__main__":
data = json.loads(Path(__file__).with_name("events.json").read_text())
print(dict(zip(STAGES, funnel(data))))
Ожидаются количества 6, 4, 3 и 2. Сортировка относится к секундам внутри вымышленной сессии. Для реального экспорта потребуются известная временная зона и правила событий с одинаковой отметкой.
Один этап считается не более одного раза. Повторный submit не увеличивает число успешно прошедших сессий. Это отличается от отчёта по количеству событий, изученного раньше.
Доли и разрыв
Общая доля результата равна 2/6, около 33,3%. Переход от начала к отправке — 3/4, то есть 75%. Переход от отправки к подтверждению — 2/3. Эти числа отвечают на разные вопросы.
Если сравнить 2 подтверждения с 4 началами, получим 50%, но это не общая конверсия показанного предложения. Подпись знаменателя должна находиться рядом с показателем. Удобное число без определения легко вводит в заблуждение.
Потеря между этапами не является доказанным дефектом. Посетитель мог передумать, уже быть подписанным или не захотеть передавать адрес. Воронка показывает место для исследования, а причина требует отдельного наблюдения.
Порядок и связь
Если confirmation пришёл раньше submit, алгоритм не засчитает его следующим этапом. Это может отражать доставку с задержкой или другую связь записи. Прежде чем назвать посещение неуспешным, нужно исследовать качество времени и идентификаторов.
События разных документов также не соединяются после выбранного первого показа. Посетитель мог начать предложение на Markdown и закончить на XML; наш договор такую последовательность не считает. Для другой модели понадобится связь предложения или заявки, а не случайное объединение всех событий сессии.
Алгоритм закрепляет первый подходящий показ и не ищет заново лучший последующий маршрут. Это известное упрощение. Оно помогает избежать скрытого подбора удобной последовательности и должно сохраняться при сравнении периодов.
Наблюдение изменений
Сначала проверьте исходные шесть сессий вручную. Затем добавьте идентичный повтор event_id: количества должны остаться прежними. Перенос подтверждения за окно изменит последнюю ступень, а повторное начало не создаст нового посетителя.
При расширении набора сначала сохраняйте договор. Если увеличить окно или изменить событие результата одновременно с интерфейсом, рост может объясняться новой методикой. Версия определения должна быть частью снимка.
Неполное наблюдение
Если источник не прислал offer_view, позднее подтверждение не создаёт подходящую единицу автоматически. Такая строка может быть полезна для диагностики доставки, но она не устанавливает отсутствующий знаменатель. Нельзя «исправлять» конверсию добавлением выдуманного начала.
Аналогично единая session_id должна иметь известную область. Повтор номера в другом снимке или на другом стенде не соединяет людей. В наших данных это искусственные короткие обозначения; contexts.json отделяет их от form-v1.
Относительные секунды уже имеют числовой тип в подготовленном JSON. Для настоящего экспорта понадобится проверить тип, пропуски, часы источника и правила задержки. Алгоритм воронки не заменяет очистку данных, которую рассматривала основная серия.
Неизвестное событие не является следующим этапом и не увеличивает счётчик. Но если оно возникло из переименования subscription_confirmed, потеря результата станет ошибкой версии договора. Сначала сравните схему источника, затем делайте вывод о поведении посетителя.
Такие случаи полезно сохранять в отдельной диагностической группе. Их скрытое удаление может сделать показатель красивее, одновременно ухудшив понимание полноты наблюдения.
Теперь конверсия имеет упорядоченные этапы и явный знаменатель. Следующий урок рассмотрит попытки формы подробнее и отделит принятие сервером от подтверждённого полезного результата.