Принятие повседневных решений на основе науки о данных

Довольно распространенная проблема для предприятий - смотреть на данные, нанесенные на график в зависимости от времени (например, продажи с течением времени), и пытаться отделить сезонность от основного тренда. Чаще всего можно увидеть тенденцию, но она скрыта в сильной сезонной составляющей. Одна из типичных стратегий решения проблемы - отслеживать изменения в годовом исчислении (YoY), выраженные в процентах, но это может быть столь же сложно, потому что изменение YoY может колебаться от низких однозначных до высоких двузначных цифр в течение несколько дней. К счастью, есть простой математический алгоритм, который может помочь: разложение по сезонному тренду с помощью лесса, широко известного под аббревиатурой STL. Если вам интересно, почему люди не используют аббревиатуру Seasonal Trend Decomposition… хорошо подумайте о коннотации аббревиатуры, такой как ЗППП. :-)

Итак, как STL помогает лучше понять данные вашего бизнеса? STL разделяет временной ряд на сезонную составляющую, составляющую тренда и «шум»:

Y = Y_seasonal + Y_trend + Y_noise

Сезонный компонент Y_seasonal будет одинаково повторяться каждый период (например, каждый год). Это та часть, которую мы хотим изолировать, чтобы лучше видеть основную тенденцию, которая находится в Y_trend. Последний компонент Y_noise будет частью данных, которую нельзя отнести ни к сезонности, ни к «плавному» тренду; это будет переменная с распределением по Гауссу со средним нулем. Его дисперсия будет определять, насколько «изменчивым» является временной ряд.

Я хочу избежать проблем с использованием конфиденциальных бизнес-данных, поэтому позвольте использовать набор данных, который доступен в открытом доступе: данные о ценах на фондовом рынке. Это не будет идеальным набором данных, потому что цены на акции не демонстрируют сильной сезонности, но он позволит нам проиллюстрировать и понять, что делает STL, как это работает и почему вам это должно быть небезразлично. Давайте возьмем курс акций Amazon.com за период с 1 января 2015 года по 23 октября 2020 года - это примерно данные о ценах за 6 лет. Я подписчик на Quandl, через которого получаю финансовые данные. Они предоставляют API, который я использую для ежедневной загрузки данных и сохранения их в базе данных MySQL в следующем формате:

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

  1. Заполните пропущенные даты, когда рынок был закрыт, что я сделал, скопировав данные о ценах за первый предыдущий день, когда он был доступен (т.е. предположим, что цена не изменилась по сравнению с тем, что было раньше). Есть способы сделать это лучше, но достаточно легко проиллюстрировать, как работает STL.
  2. Сделайте каждый год 365 дней, что я и сделал, исключив все данные о ценах на 29 февраля в високосные годы.

Я приведу полный код R в конце, а пока позвольте мне выделить только соответствующие аналитические строки:

# Convert the data into a time series structure
start_date <- merged.data$time[1];
end_date <- merged.data$time[length(merged.data$close)];
data.ts<-ts(merged.data$close, frequency=365, start=c(as.numeric(strftime(start_date, format = "%Y")),as.numeric(strftime(start_date, format = "%j"))), end=c(as.numeric(strftime(end_date, format = "%Y")), as.numeric(strftime(end_date, format = "%j"))-1));
# Apply the seasonal trend decomposition
data.stl<-stl(data.ts, s.window = 365);

Первые две строки (например, start_date и end_date) выбирают дату начала и окончания для временного ряда, которые в данном случае будут первой и последней строкой данных. Третья строка (например, data.ts) преобразует кадр данных R во временной ряд (ts), который требуется алгоритму STL для ввода. Последняя строка (например, data.stl) применяет алгоритм STL к временному ряду с использованием окна выборки 365 (т.е. мы говорим, что сезонность будет повторяться каждые 365 дней) для извлечения сезонной составляющей. Результатом является диаграмма ниже. Поговорим о каждой строке.

Первая строка с надписью данные - это просто входные данные для алгоритма STL. В данном случае это цена закрытия для акций Amazon.com. Вторая строка с пометкой сезонный - это сезонный компонент данных временного ряда. В этом случае сезонная составляющая колеблется примерно от + 150 до + 150 долларов в течение года. Третья строка с надписью тренд - это компонент тренда данных временного ряда. Сейчас данные о ценах на фондовом рынке не самые лучшие, потому что из исходных данных было очевидно, какова была тенденция, но этот пример действительно показывает, что алгоритм STL может легко выделить основную тенденцию. Третья строка, помеченная как остаток, представляет собой шумовой компонент данных. Теперь давайте углубимся в каждую из них.

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

Сезонность Amazon такова, что максимум (например, от +100 до + 150 долларов) приходится на период с июля по сентябрь, а минимальный (например, от -100 до -150 долларов) - с декабря по апрель. Спред от минимума до пика сезонности составляет около 300 долларов в течение года, что составляет примерно +/- 10% отклонение сегодня, когда цена акций Amazon.com составляет около 3000 долларов за акцию. Итак, лучшее время для покупки акций Amazon.com - с декабря по апрель, а лучшее время для продажи акций Amazon.com - с июля по сентябрь.

Компонент тренда говорит сам за себя. Если вы владеете акциями Amazon.com с 2015 года, вы должны быть очень счастливы.

В период с 2015 по конец 2020 года цена акций Amazon выросла более чем в 6 раз. Итак, если в 2015 году у вас было 100000 долларов в акциях Amazon, к концу 2020 года эти инвестиции составили более 600000 долларов. Это эквивалент примерно 43% совокупного годового темпа роста (CAGR) за 5 лет (то есть 5-летнего CAGR), что намного превышает общую доходность фондового рынка, как вы увидите ниже (и намного выше инфляции).

