Зачем маркетологу прогнозирование на sktime? Часть 2: Практическая на примере посетителей сайта

Первая часть тут. В первой части я рассмотрел возможности автоматизированного прогнозирования на данных из учебного датасета. Во второй части посмотрим насколько хорошо можно предсказывать трафик из Демонстрационного счетчика Метрики. Загрузим данные тут.

Скачайте данные в удобном формате
Скачайте данные в удобном формате

Для начала взглянем на данные:

Длинные красные и желтые пики как бы намекают, что в данных большие выбросы, но об этом позже.
Длинные красные и желтые пики как бы намекают, что в данных большие выбросы, но об этом позже.

Сначала заглушим все предупреждения, популярная практика в машинном обучении:

import warnings # Отключение всех предупреждений warnings.filterwarnings("ignore")

Необходимые импорты:

from sktime.forecasting.ets import AutoETS from sktime.forecasting.base import ForecastingHorizon from sktime.param_est.seasonality import SeasonalityACF from sktime.utils.plotting import plot_series from sktime.split import temporal_train_test_split from sktime.performance_metrics.forecasting import mean_squared_error

Загрузим данные:

import pandas as pd df = pd.read_excel('названиефайла.xlsx', skiprows=5) df = df.drop(index=0) # удалил первую строку с суммой df['Дата визита'] = pd.to_datetime(df['Дата визита']) df = df.set_index('Дата визита')

Выберем все источники трафика:

df['Источник трафика'].unique()

Результат:

array(['Прямые заходы', 'Переходы по ссылкам на сайтах', 'Переходы из поисковых систем', 'Внутренние переходы', 'Переходы из социальных сетей', 'Переходы по рекламе', 'Не определено', 'Переходы по QR-коду', 'Переходы из мессенджеров', 'Переходы с почтовых рассылок', 'Переходы с сохранённых страниц', 'Переходы из рекомендательных систем'], dtype=object)

Пропишем частоту, методом .asfreq('D'). Вангую, что в данных еще полно пропусков, чтобы от них избавится воспользуемеся лайфхаком .resample('D') что дальше указывать, без разницы, т.к. сумма и среднее за 1 день одинаковые:

df['Визиты'][df['Источник трафика'] == 'Прямые заходы'].asfreq('D').resample('D').sum()

И дальше просто перебираем все значения из списка трафика по циклу. Игрек у нас целевая переменная, которую предсказываем:

for source in df['Источник трафика'].unique(): y = df['Визиты'][df['Источник трафика'] == source].asfreq('D').resample('D').sum()

Чтобы отфильтровать источники, где мало трафика, я использую условие:

if sum(y) > 600:

Я просто скопирую весь код из первой части и получу прогнозы:

RMSE: 92.7407873900574
RMSE: 92.7407873900574
RMSE: 823.8282501025691
RMSE: 823.8282501025691
RMSE: 10.963368558301537
RMSE: 10.963368558301537
RMSE: 11.33234471603892
RMSE: 11.33234471603892
RMSE: 2.43193144362868
RMSE: 2.43193144362868
RMSE: 2.512959448749721
RMSE: 2.512959448749721
RMSE: 0.7466906330369009
RMSE: 0.7466906330369009

Как видите, модель на автоматических настройках неплохо справилась с предсказаниями, на что указывает низкая ошибка RMSE: 10.96 для "Переходов из поисковых систем". Но в в источнике "Не определено" тоже низкая ошибка RMSE: 0.7466906330369009. Но данных мало, поэтому прогноз получается не очень удачный, так низкая ошибка не всегда означает хороший прогноз.

Но в прямых заходах мы сразу видим сильный выброс:

Средний уровень меньше 200, а выброс больше 1000, в 5 раз.
Средний уровень меньше 200, а выброс больше 1000, в 5 раз.

Что бы улучшить результаты очистим от выбросов:

# Агрегируем данные по дням y = df['Визиты'][df['Источник трафика'] == 'Прямые заходы'].asfreq('D').resample('D').sum() # Устанавливаем границы по квантилям 0.01 и 0.9 lower_bound, upper_bound = y.quantile(0.01), y.quantile(0.9) # Заменим выбросы средним значением без выбросов y = y.mask((y < lower_bound) | (y > upper_bound), y[(y >= lower_bound) & (y <= upper_bound)].mean())

И таким образом финальный код примет вид:

