Эконометрика в ритейле: как не потратить миллионы на заведомо неэффективные эксперименты

Всем привет! На связи команда ad-hoc аналитики X5 Tech. Если вы уже знакомы с нашими статьями, то наверняка знаете, что нашей ключевой темой является А/Б тестирование. Важной составляющей А/Б теста является дизайн: для успешного проведения эксперимента необходимо оценить размер тестовой и контрольной групп, зафиксировав предварительно ожидаемый эффект. Но возникает вопрос: как убедиться в обоснованности гипотезы и рассчитать ожидаемые эффекты от инициативы?

В статье мы рассмотрим ключевые понятия из эконометрики, такие как коинтеграция и модель коррекции ошибок, и продемонстрируем их применение на ретроспективных данных. Мы подробно разберём, как использовать эти инструменты для анализа взаимосвязей между временными рядами. В качестве практического примера с помощью функции импульсного отклика мы проведём количественную оценку ожидаемого влияния повышения комплектности персонала на списания на выбранном кейсе.

Представьте, что к вам пришёл заказчик — например, представитель отдела HR — и выдвинул следующую гипотезу: повышение комплектности персонала ведёт к снижению списаний в магазинах продуктовой сети. Под списаниями в данном случае понимаются списанные с учёта товары, которые не были реализованы и ушли в убыток компании. Комплектность персонала — это отношение числа отработанных часов к числу запланированных часов. Логика проста: чем ближе количество часов к плановому, тем больше времени уделяется выкладке товаров, контролю запасов и улучшению обслуживания. Таким образом, перед заказчиком стоит вопрос об окупаемости инвестиций в персонал: если компании придётся потратить определённую сумму денег для увеличения комплектности, окупится ли эта затрата через снижение потерь? Итак, наша цель — понять, на сколько процентов потенциально снизятся потери при повышении комплектности на X%.

Коинтеграция

Исторически в эконометрике для анализа взаимосвязей в данных с явной временной зависимостью было разработано понятие коинтеграции. Давайте познакомимся с ним подробнее. 

Разностью порядка k называют последовательное вычитание предыдущего значения ряда из текущего, повторенное k раз. Например, для ряда X_t, первая разность определяется как X_t - X_{t-1}​, вторая разность как (X_t - X_{t-1}) - (X_{t-1} - X_{t-2}) и так далее.

Временной ряд называется интегрированным порядка k, если разности k-го порядка являются стационарными рядами, и при этом разности 0, …, k-1порядков не стационарны. Обозначается это следующим образом:  

X_t \sim I(k).

По определению стационарный временной ряд является I(0)процессом. 

Мы говорим, что два I(k)временных ряда X_t и Y_tкоинтегрированы, когда существует линейная комбинация Z_t = \alpha X_t + \beta Y_t \sim I(k-d), где 0<d\leq k. Вектор (\alpha, \beta)^T называется коинтеграционным вектором. 

Чаще всего на практике мы сталкиваемся с нестационарными I(1) временными рядами. В частности, если два интегрированных временных ряда первого порядка X_t, Y_t \sim I(1) коинтегрированы, то это означает, что существует их стационарная линейная комбинация \alpha X_t + \beta Y_t \sim I(0). Из данной комбинации можно выразить Y_t, в итоге мы получим следующее соотношение, переобозначив коэффициенты:

Y_t = \delta + \gamma X_t + u_t,

где \mathbb{E} u_t = 0, u_t \sim I(0), то есть u_t является стационарным временным рядом. Данное уравнение называется коинтеграционным

Какая интуиция лежит за этим понятием? Коинтеграция двух временных рядов означает, что несмотря на то, что каждый из рядов может быть не стационарен сам по себе (например, представлять собой случайные блуждания), между ними существует долгосрочная взаимосвязь. Иными словами, временные ряды «притягиваются» друг к другу через некую устойчивую зависимость, несмотря на свою индивидуальную изменчивость.

dc2187e43b97271a12882f7a60127600.png

Оказывается, для коинтегрированных временных рядов выполняются несколько замечательных свойств:  

  1.  Первое свойство

Наверняка вы уже заметили сходство коинтеграционного уравнения Y_t = \delta + \gamma X_t + u_t с моделью линейной регрессии y = \delta + \gamma x + \varepsilon. И если два ряда коинтегрированы, то \gamma в коинтеграционном уравнении можно оценивать через OLS, при этом оценка будет суперсостоятельной. Иными словами, для оценки нам достаточно воспользоваться методом наименьших квадратов, при этом \hat{\gamma} будет сходиться к истинному значению \gamma очень быстро. 

  1.   Второе свойство

