Введение

Добрый день, уважаемые читатели.

После написания предыдущего поста про анализ временных рядов на Python, я решил исправить замечания, которые были указаны в комментариях, но при их исправлении я столкнулся с рядом проблем, например при построении сезонной модели ARIMA, т.к. подобной функции а пакете statsmodels я не нашел. В итоге я решил использовать для этого функции из R, а поиски привели меня к библиотеке rpy2 которая позволяетиспользовать функции из библиотек упомянутого языка.

У многих может возникнуть вопрос “зачем это нужно?”, ведь проще просто взять R и выполнить всю работу в нем. Я полность согласен с этим утверждением, но как мне кажется , если данные требуют предварительной обработки, то ее проще произвести на Python, а возможности R использовать при необходимости именно для анализа.

Кроме этого, будет показано как интегрировать результаты выдачи работы функции R в IPython Notebook.

Установка и настройки Rpy2

Для начала работы надо установть rpy2. Сделать это можно с помощью команды: pip install rpy2 Надо отметить что, для работы данной библиотеки необходим установленный язык R. Скачать его можно с офф. сайта.

Следующиее что необходимо сделать, это добавить нужные системные переменные. Для Windows добавить следующие переменные:

Также производилась установка на Mac OS X, там данных манипуляций не потребовалось.

Начало работы

Итак, если вы работаете в IPython Notebook, нужно добавить инструкцию:

%load_ext rmagic

Данное расширение позволяет вызывать некторые функции R через rpy2, и выводит результат прямо в консоль IPython Notebook, что очень удобно (ниже будет показано как это сделать). Подробнее написано здесь.

Теперь же загрузим, нужные библиотеки:

from pandas import read_csv, DataFrame, Series
import statsmodels.api as sm
import rpy2.robjects as R
from rpy2.robjects.packages import importr
import pandas.rpy.common as com
from pandas import date_range

Теперь, как и в предыдущей статье, загрузим данные и перейдем к недельным интервалам:

dataset = read_csv('DataSets/tovar_moving.csv',';', index_col=['date'], parse_dates=['date'], dayfirst=True)
otg = dataset.qty
w = otg.resample('w', how='sum')
w.plot(figsize=(12,6))
<matplotlib.axes.AxesSubplot at 0x10bc50e50>

png

Итак, из графика можно заметить ежегодную сезонность (52 недели) и ярко выраженный тренд. Поэтому перед построением модели нам необходимо избавиться от тренда и сезонности.

Предварительный анализ данных

Итак для начала, прологорифмируем исходный ряд, для выравнивания значений:

w_log = log(w)
w_log.plot(figsize=(12,6))
<matplotlib.axes.AxesSubplot at 0x10bc84f50>

png

Как видно у нас в графике присутствует сезонность и соответственно ряд не стационарен. Проверим это с помощью теста Дикки-Фулера, который проверяет гипотизу о наличии единичных корней и соотвтвенно если они есть ряд будет не стационарным. Как провести данный тест с помощью библиотеки statsmodels, я показывал в прошлый раз. Сейчас я продемонстрирую как это можно сделать с помощью функции adf.test() из R.

Итак, данная функция находится в R-ой библиотеке tseries. Она предназначена для анализа временных рядов и устанавливается дополнительно. Загрузить нужную библиотеку можно с помощью функции importr().

stats = importr('stats')
tseries = importr('tseries')

Можно заметить, что кроме tseries, мы загрузили еще и библиотеку stats. Она нам понадобитьсядля преобразования типов.

Теперь необходимо перевести данные из типа Python в тип понятный R. Сделать это можно с помощью функции convert_to_r_dataframe() на вход которой подается DataFrame, а на выходе получается вектор для R

r_df = com.convert_to_r_dataframe(DataFrame(w_log))

Итак, вектор есть следующим шагом надо перевести его в формат временного ряда. Для этого в R существует функция ts(), вызов ее будет выглядеть так:

y = stats.ts(r_df)

Предварительная подготовка данных закончена и мы можем вызвать нужную нам функцию:

ad = tseries.adf_test(y, alternative="stationary", k=52)

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

Теперь в переменной ad содержится R-объект. Его структура описана в виде списка, описание которого мне найти не удалось. Поэтому с помощью визуального анализа я написал код, который выводит результат работы функции в понятном виде:

a = ad.names[:5]
{ad.names[i]:ad[i][0] for i in xrange(len(a))}
{'alternative': 'stationary',
 'method': 'Augmented Dickey-Fuller Test',
 'p.value': 0.23867869477446427,
 'parameter': 52.0,
 'statistic': -2.8030060277420006}

Исходя из результатов теста, на исходный ряд не стационарен. Соответственно теперь проверим на стационарность ряд первых разностей.