Остаток или шумовая составляющая интересны. Это часть временного ряда, которая не является ни частью сезонности, ни частью тренда.

Я думаю об этом компоненте как о показателе волатильности временного ряда. Чем больше стандартное отклонение остатка, тем больший риск содержится в базовых данных. Через несколько минут я покажу вам пример того, почему я считаю этот компонент мерой риска. Для AMZN остаток имеет стандартное отклонение 106,88 доллара и среднее значение - 10,10 доллара. Остаток распределяется как гауссовская случайная величина, как показано на следующей диаграмме.

Так почему я считаю остаток мерой волатильности или риска? Позвольте мне ответить на это тем, что мы видим, когда применяем алгоритм STL к торгуемому на бирже фонду (ETF), который представляет собой тип индексного фонда, который содержит много разных акций. Смысл индексного фонда - снизить риск путем смешивания множества акций и снижения волатильности. Я буду использовать общий индексный фонд Vanguard (VTI), который инвестирует на весь фондовый рынок… точнее, он инвестирует более чем в 3600 различных отдельных акций. Вот что возвращает алгоритм STL:

Если мы сосредоточимся только на остатке, мы увидим, что он ограничен примерно от -30 до +10 долларов, в отличие от Amazon, который ограничен примерно +/- 400 долларов. Таким образом, шум VTI, как правило, намного меньше, чем то, что мы видим в AMZN, как и следовало ожидать от индексного фонда.

Остаток для VTI распределяется как гауссовская случайная величина со средним значением - 0,18 доллара США и стандартным отклонением 6,05 доллара США (против 106,88 доллара США для AMZN). Поскольку VTI состоит из более чем 3600 отдельных акций, его волатильность составляет примерно 1/18 от волатильности AMZN, чего и следовало ожидать от ETF. В обмен на гораздо более низкий риск (т. Е. Волатильность), 5-летний CAGR для VTI составил 9,7%, что составляет примерно 1/4 5-летнего CAGR для Amazon (т.е. 43% 5-летний CAGR).

Если вы немного подумаете об этом, вы поймете, что можете использовать алгоритм STL для отображения акций (и других финансовых инструментов) на четырехквадрантной диаграмме, где одна ось обозначает волатильность, а другая - N-летний CAGR. . Такой график позволит вам решить, в какие акции инвестировать, сбалансировав риск и доход. Фактически, алгоритм STL можно использовать для принятия множества других интересных повседневных решений, таких как Какой месяц в году является лучшим для подписания нового договора аренды квартиры? ».

Прежде чем я потеряю вас в изучении R-кода, если вы хотите прочитать что-то менее техническое, я рекомендую мою статью о лучших практиках приоритизации аналитической работы. Если сегодня вас интересует статистика, попробуйте эту статью об A / B-тестировании или эту статью о трех (3) наиболее важных статистических тестах. Наконец, если вы хотите очень быстро прочитать 1-2 минуты, попробуйте эту статью с восемью (8) советами по улучшению взаимодействия между наукой о данных и бизнес-пользователями.

Как и обещал, вот полный код R. Я удалил конфиденциальную информацию, такую ​​как мои пароли и URL-адреса серверов, но в остальном (почти) все, что я использовал, адаптировано к тому, как у меня хранятся данные в моем экземпляре MySQL.

library('DBI', quietly = TRUE);
library('lubridate')
user <- 'XYZ';
password <- 'XYZ';
dbname <- 'XYZ'
host <- 'XYZ';
mydb <- dbConnect(RMariaDB::MariaDB(), host=host, user=user, password=password, dbname);
rs <- dbSendQuery(mydb, "SELECT * FROM SEP WHERE ticker ='AMZN' AND date >='2015-01-01'");
data <- dbFetch(rs)
dbClearResult(rs)
names(data)[names(data) == 'date'] <- 'time'
data$time <- as.Date(data$time)
sorted.data <- data[order(data$time),]
data.length <- length(sorted.data$time)
time.min <- sorted.data$time[1]
time.max <- sorted.data$time[data.length]
all.dates <- seq(time.min, time.max, by="day")
all.dates.frame <- data.frame(list(time=all.dates))
merged.data <- merge(all.dates.frame, sorted.data, all=T)
# Identify all the dates without data (e.g., the market is closed)
x<-which(is.na(merged.data$close));
y<-which(!is.na(merged.data$close));
z<-lapply(x, function(z) y[max(which(y<z))]);
# Set missing prices to the most recent price; interpolation would be better
merged.data$volume[x]<-0;
merged.data$ticker[x]<-'AMZN';
merged.data$close[x] <- merged.data$close[unlist(z)];
# Remove any leap year data to have every year be 365 days
merged.data<-merged.data[-c(which(strftime(merged.data$time, format = "%m") == "02" & strftime(merged.data$time, format = "%d") == "29")),];
# Convert the data into a time series structure
start_date <- merged.data$time[1];
end_date <- merged.data$time[length(merged.data$close)];
data.ts<-ts(merged.data$close, frequency=365, start=c(as.numeric(strftime(start_date, format = "%Y")),as.numeric(strftime(start_date, format = "%j"))), end=c(as.numeric(strftime(
end_date, format = "%Y")), as.numeric(strftime(
end_date, format = "%j"))-1));
# Apply the seasonal trend decomposition
data.stl<-stl(data.ts, s.window = 365);


Получите доступ к экспертному обзору - Подпишитесь на DDI Intel