Пара I(1) временных рядов является коинтегрированной тогда и только тогда, когда существует представление её динамики в виде модели VECM (векторной модели коррекции ошибок). Данный результат известен как теорема Грейнджера о представлении. 

Если с первым свойством всё понятно, то второе требует некоторых пояснений. Давайте подробнее посмотрим на то, что такое VECM. 

VECM

Модель коррекции ошибок (VECM) специально разработана для обработки нестационарных рядов с учётом как краткосрочной динамики, так и долгосрочных равновесных отношений между несколькими временными рядами.

Рассмотрим два временных ряда X_t, Y_t \sim I(1). Мы ожидаем, что их разности будут иметь стационарные представления. Например, они могут быть представлены двумя стационарными AR (k)-процессами:

\begin{align*}\Delta Y_t &= \alpha^Y + \eta_1^Y \Delta Y_{t-1} + \cdots + \eta_k^Y \Delta Y_{t-k} + \epsilon_t^Y, \\\Delta X_t &= \alpha^X + \eta_1^X \Delta X_{t-1} + \cdots + \eta_k^X \Delta X_{t-k} + \epsilon_t^X.\end{align*}

Допустим, что X_tи Y_t коинтегрированы. Тогда по определению существует коэффициент \beta, такой, что Y_t - \beta X_t \sim I(0). Но в приведённых выше AR-процессах пока что нет членов, отвечающих за взаимосвязь между рядами. Ряды не зависят друг от друга. Давайте попробуем добавить дополнительное стационарное слагаемое вида Y_{t-1} - \gamma X_{t-1} к правым частям, чтобы включить связь искусственно:  

\begin{align*}\Delta Y_t &= \alpha^Y + \eta_1^Y \Delta Y_{t-1} + \cdots + \eta_k^Y \Delta Y_{t-k} + \theta_Y (Y_{t-1} - \gamma X_{t-1}) + \epsilon_t^Y, \\\Delta X_t &= \alpha^X + \eta_1^X \Delta X_{t-1} + \cdots + \eta_k^X \Delta X_{t-k} + \theta_X (Y_{t-1} - \gamma X_{t-1}) + \epsilon_t^X.\end{align*}

Данные уравнения являются представлением динамики двух временных рядов в виде модели VECM. Так как изначально ряды разностей были стационарны, после прибавления слагаемых Y_{t-1} - \gamma X_{t-1} итоговая сумма также останется стационарной. 

Дополнительно \Delta Y_t можно включить слагаемые вида \Delta X_{t - i} и наоборот. 

В целях иллюстрации рассмотрим случай, когда \theta_Y \leq 0, \theta_X \geq 0. Если Y_t выросло слишком сильно и вышло за пределы «равновесного состояния» в стационарном процессе Z_t = \alpha Y_t - \beta X_t \sim I(0), то Y_t должно затем упасть (ввиду отрицательности \theta_y) и/или в ответ должно вырасти значение X_t. Таким образом, пара временных рядов остаётся коинтегрированной, то есть будет находиться в «равновесии».

d0f994f867b33b79d30fec7bf18ffccf.png

Тестирование рядов на коинтеграцию

Как проверить ряды на коинтеграцию? Для этого существует статистический тест Йохансена, позволяющий определить ранг коинтеграции — размерность пространства коинтеграционных векторов. В двумерном случае, если два I(1) ряда коинтегрированы, то ранг коинтеграции равен 1. 

Нулевая гипотеза теста — отсутствие коинтеграции между рядами. Если гипотеза отвергается, можно оценить количество линейно независимых коинтеграционных векторов.

Подробнее с математикой, лежащей в основе теста Йохансена, можно ознакомиться в данной статье. 

Имплементация теста Йохансена

from typing import List, Tuple

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from statsmodels.tsa.vector_ar import vecm
from statsmodels.tsa.vector_ar.vecm import coint_johansen, select_order
from tqdm import tqdm


