Skip to content

Periodic effects and broadcasting

A common practice in time series forecasting is featuring periodic effects such as day of the week or weekend effects. The arrayutils.PeriodicBroadcaster class provides this functionality. This tutorial shows how to use this to model both repeating elements of a sequence and repeating a sequence as a whole (tiling).

Repeated units

The RtPeriodicDiff and RtWeeklyDiff classes use PeriodicBroadcaster to repeat each vector element for \(\mathcal{R}(t)\) values held constant within a period.

import jax.numpy as jnp
import numpy as np
import numpyro
from pyrenew import process, deterministic
# The random process for Rt
rt_proc = process.RtWeeklyDiffARProcess(
    name="rt_weekly_diff",
    offset=0,
    log_rt_rv=deterministic.DeterministicVariable(
        name="log_rt", value=jnp.array([0.1, 0.2])
    ),
    autoreg_rv=deterministic.DeterministicVariable(
        name="autoreg", value=jnp.array([0.7])
    ),
    periodic_diff_sd_rv=deterministic.DeterministicVariable(
        name="periodic_diff_sd", value=jnp.array([0.1])
    ),
)
with numpyro.handlers.seed(rng_seed=20):
    sim_data = rt_proc(duration=30)

# Plotting the Rt values
import matplotlib.pyplot as plt

plt.step(np.arange(len(sim_data)), sim_data, where="post")
plt.xlabel("Days")
plt.ylabel("Rt")
plt.title("Simulated Rt values")

# Adding bands to mark weeks
for i in range(0, 30, 7):
    plt.axvline(i, color="black", linestyle="--", alpha=0.5)
plt.show()

The implementation of the RtWeeklyDiffARProcess (which is an instance of RtPeriodicDiffARProcess), uses repeat_until_n to repeat values: repeat_until_n(..., period_size=7). The RtWeeklyDiff class is a particular case of RtPeriodicDiff with a period size of seven.

Repeated sequences (tiling)

By specifying broadcast_type = "tile", the PeriodicBroadcaster repeats the sequence as a whole. The DayOfWeekEffect class is a particular case of PeriodicEffect with a period size of seven. We sample from a scaled Dirchlet distribution such that the sum of the samples is 7:

import numpyro.distributions as dist
from pyrenew import transformation, randomvariable

# Building the transformed prior: Dirichlet * 7
mysimplex = dist.TransformedDistribution(
    dist.Dirichlet(concentration=jnp.ones(7)),
    transformation.AffineTransform(loc=0, scale=7.0),
)

# Constructing the day of week effect
dayofweek = process.DayOfWeekEffect(
    offset=0,
    quantity_to_broadcast=randomvariable.DistributionalVariable(
        name="simp", distribution=mysimplex
    ),
)

We use the sample method to generate samples from the day of week effect:

with numpyro.handlers.seed(rng_seed=20):
    sim_data = dayofweek(duration=30)

# Plotting the effect values
import matplotlib.pyplot as plt

plt.step(np.arange(len(sim_data)), sim_data, where="post")
plt.xlabel("Days")
plt.ylabel("Effect size")
plt.title("Simulated Day of Week Effect values")

# Adding bands to mark weeks
for i in range(0, 30, 7):
    plt.axvline(i, color="black", linestyle="--", alpha=0.5)
plt.show()