import warnings # Отключение всех предупреждений warnings.filterwarnings("ignore") from sktime.forecasting.ets import AutoETS from sktime.forecasting.base import ForecastingHorizon from sktime.param_est.seasonality import SeasonalityACF from sktime.utils.plotting import plot_series from sktime.split import temporal_train_test_split from sktime.performance_metrics.forecasting import mean_squared_error import pandas as pd df = pd.read_excel('Metrica_live_demo.xlsx', skiprows=5) df = df.drop(index=0) # удалил первую строку с суммой df['Дата визита'] = pd.to_datetime(df['Дата визита']) # не забудьте задать формат Дейттайм df = df.set_index('Дата визита') for source in df['Источник трафика'].unique(): y = df['Визиты'][df['Источник трафика'] == source].asfreq('D').resample('D').sum() if sum(y) > 600: # Устанавливаем границы по квантилям 0.01 и 0.9 lower_bound, upper_bound = y.quantile(0.01), y.quantile(0.9) # Заменим выбросы средним значением без выбросов y = y.mask((y < lower_bound) | (y > upper_bound), y[(y >= lower_bound) & (y <= upper_bound)].mean()) # Оцениваем сезонность sp_est = SeasonalityACF() sp_est.fit(y.diff().dropna()) sp = sp_est.get_fitted_params().get("sp") y_train, y_test = temporal_train_test_split(y=y, test_size=7) # fh = range(1, 7+1) # Одно и тоже fh = ForecastingHorizon(y_test.index, is_relative=False) forecaster = AutoETS(auto=True, sp=sp) forecaster.fit(y_train, fh=fh) y_pred = forecaster.predict() rmse = mean_squared_error(y_test, y_pred, square_root=True) print(f"RMSE: {rmse}") plot_series(y_train[-30:], y_test, y_pred, labels=["Тренировочные данные", "Тестовые данные", "Прогноз"], title=source); y_pred = forecaster.fit_predict(y, fh=range(1, 7+1)) plot_series(y[-30:], y_pred, labels=["Исходные данные", "Прогноз"]);

В результате получатся прогнозы каждого канала отдельно:

RMSE: 14.51167840868573
RMSE: 14.51167840868573
Прогноз на неделю вперед
Прогноз на неделю вперед
RMSE: 13.428761193935136
RMSE: 13.428761193935136
Прогноз на неделю вперед
Прогноз на неделю вперед
RMSE: 12.707287737822499
RMSE: 12.707287737822499
Прогноз на неделю вперед
Прогноз на неделю вперед
RMSE: 20.491963781768874
RMSE: 20.491963781768874
Модели сложно предсказывать, когда данных мало
Модели сложно предсказывать, когда данных мало
RMSE: 1.492855746504661
RMSE: 1.492855746504661
Несмотря на низкую ошибку, такой прогноз вряд ли можно называть удачным
Несмотря на низкую ошибку, такой прогноз вряд ли можно называть удачным
RMSE: 1.439218659182318
RMSE: 1.439218659182318
Часто модель просто рисует какой-то средний уровень
Часто модель просто рисует какой-то средний уровень
RMSE: 0.35574552297004924
RMSE: 0.35574552297004924
Тут уже предсказывается среднее количество трафика за неделю. А еще я удалил половину источников трафика, где было совсем мало данных.
Тут уже предсказывается среднее количество трафика за неделю. А еще я удалил половину источников трафика, где было совсем мало данных.

Можно ли улучшить этот результат? Конечно можно! Из этой библиотеки sktime я еще и половины ее возможностей не выжал.

Как правило это достигается за счет:

0. Предобработки данных: удаление выбросов (реализовано) и фильтрации/преобразовании данных (не реализовано).

1. Декомпозици трафика и предсказания каждой компоненты отдельно (ETS модель уже делает это)

2. Подбора гиперпараметров модели (не реализовано).

3. Усреднения прогнозов моделей (не реализовано).

Выводы:

1. Машинное обучение - это не космическая наука, и при правильном подходе можно получить хороший результат за 50 строк кода.

2. На полностью автоматических настройках моделей с автоматическим определением сезонности можно получать хорошие прогнозы вообще не вникая в процессы "под капотом".

3. Данный подход можно использовать для автоматизации предиктивной аналитики, поиска выбросов, аномалий, точек перехода и много чего еще.

В следующих уроках я хочу рассмотреть:

  • Разделение временного ряда на компоненты
  • Автоматический выбор модели для прогноза
  • Подбор гиперпараметров
  • Кросс-валидацию
  • Бектестинг

Всем спасибо, кто дочитал. Ставьте лайки, пишите комментарии, чтобы ускорить выход следующих частей.

11
Начать дискуссию