def johansen_trace_test(
  pair: Tuple[np.ndarray, np.ndarray],
  k_ar_diff: int,
  significance_level: float = 0.05
) -> int:
    """
    Тест Йохансена на коинтеграцию.
    Проверяет гипотезы о числе коинтеграционных связей:
    H_0: r = r_0 (число коинтеграционных связей равно r_0) против альтернативы H_1: r_0 < r <= K,
    где K - общее количество временных рядов.
    Тест проводится на основе trace statistic
    Параметры:
    -----------
    pair : tuple of numpy arrays
        Кортеж, содержащий два временных ряда (x, y)  
    k_ar_diff : int
        Количество лагов для включения в модель
 
    significance_level : float, optional
        Уровень значимости
    Возвращает:
    --------
    int
        Число коинтеграционных связей между временными рядами на заданном уровне значимости
    """
    df = pd.DataFrame({'x': pair[0], 'y': pair[1]})
    johansen_test = coint_johansen(df, det_order=0, k_ar_diff=k_ar_diff)
 
    # Критические значения
    trace_stat = johansen_test.lr1
    critical_values = johansen_test.cvt[:, {0.1: 2, 0.05: 1, 0.01: 0}[significance_level]]
 
    # Ранг коинтеграции
    num_cointegrating_rels = sum(trace_stat > critical_values)
 
    return num_cointegrating_rels

Выбор порядка

Процесс выбора оптимального числа лагов в модели VECM чаще всего основывается на информационных критериях:

BIC(k) = -2 \log(L) + k\log(n),

где L — функция правдоподобия, k — число учитываемых лагов, n — длина временных рядов. Лаги в данном случае — значение переменных на предыдущих шагах времени, которые учитываются в модели для описания текущей динамики. Функция правдоподобия L показывает, насколько хорошо модель объясняет данные, а параметры k и n используются для вычисления штрафа за сложность модели.

В контексте временных рядов минимизация AIC и BIC позволяет найти такой минимальный параметр k, при котором модель лучше всего описывает данные.

Код

from statsmodels.tsa.vector_ar.vecm import select_order


def select_k_ar_diff(df: pd.DataFrame, maxlags: int, criterion: str = 'aic') -> int:
   """
   Выбор оптимального количества лагов для модели VECM с помощью информационного критерия.
   """
   # Определяем порядок лагов на основе выбранного критерия
   result = select_order(df, maxlags=maxlags, deterministic='n')
  
   if criterion == 'aic':
       return result.aic
   elif criterion == 'bic':
       return result.bic

Поиск взаимосвязей между списаниями и комплектностью персонала

Приступим к основной части анализа — проверке взаимосвязи между показателями списаний и комплектности персонала. Давайте вспомним постановку изначальной задачи. Нам необходимо понять, есть ли взаимосвязь между списаниями и комплектностью, а также численно оценить степень зависимости, если она действительно существует. 

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

\begin{align*}\Delta x_t &= \text{trend} + \epsilon_{1,t}, \\\Delta y_t &= -\gamma \left( y_{t-1} + \frac{\alpha_1}{\alpha_2} x_{t-1} \right) + \epsilon_{2,t},\end{align*}

где 0 < \gamma < 1.

Первый ряд представляет собой случайное блуждание с трендом и будет означать растущую комплектность. Второй ряд, зависящий от первого, и связанный с ним коинтеграционным слагаемым, будет обозначать списания в магазинах. Как мы уже знаем из теоремы Грейнджера о представлении, в таком случае теоретически мы должны получить два коинтегрированных временных ряда. 

Зафиксируем коэффициенты так, чтобы увеличение комплектности сопровождалось снижением списаний (что было бы логичным). Длину временных рядов выберем равной 365 дням.

Сгенерируем пару временных рядов комплектности и списаний для каждого из 1000 магазинов:

Код генерации

def generate_vecm_processes(
   num_pairs: int,
   series_length: int,
   seed: int = None,
   trend: float = 0.05,
   alpha1: float = 1.0,
   alpha2: float = -1.0,
   gamma: float = 0.5,
   initial_values: Tuple[float, float] = (0.0, 0.0),
   error_std: Tuple[float, float] = (1.0, 1.0)
) -> List[Tuple[np.ndarray, np.ndarray]]:
   """
   Генерирует num_pairs пар коинтегрированнвых временных рядов.
   Первый ряд в паре обозначает комплектность, второй - списания.
   """
   if seed is not None:
       np.random.seed(seed)
  
   series_pairs = []
  
   for pair_idx in range(num_pairs):
       # Инициализация
       x = np.zeros(series_length)
       y = np.zeros(series_length)
       x[0], y[0] = initial_values
      
       # Генерируем шум из нормального распределения
       epsilon1 = np.random.normal(0, error_std[0], series_length)
       epsilon2 = np.random.normal(0, error_std[1], series_length)
      
       for t in range(series_length - 1):
           # x обозначает комплектность, которая влияет на списания,
           # следовательно, коинтеграционное слагаемое в x_{t+1} не включается
           x[t + 1] = trend + x[t] + epsilon1[t]
           # в y_{t+1} присутствует коинтеграционное слагаемое
           y[t + 1] = y[t] - gamma * (y[t] + (alpha1 / alpha2) * x[t]) + epsilon2[t]
      
       series_pairs.append((x, y))
  
   return series_pairs