Для начала получим первые разности с помощью Python, а затем применим ADF-тест:

diff1lev = w.diff(periods=1).dropna()
print 'p.value: %f' % sm.tsa.adfuller(diff1lev, maxlag=52)[1]
p.value: 0.000000
diff1lev.plot(figsize=(12,6))
<matplotlib.axes.AxesSubplot at 0x107d827d0>

png

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

Для этого нужно взять сезонную разность, от нашего получившегося ряда. Подробнее можно прочитать тут. Если полученный ряд будет стационармы необходимо будет вязть его превую разность и проверить ее.

Возьмем сезонную разность:

diff1lev_season = diff1lev.diff(52).dropna()

Проверим ее на стационарность с помощью ADF-тест из R:

r_df = com.convert_to_r_dataframe(DataFrame(diff1lev_season))
y = stats.ts(r_df)
ad = tseries.adf_test(y, alternative="stationary", k=52)
a = ad.names[:5]
{ad.names[i]:ad[i][0] for i in xrange(len(a))}
{'alternative': 'stationary',
 'method': 'Augmented Dickey-Fuller Test',
 'p.value': 0.551977997289418,
 'parameter': 52.0,
 'statistic': -2.0581183466564776}

Итак, ряд не стационарен, возьмем его первые разности:

diff1lev_season1lev = diff1lev_season.diff().dropna()
print 'p.value: %f' % sm.tsa.adfuller(diff1lev_season1lev, maxlag=52)[1]
p.value: 0.000395

Получившийся ряд стационарен. Теперь можно перейти к построению модели

Построение модели.

Итак, предварительный анализ закончен, и мы можем перейти к построению сезонной модели ARIMA (SARIMA).

Общий вид данной модели $ARIMA(p,d,q)(P,D,Q)_s$

В этой модели параметры обозначают следующее:

Как определять p, d, q, я показывал в прошлый раз. Сейчас я опишу определять порядок сезонных составляющих P,D,Q.

Начнем с определения параметра D. Он определет порядок интегрированности сезонной разности, т.е. в нашем случае он равен 1. Для определения P и Q нам как и прежде надо построить коррелограммы ACF и PACF

fig = plt.figure(figsize=(12,8))
ax1 = fig.add_subplot(211)
fig = sm.graphics.tsa.plot_acf(diff1lev_season1lev.values.squeeze(), lags=150, ax=ax1)
ax2 = fig.add_subplot(212)
fig = sm.graphics.tsa.plot_pacf(diff1lev_season1lev, lags=150, ax=ax2)

png

Из гарфика PACF видно, что порядок AR будет p=4, а по ACF видно, что порядок MA q = 13, т.к. 13 лаг - это последний лаг отличный от 0.

Теперь перейдем к сезонным составляющим. Для их оценки надо смотреть на лаги кратные размеру сезонности, т.е., если для нашего примера, сезонность 52, то надо рассматривать лаги 52, 104, 156, …

В нашем случае параметры P и Q будут равны 0 (это видно если посмотреть на ACF и PACF на указанных выше лагах).

В результате наших исследований мы получили модель $SARIMA(4,1,13)(0,1,0)_{52}$

Как было указано в начале данной статьи, что найти способы построения данной модели на Python я не нашел, поэтому я принял решение воспользоваться для это функцией arima() из R. В качестве параметров ей передаются порядок модели ARIMA и, при необходимости, порядок сезонной составляющей. Но перед вызовом ее необходимо подготовить некоторые данные.

Для начала переведем наш исходный набор в формат R и переведем в формат временного ряда.

r_df = com.convert_to_r_dataframe(DataFrame(w))
y = stats.ts(r_df)

Порядок модели передается в качестве вектора R, поэтому давайте создадим его:

order = R.IntVector((4,1,13))

Так же в качестве параметра сезонной составляющей передается список, который содержит ее порядок и размер периода:

season = R.ListVector({'order': R.IntVector((0,1,0)), 'period' : 52})

Теперь мы готовы к тому, чтобы построить модель:

model = stats.arima(y, order = order, seasonal=season)

Итак наша модель готова и можно перейти к построению прогноза на ее основе.

Проверка адекватности модели

Итак для проверки адекватности модели надо проверить соответвуют ли остатки модели “белому шуму”. Проверим это проведя Q тест и проверим корреляцию остатков. Для этого в R существует функция tsdiag(), в качестве параметра передается модель и количество лагов для теста.

Вызвать данную функцию можно так:

%Rpush model
%R tsdiag(model, 100)

png

В первой строке инструкция %Rpush загружает объекты для использования в R. Иструкция %R во второй строке вызывает код в формате языка R. Данная конструкция работает в IPython Notebook.

