Local block bootstrap
Opened this issue · 0 comments
yngvem commented
One feature I think would be useful is bootstrap methods for nonstationary time-series. I am currently looking at the Local block bootstrap (LBB), which was relatively straightforward to implement.
The LBB works by assuming that the time-series is almost stationary, but with slowly changing properties (e.g. the seasons in a year). Then, it creates bootstrap samples by sampling blocks near each other and stitching them together.
I have created a prototype, which seems to work, but I don't have any unit tests yet.
import numpy as np
from arch.bootstrap.base import IIDBootstrap, _get_random_integers, ArrayLike, RandomState, Generator, Int64Array
class LocalBlockBootstrap(IIDBootstrap):
_name = "Local Block Bootstrap"
def __init__(
self,
block_size: int,
max_step: int,
*args: ArrayLike,
random_state: RandomState | None = None,
seed: None | int | Generator | RandomState = None,
**kwargs: ArrayLike,
) -> None:
super().__init__(*args, random_state=random_state, seed=seed, **kwargs)
self.block_size: int = block_size
self.max_step: int = max_step
self._parameters = [block_size, max_step]
def clone(
self,
*args: ArrayLike,
seed: None | int | Generator | RandomState = None,
**kwargs: ArrayLike,
) -> 'LocalBlockBootstrap':
block_size = self._parameters[0]
max_step = self._parameters[1]
return self.__class__(block_size, max_step, *args, random_state=None, seed=seed, **kwargs)
def update_indices(self) -> Int64Array:
num_blocks = self._num_items // self.block_size
if num_blocks * self.block_size < self._num_items:
num_blocks += 1
m = np.arange(num_blocks)
lower = np.maximum(0, self.block_size * m - self.max_step - 1)
upper = np.minimum(self._num_items - self.block_size, self.block_size * m + self.max_step - 1)
step = upper - lower
indices = lower + _get_random_integers(self.generator, step, size=len(step))
indices = indices[:, np.newaxis] + np.arange(self.block_size)[np.newaxis, :]
indices = indices.flatten()
if indices.shape[0] > self._num_items:
return indices[: self._num_items]
else:
return indices