# приведение временных рядов к разумному масштабу
def scale_series(series_pairs, x_start, x_end, y_start, y_end):
   scaled_pairs = []
   for x, y in series_pairs:
       x_scaled = (x - x[0]) / (x[-1] - x[0]) * (x_end - x_start) + x_start
       y_scaled = (y - y[0]) / (y[-1] - y[0]) * (y_end - y_start) + y_start
       scaled_pairs.append([x_scaled, y_scaled])
 
   return scaled_pairs


num_pairs = 1000  # число пар временных рядов (каждая пара соответствует одному магазину)
series_length = 365  # длины временных рядов
seed = 42  # seed для воспроизводимости
trend = 0.1  # положительный тренд по комплектности
alpha1 = 0.9  # параметр коинтеграционного уравнения
alpha2 = 0.8  # параметр коинтеграционного уравнения
gamma = 0.2  # степень связи рядов
initial_values = (0.0, 0.0)  # стартовые значения
error_std = (1.0, 2.0)  # стандартные откленения для шумов

# сгенерируем пары коинтегрированных рядов
series_pairs = generate_vecm_processes(
   num_pairs=num_pairs,
   series_length=series_length,
   seed=seed,
   trend=trend,
   alpha1=alpha1,
   alpha2=alpha2,
   gamma=gamma,
   initial_values=initial_values,
   error_std=error_std
)

series_pairs = scale_series(series_pairs, x_start=0.8, x_end=0.9, y_start=0.04, y_end=0.02)

fig, ax1 = plt.subplots(figsize=(8, 4))

color = 'tab:blue'
ax1.set_xlabel('День')
ax1.set_ylabel('Комплектность', color=color)
ax1.plot(series_pairs[0][0], color=color)
ax1.tick_params(axis='y', labelcolor=color)
ax2 = ax1.twinx()

color = 'tab:orange'
ax2.set_ylabel('Списания', color=color)
ax2.plot(series_pairs[0][1], color=color)
ax2.grid(False)
ax2.tick_params(axis='y', labelcolor=color)
plt.show()

Пример пары сгенерированных временных рядов

Пример пары сгенерированных временных рядов

Предположим, что сгенерированные данные соответствуют реальным наблюдениям. Первый шаг, который нам необходимо сделать, — это проверить ряды на коинтеграцию. Сделаем это с помощью теста Йохансена:

Проверка на коинтеграцию

maxlags = int(np.around((4 * num_pairs / 100) ** (1 / 4)))  # эвристика верхней границы для перебора лагов
coint_dict = {}  # словарь с рангами коинтеграции для каждой пары
k_ar_diff_dict = {}


for idx, pair in tqdm(enumerate(series_pairs), total=num_pairs):
  # Определяем количество лагов для включения в модель
  df_pair = pd.DataFrame({'x': pair[0], 'y': pair[1]})
  k_ar_diff = select_k_ar_diff(df_pair, maxlags)
  r = johansen_trace_test(pair, 1)

  k_ar_diff_dict[idx] = k_ar_diff
  coint_dict[idx] = r

coint_df = pd.DataFrame(coint_dict.items(), columns=['pair_id', 'coint_rank'])
k_ar_diff_df = pd.DataFrame(k_ar_diff_dict.items(), columns=['pair_id', 'k_ar_diff'])

# Статистика по рангам коинтеграции
print(coint_df['coint_rank'].value_counts())
coint_df['coint_rank'].value_counts())

f9dc40469446a6a7e48150d85e008ed1.png

Отлично. В подавляющем числе случаев ряды коинтегрированы (ранг коинтеграции равен 1), а в части случаев вообще являются стационарными (ранг коинтеграции равен 2). Делаем вывод, что между рядами действительно существует долгосрочная связь.