Из графиков выше можно заметить, что остатки независимы (это видно по ACF). Кроме того из графика Q-статистики можно заметить что во всех точках значение p-value больше уровня значимости, из этого можно сделать вывод, что остатки, с большой вероятностью, являются “белым шумом”.

Прогнозирование

Для прогонизования нужно подгрузить библиотеку forecast

forecast = importr('forecast')

Вывести результы прогнозирования можно двумя способами.

Способ 1. Это использовать возможности интеграции IPython и R. Который был показан в предыдущем разделе:

%R plot(forecast(model))

png

array([[  2.86328082e+006,   2.92010002e+006,   2.67021783e+006,
          3.32234825e+006,   3.71572899e+006,   3.16813442e+006,
          3.71351448e+006,   3.58056771e+006,   4.10672158e+006,
          3.81160883e+006,   3.05720475e+006,   3.34268377e+006,
          3.51950286e+006,   3.60200198e+006,   3.69009069e+006,
          3.99953781e+006,   3.74356371e+006,   3.41705537e+006,
          3.46488074e+006,   3.58134846e+006,   2.98063306e+006,
          3.11447713e+006,   2.95918780e+006,   3.85447359e+006,
          3.38496693e+006,   3.54933738e+006,   3.85404795e+006,
          4.02177934e+006,   3.81534290e+006,   3.71247703e+006,
          4.08066095e+006,   3.09674911e+006,   3.45913030e+006,
          4.12116394e+006,   4.38041861e+006,   3.67277888e+006,
          4.01818926e+006,   4.30706638e+006,   4.48838829e+006,
          3.94701251e+006,   3.48191975e+006,   3.27344791e+006,
          2.98512555e+006,   1.78136352e+006,   3.15110750e+006,
          2.87244114e+006,   3.48920326e+006,   3.78156529e+006,
          4.11129897e+006,   4.08708516e+006,   4.15437300e+006,
          2.64186310e+006,   3.47456204e+006,   3.53057776e+006,
          3.27733315e+006,   3.93565640e+006,   4.32168433e+006,
          3.78070425e+006,   4.32168058e+006,   4.19031857e+006,
          4.71762108e+006,   4.41922407e+006,   3.66919343e+006,
          3.95040203e+006,   4.13044368e+006,   4.21131835e+006,
          4.29928330e+006,   4.61033621e+006,   4.35186729e+006,
          4.02804212e+006,   4.07361934e+006,   4.19145488e+006,
          3.59044283e+006,   3.72360240e+006,   3.56967591e+006,
          4.46332474e+006,   3.99532143e+006,   4.15864631e+006,
          4.46377298e+006,   4.63171833e+006,   4.42457782e+006,
          4.32267723e+006,   4.68989169e+006,   3.70673217e+006,
          4.06871653e+006,   4.73075371e+006,   4.99034249e+006,
          4.28215545e+006,   4.62817093e+006,   4.91653065e+006,
          5.09817809e+006,   4.55671468e+006,   4.09148610e+006,
          3.88331023e+006,   3.59462213e+006,   2.39120274e+006,
          3.76070143e+006,   3.48214146e+006,   4.09894005e+006,
          4.39115180e+006,   4.72109904e+006,   4.69666591e+006,
          4.76412827e+006,   3.25152171e+006],
       [  3.27000000e+002,   3.28000000e+002,   3.29000000e+002,
          3.30000000e+002,   3.31000000e+002,   3.31000000e+002,
          3.30000000e+002,   3.29000000e+002,   3.28000000e+002,
          3.27000000e+002,   3.26000000e+002,   3.25000000e+002,
          3.24000000e+002,   3.23000000e+002,   3.22000000e+002,
          3.21000000e+002,   3.20000000e+002,   3.19000000e+002,
          3.18000000e+002,   3.17000000e+002,   3.16000000e+002,
          3.15000000e+002,   3.14000000e+002,   3.13000000e+002,
          3.12000000e+002,   3.11000000e+002,   3.10000000e+002,
          3.09000000e+002,   3.08000000e+002,   3.07000000e+002,
          3.06000000e+002,   3.05000000e+002,   3.04000000e+002,
          3.03000000e+002,   3.02000000e+002,   3.01000000e+002,
          3.00000000e+002,   2.99000000e+002,   2.98000000e+002,
          2.97000000e+002,   2.96000000e+002,   2.95000000e+002,
          2.94000000e+002,   2.93000000e+002,   2.92000000e+002,
          2.91000000e+002,   2.90000000e+002,   2.89000000e+002,
          2.88000000e+002,   2.87000000e+002,   2.86000000e+002,
          2.85000000e+002,   2.84000000e+002,   2.83000000e+002,
          2.82000000e+002,   2.81000000e+002,   2.80000000e+002,
          2.79000000e+002,   2.78000000e+002,   2.77000000e+002,
          2.76000000e+002,   2.75000000e+002,   2.74000000e+002,
          2.73000000e+002,   2.72000000e+002,   2.71000000e+002,
          2.70000000e+002,   2.69000000e+002,   2.68000000e+002,
          2.67000000e+002,   2.66000000e+002,   2.65000000e+002,
          2.64000000e+002,   2.63000000e+002,   2.62000000e+002,
          2.61000000e+002,   2.60000000e+002,   2.59000000e+002,
          2.58000000e+002,   2.57000000e+002,   2.56000000e+002,
          2.55000000e+002,   2.54000000e+002,   2.53000000e+002,
          2.52000000e+002,   2.51000000e+002,   2.50000000e+002,
          2.49000000e+002,   2.48000000e+002,   2.47000000e+002,
          2.46000000e+002,   2.45000000e+002,   2.44000000e+002,
          2.43000000e+002,   2.42000000e+002,   2.41000000e+002,
          2.40000000e+002,   2.39000000e+002,   2.38000000e+002,
          2.37000000e+002,   2.36000000e+002,   2.35000000e+002,
          2.34000000e+002,   2.33000000e+002],
       [  2.32000000e+002,   2.31000000e+002,   2.30000000e+002,
          2.29000000e+002,   2.28000000e+002,               nan,
                      nan,               nan,   1.33685735e-312,
          3.43763318e-312,               nan,               nan,
                      nan,               nan,               nan,
          2.71615461e-312,   1.08221785e-312,               nan,
          3.18299369e-313,   1.95223613e-312,   3.24665356e-312,
          1.06099790e-312,               nan,               nan,
                      nan,               nan,   2.41907520e-312,
                      nan,               nan,   3.20421364e-312,
                      nan,   2.54639495e-312,               nan,
          1.54905693e-312,               nan,               nan,
          6.22522714e-322,               nan,               nan,
          1.86735630e-312,   3.73471259e-312,               nan,
          3.33153339e-312,               nan,   1.37929726e-312,
                      nan,   2.54639495e-313,   1.88857625e-312,
          2.31584178e+077,   2.68156175e+154,               nan,
          3.62861280e-312,               nan,  -3.17631000e+005,
          1.27835000e+005,  -6.29130000e+004,  -5.41132000e+005,
          6.80077000e+005,  -1.65948000e+005,   3.16545000e+005,
         -5.98810000e+005,   3.03810000e+004,   7.14838000e+005,
          7.33400000e+003,  -5.11113000e+005,  -1.63681000e+005,
          2.48866000e+005,  -8.33843000e+005,   3.27470000e+005,
          7.05885000e+005,   2.23397000e+005,  -6.64684000e+005,
          2.06484000e+005,   4.83778000e+005,  -8.78890000e+004,
         -6.18262000e+005,  -1.17473000e+005,  -2.12759000e+005,
         -2.56776000e+005,  -6.36738000e+005,   1.70896600e+006,
          5.09440000e+004,   8.31320000e+004,  -2.30170000e+005,
          3.42785000e+005,  -1.56883000e+005,   5.75541000e+005,
         -1.06717900e+006,  -1.79730693e-007,  -9.69908273e-009,
          3.18329571e-009,   1.14056818e-008,   8.97916272e-008,
          1.58627183e-008,   2.77473949e-008,   3.45141778e-008,
          5.56086009e-008,   5.76118665e-008,   3.53865842e-008,
          1.07705809e-007,   4.42468744e-008,  -2.60276451e-009,
          9.48250696e-008,   7.76501800e-008]])

Способ 2. Второй способ это сделать прогноз с помощью библиотеки forecast, а потом перевести результат в во временную серию pandas и вывести их на экран. Код который будет выполнять написан ниже:

f = forecast.forecast(model) #строим прогноз
pred = [i[1] for i in f[3].iteritems()] #парсим результат
dt = date_range(w.index[-1], periods=len(pred)+1,freq='W')[1:] #создаем индекс из дат
pr = Series(pred, index = dt)  #записываем данные в TimeSeries

Теперь выведем результат на экран

w.plot(figsize=(12,6))
pr.plot(color = 'red')
<matplotlib.axes.AxesSubplot at 0x1066732d0>

png

Заключение

В качестве заключения хотелось бы отметить, что для более точного анализа сезонности нужно иметь данные с 7-10 сезонами. Кроме того, хотелось бы сказать большое спасибо werwooolf за оказанную помощь в процессе подготовки статьи.

В данной статье я постарался показать как строить сезонную модель ARIMA, а также показал как можно использовать связку языков R и Python для анализа данных. Я думаю специалисты и по одному и подругому языку найдут как эффективно применить описанную связку.