Инсталляция пакета tsibble
Пакет tsibble можно установить обычным образом из хранилища CRAN:
install.packages("tsibble")
Данные, используемые в примерах
В приведенных ниже примерах использованы данные по стоимости 22 различных криптовалют на момент закрытия торгов в период с 1 января 2018 г. по 6 декабря 2019 г. Эти данные были собраны с сайта CoinMarketCap. Скрипт для сбора данных, а также весь код для воспроизведения примеров, можно найти в репозитории ranalytics/tsibble_format. Данные имеют простую структуру:
require(readr)
(dat <- read_csv("../data/cypto_coins.csv"))
# A tibble: 15,510 x 3
y ds coin
<dbl> <date> <chr>
1 7547 2019-12-06 bitcoin
2 7448. 2019-12-05 bitcoin
3 7252. 2019-12-04 bitcoin
4 7320. 2019-12-03 bitcoin
5 7322. 2019-12-02 bitcoin
6 7424. 2019-12-01 bitcoin
7 7570. 2019-11-30 bitcoin
8 7761. 2019-11-29 bitcoin
9 7463. 2019-11-28 bitcoin
10 7532. 2019-11-27 bitcoin
# ... with 15,500 more rows
Столбец y - это стоимость ($) криптовалюты coin, отмеченная в день ds.
Графически эти данные выглядят так:
require(ggplot2)
dat %>%
ggplot(., aes(ds, y, group = coin)) +
geom_line() +
scale_y_log10() +
theme_bw()
Рис. 1 |
Формат данных tsibble
Как отмечено выше, пакет tsibble предназначен для создания объектов с данными временных рядов в соответствии с принципами "опрятных данных", а именно:- данные хранятся в табличном виде;
- в такой таблице должны присутствовать как минимум два столбца - со значениями наблюдаемой во времени переменной и с упорядоченными по возрастанию (т.е. от прошлого к будущему) временными отметками (столбец с временными отметками называется индексирующим - index);
- кроме того, в таблицу могут входить одна или несколько группирующих переменных (key) - значения этих переменных указывают на принадлежность каждого наблюдения к соответствующему временному ряду;
- любое наблюдение в таблице можно уникально идентифицировать по сочетанию значений индексирующей и группирующих переменных.
Для создания объектов класса tsibble служит функция as_tsibble(), которая имеет следующие аргументы:
- x - объект с данными, подлежащий преобразованию в объект класса tsibble (это может быть, например, числовой вектор, матрица, таблица с данными (data.frame или tibble) и др.).
- index - переменная с временными отметками (указывается без кавычек). Допускается использование временных отметок на шкале от наносекунд до года.
- key - одна или несколько группирующих переменных, уникально определяющих каждый хранящийся в таблице временной ряд. Названия переменных указываются без кавычек и объединяются с помощью функции конкатенации c(). По умолчанию этот аргумент равен NULL, т.е. предполагается, что группирующих переменных в таблице нет.
- regular - логический аргумент, указывает на регулярность учета хранящихся в таблице наблюдений. Значение TRUE (принято по умолчанию) предполагает, что учет выполнялся с одинаковым интервалом (например, каждую минуту, час, день, и т.п.).
- validate - логический аргумент (по умолчанию равен TRUE), позволяющий выполнить проверку уникальности каждого наблюдения по сочетанию значений переменных index и key. Если вы уверены, что каждое наблюдение уникально, то эту проверку можно отключить (FALSE), что приведен к более быстрому выполнению команды as_tsibble() в случае с большими наборами данных.
- .drop - логический аргумент, позволяющий исключить из таблицы "пустые" временные ряды, т.е. такие сочетания значений группирующих переменных, для которых значения x отсутствуют.
require(tsibble)
(dat_ts <- as_tsibble(dat, key = coin, index = ds))
# A tsibble: 15,510 x 3 [1D]
# Key: coin [22]
y ds coin
<dbl> <date> <chr>
1 74.5 2018-01-01 augur
2 79.5 2018-01-02 augur
3 77.5 2018-01-03 augur
4 73.7 2018-01-04 augur
5 72.8 2018-01-05 augur
6 77.5 2018-01-06 augur
7 80.2 2018-01-07 augur
8 98.1 2018-01-08 augur
9 91.9 2018-01-09 augur
10 103. 2018-01-10 augur
# ... with 15,500 more rows
Если внимательно присмотреться, можно увидеть некоторые внешние отличия полученной таблицы dat_ts от исходной dat:
Обратите внимание на то, что перед вычислением агрегированных показателей мы сначала сгруппировали данные по каждой криптовалюте с помощью функции group_by_key(). Поскольку в нашем примере есть только одна группирующая переменная и она была указана при создании таблицы dat_ts, при вызове group_by_key() не было необходимости указывать эту группирующую переменную в явном виде.
На приведенном ниже рисунке показан результат вычисления среднемесячной стоимости анализируемых крипотовалют:
- R теперь "знает", что таблица dat_ts является объектом класса tsibble с 15510 наблюдениями, учтенными с дневным интервалом (см. комментарий "# A tsibble: 15,510 x 3 [1D]");
- у таблицы dat_ts есть группирующая переменная coin с 22 уровнями (см. "# Key: coin [22]");
- все наблюдения каждого временного ряда упорядочены по возрастанию значений индексирующей переменной ds.
Однако это всего лишь внешние различия. Гораздо важнее то, что к данным из таблицы dat_ts теперь можно применять методы анализа, специфичные для временных рядов, и делать это обычным для инструментов tidyverse образом. Рассмотрим некоторые примеры.
Агрегирование по календарному периоду
Обычной задачей при работе с временными рядам является расчет агрегированных показателей за определенный календарный период (например, средние значения за неделю, месяц, квартал и т.п.). В пакете tsibble для таких вычислений используется связка из двух функций: index_by() + summarise(). Первая из этих функций входит в состав пакета tsibble и аналогична group_by() из пакета dplyr. Как следует из ее названия, index_by() группирует данные по заданному пользователем календарному периоду. Для указания необходимого периода используются такие функции из пакета tsibble, как yearweek() (неделя), yearmonth() (месяц) и yearquarter() (квартал). Кроме того, можно также использовать базовую as.Date() и многие функции из пакета lubridate. Вторая функция из указанной выше связки - summarise() - входит в состав пакета dplyr и служит для вычисления соответствующих агрегированных величин. В качестве примера рассчитаем среднемесячную стоимость каждой криптовалюты, а также общее число наблюдений, учтенных в каждом месяце:
require(dplyr)
monthly <- dat_ts %>%
group_by_key() %>%
index_by(year_month = ~ yearmonth(.)) %>%
summarise(
avg_y = mean(y),
n = n()
)
monthly
# A tsibble: 528 x 4 [1M]
# Key: coin [22]
coin year_month avg_y n
<chr> <mth> <dbl> <int>
1 augur 2018 Jan 84.1 31
2 augur 2018 Feb 51.1 28
3 augur 2018 Mar 35.9 31
4 augur 2018 Apr 32.5 30
5 augur 2018 May 45.8 31
6 augur 2018 Jun 34.5 30
7 augur 2018 Jul 31.9 31
8 augur 2018 Aug 21.7 31
9 augur 2018 Sep 14.7 30
10 augur 2018 Oct 13.1 31
# ... with 518 more rows
Обратите внимание на то, что перед вычислением агрегированных показателей мы сначала сгруппировали данные по каждой криптовалюте с помощью функции group_by_key(). Поскольку в нашем примере есть только одна группирующая переменная и она была указана при создании таблицы dat_ts, при вызове group_by_key() не было необходимости указывать эту группирующую переменную в явном виде.
На приведенном ниже рисунке показан результат вычисления среднемесячной стоимости анализируемых крипотовалют:
monthly %>%
ggplot(., aes(year_month, avg_y, group = coin)) +
geom_line() +
scale_y_log10() +
theme_bw()
Рис. 2 |
Агрегирование по скользящему окну
Еще одной распространенной задачей при работе с временными рядами является расчет агрегированных показателей в пределах скользящего блока ("окна") данных. В пакете tsibble для таких вычислений есть несколько функций, в частности функции со следующими приставками в названии:- slide_ - выполняют вычисления в пределах перекрывающихся окон размером .size (размер "нахлеста" можно контролировать с помощью аргумента .step);
- tile_ - вычисления ведутся в пределах следующих друг за другом, но неперекрывающихся окон размером .size;
- stretch_ - исходная ширина окна .init постепенно увеличивается на .step временных отметок.
В зависимости от типа переменной, по которой ведутся вычисления, названия перечисленных выше функций заканчиваются на lgl (логические переменные), int (целочисленные переменные), dbl (числовые переменные), или chr (текстовые переменные). Например, при работе с числовыми переменными агрегирование по скользящему окну с перекрывающимися наблюдениями выполняется с помощью функции slide_dbl().
Приведенный ниже анимированный рисунок поможет понять суть этих вычислений (заимствован из официальной документации по пакету tsibble):
Приведенный ниже анимированный рисунок поможет понять суть этих вычислений (заимствован из официальной документации по пакету tsibble):
Рис. 3 |
В качестве примера вычислим скользящую среднюю стоимость каждой криптовалюты в пределах окна шириной в 7 дней:
Обратите внимание: описанные выше функции для агрегирования по скользящему окну будут, скорее всего, исключены из пакета tsibble. Вместо них авторы tsibble рекомендуют начать использовать похожие функции из пакета slide.
Для начала можно воспользоваться функцией has_gaps(), чтобы узнать о наличии хотя бы одного пропущенного наблюдения в каждом из анализируемых временных рядов:
Как видим, в нашем наборе данных пропущенных наблюдений нет - все значения столбца .gaps в полученной таблице с результатами проверки равны FALSE. Посмотрим, что получится, если мы случайным образом удалим несколько строк из таблицы dat_ts:
Теперь пропущенные наблюдения есть в каждом временном ряду. С помощью функции scan_gaps() можно выяснить, какие именно наблюдения отсутствуют в каком из рядов:
Функция count_gaps() дает более развернутый отчет - в частности, она определяет длину каждого "пробела" в каждом временном ряду:
Как видим, большинство "пробелов" в наших данных имеют длину в одно наблюдение (что неудивительно, учитывая, что при создании таблицы dat_ts_na мы удалили данные совершенно случайным образом). Эти пробелы можно легко визуализировать с помощью пакета ggplot2:
Теперь, когда мы выяснили наличие пропущенных наблюдений и характер их распределения, хорошей практикой будет в явном виде добавить эти пропушенные наблюдения в таблицу с данными, заменив их на стандартные в таких случаях значения NA. Для этого служит функция fill_gaps():
Вместо NA пропущенные наблюдения можно заменить любыми другими подходящими ситуации значениями. Например, мы можем заменить их медианными значениями y, рассчитанными отдельно по каждому временному ряду (это, правда, не очень хорошая идея, учитывая наличие четко выраженных трендов в каждом ряду; изобразите получающиеся данные графически, чтобы понять почему это плохая идея):
dat_ts %>%
group_by_key() %>%
mutate(moving_avg_y = slide_dbl(y, ~mean(.), .size = 7))
# A tsibble: 15,510 x 4 [1D]
# Key: coin [22]
# Groups: coin [22]
y ds coin moving_avg_y
<dbl> <date> <chr> <dbl>
1 74.5 2018-01-01 augur NA
2 79.5 2018-01-02 augur NA
3 77.5 2018-01-03 augur NA
4 73.7 2018-01-04 augur NA
5 72.8 2018-01-05 augur NA
6 77.5 2018-01-06 augur NA
7 80.2 2018-01-07 augur 76.5
8 98.1 2018-01-08 augur 79.9
9 91.9 2018-01-09 augur 81.7
10 103. 2018-01-10 augur 85.3
# ... with 15,500 more rows
Обратите внимание: описанные выше функции для агрегирования по скользящему окну будут, скорее всего, исключены из пакета tsibble. Вместо них авторы tsibble рекомендуют начать использовать похожие функции из пакета slide.
Обработка пропущенных наблюдений
Очень часто на практике можно столкнуться с пропущенными наблюдениями во временных рядах. Это может стать проблемой при использовании многих методов анализа и моделирования, которые предполагают наличие полных наборов упорядоченных во времени наблюдений. В пакете tsibble есть несколько функций, позволяющих выполнить полную диагностику данных на наличие пропущенных наблюдений, а также восстановить такие наблюдения, используя любую подходящую ситуации логику.Для начала можно воспользоваться функцией has_gaps(), чтобы узнать о наличии хотя бы одного пропущенного наблюдения в каждом из анализируемых временных рядов:
has_gaps(dat_ts)
# A tibble: 22 x 2
coin .gaps
<chr> <lgl>
1 augur FALSE
2 bitcoin FALSE
3 cardano FALSE
4 chainlink FALSE
5 dash FALSE
6 decred FALSE
7 dogecoin FALSE
8 eos FALSE
9 ethereum FALSE
10 iota FALSE
11 litecoin FALSE
12 maker FALSE
13 monero FALSE
14 nano FALSE
15 neo FALSE
16 qtum FALSE
17 stellar FALSE
18 tether FALSE
19 tezos FALSE
20 tron FALSE
21 xrp FALSE
22 zcash FALSE
Как видим, в нашем наборе данных пропущенных наблюдений нет - все значения столбца .gaps в полученной таблице с результатами проверки равны FALSE. Посмотрим, что получится, если мы случайным образом удалим несколько строк из таблицы dat_ts:
set.seed(42)
dat_ts_na <- dat %>%
sample_n(., 15000) %>%
as_tsibble(., key = coin, index = ds)
has_gaps(dat_ts_na)
# A tibble: 22 x 2
coin .gaps
<chr> <lgl>
1 augur TRUE
2 bitcoin TRUE
3 cardano TRUE
4 chainlink TRUE
5 dash TRUE
6 decred TRUE
7 dogecoin TRUE
8 eos TRUE
9 ethereum TRUE
10 iota TRUE
11 litecoin TRUE
12 maker TRUE
13 monero TRUE
14 nano TRUE
15 neo TRUE
16 qtum TRUE
17 stellar TRUE
18 tether TRUE
19 tezos TRUE
20 tron TRUE
21 xrp TRUE
22 zcash TRUE
Теперь пропущенные наблюдения есть в каждом временном ряду. С помощью функции scan_gaps() можно выяснить, какие именно наблюдения отсутствуют в каком из рядов:
scan_gaps(dat_ts_na)
# A tsibble: 509 x 2 [1D]
# Key: coin [22]
coin ds
<chr> <date>
1 augur 2018-01-06
2 augur 2018-02-19
3 augur 2018-03-29
4 augur 2018-04-06
5 augur 2018-04-23
6 augur 2018-05-27
7 augur 2018-06-03
8 augur 2018-06-12
9 augur 2018-06-27
10 augur 2018-09-15
# ... with 499 more rows
Функция count_gaps() дает более развернутый отчет - в частности, она определяет длину каждого "пробела" в каждом временном ряду:
(gaps <- count_gaps(dat_ts_na))
# A tibble: 496 x 4
coin .from .to .n
<chr> <date> <date> <int>
1 augur 2018-01-06 2018-01-06 1
2 augur 2018-02-19 2018-02-19 1
3 augur 2018-03-29 2018-03-29 1
4 augur 2018-04-06 2018-04-06 1
5 augur 2018-04-23 2018-04-23 1
6 augur 2018-05-27 2018-05-27 1
7 augur 2018-06-03 2018-06-03 1
8 augur 2018-06-12 2018-06-12 1
9 augur 2018-06-27 2018-06-27 1
10 augur 2018-09-15 2018-09-15 1
# ... with 486 more rows
Как видим, большинство "пробелов" в наших данных имеют длину в одно наблюдение (что неудивительно, учитывая, что при создании таблицы dat_ts_na мы удалили данные совершенно случайным образом). Эти пробелы можно легко визуализировать с помощью пакета ggplot2:
gaps %>%
ggplot(., aes(x = coin)) +
geom_linerange(aes(ymin = .from, ymax = .to)) +
geom_point(aes(y = .from)) +
geom_point(aes(y = .to)) +
coord_flip() +
theme_bw()
Рис. 4 |
Теперь, когда мы выяснили наличие пропущенных наблюдений и характер их распределения, хорошей практикой будет в явном виде добавить эти пропушенные наблюдения в таблицу с данными, заменив их на стандартные в таких случаях значения NA. Для этого служит функция fill_gaps():
dat_ts_na_filled <- dat_ts_na %>%
fill_gaps()
dat_ts_na_filled
# A tsibble: 15,510 x 3 [1D]
# Key: coin [22]
y ds coin
<dbl> <date> <chr>
1 74.5 2018-01-01 augur
2 79.5 2018-01-02 augur
3 77.5 2018-01-03 augur
4 73.7 2018-01-04 augur
5 72.8 2018-01-05 augur
6 NA 2018-01-06 augur
7 80.2 2018-01-07 augur
8 98.1 2018-01-08 augur
9 91.9 2018-01-09 augur
10 103. 2018-01-10 augur
# ... with 15,500 more rows
Вместо NA пропущенные наблюдения можно заменить любыми другими подходящими ситуации значениями. Например, мы можем заменить их медианными значениями y, рассчитанными отдельно по каждому временному ряду (это, правда, не очень хорошая идея, учитывая наличие четко выраженных трендов в каждом ряду; изобразите получающиеся данные графически, чтобы понять почему это плохая идея):
dat_ts_na_filled_median <- dat_ts_na %>%
group_by_key() %>%
fill_gaps(y = median(y, na.rm = TRUE))
dat_ts_na_filled_median
# A tsibble: 15,510 x 3 [1D]
# Key: coin [22]
# Groups: coin [22]
y ds coin
<dbl> <date> <chr>
1 74.5 2018-01-01 augur
2 79.5 2018-01-02 augur
3 77.5 2018-01-03 augur
4 73.7 2018-01-04 augur
5 72.8 2018-01-05 augur
6 15.3 2018-01-06 augur
7 80.2 2018-01-07 augur
8 98.1 2018-01-08 augur
9 91.9 2018-01-09 augur
10 103. 2018-01-10 augur
# ... with 15,500 more rows
Вы пробовали использовать новый объект для прогностических библиотек?
Насколько я понимаю, теперь вместо
dat_ts %>%
group_by_key() %>%
mutate(moving_avg_y = slide_dbl(y, ~mean(.), .size = 7))
следует писать
require(slider)
...
dat_ts %>%
group_by_key() %>%
mutate(moving_avg_y = slide_dbl(y, mean, .before=6, .complete = TRUE))
чтобы получить аналогичный результат.
Отправить комментарий