Следующий шаг — понять, как эта зависимость выражается в числах.

Функция импульсного отклика

Итак, переходим к самому интересному: как численно оценить влияние одного временного ряда на другой.

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

1) Первый подход — построить регрессию вида Y_t = \delta + \gamma X_t и оценить коэффициент \hat{\gamma} с помощью OLS. Если два ряда коинтегрированы, то OLS-оценка \hat{\gamma} будет суперсостоятельной. В этом случае \hat{\gamma} можно интерпретировать как эластичность: на сколько процентов изменится Y при изменении X на 1%, если предварительно прологарифмировать ряды. Такой подход позволяет учесть долгосрочные зависимости между рядами.

Однако, несмотря на простоту и интуитивность, OLS имеет ограничение: данный метод не учитывает краткосрочные эффекты. Коинтегрированные временные ряды имеют как долгосрочные зависимости (то есть постоянная связь между рядами, которая сохраняется на длительном горизонте времени), так и краткосрочные (временные колебания перед установлением долгосрочного равновесия), которые OLS игнорирует. 

2) Второй подход, который позволяет учесть также и краткосрочные зависимости, — это функция импульсного отклика (Impulse Response Function, IRF). IRF описывает траекторию реакции переменной Y_t на одномоментное возмущение в X_t. Важная характеристика функции импульсного отклика — она показывает, как быстро система восстанавливается к своему равновесию после возмущения. Например, если в модели два коинтегрированных временных ряда X и Y, функция импульсного отклика продемонстрирует, как одномоментное увеличение X повлияет на Y, и как долго этот эффект будет длиться. С более строгим и подробным теоретическим изложением можно ознакомиться в книге. 

Так как функция импульсного отклика позволяет учесть не только долгосрочная эффекты, но и краткосрочные, именно её мы будем использовать для расчёта влияния увеличения комплектности на потери. Давайте применим IRF, чтобы количественно оценить степень влияния и получить абсолютное снижение списаний в ответ на увеличение комплектности на один процентный пункт:

Применение IRF

maxlags = int(np.around((4 * num_pairs / 100) ** (1 / 4)))  # эвристика верхней границы для перебора лагов
coint_dict = {}  # словарь с рангами коинтеграции для каждой пары
irf_dict = {}  # словар со значениями IRF для каждой пары
horizon = 100  # max число шагов в будущее после которого считаем что irf сойдется


for idx, pair in tqdm(enumerate(series_pairs), total=num_pairs):
    df_pair = pd.DataFrame({'x': pair[0] * 100, 'y': pair[1]}) # умножаем x (комплектность) на 100 для перевода в проценты

    # Определяем количество лагов для включения в модель VECM
    k_ar_diff = select_k_ar_diff(df_pair, maxlags)
    r = johansen_trace_test(pair, k_ar_diff)
    if r != 1:
        continue
    coint_dict[idx] = r


 model = vecm.VECM(df_pair, k_ar_diff=k_ar_diff, coint_rank=r, deterministic='co')
 model_fit = model.fit()
 irf_dict[idx] = model_fit.irf(horizon).irfs[:, 1, 0][-1]  # x -> y (комплектность -> списания)


# Отфильтруем выбросы
irfs = np.array(list(irf_dict.values()))
low_quantile = np.quantile(irfs, 0.025)
high_quantile = np.quantile(irfs, 0.975)
irfs_filtered = irfs[(irfs >= low_quantile) & (irfs <= high_quantile)]


# Визуализация
plt.figure()
plt.hist(irfs_filtered * 100, bins=10)
plt.xlabel('IRF x 10$^{-2}$')
plt.ylabel('Число магазинов')
plt.title('Гистограмма IRF')
plt.show()

328c438bfde1e09614b054ec212a896d.png

В нашем подходе мы интерпретируем функцию импульсного отклика для пары временных рядов, как случайную величину. Соответственно, мы можем находить для неё точечные оценки параметров распределения и построить доверительные интервалы. Давайте найдём среднее значение IRF, а также доверительный интервал для него, исходя из всех доступных данных для магазинов сети:  

Код

