BhalalaDaksh commited on
Commit
82f5ce2
·
verified ·
1 Parent(s): 34adaa0

docs: update license header in compactts/agent/profiler.py to BSL-1.1

Browse files
Files changed (1) hide show
  1. compactts/agent/profiler.py +134 -102
compactts/agent/profiler.py CHANGED
@@ -1,102 +1,134 @@
1
- """Series Profiler for CompactTS-Agent.
2
-
3
- Extracts statistical signatures, spectral characteristics, seasonality,
4
- trend strength, and volatility profiles from time-series trajectories.
5
- """
6
-
7
- from typing import Dict, Any, Optional
8
- import numpy as np
9
- import pandas as pd
10
- from scipy import stats
11
- from gluonts.time_feature import get_seasonality
12
-
13
-
14
- class SeriesProfiler:
15
- """Profiles univariate and multivariate time series for autonomous agent arbitration."""
16
-
17
- def __init__(self, freq: str = "H"):
18
- self.freq = freq
19
- try:
20
- self.base_seasonality = max(get_seasonality(freq), 1)
21
- except Exception:
22
- self.base_seasonality = 1
23
-
24
- def profile(self, series: np.ndarray) -> Dict[str, Any]:
25
- """Extract comprehensive statistical and structural characteristics.
26
-
27
- Parameters
28
- ----------
29
- series : np.ndarray
30
- 1D array of historical observations.
31
-
32
- Returns
33
- -------
34
- Dict[str, Any]
35
- Dictionary of structural features:
36
- - length: sequence length
37
- - mean, std: empirical scale
38
- - seasonal_period: detected dominant lag
39
- - seasonal_strength: autocorrelation at seasonal lag
40
- - trend_strength: relative trend variance ratio
41
- - kurtosis: tail weight indicator
42
- - has_missing: boolean indicating gaps or NaNs
43
- """
44
- y = np.asarray(series, dtype=np.float32)
45
- if y.ndim > 1:
46
- y = y.squeeze()
47
-
48
- has_missing = bool(np.isnan(y).any() or np.isinf(y).any())
49
- if has_missing:
50
- s = pd.Series(y).ffill().bfill().to_numpy()
51
- y = np.nan_to_num(s, nan=0.0).astype(np.float32)
52
-
53
- n = len(y)
54
- if n < 4:
55
- return {
56
- "length": n,
57
- "mean": float(np.mean(y)) if n > 0 else 0.0,
58
- "std": float(np.std(y)) if n > 0 else 1.0,
59
- "seasonal_period": self.base_seasonality,
60
- "seasonal_strength": 0.0,
61
- "trend_strength": 0.0,
62
- "kurtosis": 0.0,
63
- "has_missing": has_missing,
64
- }
65
-
66
- mean_val = float(np.mean(y))
67
- std_val = float(np.std(y))
68
- if std_val < 1e-8:
69
- std_val = 1.0
70
-
71
- # Kurtosis for heavy-tail detection
72
- try:
73
- kurt = float(stats.kurtosis(y))
74
- except Exception:
75
- kurt = 0.0
76
-
77
- # Seasonal autocorrelation
78
- S = self.base_seasonality
79
- s_corr = 0.0
80
- if n > 2 * S:
81
- try:
82
- c = np.corrcoef(y[S:], y[:-S])[0, 1]
83
- if np.isfinite(c):
84
- s_corr = max(0.0, float(c))
85
- except Exception:
86
- s_corr = 0.0
87
-
88
- # Trend strength: ratio of diff variance to raw variance
89
- diff_var = float(np.var(np.diff(y)))
90
- raw_var = float(np.var(y))
91
- trend_ratio = max(0.0, 1.0 - (diff_var / (raw_var + 1e-8)))
92
-
93
- return {
94
- "length": n,
95
- "mean": mean_val,
96
- "std": std_val,
97
- "seasonal_period": S,
98
- "seasonal_strength": s_corr,
99
- "trend_strength": trend_ratio,
100
- "kurtosis": kurt,
101
- "has_missing": has_missing,
102
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Series Profiler for CompactTS-Agent.
2
+
3
+ Extracts statistical signatures, spectral characteristics, seasonality,
4
+ trend strength, intermittency, and regime shifts from time-series trajectories.
5
+
6
+ Attribution: Daksh Bhalala
7
+ License: Business Source License 1.1 (BSL-1.1)
8
+ """
9
+
10
+ from typing import Dict, Any, Optional
11
+ import numpy as np
12
+ import pandas as pd
13
+ from scipy import stats
14
+ from gluonts.time_feature import get_seasonality
15
+
16
+
17
+ class SeriesProfiler:
18
+ """Profiles univariate and multivariate time series for autonomous agent arbitration."""
19
+
20
+ def __init__(self, freq: str = "H"):
21
+ self.freq = freq
22
+ try:
23
+ self.base_seasonality = max(get_seasonality(freq), 1)
24
+ except Exception:
25
+ freq_map = {
26
+ "10S": 60, "5T": 12, "10T": 6, "15T": 4,
27
+ "H": 24, "D": 7, "W": 52, "M": 12, "Q": 4, "A": 1
28
+ }
29
+ self.base_seasonality = freq_map.get(freq, 24)
30
+
31
+ def profile(self, series: np.ndarray) -> Dict[str, Any]:
32
+ """Extract comprehensive statistical and structural characteristics.
33
+
34
+ Parameters
35
+ ----------
36
+ series : np.ndarray
37
+ 1D or 2D array of historical observations.
38
+
39
+ Returns
40
+ -------
41
+ Dict[str, Any]
42
+ Dictionary of structural features:
43
+ - length: sequence length
44
+ - mean, std: empirical scale
45
+ - zero_fraction: proportion of near-zero values (intermittency/solar)
46
+ - cv: coefficient of variation (volatility)
47
+ - kurtosis: tail weight indicator
48
+ - seasonal_period: detected dominant lag
49
+ - seasonal_strength: autocorrelation at seasonal lag
50
+ - trend_strength: relative trend variance ratio
51
+ - mean_shift: shift in mean between first and second half (non-stationarity)
52
+ - std_shift: shift in std between first and second half
53
+ - has_missing: boolean indicating gaps or NaNs
54
+ """
55
+ y = np.asarray(series, dtype=np.float32)
56
+ if y.ndim > 1:
57
+ y = y.squeeze()
58
+ if y.ndim > 1:
59
+ # If multivariate, profile primary target channel
60
+ y = y[0] if y.shape[0] < y.shape[1] else y[:, 0]
61
+
62
+ has_missing = bool(np.isnan(y).any() or np.isinf(y).any())
63
+ if has_missing:
64
+ s = pd.Series(y).ffill().bfill().to_numpy()
65
+ y = np.nan_to_num(s, nan=0.0).astype(np.float32)
66
+
67
+ n = len(y)
68
+ if n < 4:
69
+ return {
70
+ "length": n,
71
+ "mean": float(np.mean(y)) if n > 0 else 0.0,
72
+ "std": float(np.std(y)) if n > 0 else 1.0,
73
+ "zero_fraction": 0.0,
74
+ "cv": 1.0,
75
+ "kurtosis": 0.0,
76
+ "seasonal_period": self.base_seasonality,
77
+ "seasonal_strength": 0.0,
78
+ "trend_strength": 0.0,
79
+ "mean_shift": 0.0,
80
+ "std_shift": 0.0,
81
+ "has_missing": has_missing,
82
+ }
83
+
84
+ mean_val = float(np.mean(y))
85
+ std_val = float(np.std(y))
86
+ if std_val < 1e-8:
87
+ std_val = 1.0
88
+
89
+ zero_fraction = float(np.mean(np.abs(y) < 1e-5))
90
+ cv = std_val / (abs(mean_val) + 1e-5)
91
+
92
+ # Kurtosis for heavy-tail detection
93
+ try:
94
+ kurt = float(stats.kurtosis(y))
95
+ except Exception:
96
+ kurt = 0.0
97
+
98
+ # Non-stationarity & regime shift metrics (split context in half)
99
+ half = n // 2
100
+ m1, m2 = float(np.mean(y[:half])), float(np.mean(y[half:]))
101
+ s1, s2 = float(np.std(y[:half])), float(np.std(y[half:]))
102
+ mean_shift = abs(m1 - m2) / (std_val + 1e-5)
103
+ std_shift = abs(s1 - s2) / (std_val + 1e-5)
104
+
105
+ # Seasonal autocorrelation
106
+ S = self.base_seasonality
107
+ s_corr = 0.0
108
+ if n > 2 * S and S > 1:
109
+ try:
110
+ c = np.corrcoef(y[S:], y[:-S])[0, 1]
111
+ if np.isfinite(c):
112
+ s_corr = max(0.0, float(c))
113
+ except Exception:
114
+ s_corr = 0.0
115
+
116
+ # Trend strength: ratio of diff variance to raw variance
117
+ diff_var = float(np.var(np.diff(y)))
118
+ raw_var = float(np.var(y))
119
+ trend_ratio = max(0.0, 1.0 - (diff_var / (raw_var + 1e-8)))
120
+
121
+ return {
122
+ "length": n,
123
+ "mean": mean_val,
124
+ "std": std_val,
125
+ "zero_fraction": zero_fraction,
126
+ "cv": cv,
127
+ "kurtosis": kurt,
128
+ "seasonal_period": S,
129
+ "seasonal_strength": s_corr,
130
+ "trend_strength": trend_ratio,
131
+ "mean_shift": mean_shift,
132
+ "std_shift": std_shift,
133
+ "has_missing": has_missing,
134
+ }