tsdr 0.1__tar.gz → 0.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tsdr
3
- Version: 0.1
3
+ Version: 0.2
4
4
  Summary: Time-series dimension reduction
5
5
  Author-Email: Kieran S Owens <kowe6911@uni.sydney.edu.au>
6
6
  Maintainer-Email: Kieran S Owens <kowe6911@uni.sydney.edu.au>
@@ -27,9 +27,9 @@ Description-Content-Type: text/markdown
27
27
 
28
28
  ## About
29
29
 
30
- Time-series dimension reduction (TSDR) methods are a class of algorithms for the dimension reduction of multivariate data that exploit temporal structure. As compared to general dimension reduction (GDR) methods, the outputs of which are invariant to temporal permutation, TSDR methods are sensitive to temporal structure.
30
+ Time-series dimension reduction (TSDR) methods are a class of algorithms for the dimension reduction of multivariate data that exploit temporal structure. In contrast to general dimension reduction (GDR) methods, the outputs of which are invariant to temporal permutation, TSDR methods are sensitive to temporal structure.
31
31
 
32
- The figure above visually summarises the categories of TSDR methods that we defined in Owens & Fulcher (2025). The first five categories (a-e) consist of methods that extract time-series components based on different aspects of temporal structure, i.e., slowness, autocorrelation, predictability, determinism, and non-stationarity. The final two categories (f-g) consist of methods that share a common methodology, i.e., diffusion-based and latente variable methods.
32
+ The figure above visually summarises the categories of TSDR methods that we defined in Owens & Fulcher (2025). The first five categories (a-e) consist of methods that extract time-series components based on different aspects of temporal structure, i.e., slowness, autocorrelation, predictability, determinism, and non-stationarity. The final two categories (f-g) consist of methods that share a common methodology, i.e., diffusion-based and latent variable methods.
33
33
 
34
34
  The <em>tsdr</em> package provides python implementations of TSDR methods from each of these seven categories. The methods are written in a simple, readable style to enable time-series analysts to better understand a subset of the TSDR methods discussed in the Owens & Fulcher (2025) review paper.
35
35
 
@@ -10,9 +10,9 @@
10
10
 
11
11
  ## About
12
12
 
13
- Time-series dimension reduction (TSDR) methods are a class of algorithms for the dimension reduction of multivariate data that exploit temporal structure. As compared to general dimension reduction (GDR) methods, the outputs of which are invariant to temporal permutation, TSDR methods are sensitive to temporal structure.
13
+ Time-series dimension reduction (TSDR) methods are a class of algorithms for the dimension reduction of multivariate data that exploit temporal structure. In contrast to general dimension reduction (GDR) methods, the outputs of which are invariant to temporal permutation, TSDR methods are sensitive to temporal structure.
14
14
 
15
- The figure above visually summarises the categories of TSDR methods that we defined in Owens & Fulcher (2025). The first five categories (a-e) consist of methods that extract time-series components based on different aspects of temporal structure, i.e., slowness, autocorrelation, predictability, determinism, and non-stationarity. The final two categories (f-g) consist of methods that share a common methodology, i.e., diffusion-based and latente variable methods.
15
+ The figure above visually summarises the categories of TSDR methods that we defined in Owens & Fulcher (2025). The first five categories (a-e) consist of methods that extract time-series components based on different aspects of temporal structure, i.e., slowness, autocorrelation, predictability, determinism, and non-stationarity. The final two categories (f-g) consist of methods that share a common methodology, i.e., diffusion-based and latent variable methods.
16
16
 
17
17
  The <em>tsdr</em> package provides python implementations of TSDR methods from each of these seven categories. The methods are written in a simple, readable style to enable time-series analysts to better understand a subset of the TSDR methods discussed in the Owens & Fulcher (2025) review paper.
18
18
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tsdr"
3
- version = "0.1"
3
+ version = "0.2"
4
4
  authors = [
5
5
  { name = "Kieran S Owens", email = "kowe6911@uni.sydney.edu.au" },
6
6
  ]
@@ -480,8 +480,9 @@ class CSA():
480
480
  The dimension (number of variables) of the time-series output.