def calculate_mean_and_bootstrap_ci(data: np.ndarray, num_bootstrap: int = 1000, alpha: float = 0.95, seed: float = 42) -> Tuple[float, Tuple[float, float]]:
    """
    Рассчитывает среднее значение и доверительный интервал с помощью бутстрепа

    Параметры:
    -----------
    data : np.ndarray
        Входные данные
    num_bootstrap : int, optional
       Количество бутстреп-выборок
    alpha : float, optional
      Уровень значимости для доверительного интервала

    Возвращает:
    --------
    float
        Среднее
    tuple of float
        Нижняя и верхняя границы доверительного интервала
    """
    np.random.seed(seed)
    mean_value = np.mean(data)
    bootstrap_means = []
    for _ in range(num_bootstrap):
        bootstrap_sample = np.random.choice(data, size=len(data), replace=True)
        bootstrap_means.append(np.mean(bootstrap_sample))

    lower_bound = np.percentile(bootstrap_means, (1 - alpha) / 2 * 100)
    upper_bound = np.percentile(bootstrap_means, (1 + alpha) / 2 * 100)

    return mean_value, (lower_bound, upper_bound), bootstrap_means


mean, (lower_bound, upper_bound), bootstrap_means = calculate_mean_and_bootstrap_ci(irfs_filtered)
scaled_mean, scaled_lower, scaled_upper = mean * 100, lower_bound * 100, upper_bound * 100
print(f"{mean:.6f} ± {(upper_bound - mean):.6f}")

plt.figure(figsize=(6, 4))
plt.hist(np.array(bootstrap_means) * 1e2, bins=10)
plt.axvline(scaled_mean, color='red', linestyle='--', linewidth=2, label=f"Mean {scaled_mean:.3f}e-2")
plt.axvline(scaled_lower, color='green', linestyle='--', linewidth=2, label=f"Lower bound {scaled_lower:.3f}e-2")
plt.axvline(scaled_upper, color='green', linestyle='--', linewidth=2, label=f"Upper bound {scaled_upper:.3f}e-2")
plt.ylim([0, 340])

plt.xlabel('Среднее IRF x 10$^{-2}$')
plt.title('Распределение средних значений IRF, полученное с помощью бутстрепа')
plt.legend()

plt.grid(True)
plt.show()

\overline{\text{IRF}} = -0.002013 \pm 0.000019

de76a1ea2c9e2dac4660730f8584d905.png

Таким образом, мы можем заключить, что среднее IRF значимо отклоняется от нуля. Интерпретация полученного значения следующая: в среднем по сети увеличение показателя комплектности относительно текущих средних значений на один процентный пункт скоррелировано с уменьшением потерь на 0.2 процентных пункта.

Интерпретация результатов и предостережения

Очевидно, что при росте комплектности невозможно бесконечное снижение списаний: в реальных условиях существует нижний предел, который определяется непредсказуемыми факторами. Таким образом, рано или поздно списания должны выйти на насыщение. Применив линейную модель VECM в комбинации с IRF, мы линейно приблизили поведение зависимости списаний от комплектности в небольшой окрестности значений рядом с текущим средним уровнем комплектности в магазинах сети. 

a006e4b2fbcabcff4206d1013934b4c2.png

С практической точки зрения, функция импульсного отклика сама по себе не показывает причинно-следственную связь, поэтому для точной оценки эффекта потребуется A/B тест. Однако функция импульсного отклика полезна для предварительного анализа: она позволяет оценить потенциальные финансовые выгоды и затраты до запуска пилота. Например, если мы хотим зафиксировать снижение списаний на 1 процентный пункт, это потребует увеличения комплектности персонала приблизительно на 5 процентных пунктов для тестовой группы магазинов. Таким образом, мы даём бизнес-заказчику инструмент для принятия управленческого решения: «проводить/не проводить эксперимент».

Подведём итоги

В этой статье на примере исследования взаимосвязи комплектности персонала и списаний магазина мы показали, как эконометрический анализ с использованием модели VECM, свойства коинтеграции, а также функции импульсного отклика могут помочь бизнесу экономить миллионы рублей на заведомо сомнительных экспериментах. 

Надеемся, что наш подход будет полезен и вам для дизайна инициатив и позволит принимать более взвешенные управленческие решения, минимизируя финансовые риски.

Над статьёй работали:  

Сорокин Вадим — DA/DS ad-hoc X5 Tech

Лавров Денис — Team Lead ad-hoc X5 Tech

Полушкин Андрей — Team Lead ad-hoc X5 Tech

Сахнов Александр — Head of DA/DS X5 Tech

© Habrahabr.ru