Usage

Installation

Install the package with:

pip install py-ssa-lib

Example data

The examples below use the global average temperature data set included with the package:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

from py_ssa_lib.datasets.load_global_average_temperatures_df import (
    load_global_average_temperatures_df,
)

df = load_global_average_temperatures_df(rawDS=False)
df = df.T

SSA

SSA operates on a single time series.

Prepare the data

The example below selects the last 100 temperature observations for Germany and uses the first M=80 observations for fitting and the remaining 20 for testing.

country_name = "Germany"

ts = df[df["Country"] == country_name]
subset_country_ts = ts["AverageTemperature"].iloc[-100:]

M = 80
train_df = subset_country_ts.iloc[:M]
test_df = subset_country_ts.iloc[M:]

steps_to_forecast = 20

Fit the model

Choose a window length \(L\) and fit the SSA model:

from py_ssa_lib.ssa_collection import SSA

window_size = 36

ssa = SSA().fit(
    data=train_df,
    L=window_size,
)

Inspect the components

The package provides visual tools for inspecting the singular-value contributions, left singular vectors, elementary matrices and weighted correlation matrix.

import py_ssa_lib.vis_tools as vis_tools

vis_tools.plot_contributions(ssa=ssa)
vis_tools.plot_eigenvectors(ssa, components=12, ncols=4)
vis_tools.plot_elementary_matrices(ssa=ssa, components=20, ncols=5)
vis_tools.plot_weighted_correlation(ssa=ssa)

Reconstruct a component group

After inspecting the decomposition, choose the elementary components to include in the reconstruction:

denoised_components = [0, 1, 2, 3, 6, 7]

denoised = ssa.reconstruct_components(
    idx_components=denoised_components
)

denoised_df = pd.DataFrame(
    {
        country_name + "_ts_reconstructed": denoised.flatten()
    },
    index=ssa.time_index,
)

denoised_df.plot()
train_df.plot()

Forecast

Both L-forecasting and K-forecasting are available. The same component group used for reconstruction can be used for forecasting.

l_forecast = ssa.L_forecast(
    idx_components=denoised_components,
    forecast_steps=steps_to_forecast,
    return_full=False,
)

k_forecast = ssa.K_forecast(
    idx_components=denoised_components,
    forecast_steps=steps_to_forecast,
    return_full=False,
)

l_forecast_df = pd.DataFrame(
    {country_name + "_L_forecast": l_forecast.flatten()},
    index=test_df.index,
)

k_forecast_df = pd.DataFrame(
    {country_name + "_K_forecast": k_forecast.flatten()},
    index=test_df.index,
)

plt.figure(figsize=(10, 5))
plt.plot(test_df, "-o", label="True average temperature")
plt.plot(l_forecast_df, "--*", label="L-forecast")
plt.plot(k_forecast_df, "--s", label="K-forecast")
plt.legend()

ESPRIT

ESPRIT estimates the complex roots \(\mu\), their magnitudes \(\rho\), and angular frequencies \(\omega\) from a selected signal subspace.

idx_components = np.arange(ssa.L - 1)

mu, rho, omega = ssa.estimate_ESPRIT(
    idx_components=idx_components
)

vis_tools.plot_esprit_roots(
    ssa=ssa,
    idx_components=idx_components,
)

MSSA

MSSA operates on several time series simultaneously. Input data are arranged as (n_timesteps, n_series).

Prepare multivariate data

The following example extracts average-temperature time series for Italy, France and Germany and stacks them horizontally:

country_names = ["Italy", "France", "Germany"]

ts = (
    df.loc[
        df["Country"].isin(country_names),
        ["Country", "AverageTemperature"],
    ]
    .pivot(columns="Country", values="AverageTemperature")
    .dropna()
)

ts = ts[country_names]

subset_country_ts = ts.iloc[-100:]

steps_to_forecast = 80
train_df = subset_country_ts.iloc[:steps_to_forecast]
test_df = subset_country_ts.iloc[steps_to_forecast:]

Fit the model

from py_ssa_lib.ssa_collection import MSSA

window_size = 36

mssa = MSSA().fit(
    data=train_df,
    L=window_size,
)

Inspect the components

The same visualization functions can be used with MSSA:

vis_tools.plot_contributions(ssa=mssa)
vis_tools.plot_eigenvectors(mssa, components=12, ncols=4)
vis_tools.plot_elementary_matrices(ssa=mssa, components=20, ncols=5)
vis_tools.plot_weighted_correlation(ssa=mssa)

Reconstruct several time series

A single component group is used to reconstruct all time series in the MSSA model:

denoised_components = [0, 1, 2, 3]

denoised = mssa.reconstruct_components(
    idx_components=denoised_components
)

denoised_df = pd.DataFrame(
    {
        country_names[i] + "_ts_reconstructed": denoised[:, i]
        for i in range(len(country_names))
    },
    index=mssa.time_index,
)

The original and reconstructed series can be compared separately:

fig, ax = plt.subplots(3, 1, figsize=(16, 9))

for i in range(len(country_names)):
    ax[i].plot(
        train_df.iloc[:, i],
        "-o",
        label="True average temperature for " + train_df.columns[i],
    )
    ax[i].plot(
        denoised_df.iloc[:, i],
        "-*",
        label=denoised_df.columns[i],
    )

    ax[i].set_title(train_df.columns[i])
    ax[i].legend()
    ax[i].grid(alpha=0.3)

plt.tight_layout()

Forecast several time series

L- and K-forecasting return forecasts for every series included in the MSSA model:

l_forecast = mssa.L_forecast(
    idx_components=denoised_components,
    forecast_steps=steps_to_forecast,
    return_full=False,
)

k_forecast = mssa.K_forecast(
    idx_components=denoised_components,
    forecast_steps=steps_to_forecast,
    return_full=False,
)

l_forecast_df = pd.DataFrame(
    {
        country_names[i] + "_L_forecast": l_forecast[:, i]
        for i in range(len(country_names))
    },
    index=test_df.index,
)

k_forecast_df = pd.DataFrame(
    {
        country_names[i] + "_K_forecast": k_forecast[:, i]
        for i in range(len(country_names))
    },
    index=test_df.index,
)

Plot the forecasts:

fig, ax = plt.subplots(3, 1, figsize=(16, 9))

for i in range(len(country_names)):
    ax[i].plot(
        test_df.iloc[:, i],
        "-o",
        label="True average temperature for " + test_df.columns[i],
    )
    ax[i].plot(
        l_forecast_df.iloc[:, i],
        "--*",
        label=l_forecast_df.columns[i],
    )
    ax[i].plot(
        k_forecast_df.iloc[:, i],
        "--s",
        label=k_forecast_df.columns[i],
    )

    ax[i].tick_params(axis="x", labelrotation=90)
    ax[i].set_title(test_df.columns[i])
    ax[i].legend()
    ax[i].grid(alpha=0.3)

plt.tight_layout()

ESPRIT with MSSA

ESPRIT is used in the same way for MSSA:

idx_components = np.arange(mssa.L - 1)

mu, rho, omega = mssa.estimate_ESPRIT(
    idx_components=idx_components
)

vis_tools.plot_esprit_roots(
    ssa=mssa,
    idx_components=idx_components,
)

Component selection

Component grouping is guided by the singular-value contributions, left singular vectors, elementary matrices and weighted correlation matrix. Forecasting performance on a held-out test set can also be used to compare different component groups.