481
481
  Default: 1.
482
482
 
483
- lag: int
484
- The maximum time lag at which to compute autocorrelation.
483
+ lag: int or list
484
+ The maximum time lag at which to compute autocorrelation,
485
+ or else a list of specified lags.
485
486
  Default: 1.
486
487
 
487
488
  method: str
@@ -497,13 +498,13 @@ class CSA():
497
498
  to the input time series X.
498
499
  """
499
500
 
500
- def __init__(self, n_components=1, lags=2, max_iters=1000):
501
+ def __init__(self, n_components=1, lag=2, max_iters=1000):
501
502
 
502
503
  self.n_components = n_components
503
- if isinstance(lags, int):
504
- self.lags = range(1, lags+1)
505
- elif isinstance(lags, list):
506
- self.lags = lags
504
+ if isinstance(lag, int):
505
+ self.lags = range(1, lag+1)
506
+ elif isinstance(lag, list):
507
+ self.lags = list(lag)
507
508
  self.max_iters = max_iters
508
509
 
509
510
  def fit(self, X, y=None):
@@ -661,7 +662,7 @@ class sPCA_dwt():
661
662
 
662
663
  whiten: bool
663
664
  Whether to whiten the data.
664
- Default: False.
665
+ Default: True
665
666
 
666
667
  method: str
667
668
  Whether to use PCA or SVD. Options: 'pca' or 'svd'.
@@ -156,12 +156,12 @@ class DMcov():
156
156
  Wnorm = D_neg_root @ W @ D_neg_root
157
157
 
158
158
  # eigendecomposition
159
- _, eigvecs = np.linalg.eig(Wnorm)
159
+ _, eigvecs = np.linalg.eigh(Wnorm)
160
160
 
161
161
  else:
162
162
 
163
163
  # eigendecomposition
164
- _, eigvecs = np.linalg.eig(D - W)
164
+ _, eigvecs = np.linalg.eigh(D - W)
165
165
 
166
166
  self.components = eigvecs[:,1:1+self.n_components]
167
167
 
@@ -292,8 +292,10 @@ class DIG():
292
292
  for i in range(D):
293
293
  for j in range(D):
294
294
  if i != j:
295
- W[i,j] += 0.5*(np.exp(-G[i,j]/knn_k[i])**self.alpha)
296
- W[i,j] += 0.5*(np.exp(-G[i,j]/knn_k[j])**self.alpha)
295
+ # W[i,j] += 0.5*(np.exp(-G[i,j]/knn_k[i])**self.alpha)
296
+ # W[i,j] += 0.5*(np.exp(-G[i,j]/knn_k[j])**self.alpha)
297
+ W[i,j] += 0.5*np.exp(-(G[i,j]/knn_k[i])**self.alpha)
298
+ W[i,j] += 0.5*np.exp(-(G[i,j]/knn_k[j])**self.alpha)
297
299
 
298
300
  return W
299
301
 
@@ -311,7 +313,7 @@ class DIG():
311
313
 
312
314
  kneedle = KneeLocator(range(1, self.tmax + 1), vne,
313
315
  S=1.0, curve='convex', direction='decreasing')
314
- return int(kneedle.elbow)
316
+ return int(kneedle.elbow) if kneedle.elbow is not None else self.tmax//2
315
317
 
316
318
  # computation of information distance
317
319
  def _information_distance(self, W):
@@ -332,7 +334,7 @@ class DIG():
332
334
 
333
335
  N = D.shape[0]
334
336
  H = np.eye(N) - np.ones((N, N)) / N
335
- K = -0.5 * (H @ D @ H)
337
+ K = -0.5 * (H @ (D **2) @ H)
336
338
  eigenvals, eigenvecs = np.linalg.eigh(K)
337
339
  eigenvals_sorted = eigenvals.argsort()[::-1]
338
340
  idxs = eigenvals_sorted[:self.n_components]
@@ -140,6 +140,7 @@ class LDS():
140
140
 
141
141
  # Sum expectations over time
142
142
  sum_Exx = Exx.sum(axis=0) # \sum_t E[x_t x_t^T]
143
+ sum_Exx0 = Exx[:-1].sum(axis=0) # sum_{t=1}^{T-1}
143
144
  sum_Exx1 = Exx[1:].sum(axis=0) # \sum_{t=2}^T E[x_t x_t^T]
144
145
  sum_Exxm1 = Exxm1.sum(axis=0) # \sum_{t=2}^T E[x_t x_{t-1}^T]
145
146
 
@@ -147,11 +148,12 @@ class LDS():
147
148
  if self.constrain_A_identity:
148
149
  A = np.eye(d)
149
150
  else:
150
- A = sum_Exxm1 @ pinv(sum_Exx1)
151
+ A = sum_Exxm1 @ pinv(sum_Exx0)
151
152
 
152
153
  # Update the latent state noise covariance matrix Q, Eq. (20)
153
154
  # and enforce symmetry
154
- Q = (sum_Exx1 - A @ sum_Exxm1.T) / (T - 1)
155
+ #Q = (sum_Exx1 - A @ sum_Exxm1.T) / (T - 1)
156
+ Q = (sum_Exx1 - A @ sum_Exxm1.T - sum_Exxm1 @ A.T + A @ sum_Exx0 @ A.T) / (T - 1)
155
157
  Q = (Q + Q.T) / 2
156
158
 
157
159
  # Update the output/Observation matrix C, Eq. (14)
@@ -118,7 +118,7 @@ class ASSA():
118
118
  S = self._compute_S(Xw)
119
119
 
120
120
  # eigendecomposition of S
121
- eigvals, eigvecs = np.linalg.eig(S)
121
+ eigvals, eigvecs = np.linalg.eigh(S)
122
122
  eigvecs_sorted = eigvecs[:,np.argsort(eigvals)]
123
123
 
124
124
  # eigenvalues
@@ -269,7 +269,7 @@ class SSAsir():
269
269
  Mm = self._compute_Mm(Xw)
270
270
 
271
271
  # eigendecomposition of S
272
- eigvals, eigvecs = np.linalg.eig(Mm)
272
+ eigvals, eigvecs = np.linalg.eigh(Mm)
273
273
  eigvecs_sorted = eigvecs[:,np.argsort(eigvals)]
274
274
 
275
275
  # get linear transformation
@@ -417,7 +417,7 @@ class SSAsave():
417
417
  Mv = self._compute_Mv(Xw)
418
418
 
419
419
  # eigendecomposition of S
420
- eigvals, eigvecs = np.linalg.eig(Mv)
420
+ eigvals, eigvecs = np.linalg.eigh(Mv)
421
421
  eigvecs_sorted = eigvecs[:,np.argsort(eigvals)]
422
422
 
423
423
  # get linear transformation
@@ -575,7 +575,7 @@ class SSAcor():
575
575
  Mtau = self._compute_Mtau(Xw)
576
576
 
577
577
  # eigendecomposition of S
578
- eigvals, eigvecs = np.linalg.eig(Mtau)
578
+ eigvals, eigvecs = np.linalg.eigh(Mtau)
579
579
  eigvecs_sorted = eigvecs[:,np.argsort(eigvals)]
580
580
 
581
581
  # get linear transformation
@@ -777,7 +777,7 @@ class WaSSAf():
777
777
  S = self._compute_S(Xw)
778
778
 
779
779
  # eigendecomposition of S
780
- eigvals, eigvecs = np.linalg.eig(S)
780
+ eigvals, eigvecs = np.linalg.eigh(S)
781
781
  eigvecs_sorted = eigvecs[:,np.argsort(eigvals)]
782
782
 
783
783
  # get linear transformation
@@ -937,7 +937,7 @@ class WaSSAr():
937
937
  S = self._compute_S(Xw)
938
938
 
939
939
  # eigendecomposition of S
940
- eigvals, eigvecs = np.linalg.eig(S)
940
+ eigvals, eigvecs = np.linalg.eigh(S)
941
941
  eigvecs_sorted = eigvecs[:,np.argsort(eigvals)]
942
942
 
943
943
  # get linear transformation
@@ -1067,7 +1067,7 @@ class BSSnonstat():
1067
1067
  T, D = X0.shape
1068
1068
 
1069
1069
  # matrix used during optimisation
1070
- M = X0.T @ Xlag + Xlag.T @ X0
1070
+ M = (X0.T @ Xlag + Xlag.T @ X0) / T
1071
1071
 
1072
1072
  # collect projection vectors
1073
1073
  W = []
@@ -1084,12 +1084,12 @@ class BSSnonstat():
1084
1084
  e1 = np.zeros(shape=w.shape)
1085
1085
  e2 = np.zeros(shape=w.shape)
1086
1086
 
1087
- for i in range(T - self.lag):
1087
+ for i in range(T):
1088
1088
  ztau = Xlag[i,:].reshape(-1,1)
1089
1089
  zt = X0[i,:].reshape(-1,1)
1090
1090
 
1091
- e1 += (zt @ w.T @ zt @ (w.T @ ztau)**2)/(T - self.lag)
1092
- e2 += (ztau @ w.T @ ztau @ (w.T @ zt)**2) / (T - self.lag)
1091
+ e1 += (zt @ w.T @ zt @ (w.T @ ztau)**2)/T
1092
+ e2 += (ztau @ w.T @ ztau @ (w.T @ zt)**2) / T
1093
1093
 
1094
1094
  w = e1 + e2 - 2*w - M @ w @ (w.T @ M @ w)
1095
1095
  w = w / np.linalg.norm(w)
@@ -95,7 +95,7 @@ class TLPC():
95
95
  Sigma = Ctau @ np.linalg.pinv(C0)
96
96
 
97
97
  # eigendecomposition of I - Sigma^2
98
- eigvals, eigvecs = np.linalg.eig(np.eye(Sigma.shape[0]) - Sigma @ Sigma)
98
+ eigvals, eigvecs = np.real(np.linalg.eig(np.eye(Sigma.shape[0]) - Sigma @ Sigma))
99
99
  eigvecs_sorted = eigvecs[:,np.argsort(eigvals)]
100
100
 
101
101
  # get linear transformation
@@ -244,18 +244,17 @@ class PrCA():
244
244
 
245
245
  else:
246
246
 
247
- # (empty) time series of prediction errors (i.e., residuals)
248
- E = np.zeros((T - (self.p + self.lag), D))
247
+ # prediction errors have T - (p + lag - 1) rows
248
+ E = np.zeros((T - (self.p + self.lag - 1), D))
249
249
 
250
- # for each feature/variable
251
250
  for i in range(D):
252
251
 
253
252
  # data to use for predictions
254
253
  x = X[:,i]
255
- x_p = self._tde(x[:-(self.lag+1)].reshape(-1,1), self.p, 1)
254
+ x_p = self._tde(x[:-self.lag].reshape(-1,1), self.p, 1) # drop last `lag` points
256
255
 
257
256
  # data to predict
258
- y = x[self.p + self.lag:]
257
+ y = x[self.p - 1 + self.lag:]
259
258
 
260
259
  # fit a model using x_p and y
261
260
  if self.model == 'linear_reg':
@@ -426,12 +425,11 @@ class DiCCA():
426
425
  # initialise AR weights beta
427
426
  beta = np.eye(self.s)[:,0]
428
427
 
429
- # prediction (Ubeta) and prediction error (Utilde)
430
- Ubeta = sum([U_list[i]*beta[i] for i in range(self.s)])
431
- Utilde = Us - Ubeta
432
-
433
428
  # iteratively optimize transformation w and AR weights beta
434
429
  for _ in range(100):
430
+ # prediction (Ubeta) and prediction error (Utilde)
431
+ Ubeta = sum([U_list[i]*beta[i] for i in range(self.s)])
432
+ Utilde = Us - Ubeta
435
433
  w = PCA().fit(Utilde).components_.T[:,-1]
436
434
  t = U @ w
437
435
  Ts = [beta[0] * U_list[0] @ w]
File without changes
File without changes
File without changes
File without changes
File without changes