forecastscope 0.1.0__py3-none-any.whl
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.
- forecastscope/__init__.py +5 -0
- forecastscope/api.py +72 -0
- forecastscope/combinations/__init__.py +1 -0
- forecastscope/combinations/context_memory.py +127 -0
- forecastscope/combinations/validated.py +144 -0
- forecastscope/diagnostics/__init__.py +236 -0
- forecastscope/diagnostics/autocorrelation.py +217 -0
- forecastscope/diagnostics/causality.py +183 -0
- forecastscope/diagnostics/changepoints.py +172 -0
- forecastscope/diagnostics/distribution.py +180 -0
- forecastscope/diagnostics/filters.py +201 -0
- forecastscope/diagnostics/fractal.py +206 -0
- forecastscope/diagnostics/nonlinear.py +267 -0
- forecastscope/diagnostics/seasonality.py +218 -0
- forecastscope/diagnostics/sequence.py +130 -0
- forecastscope/diagnostics/stationarity.py +233 -0
- forecastscope/diagnostics/volatility.py +187 -0
- forecastscope/forecasting.py +263 -0
- forecastscope/types.py +12 -0
- forecastscope-0.1.0.dist-info/METADATA +85 -0
- forecastscope-0.1.0.dist-info/RECORD +24 -0
- forecastscope-0.1.0.dist-info/WHEEL +5 -0
- forecastscope-0.1.0.dist-info/licenses/LICENSE +21 -0
- forecastscope-0.1.0.dist-info/top_level.txt +1 -0
forecastscope/api.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Validated entry points; legacy numerical kernels retain their original equations."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from itertools import pairwise
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from .combinations.context_memory import MemoryFit, fit_context_memory
|
|
9
|
+
from .combinations.validated import ValidatedFit, fit_validated
|
|
10
|
+
from .forecasting import METHOD_FNS, gaussian_quantiles
|
|
11
|
+
|
|
12
|
+
METHOD_NAMES = tuple(METHOD_FNS)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _request(history, seasonality, horizon, quantiles):
|
|
16
|
+
y = np.asarray(history, dtype=float)
|
|
17
|
+
if y.ndim != 1 or len(y) < 2 or not np.isfinite(y).all():
|
|
18
|
+
raise ValueError("A contiguous finite one-dimensional history of at least two observations is required")
|
|
19
|
+
for name, value in [("seasonality", seasonality), ("horizon", horizon)]:
|
|
20
|
+
if isinstance(value, (bool, np.bool_)) or not isinstance(value, (int, np.integer)) or value < 1:
|
|
21
|
+
raise ValueError(f"{name} must be a positive integer")
|
|
22
|
+
if seasonality >= len(y):
|
|
23
|
+
raise ValueError("seasonality must be smaller than the observed history length")
|
|
24
|
+
levels = tuple(float(q) for q in quantiles)
|
|
25
|
+
if not levels or not np.isfinite(levels).all() or not all(0 < q < 1 for q in levels) or not all(a < b for a, b in pairwise(levels)):
|
|
26
|
+
raise ValueError("Quantiles must be finite, strictly increasing probabilities between zero and one")
|
|
27
|
+
return y, int(seasonality), int(horizon), levels
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _output(values, horizon, levels):
|
|
31
|
+
a = np.asarray(values, dtype=float)
|
|
32
|
+
if a.shape != (horizon, len(levels)) or not np.isfinite(a).all() or np.any(np.diff(a, axis=1) < 0):
|
|
33
|
+
raise ValueError("The method returned invalid forecast dimensions, nonfinite values or crossing columns")
|
|
34
|
+
return a
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def forecast(method: str, history, *, seasonality: int = 1, horizon: int = 1, quantiles=(.1, .5, .9)) -> np.ndarray:
|
|
38
|
+
"""Return full-precision horizon-by-quantile Gaussian residual columns.
|
|
39
|
+
|
|
40
|
+
Inputs must be contiguous and finite; no imputation, source acquisition or
|
|
41
|
+
seasonal-period selection is performed. These approximate intervals carry no
|
|
42
|
+
calibration guarantee. Methods refit on the whole supplied prefix.
|
|
43
|
+
"""
|
|
44
|
+
y, m, h, levels = _request(history, seasonality, horizon, quantiles)
|
|
45
|
+
if method not in METHOD_FNS:
|
|
46
|
+
raise ValueError(f"Unknown method {method!r}; choose from {METHOD_NAMES}")
|
|
47
|
+
point, sigma = METHOD_FNS[method](y, m, h)
|
|
48
|
+
return _output(gaussian_quantiles(point, sigma, levels), h, levels)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def combine(method: str, history, *, seasonality: int = 1, horizon: int = 1, quantiles=(.1, .5, .9)) -> MemoryFit | ValidatedFit:
|
|
52
|
+
"""Fit a five-expert combination without accepting a reserved future block.
|
|
53
|
+
|
|
54
|
+
The returned object retains weights and complete historical origins. The
|
|
55
|
+
validated variants additionally retain candidate scores and inner splits.
|
|
56
|
+
Their canonical probability grid is fixed; unsupported grids are refused.
|
|
57
|
+
"""
|
|
58
|
+
y, m, h, levels = _request(history, seasonality, horizon, quantiles)
|
|
59
|
+
modes = {"ContextMemory": "context", "RecencyMean": "recency", "EqualMean": "uniform"}
|
|
60
|
+
if method in modes:
|
|
61
|
+
result = fit_context_memory(y, m, h, levels, mode=modes[method])
|
|
62
|
+
elif method in ("ContextMemoryCV", "ValidationMix"):
|
|
63
|
+
if levels != (.1, .5, .9):
|
|
64
|
+
raise ValueError("Temporally validated combinations require quantiles (0.1, 0.5, 0.9)")
|
|
65
|
+
result = fit_validated(y, m, h, stacking=method == "ValidationMix")
|
|
66
|
+
else:
|
|
67
|
+
raise ValueError(f"Unknown combination {method!r}")
|
|
68
|
+
_output(result.quantiles, h, levels)
|
|
69
|
+
weights = np.asarray(result.weights)
|
|
70
|
+
if weights.shape != (5,) or not np.isfinite(weights).all() or np.any(weights < 0) or not np.isclose(weights.sum(), 1):
|
|
71
|
+
raise ValueError("Invalid expert allocation")
|
|
72
|
+
return result
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Fixed-context and temporally validated combinations of five reference experts."""
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""ContextMemory: a within-series forecast-combination proposal and its ablations.
|
|
2
|
+
|
|
3
|
+
Published feature-based averaging and dynamic ensembles motivate the proposal; this
|
|
4
|
+
specific recurrence/recency rule is an experiment, with no claim of priority or superiority.
|
|
5
|
+
All training targets end before the supplied history ends. The final holdout is never an input.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from functools import lru_cache
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
from ..forecasting import METHODS, Forecaster, _clean, gaussian_quantiles
|
|
15
|
+
|
|
16
|
+
CONFIG = {
|
|
17
|
+
"memory_origins": 12,
|
|
18
|
+
"half_life_origins": 6.0,
|
|
19
|
+
"global_shrinkage": 0.25,
|
|
20
|
+
"uniform_floor": 0.05,
|
|
21
|
+
"temperature": 1.0,
|
|
22
|
+
"kernel_bandwidth_floor": 0.25,
|
|
23
|
+
}
|
|
24
|
+
EXPERT_NAMES = tuple(name for name, _, _ in METHODS)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True)
|
|
28
|
+
class MemoryFit:
|
|
29
|
+
quantiles: np.ndarray
|
|
30
|
+
weights: np.ndarray
|
|
31
|
+
origins: tuple[int, ...]
|
|
32
|
+
context: np.ndarray
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def context_features(y: np.ndarray, m: int) -> np.ndarray:
|
|
36
|
+
"""Four bounded, location-invariant and positive-scale-invariant summaries."""
|
|
37
|
+
w = min(len(y) // 2, max(8, min(2 * max(m, 1), 48)))
|
|
38
|
+
if w < 2:
|
|
39
|
+
return np.zeros(4)
|
|
40
|
+
recent, previous = y[-w:], y[-2 * w:-w]
|
|
41
|
+
scale = float(np.std(y[-2 * w:]))
|
|
42
|
+
if scale < 1e-12:
|
|
43
|
+
return np.zeros(4)
|
|
44
|
+
half = max(1, w // 2)
|
|
45
|
+
slope = (np.mean(recent[-half:]) - np.mean(recent[:half])) / scale
|
|
46
|
+
shift = (np.mean(recent) - np.mean(previous)) / scale
|
|
47
|
+
vr, vp = float(np.std(np.diff(recent))), float(np.std(np.diff(previous)))
|
|
48
|
+
volatility = np.log((vr + scale * 1e-6) / (vp + scale * 1e-6))
|
|
49
|
+
periodicity = 0.0
|
|
50
|
+
if m >= 2 and len(y) >= 2 * m + 2:
|
|
51
|
+
a, b = y[-2 * m:-m], y[-m:]
|
|
52
|
+
if np.std(a) > 1e-12 and np.std(b) > 1e-12:
|
|
53
|
+
periodicity = float(np.corrcoef(a, b)[0, 1])
|
|
54
|
+
return np.clip([slope, shift, volatility, periodicity], -4, 4)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _scale(y: np.ndarray, m: int) -> float:
|
|
58
|
+
lag = max(1, min(m, len(y) - 1))
|
|
59
|
+
value = float(np.mean(np.abs(y[lag:] - y[:-lag])))
|
|
60
|
+
return value if value > 1e-12 else max(float(np.std(y)), 1e-12)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@lru_cache(maxsize=64)
|
|
64
|
+
def _memory(values: tuple[float, ...], m: int, h: int):
|
|
65
|
+
y = np.asarray(values)
|
|
66
|
+
first, last = max(2 * m, 2 * h + 8, 24), len(y) - h
|
|
67
|
+
if last < first:
|
|
68
|
+
return (), np.empty((0, 4)), np.empty((0, len(METHODS)))
|
|
69
|
+
origins = tuple(int(o) for o in np.unique(np.linspace(first, last, CONFIG["memory_origins"], dtype=int)))
|
|
70
|
+
features, losses = [], []
|
|
71
|
+
for origin in origins:
|
|
72
|
+
history, truth = y[:origin], y[origin:origin + h]
|
|
73
|
+
features.append(context_features(history, m))
|
|
74
|
+
losses.append([
|
|
75
|
+
float(np.mean(np.abs(fn(history, m, h)[0] - truth))) / _scale(history, m)
|
|
76
|
+
for _, _, fn in METHODS
|
|
77
|
+
])
|
|
78
|
+
return origins, np.asarray(features), np.asarray(losses)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def fit_context_memory(y: np.ndarray, m: int, h: int, levels=(0.1, 0.5, 0.9), mode="context") -> MemoryFit:
|
|
82
|
+
"""Fit from completed historical origins, then combine experts at the query origin.
|
|
83
|
+
|
|
84
|
+
The recency ablation removes the context kernel; the uniform ablation removes
|
|
85
|
+
both learning components. Configuration is fixed before the released holdout run.
|
|
86
|
+
Weighted quantiles are a quantile combination, not a calibrated mixture distribution.
|
|
87
|
+
"""
|
|
88
|
+
y = _clean(np.asarray(y, dtype=float))
|
|
89
|
+
if len(y) < 2 or not np.isfinite(y).all() or h < 1 or m < 1:
|
|
90
|
+
raise ValueError("ContextMemory requires finite history, positive horizon and seasonality")
|
|
91
|
+
if mode not in {"context", "recency", "uniform"}:
|
|
92
|
+
raise ValueError(f"Unknown combination mode: {mode}")
|
|
93
|
+
origins, features, losses = _memory(tuple(float(v) for v in y), m, h)
|
|
94
|
+
query = context_features(y, m)
|
|
95
|
+
weights = np.full(len(METHODS), 1 / len(METHODS))
|
|
96
|
+
if len(origins) and mode != "uniform":
|
|
97
|
+
ages = np.arange(len(origins) - 1, -1, -1)
|
|
98
|
+
recency = np.exp2(-ages / CONFIG["half_life_origins"])
|
|
99
|
+
global_loss = np.average(losses, axis=0, weights=recency)
|
|
100
|
+
score = global_loss
|
|
101
|
+
if mode == "context":
|
|
102
|
+
distance = np.sqrt(np.mean((features - query) ** 2, axis=1))
|
|
103
|
+
bandwidth = max(float(np.median(distance)), CONFIG["kernel_bandwidth_floor"])
|
|
104
|
+
local = recency * np.exp(-0.5 * (distance / bandwidth) ** 2)
|
|
105
|
+
local_loss = np.average(losses, axis=0, weights=local)
|
|
106
|
+
shrink = CONFIG["global_shrinkage"]
|
|
107
|
+
score = (1 - shrink) * local_loss + shrink * global_loss
|
|
108
|
+
logits = -(score - np.min(score)) / CONFIG["temperature"]
|
|
109
|
+
weights = np.exp(np.clip(logits, -700, 0))
|
|
110
|
+
weights /= weights.sum()
|
|
111
|
+
floor = CONFIG["uniform_floor"]
|
|
112
|
+
weights = (1 - floor) * weights + floor / len(METHODS)
|
|
113
|
+
forecasts = []
|
|
114
|
+
for _, _, fn in METHODS:
|
|
115
|
+
point, sigma = fn(y, m, h)
|
|
116
|
+
forecasts.append(gaussian_quantiles(point, sigma, tuple(levels)))
|
|
117
|
+
combined = np.tensordot(weights, np.asarray(forecasts), axes=(0, 0))
|
|
118
|
+
return MemoryFit(combined, weights, origins, query)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class ContextMemoryForecaster(Forecaster):
|
|
122
|
+
name = "ContextMemory"
|
|
123
|
+
family = "adaptive ensemble"
|
|
124
|
+
max_windows = 3
|
|
125
|
+
|
|
126
|
+
def quantiles(self, y, m, h, levels):
|
|
127
|
+
return fit_context_memory(y, m, h, levels).quantiles
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""Forecast combinations selected by validation wholly inside the observed prefix.
|
|
2
|
+
|
|
3
|
+
No outer target is accepted by this API. Model selection compares completed
|
|
4
|
+
historical blocks, and each inner fit excludes blocks not completed at its query.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from functools import lru_cache
|
|
10
|
+
from itertools import combinations
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
from ..forecasting import METHODS, gaussian_quantiles
|
|
15
|
+
from .context_memory import CONFIG, _scale, context_features
|
|
16
|
+
|
|
17
|
+
CANDIDATES = (
|
|
18
|
+
("recency", 1., 1.), ("equal", 0., 1.),
|
|
19
|
+
*(("context", shrink, temp) for shrink in (0., .5, 1.) for temp in (.15, .5, 1.)),
|
|
20
|
+
*(("stack", penalty, 1.) for penalty in (.01, .1, 1., 10.)),
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class ValidatedFit:
|
|
26
|
+
quantiles: np.ndarray
|
|
27
|
+
weights: np.ndarray
|
|
28
|
+
selected: tuple[str, float, float]
|
|
29
|
+
candidates: tuple[dict, ...]
|
|
30
|
+
origins: tuple[int, ...]
|
|
31
|
+
validation_splits: tuple[dict, ...]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def convex_weights(errors, penalty):
|
|
35
|
+
"""Convex squared-error stacking with shrinkage toward uniform allocation."""
|
|
36
|
+
a = np.asarray(errors, dtype=float)
|
|
37
|
+
if a.ndim != 2 or not len(a) or not np.isfinite(a).all() or penalty <= 0:
|
|
38
|
+
raise ValueError("Stacking requires finite error rows and positive regularization")
|
|
39
|
+
k = a.shape[1]
|
|
40
|
+
uniform = np.full(k, 1 / k)
|
|
41
|
+
gram = a.T @ a / len(a)
|
|
42
|
+
|
|
43
|
+
def objective(w):
|
|
44
|
+
return float(w @ gram @ w + penalty * np.sum((w - uniform) ** 2))
|
|
45
|
+
|
|
46
|
+
# Five experts permit exhaustive active-set solutions (31 nonempty faces).
|
|
47
|
+
# Each face has a positive-definite regularized quadratic; the best feasible
|
|
48
|
+
# face is the constrained optimum, without iterative solver stopping rules.
|
|
49
|
+
best, value = uniform, objective(uniform)
|
|
50
|
+
for size in range(1, k + 1):
|
|
51
|
+
for active in combinations(range(k), size):
|
|
52
|
+
ids = list(active)
|
|
53
|
+
matrix = gram[np.ix_(ids, ids)] + penalty * np.eye(size)
|
|
54
|
+
rhs = np.column_stack((penalty * uniform[ids], np.ones(size)))
|
|
55
|
+
try:
|
|
56
|
+
solution = np.linalg.solve(matrix, rhs)
|
|
57
|
+
except np.linalg.LinAlgError:
|
|
58
|
+
continue
|
|
59
|
+
x, direction = solution[:, 0], solution[:, 1]
|
|
60
|
+
x = x + (1 - x.sum()) * direction / direction.sum()
|
|
61
|
+
if not np.isfinite(x).all() or np.min(x) < -1e-10:
|
|
62
|
+
continue
|
|
63
|
+
weights = np.zeros(k)
|
|
64
|
+
weights[ids] = np.maximum(x, 0.)
|
|
65
|
+
weights /= weights.sum()
|
|
66
|
+
score = objective(weights)
|
|
67
|
+
if score < value:
|
|
68
|
+
best, value = weights, score
|
|
69
|
+
return best
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def candidate_weights(candidate, features, losses, errors, query):
|
|
73
|
+
k = len(METHODS)
|
|
74
|
+
kind, shrink, temp = candidate
|
|
75
|
+
if not len(losses) or kind == "equal":
|
|
76
|
+
return np.full(k, 1 / k)
|
|
77
|
+
if kind == "stack":
|
|
78
|
+
weights = convex_weights(errors.reshape(-1, k), shrink)
|
|
79
|
+
else:
|
|
80
|
+
ages = np.arange(len(losses) - 1, -1, -1)
|
|
81
|
+
recency = np.exp2(-ages / CONFIG["half_life_origins"])
|
|
82
|
+
global_loss = np.average(losses, axis=0, weights=recency)
|
|
83
|
+
score = global_loss
|
|
84
|
+
if kind == "context":
|
|
85
|
+
distance = np.sqrt(np.mean((features - query) ** 2, axis=1))
|
|
86
|
+
bandwidth = max(float(np.median(distance)), CONFIG["kernel_bandwidth_floor"])
|
|
87
|
+
local = recency * np.exp(-.5 * (distance / bandwidth) ** 2)
|
|
88
|
+
score = (1 - shrink) * np.average(losses, axis=0, weights=local) + shrink * global_loss
|
|
89
|
+
weights = np.exp(np.clip(-(score - np.min(score)) / temp, -700, 0))
|
|
90
|
+
weights /= weights.sum()
|
|
91
|
+
return .95 * weights + .05 / k
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@lru_cache(maxsize=16)
|
|
95
|
+
def memory(values, m, h):
|
|
96
|
+
y = np.asarray(values, dtype=float)
|
|
97
|
+
first, last = max(2 * m, 2 * h + 8, 24), len(y) - h
|
|
98
|
+
if first > last:
|
|
99
|
+
return (), np.empty((0, 4)), np.empty((0, len(METHODS))), np.empty((0, h, len(METHODS))), ()
|
|
100
|
+
origins = tuple(int(o) for o in np.unique(np.linspace(first, last, CONFIG["memory_origins"], dtype=int)))
|
|
101
|
+
features, losses, errors, paths = [], [], [], []
|
|
102
|
+
for origin in origins:
|
|
103
|
+
prefix, truth = y[:origin], y[origin:origin+h]
|
|
104
|
+
quantiles = np.array([gaussian_quantiles(*fn(prefix, m, h), (.1, .5, .9)) for _, _, fn in METHODS])
|
|
105
|
+
normalized_error = (quantiles[:, :, 1].T - truth[:, None]) / _scale(prefix, m)
|
|
106
|
+
features.append(context_features(prefix, m))
|
|
107
|
+
losses.append(np.mean(abs(normalized_error), axis=0))
|
|
108
|
+
errors.append(normalized_error)
|
|
109
|
+
paths.append(quantiles)
|
|
110
|
+
return origins, np.asarray(features), np.asarray(losses), np.asarray(errors), tuple(paths)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def temporal_splits(origins, horizon):
|
|
114
|
+
"""Training outcomes must be fully observed when an inner forecast is issued."""
|
|
115
|
+
return tuple({"query": query, "train": tuple(i for i, origin in enumerate(origins) if origin+horizon <= query)}
|
|
116
|
+
for query in origins)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def fit_validated(y, m, h, *, stacking=True):
|
|
120
|
+
y = np.asarray(y, dtype=float)
|
|
121
|
+
if y.ndim != 1 or len(y) < 2 or not np.isfinite(y).all() or m < 1 or h < 1:
|
|
122
|
+
raise ValueError("Finite contiguous history, positive seasonality and horizon are required")
|
|
123
|
+
origins, features, losses, errors, _paths = memory(tuple(y), m, h)
|
|
124
|
+
candidates = CANDIDATES if stacking else tuple(c for c in CANDIDATES if c[0] != "stack")
|
|
125
|
+
splits = tuple(s for s in temporal_splits(origins, h) if len(s["train"]) >= 3)
|
|
126
|
+
scores = []
|
|
127
|
+
for candidate in candidates:
|
|
128
|
+
values = []
|
|
129
|
+
for split in splits:
|
|
130
|
+
j = origins.index(split["query"])
|
|
131
|
+
train = list(split["train"])
|
|
132
|
+
w = candidate_weights(candidate, features[train], losses[train], errors[train], features[j])
|
|
133
|
+
values.append(float(np.mean(abs(errors[j] @ w))))
|
|
134
|
+
scores.append({"configuration": candidate, "mean_validation_mae_scaled": float(np.mean(values)) if values else None,
|
|
135
|
+
"validation_blocks": len(values)})
|
|
136
|
+
if splits:
|
|
137
|
+
winner = min(range(len(candidates)), key=lambda i: (scores[i]["mean_validation_mae_scaled"], i))
|
|
138
|
+
else:
|
|
139
|
+
winner = 0 # explicit original recency-only fallback
|
|
140
|
+
selected = candidates[winner]
|
|
141
|
+
w = candidate_weights(selected, features, losses, errors, context_features(y, m))
|
|
142
|
+
experts = np.array([gaussian_quantiles(*fn(y, m, h), (.1, .5, .9)) for _, _, fn in METHODS])
|
|
143
|
+
combined = np.tensordot(w, experts, axes=(0, 0))
|
|
144
|
+
return ValidatedFit(combined, w, selected, tuple(scores), origins, splits)
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"""Optional time-series diagnostics delegated to named scientific implementations.
|
|
2
|
+
|
|
3
|
+
Results describe the supplied sequence. Retrospective use is distinct from
|
|
4
|
+
prefix-only forecasting features; statistical assumptions remain method-specific.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from . import (
|
|
9
|
+
autocorrelation,
|
|
10
|
+
causality,
|
|
11
|
+
changepoints,
|
|
12
|
+
distribution,
|
|
13
|
+
filters,
|
|
14
|
+
fractal,
|
|
15
|
+
nonlinear,
|
|
16
|
+
seasonality,
|
|
17
|
+
stationarity,
|
|
18
|
+
volatility,
|
|
19
|
+
)
|
|
20
|
+
from .autocorrelation import (
|
|
21
|
+
Correlogram,
|
|
22
|
+
PortmanteauTest,
|
|
23
|
+
acf,
|
|
24
|
+
autocorrelation_report,
|
|
25
|
+
bartlett_band,
|
|
26
|
+
box_pierce,
|
|
27
|
+
durbin_watson,
|
|
28
|
+
lag_plot_pairs,
|
|
29
|
+
ljung_box,
|
|
30
|
+
pacf,
|
|
31
|
+
)
|
|
32
|
+
from .causality import (
|
|
33
|
+
CointegrationResult,
|
|
34
|
+
CrossCorrelation,
|
|
35
|
+
GrangerResult,
|
|
36
|
+
causality_report,
|
|
37
|
+
cointegration,
|
|
38
|
+
cross_correlation,
|
|
39
|
+
granger_causality,
|
|
40
|
+
)
|
|
41
|
+
from .changepoints import (
|
|
42
|
+
ChangePoints,
|
|
43
|
+
CusumResult,
|
|
44
|
+
RegimeFit,
|
|
45
|
+
binseg,
|
|
46
|
+
changepoints_report,
|
|
47
|
+
cusum_stability,
|
|
48
|
+
markov_regimes,
|
|
49
|
+
pelt,
|
|
50
|
+
)
|
|
51
|
+
from .distribution import (
|
|
52
|
+
ComplexitySummary,
|
|
53
|
+
DistributionSummary,
|
|
54
|
+
catch22_features,
|
|
55
|
+
complexity,
|
|
56
|
+
distribution_report,
|
|
57
|
+
kde_curve,
|
|
58
|
+
qq_points,
|
|
59
|
+
summary,
|
|
60
|
+
)
|
|
61
|
+
from .filters import (
|
|
62
|
+
EMDResult,
|
|
63
|
+
FilterResult,
|
|
64
|
+
Scalogram,
|
|
65
|
+
baxter_king,
|
|
66
|
+
ceemdan,
|
|
67
|
+
christiano_fitzgerald,
|
|
68
|
+
cwt_scalogram,
|
|
69
|
+
emd,
|
|
70
|
+
filters_report,
|
|
71
|
+
hodrick_prescott,
|
|
72
|
+
)
|
|
73
|
+
from .fractal import (
|
|
74
|
+
FractalDimension,
|
|
75
|
+
HurstResult,
|
|
76
|
+
MultifractalSpectrum,
|
|
77
|
+
dcca_coefficient,
|
|
78
|
+
fractal_dimension,
|
|
79
|
+
fractal_report,
|
|
80
|
+
hurst,
|
|
81
|
+
mfdfa,
|
|
82
|
+
)
|
|
83
|
+
from .nonlinear import (
|
|
84
|
+
RQA,
|
|
85
|
+
ChaosMeasures,
|
|
86
|
+
chaos_measures,
|
|
87
|
+
iaaft_surrogate,
|
|
88
|
+
nonlinear_report,
|
|
89
|
+
recurrence_quantification,
|
|
90
|
+
time_delay_embed,
|
|
91
|
+
zero_one_test,
|
|
92
|
+
)
|
|
93
|
+
from .seasonality import (
|
|
94
|
+
Decomp,
|
|
95
|
+
SeasonalSubseries,
|
|
96
|
+
Spectrum,
|
|
97
|
+
mstl_decompose,
|
|
98
|
+
periodogram,
|
|
99
|
+
seasonal_strength,
|
|
100
|
+
seasonal_subseries,
|
|
101
|
+
seasonality_report,
|
|
102
|
+
stl_decompose,
|
|
103
|
+
welch,
|
|
104
|
+
)
|
|
105
|
+
from .stationarity import (
|
|
106
|
+
TestResult,
|
|
107
|
+
adf,
|
|
108
|
+
combined_verdict,
|
|
109
|
+
dfgls,
|
|
110
|
+
kpss,
|
|
111
|
+
ndiffs,
|
|
112
|
+
nsdiffs,
|
|
113
|
+
phillips_perron,
|
|
114
|
+
stationarity_report,
|
|
115
|
+
zivot_andrews,
|
|
116
|
+
)
|
|
117
|
+
from .volatility import (
|
|
118
|
+
ArchTest,
|
|
119
|
+
BoxCoxResult,
|
|
120
|
+
GarchFit,
|
|
121
|
+
arch_lm,
|
|
122
|
+
box_cox,
|
|
123
|
+
garch,
|
|
124
|
+
rolling_volatility,
|
|
125
|
+
volatility_report,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
__all__ = [
|
|
129
|
+
"RQA",
|
|
130
|
+
# volatility
|
|
131
|
+
"ArchTest",
|
|
132
|
+
"BoxCoxResult",
|
|
133
|
+
# changepoints
|
|
134
|
+
"ChangePoints",
|
|
135
|
+
# nonlinear
|
|
136
|
+
"ChaosMeasures",
|
|
137
|
+
"CointegrationResult",
|
|
138
|
+
"ComplexitySummary",
|
|
139
|
+
# autocorrelation
|
|
140
|
+
"Correlogram",
|
|
141
|
+
# causality
|
|
142
|
+
"CrossCorrelation",
|
|
143
|
+
"CusumResult",
|
|
144
|
+
"Decomp",
|
|
145
|
+
# distribution
|
|
146
|
+
"DistributionSummary",
|
|
147
|
+
"EMDResult",
|
|
148
|
+
# filters
|
|
149
|
+
"FilterResult",
|
|
150
|
+
"FractalDimension",
|
|
151
|
+
"GarchFit",
|
|
152
|
+
"GrangerResult",
|
|
153
|
+
# fractal
|
|
154
|
+
"HurstResult",
|
|
155
|
+
"MultifractalSpectrum",
|
|
156
|
+
"PortmanteauTest",
|
|
157
|
+
"RegimeFit",
|
|
158
|
+
"Scalogram",
|
|
159
|
+
"SeasonalSubseries",
|
|
160
|
+
# seasonality (seasonal_strength lives here as the canonical home)
|
|
161
|
+
"Spectrum",
|
|
162
|
+
# stationarity
|
|
163
|
+
"TestResult",
|
|
164
|
+
"acf",
|
|
165
|
+
"adf",
|
|
166
|
+
"arch_lm",
|
|
167
|
+
"autocorrelation",
|
|
168
|
+
"autocorrelation_report",
|
|
169
|
+
"bartlett_band",
|
|
170
|
+
"baxter_king",
|
|
171
|
+
"binseg",
|
|
172
|
+
"box_cox",
|
|
173
|
+
"box_pierce",
|
|
174
|
+
"catch22_features",
|
|
175
|
+
"causality",
|
|
176
|
+
"causality_report",
|
|
177
|
+
"ceemdan",
|
|
178
|
+
"changepoints",
|
|
179
|
+
"changepoints_report",
|
|
180
|
+
"chaos_measures",
|
|
181
|
+
"christiano_fitzgerald",
|
|
182
|
+
"cointegration",
|
|
183
|
+
"combined_verdict",
|
|
184
|
+
"complexity",
|
|
185
|
+
"cross_correlation",
|
|
186
|
+
"cusum_stability",
|
|
187
|
+
"cwt_scalogram",
|
|
188
|
+
"dcca_coefficient",
|
|
189
|
+
"dfgls",
|
|
190
|
+
"distribution",
|
|
191
|
+
"distribution_report",
|
|
192
|
+
"durbin_watson",
|
|
193
|
+
"emd",
|
|
194
|
+
"filters",
|
|
195
|
+
"filters_report",
|
|
196
|
+
"fractal",
|
|
197
|
+
"fractal_dimension",
|
|
198
|
+
"fractal_report",
|
|
199
|
+
"garch",
|
|
200
|
+
"granger_causality",
|
|
201
|
+
"hodrick_prescott",
|
|
202
|
+
"hurst",
|
|
203
|
+
"iaaft_surrogate",
|
|
204
|
+
"kde_curve",
|
|
205
|
+
"kpss",
|
|
206
|
+
"lag_plot_pairs",
|
|
207
|
+
"ljung_box",
|
|
208
|
+
"markov_regimes",
|
|
209
|
+
"mfdfa",
|
|
210
|
+
"mstl_decompose",
|
|
211
|
+
"ndiffs",
|
|
212
|
+
"nonlinear",
|
|
213
|
+
"nonlinear_report",
|
|
214
|
+
"nsdiffs",
|
|
215
|
+
"pacf",
|
|
216
|
+
"pelt",
|
|
217
|
+
"periodogram",
|
|
218
|
+
"phillips_perron",
|
|
219
|
+
"qq_points",
|
|
220
|
+
"recurrence_quantification",
|
|
221
|
+
"rolling_volatility",
|
|
222
|
+
"seasonal_strength",
|
|
223
|
+
"seasonal_subseries",
|
|
224
|
+
"seasonality",
|
|
225
|
+
"seasonality_report",
|
|
226
|
+
"stationarity",
|
|
227
|
+
"stationarity_report",
|
|
228
|
+
"stl_decompose",
|
|
229
|
+
"summary",
|
|
230
|
+
"time_delay_embed",
|
|
231
|
+
"volatility",
|
|
232
|
+
"volatility_report",
|
|
233
|
+
"welch",
|
|
234
|
+
"zero_one_test",
|
|
235
|
+
"zivot_andrews",
|
|
236
|
+
]
|