py-ssa-lib 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- py_ssa_lib-0.0.1/MANIFEST.in +2 -0
- py_ssa_lib-0.0.1/PKG-INFO +18 -0
- py_ssa_lib-0.0.1/README.md +7 -0
- py_ssa_lib-0.0.1/pyproject.toml +3 -0
- py_ssa_lib-0.0.1/setup.cfg +47 -0
- py_ssa_lib-0.0.1/src/py_ssa_lib/MSSA.py +438 -0
- py_ssa_lib-0.0.1/src/py_ssa_lib/SSA.py +464 -0
- py_ssa_lib-0.0.1/src/py_ssa_lib/__init__.py +0 -0
- py_ssa_lib-0.0.1/src/py_ssa_lib/datasets/__init__.py +0 -0
- py_ssa_lib-0.0.1/src/py_ssa_lib/datasets/energy_consumption_df.csv +6 -0
- py_ssa_lib-0.0.1/src/py_ssa_lib/datasets/load_energy_consumption_df.py +15 -0
- py_ssa_lib-0.0.1/src/py_ssa_lib/datasets/total-primary-energy-consumption-csv-version-4.csv +225 -0
- py_ssa_lib-0.0.1/src/py_ssa_lib.egg-info/PKG-INFO +18 -0
- py_ssa_lib-0.0.1/src/py_ssa_lib.egg-info/SOURCES.txt +17 -0
- py_ssa_lib-0.0.1/src/py_ssa_lib.egg-info/dependency_links.txt +1 -0
- py_ssa_lib-0.0.1/src/py_ssa_lib.egg-info/top_level.txt +1 -0
- py_ssa_lib-0.0.1/test/test_MSSA.py +102 -0
- py_ssa_lib-0.0.1/test/test_SSA.py +99 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: py_ssa_lib
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: "This package implements MSSA and SSA in Python"
|
|
5
|
+
Home-page: https://github.com/K-Ibadullaev/py_ssa
|
|
6
|
+
Author: [
|
|
7
|
+
{ Konstantin Ibadullaev, email= <konstantin.ibadullaev.post@gmail.com>}]
|
|
8
|
+
License: GPL-3.0-or-later
|
|
9
|
+
Requires-Python: >=3.10.0
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# PY-SSA PACKAGE
|
|
13
|
+
This package contains python implementations of the **Singular Spectrum Analysis(SSA)** and **Multichannel Singular Spectrum Analysis(MSSA)**. One can use them for the time series analysis and forecasting.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
```shell
|
|
17
|
+
$ python -m pip install py_ssa
|
|
18
|
+
```
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# PY-SSA PACKAGE
|
|
2
|
+
This package contains python implementations of the **Singular Spectrum Analysis(SSA)** and **Multichannel Singular Spectrum Analysis(MSSA)**. One can use them for the time series analysis and forecasting.
|
|
3
|
+
|
|
4
|
+
## Installation
|
|
5
|
+
```shell
|
|
6
|
+
$ python -m pip install py_ssa
|
|
7
|
+
```
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
[metadata]
|
|
2
|
+
name = py_ssa_lib
|
|
3
|
+
version = 0.0.1
|
|
4
|
+
url = https://github.com/K-Ibadullaev/py_ssa
|
|
5
|
+
author = [
|
|
6
|
+
{ Konstantin Ibadullaev, email= <konstantin.ibadullaev.post@gmail.com>}]
|
|
7
|
+
description = "This package implements MSSA and SSA in Python"
|
|
8
|
+
long_description = file: README.md
|
|
9
|
+
long_description_content_type = text/markdown
|
|
10
|
+
license = GPL-3.0-or-later
|
|
11
|
+
license_files = LICENSE
|
|
12
|
+
|
|
13
|
+
[options]
|
|
14
|
+
package_dir =
|
|
15
|
+
=src
|
|
16
|
+
packages = find:
|
|
17
|
+
include_package_data = True
|
|
18
|
+
python_requires = >=3.10.0
|
|
19
|
+
install_requires =
|
|
20
|
+
numpy> = 2.0.0;
|
|
21
|
+
matplotlib> = 3.9.1;
|
|
22
|
+
scipy> = 1.14.0;
|
|
23
|
+
scikit-learn> = 1.5.1;
|
|
24
|
+
pandas> = 2.2.2;
|
|
25
|
+
|
|
26
|
+
[options.packages.find]
|
|
27
|
+
where = src
|
|
28
|
+
exclude =
|
|
29
|
+
test*
|
|
30
|
+
|
|
31
|
+
[tox:tox]
|
|
32
|
+
envlist = py310
|
|
33
|
+
isolated_build = True
|
|
34
|
+
|
|
35
|
+
[testenv]
|
|
36
|
+
deps =
|
|
37
|
+
|
|
38
|
+
pytest
|
|
39
|
+
pytest-cov
|
|
40
|
+
commands =
|
|
41
|
+
python -m pip install numpy matplotlib scipy scikit-learn pandas
|
|
42
|
+
pytest
|
|
43
|
+
|
|
44
|
+
[egg_info]
|
|
45
|
+
tag_build =
|
|
46
|
+
tag_date = 0
|
|
47
|
+
|
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import numpy as np
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
import scipy as sp
|
|
5
|
+
from matplotlib.ticker import MaxNLocator
|
|
6
|
+
from sklearn.utils.extmath import randomized_svd
|
|
7
|
+
|
|
8
|
+
class MSSA():
|
|
9
|
+
"""
|
|
10
|
+
Creates an instance of the MSSA object
|
|
11
|
+
|
|
12
|
+
Parameters
|
|
13
|
+
----------
|
|
14
|
+
Verbose: bool, outputs parameters used for an instance
|
|
15
|
+
|
|
16
|
+
Returns
|
|
17
|
+
-------
|
|
18
|
+
object of class MSSA
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, Verbose=False):
|
|
22
|
+
self.Verbose = Verbose
|
|
23
|
+
if self.Verbose==True:
|
|
24
|
+
print("MSSA is initialized")
|
|
25
|
+
|
|
26
|
+
def construct_trajectory_matrix(self):
|
|
27
|
+
"""
|
|
28
|
+
Constructs a trajectory matrix from a lagged versions of the time series
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
Takes arguments from the instance
|
|
33
|
+
|
|
34
|
+
Returns
|
|
35
|
+
-------
|
|
36
|
+
X:numpy.array, representing the trajectory matrix
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
X = np.column_stack([np.column_stack([self.X_s[i:i+self.L,s] for i in range(0,self.K)]) for s in range(self.S)])
|
|
40
|
+
return X
|
|
41
|
+
|
|
42
|
+
def decompose_trajectory_matrix(self,**kwargs):
|
|
43
|
+
"""
|
|
44
|
+
Decomposes a trajectory matrix using full svd or randomized svd
|
|
45
|
+
|
|
46
|
+
Parameters
|
|
47
|
+
----------
|
|
48
|
+
Takes arguments from the instance
|
|
49
|
+
**kwargs for the svd routine
|
|
50
|
+
|
|
51
|
+
Returns
|
|
52
|
+
-------
|
|
53
|
+
U,Sigma, V.T: all are numpy.arrays, representing the left-hand side and right-hand side eigenvectors and
|
|
54
|
+
corresponding eigenvalues
|
|
55
|
+
"""
|
|
56
|
+
if self.decomposition == "svd":
|
|
57
|
+
if self.Verbose==True:
|
|
58
|
+
print("Using full SVD")
|
|
59
|
+
self.d = np.linalg.matrix_rank(self.X_ss)
|
|
60
|
+
U, Sigma, V = np.linalg.svd(self.X_ss,**kwargs)
|
|
61
|
+
elif self.decomposition == "rand_svd" :
|
|
62
|
+
if self.Verbose==True:
|
|
63
|
+
print("Using randomized SVD ")
|
|
64
|
+
self.d = self.L // 2 - 1
|
|
65
|
+
|
|
66
|
+
U, Sigma, V = randomized_svd(self.X_ss,n_components= self.d,#self.L - self.L//3 ,
|
|
67
|
+
n_oversamples=100, random_state=0,power_iteration_normalizer='LU',n_iter=15)
|
|
68
|
+
#self.d = np.max(np.where(Sigma>0))
|
|
69
|
+
|
|
70
|
+
else:
|
|
71
|
+
self.d = 1
|
|
72
|
+
raise ValueError("Wrong decomposition type")
|
|
73
|
+
return U, Sigma, V.T
|
|
74
|
+
|
|
75
|
+
def elementary_matrix(self):
|
|
76
|
+
"""
|
|
77
|
+
Constructs a matrix(tensor) of all elementary components
|
|
78
|
+
|
|
79
|
+
Parameters
|
|
80
|
+
----------
|
|
81
|
+
Takes arguments from the instance
|
|
82
|
+
|
|
83
|
+
Returns
|
|
84
|
+
-------
|
|
85
|
+
X_elem:numpy.array, representing the matrix(tensor) of all elementary components
|
|
86
|
+
"""
|
|
87
|
+
X_elem = np.array( [self.Sigma[i] * np.outer(self.U[:,i], self.V[:,i]) for i in range(0,self.d)] )
|
|
88
|
+
return X_elem
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def X_to_TS(self, X_i):
|
|
94
|
+
"""
|
|
95
|
+
Averages the anti-diagonals of the given elementary matrix, X_i,
|
|
96
|
+
and returns a reconstructed time series for the given component X_i.
|
|
97
|
+
|
|
98
|
+
Parameters
|
|
99
|
+
----------
|
|
100
|
+
X_i: numpy.array, elementary matrix for the given component
|
|
101
|
+
|
|
102
|
+
Returns
|
|
103
|
+
-------
|
|
104
|
+
rec_ts: numpy.array, reconstructed time series for the given component X_i
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
# Reverse the column ordering of X_i
|
|
108
|
+
X_rev = X_i[::-1]
|
|
109
|
+
rec_ts = np.array([X_rev.diagonal(i).mean() for i in range(-X_i.shape[0]+1, X_i.shape[1])])
|
|
110
|
+
return rec_ts
|
|
111
|
+
|
|
112
|
+
def fit(self, df, L, decomposition,idx_start_ts, **kwargs ):
|
|
113
|
+
"""
|
|
114
|
+
Fits the instance of MSSA to the data
|
|
115
|
+
Parameters
|
|
116
|
+
----------
|
|
117
|
+
df:pandas.DataFrame, data frame of the time series, it is a source of the data, used to infer the length of each time series N
|
|
118
|
+
and number of time series S in the data set,
|
|
119
|
+
Dataframe should have the following structure:
|
|
120
|
+
the rows must contain values for each sample, while the column contain the data for a particular time period.
|
|
121
|
+
(WIDE FORMAT)
|
|
122
|
+
|
|
123
|
+
L:int, Window Size, the most important parameter for the (M)SSA.
|
|
124
|
+
|
|
125
|
+
decomposition:str, type of decomposition of the trajectory matrix.
|
|
126
|
+
Available options are "svd" meaning full svd-decomposition, and "rand_svd" meaning its
|
|
127
|
+
"truncated version".
|
|
128
|
+
|
|
129
|
+
idx_start_ts:int, the first column index of the data frame where the first nummeric value occours,
|
|
130
|
+
i.e where the time series start(s). Used to cutoff irrelevant columns from the input data set.
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
Returns
|
|
134
|
+
-------
|
|
135
|
+
No output
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
self.decomposition = decomposition
|
|
139
|
+
self.df = df
|
|
140
|
+
self.idx_start_ts = idx_start_ts
|
|
141
|
+
self.ts_df = self.df.iloc[:,self.idx_start_ts:].T
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
self.S = self.ts_df.shape[1]
|
|
145
|
+
self.X_s = self.ts_df.to_numpy()
|
|
146
|
+
self.N = int(self.X_s.shape[0])
|
|
147
|
+
self.L = int(self.X_s.shape[0]/1.5) if (L == None) else L
|
|
148
|
+
self.K = int( self.N - self.L + 1 )
|
|
149
|
+
|
|
150
|
+
if self.Verbose==True:
|
|
151
|
+
print(f' N = {self.N}, K = {self.K}, L = {self.L}, S = {self.S} ')
|
|
152
|
+
|
|
153
|
+
self.X_ss = self.construct_trajectory_matrix(**kwargs)
|
|
154
|
+
self.U, self.Sigma, self.V = self.decompose_trajectory_matrix(**kwargs)
|
|
155
|
+
self.X_elem = self.elementary_matrix(**kwargs)
|
|
156
|
+
self.sigma_sumsq = (self.Sigma**2).sum()
|
|
157
|
+
self.rel_contribution = self.Sigma**2 / self.sigma_sumsq * 100
|
|
158
|
+
self.cumsum_contr = (self.Sigma**2).cumsum() / self.sigma_sumsq * 100
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def plot_eigenvals_contribution(self,**kwargs):
|
|
162
|
+
"""
|
|
163
|
+
Plots the contribution of eigenvalues
|
|
164
|
+
Parameters
|
|
165
|
+
----------
|
|
166
|
+
**kwargs some additional parameters for visualization functions of pyplot
|
|
167
|
+
Returns
|
|
168
|
+
-------
|
|
169
|
+
No output
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
fig, ax = plt.subplots(1, 2, figsize=(14,5),**kwargs)
|
|
174
|
+
ax[0].plot(self.rel_contribution , lw=2.5,**kwargs)
|
|
175
|
+
ax[0].set_xlim(0,self.d)
|
|
176
|
+
ax[0].grid()
|
|
177
|
+
ax[0].set_title(r"Relative Contribution of $\mathbf{X}_i$ to Trajectory Matrix")
|
|
178
|
+
ax[0].set_xlabel(r"$i$")
|
|
179
|
+
ax[0].set_ylabel("Contribution (%)")
|
|
180
|
+
ax[1].plot(self.cumsum_contr, lw=2.5,**kwargs)
|
|
181
|
+
ax[1].set_xlim(0,self.d)
|
|
182
|
+
ax[1].set_title(r"Cumulative Contribution of $\mathbf{X}_i$ to Trajectory Matrix")
|
|
183
|
+
ax[1].set_xlabel(r"$i$")
|
|
184
|
+
ax[1].grid()
|
|
185
|
+
ax[1].set_ylabel(r"Contribution (%)");
|
|
186
|
+
|
|
187
|
+
def plot_eigenvectors(self,i_start, i_end,**kwargs):
|
|
188
|
+
"""
|
|
189
|
+
Plots the several eigenvectors U with indices in [i_start, i_end]
|
|
190
|
+
Parameters
|
|
191
|
+
----------
|
|
192
|
+
i_start: int, the first eigenvector to plot
|
|
193
|
+
i_end: int the last eigenvector to plot
|
|
194
|
+
**kwargs some additional parameters for visualization functions of pyplot
|
|
195
|
+
Returns
|
|
196
|
+
-------
|
|
197
|
+
No output
|
|
198
|
+
"""
|
|
199
|
+
plt.figure(figsize=(25, 25),**kwargs)
|
|
200
|
+
for i in range(i_start, i_end):
|
|
201
|
+
if i_end%2!=0:
|
|
202
|
+
plt.subplot(2,i_end//2+1, i+1-i_start,**kwargs)
|
|
203
|
+
else:
|
|
204
|
+
plt.subplot(2,i_end//2 , i+1-i_start,**kwargs)
|
|
205
|
+
|
|
206
|
+
title = r" $\mathbf{U}_{" + str(i) + "} $" +f' {self.rel_contribution[i]} %'
|
|
207
|
+
plt.plot(self.U[:, i],**kwargs)
|
|
208
|
+
plt.title(title)
|
|
209
|
+
plt.tight_layout()
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def construct_hankel_weights(self):
|
|
213
|
+
"""
|
|
214
|
+
Constructs hankel weights for the weighted correlation matrix
|
|
215
|
+
Parameters
|
|
216
|
+
----------
|
|
217
|
+
|
|
218
|
+
Returns
|
|
219
|
+
-------
|
|
220
|
+
w: numpy.array, weights used further for the computation of the weighted correlation matrix
|
|
221
|
+
"""
|
|
222
|
+
L_ = np.minimum(self.L, self.K)
|
|
223
|
+
K_ = np.maximum(self.L, self.K)
|
|
224
|
+
|
|
225
|
+
weights = []
|
|
226
|
+
for i in range(self.N):
|
|
227
|
+
if i <= (L_ - 1):
|
|
228
|
+
weights.append(i+1)
|
|
229
|
+
elif i <= K_:
|
|
230
|
+
weights.append(L_)
|
|
231
|
+
else:
|
|
232
|
+
weights.append(self.N - i)
|
|
233
|
+
|
|
234
|
+
weights = np.array(weights)
|
|
235
|
+
return weights
|
|
236
|
+
|
|
237
|
+
def compute_weighted_correlation_matrix(self):
|
|
238
|
+
"""
|
|
239
|
+
Computes the weighted correlation matrix used for component grouping
|
|
240
|
+
Parameters
|
|
241
|
+
----------
|
|
242
|
+
|
|
243
|
+
Returns
|
|
244
|
+
-------
|
|
245
|
+
Wcorr: numpy.array, weighted correlation matrix used for component grouping
|
|
246
|
+
"""
|
|
247
|
+
w = self.construct_hankel_weights()
|
|
248
|
+
|
|
249
|
+
TS_elem = np.zeros((self.N,self.d)).T
|
|
250
|
+
for s in range(self.S):
|
|
251
|
+
TS_elem += np.array([self.X_to_TS(self.X_elem[i,:,s*self.K:(s+1)*self.K ]) for i in range(self.d)])
|
|
252
|
+
|
|
253
|
+
TS_wnorms = np.array([w.dot((TS_elem[i]**2)) for i in range(self.d)])
|
|
254
|
+
TS_wnorms = TS_wnorms**-0.5
|
|
255
|
+
Wcorr = np.identity(self.d)
|
|
256
|
+
for i in range(self.d):
|
|
257
|
+
for j in range(i+1,self.d):
|
|
258
|
+
Wcorr[i,j] = abs(w.dot(TS_elem[i] * TS_elem[j])*TS_wnorms[i] * TS_wnorms[j])
|
|
259
|
+
Wcorr[j,i] = Wcorr[i,j]
|
|
260
|
+
return Wcorr
|
|
261
|
+
|
|
262
|
+
def plot_weighted_correlation_matrix(self):
|
|
263
|
+
"""
|
|
264
|
+
Plots the weighted correlation matrix used for component grouping
|
|
265
|
+
Parameters
|
|
266
|
+
----------
|
|
267
|
+
|
|
268
|
+
Returns
|
|
269
|
+
-------
|
|
270
|
+
None
|
|
271
|
+
"""
|
|
272
|
+
W_corr = self.compute_weighted_correlation_matrix()
|
|
273
|
+
ax = plt.imshow(W_corr)
|
|
274
|
+
plt.xlabel(r"$\tilde{Ts}_i$")
|
|
275
|
+
plt.ylabel(r"$\tilde{Ts}_j$")
|
|
276
|
+
plt.colorbar(ax.colorbar, fraction=0.045)
|
|
277
|
+
ax.colorbar.set_label("$W_{ij}$")
|
|
278
|
+
plt.xlim(0,self.d-0.5)
|
|
279
|
+
plt.ylim(self.d-0.5,0)
|
|
280
|
+
plt.xticks(np.arange(self.d))
|
|
281
|
+
plt.yticks(np.arange(self.d))
|
|
282
|
+
plt.clim(0,1)
|
|
283
|
+
plt.title(f"The Weighted Correlation Matrix for the Time Series ");
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def reconstruct_ts(self, idx_chosen_components, return_as_df=False):
|
|
288
|
+
"""
|
|
289
|
+
Reconstructs the time series from the chosen components
|
|
290
|
+
Parameters
|
|
291
|
+
----------
|
|
292
|
+
idx_chosen_components:list or numpy.arange of positive integer numbers, denotes the indices of elementary components used for the reconstruction
|
|
293
|
+
return_as_df:bool, whether to return the resulting reconstructed time series as pandas.DataFrame
|
|
294
|
+
|
|
295
|
+
Returns
|
|
296
|
+
-------
|
|
297
|
+
ts_rec: numpy.array or pandas.DataFrame, reconstructed time series
|
|
298
|
+
"""
|
|
299
|
+
chosen_components = self.X_elem[0,:,self.K]
|
|
300
|
+
chosen_components = 0
|
|
301
|
+
for i in idx_chosen_components:
|
|
302
|
+
chosen_components += self.X_elem[i,:,:]
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
ts_rec = self.X_s
|
|
306
|
+
ts_rec[:,:] = 0
|
|
307
|
+
for s in range(0,self.S):
|
|
308
|
+
ts_rec[:,s] = self.X_to_TS(chosen_components[:,s*self.K:(s+1)*self.K])
|
|
309
|
+
|
|
310
|
+
if return_as_df==True:
|
|
311
|
+
return pd.DataFrame(columns=self.df.columns, data=np.column_stack([self.df.iloc[:,:self.idx_start_ts ].values,ts_rec.T ]))
|
|
312
|
+
return ts_rec.T
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def estimate_LRR(self, idx_components):
|
|
318
|
+
"""
|
|
319
|
+
Estimates Linear Recurrence Relations(LRR) coefficients for the MSSA, which is used for forecasting
|
|
320
|
+
Parameters
|
|
321
|
+
----------
|
|
322
|
+
idx_components:list or numpy.arange of positive integer numbers, denotes the indices of elementary components
|
|
323
|
+
|
|
324
|
+
Returns
|
|
325
|
+
-------
|
|
326
|
+
R: numpy.array, Linear Recurrence Relations(LRR) coefficients
|
|
327
|
+
"""
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
P_orth = sp.linalg.qr(self.U[:,:self.d])[0]#self.L
|
|
331
|
+
nu_sq = np.sum(P_orth[-1,idx_components]**2)
|
|
332
|
+
|
|
333
|
+
if nu_sq !=1:
|
|
334
|
+
R = 1/(1-nu_sq) * (P_orth[:-1,idx_components] @ P_orth[-1,idx_components] )
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
return R
|
|
338
|
+
else :
|
|
339
|
+
print(nu_sq)
|
|
340
|
+
|
|
341
|
+
def L_Forecast(self, ts, M, idx_components, mode='forward'):
|
|
342
|
+
"""
|
|
343
|
+
Forecasts or estimates M values for a given time series using LRR
|
|
344
|
+
Parameters
|
|
345
|
+
----------
|
|
346
|
+
idx_components:list or numpy.arange of positive integer numbers, denotes the indices of elementary components
|
|
347
|
+
ts: numpy.array, input time series
|
|
348
|
+
M:int, number of values to forecast or estimate
|
|
349
|
+
mode:str, forecasts M future values for S time series if mode is "forward", or estimates the last M values for S input time series, if mode is 'retrospective'
|
|
350
|
+
|
|
351
|
+
Returns
|
|
352
|
+
-------
|
|
353
|
+
y_pred: numpy.array, original time series + M forecasted values, or original time series, where the last M values are estimated
|
|
354
|
+
"""
|
|
355
|
+
R = self.estimate_LRR(idx_components)
|
|
356
|
+
R = R.reshape(-1,1)
|
|
357
|
+
if M<=0:
|
|
358
|
+
y_pred = ts[:,:self.N]
|
|
359
|
+
return y_pred
|
|
360
|
+
|
|
361
|
+
if mode == 'forward':
|
|
362
|
+
y_pred = np.zeros((ts.shape[0],self.N+M))
|
|
363
|
+
y_pred[:,:self.N] = ts[:,:self.N]
|
|
364
|
+
|
|
365
|
+
for m in range(0,M):
|
|
366
|
+
y_pred[:,self.N+m] = (y_pred[:,self.N-self.L+m+1:y_pred.shape[1]-M+m]@R).flatten()
|
|
367
|
+
|
|
368
|
+
elif mode == 'retrospective':
|
|
369
|
+
y_pred = np.zeros((ts.shape[0],self.N))
|
|
370
|
+
y_pred[:,:self.N-M] = ts[:,:self.N-M]
|
|
371
|
+
|
|
372
|
+
for m in range(0,M):
|
|
373
|
+
|
|
374
|
+
y_pred[:,self.N+m-M] = (y_pred[:,self.N-self.L+m+1-M:y_pred.shape[1]-M+m] @ R).flatten()
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
else:
|
|
378
|
+
|
|
379
|
+
raise ValueError('Wrong type of the forecasting mode')
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
return y_pred
|
|
384
|
+
|
|
385
|
+
def estimate_ESPRIT(self, idx_components=[0], decompose_rho_omega=False):
|
|
386
|
+
|
|
387
|
+
"""
|
|
388
|
+
Estimates polynomial roots of the signal using ESPRIT algorithm and LS
|
|
389
|
+
Parameters
|
|
390
|
+
----------
|
|
391
|
+
idx_components:list or numpy.arange of positive integer numbers, denotes the indices of elementary components used
|
|
392
|
+
decompose_rho_omega: bool, decompose the roots into real and imaginery part
|
|
393
|
+
Returns
|
|
394
|
+
-------
|
|
395
|
+
mu: numpy.array, complex polynomial roots
|
|
396
|
+
rho: numpy.array, real polynomial roots
|
|
397
|
+
omega: numpy.array, complex part polynomial roots
|
|
398
|
+
"""
|
|
399
|
+
# P_orth = self.U[:,idx_components]
|
|
400
|
+
|
|
401
|
+
P_orth = sp.linalg.qr(self.U[:,:self.L])[0]
|
|
402
|
+
|
|
403
|
+
P_ = P_orth[:-1,idx_components] #last row removed
|
|
404
|
+
_P = P_orth[1:,idx_components] #first row removed
|
|
405
|
+
|
|
406
|
+
Inv_ = np.linalg.inv(P_.T @ P_) @ P_.T
|
|
407
|
+
MM = Inv_ @ _P
|
|
408
|
+
mu = np.flip(np.sort( np.linalg.eigvals(MM)))
|
|
409
|
+
if decompose_rho_omega == True:
|
|
410
|
+
rho = np.imag(mu)
|
|
411
|
+
omega = np.real(mu)
|
|
412
|
+
return mu, rho, omega
|
|
413
|
+
else:
|
|
414
|
+
return mu
|
|
415
|
+
|
|
416
|
+
def plot_polynomial_roots(self, idx_components):
|
|
417
|
+
"""
|
|
418
|
+
Plots estimated polynomial roots of the signal using ESPRIT algorithm and LS
|
|
419
|
+
Parameters
|
|
420
|
+
----------
|
|
421
|
+
idx_components:list or numpy.arange of positive integer numbers, denotes the indices of elementary components used
|
|
422
|
+
decompose_rho_omega: bool, decompose the roots into real and imaginery part
|
|
423
|
+
Returns
|
|
424
|
+
-------
|
|
425
|
+
|
|
426
|
+
"""
|
|
427
|
+
_,rho, omega = self.estimate_ESPRIT(idx_components=idx_components, decompose_rho_omega=True )
|
|
428
|
+
fig, ax = plt.subplots()
|
|
429
|
+
unit_circle = plt.Circle((0, 0), 1, color='b', fill=False)
|
|
430
|
+
ax.add_patch(unit_circle)
|
|
431
|
+
|
|
432
|
+
plt.title("Polynomial roots on the unit circle")
|
|
433
|
+
ax.plot( omega, rho, "r*")
|
|
434
|
+
plt.gca().set_aspect('equal')
|
|
435
|
+
ax.set_xlabel('Real Part')
|
|
436
|
+
ax.set_ylabel('Imaginary Part')
|
|
437
|
+
plt.show()
|
|
438
|
+
|