Creating MA, AR, and ARMA Graphs#

[1]:
import numpy as np
import matplotlib.pyplot as plt
[2]:
import halerium.core as hal

By using this Application, You are agreeing to be bound by the terms and conditions of the Halerium End-User License Agreement that can be downloaded here: https://erium.de/halerium-eula.txt

the MA model (moving average)#

An MA model is defined as

\[\begin{split}y_t = c + \epsilon_t + \sum_{j=1}^q b_j \epsilon_{t-j}\\ \epsilon_t \hookleftarrow N(\epsilon_t| 0, \sigma^2)\end{split}\]

we can create this explicitly in Halerium using the TimeIndex.

[3]:
q = 5
sigma = 1.
sigma_obs = 1.e-2

ma_graph = hal.Graph("graph")
with ma_graph:
    hal.StaticVariable("b", shape=(q,), mean=0, variance=1)
    hal.StaticVariable("c", shape=(), mean=0, variance=1)

    hal.Variable("epsilon", mean=0, variance=sigma**2)
    hal.Variable("y")
    y.mean = sum([
        b[i] * epsilon[hal.TimeIndex-i-1]
        for i in range(q)
    ]) + c
    y.variance = (sigma/100)**2
    # TODO: or the next
    hal.Variable("observed", mean=y, variance=sigma_obs**2)
    # this "observed" variable is strictly speaking not part of the model,
    # but it is recommended when we want to fit data to the model as it allows us
    # to include observation noise.

The [TimeIndex...] is a shorthand convenience for the TimeShift operator with initial values set to 0.

So instead of epsilon[TimeIndex-i-1] we could write TimeShift(epsilon, shift=-i-1, initial_values=0).

Note that in the Graph definition we did no specify the length of the time series. Halerium will utilize the dynamic axis of the Variable class for the time steps, which means we specify the length of the time series with n_data.

[4]:
data = hal.get_data_linker(
    {ma_graph.b: np.ones(q),
     ma_graph.c: 0.},
    n_data=150
)
m = hal.get_generative_model(ma_graph, data)
plt.plot(m.get_example(ma_graph.y))
[4]:
[<matplotlib.lines.Line2D at 0x193ad6fc940>]
../../_images/examples_06_time_series_01-creating-arma-graphs_8_1.png

the AR model (autoregressive)#

An AR model is defined as

\[y_t = c + \epsilon_t + \sum_{i=1}^p a_i y_{t-i}\]

which as an Halerium graph is

[5]:
p = 7
sigma = 1.
sigma_obs = 1.e-2

ar_graph = hal.Graph("graph")
with ar_graph:
    hal.StaticVariable("a", shape=(p,), mean=0, variance=1)
    hal.StaticVariable("c", shape=(), mean=0, variance=1)

    hal.Variable("y")
    y.mean = sum([
        a[i] * y[hal.TimeIndex-i-1]
        for i in range(p)
    ]) + c
    y.variance = sigma**2 # since there is no moving average in the AR model
                          # time we include epsilon as the variance of y.

    hal.Variable("observed", mean=y, variance=sigma_obs**2)
    # this "observed" variable is strictly speaking not part of the model,
    # but it is recommended when we want to fit data to the model as it allows us
    # to include observation noise.

Let’s look at an example for the AR process

[6]:
data = hal.get_data_linker(
    {ar_graph.a: np.ones(p)/p,
     ar_graph.c: 0.},
    n_data=150
)
m = hal.get_generative_model(ar_graph, data)
plt.plot(m.get_example(ar_graph.y))
[6]:
[<matplotlib.lines.Line2D at 0x193ae10e160>]
../../_images/examples_06_time_series_01-creating-arma-graphs_13_1.png

the ARMA model#

The ARMA model combines the autoregressive and moving average concept

\[y_t = c + \epsilon_t + \sum_{i=1}^p a_i y_{t-i} + \sum_{j=1}^q b_j \epsilon_{t-j}\]
[7]:
p = 7
q = 5
sigma = 1.
sigma_obs = 1.e-2

arma_graph = hal.Graph("graph")
with arma_graph:
    hal.StaticVariable("a", shape=(p,), mean=0, variance=1)
    hal.StaticVariable("b", shape=(q,), mean=0, variance=1)
    hal.StaticVariable("c", shape=(), mean=0, variance=1)

    hal.Variable("epsilon", mean=0, variance=sigma**2)

    hal.Variable("y")

    ar_contrib = sum([
        a[i] * y[hal.TimeIndex-i-1]
        for i in range(p)
    ])

    ma_contrib = sum([
        b[i] * epsilon[hal.TimeIndex-i-1]
        for i in range(q)
    ])

    y.mean = c + ar_contrib + ma_contrib
    y.variance = (sigma/100)**2
    # OR
    hal.Variable("observed", mean=y, variance=sigma_obs**2)
    # this "observed" variable is strictly speaking not part of the model,
    # but it is recommended when we want to fit data to the model as it allows us
    # to include observation noise.
[8]:
data = hal.get_data_linker(
    {arma_graph.a: np.ones(p)/p,
     arma_graph.b: np.ones(q),
     arma_graph.c: 0.},
    n_data=150
)
m = hal.get_generative_model(arma_graph, data)
plt.plot(m.get_example(arma_graph.y))
[8]:
[<matplotlib.lines.Line2D at 0x193ae18df40>]
../../_images/examples_06_time_series_01-creating-arma-graphs_16_1.png

the ARIMA model#

The arima model is an integrated ARMA model. So the ARMA process is simply intergrated \(d\) times.

In Halerium it can be defined like this

[9]:
d = 3
p = 7
q = 5
sigma = 1.
sigma_obs = 1.e-2

arima_graph = hal.Graph("graph")
with arima_graph:
    hal.StaticVariable("a", shape=(p,), mean=0, variance=1)
    hal.StaticVariable("b", shape=(q,), mean=0, variance=1)
    hal.StaticVariable("c", shape=(), mean=0, variance=1)

    hal.Variable("epsilon", mean=0, variance=sigma**2)

    hal.Variable("x")

    ar_contrib = sum([
        a[i] * x[hal.TimeIndex-i-1]
        for i in range(p)
    ])

    ma_contrib = sum([
        b[i] * epsilon[hal.TimeIndex-i-1]
        for i in range(q)
    ])

    x.mean = c + ar_contrib + ma_contrib

    # x follows the ARMA model

    # to integrate we define a helper variable int_i
    temp = x
    for i in range(d):
        temp_integrator = hal.Variable(f"int_{i}")
        temp_integrator.mean = temp_integrator[hal.TimeIndex-1] + temp
        temp = temp_integrator

    hal.Variable("y")
    y.mean = temp

    hal.Variable("observed", mean=y, variance=sigma_obs**2)
    # this "observed" variable is strictly speaking not part of the model,
    # but it is recommended when we want to fit data to the model as it allows us
    # to include observation noise.
[10]:
data = hal.get_data_linker(
    {arma_graph.a: np.ones(p)/p,
     arma_graph.b: np.ones(q),
     arma_graph.c: 0.},
    n_data=150
)
m = hal.get_generative_model(arima_graph, data)
x_ex, y_ex = m.get_example([arima_graph.x, arima_graph.y])
[11]:
plt.plot(y_ex)
[11]:
[<matplotlib.lines.Line2D at 0x193adf84160>]
../../_images/examples_06_time_series_01-creating-arma-graphs_20_1.png

Note that an ARIMA model is not stationary, but can show significant growth and trends due to the integration. It is therefore often used to fit non-stationary time-series. In practice one usually differentiates the data and uses an ARMA model on the differetiated data though.

[ ]: