add timeseries endpoints and update server.ts
/api/series/auto-arima-find: find parameters of SARIMA model automatically /api/series/manual-forecast: use determined model with parameters to forecast next values /api/series/identify-correlations: Calculate ACF and PACF for a time series /api/series/decompose-stl: Applies Seasonal-Trend-Loess (STL) decomposition to separate the series into trend, seasonal, and residual components.
This commit is contained in:
parent
13f3c7b053
commit
b1b2dcf18c
3 changed files with 682 additions and 26 deletions
133
analysis_pipelines.ts
Normal file
133
analysis_pipelines.ts
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
// analysis_pipelines.ts - High-level workflows for common analysis tasks.
|
||||
|
||||
import { SignalProcessor } from './signal_processing_convolution';
|
||||
import { TimeSeriesAnalyzer, STLDecomposition } from './timeseries';
|
||||
|
||||
/**
|
||||
* The comprehensive result of a denoise and detrend operation.
|
||||
*/
|
||||
export interface DenoiseAndDetrendResult {
|
||||
original: number[];
|
||||
smoothed: number[];
|
||||
decomposition: STLDecomposition;
|
||||
}
|
||||
|
||||
/**
|
||||
* The result of an automatic SARIMA parameter search.
|
||||
*/
|
||||
export interface AutoArimaResult {
|
||||
bestModel: {
|
||||
p: number;
|
||||
d: number;
|
||||
q: number;
|
||||
P: number;
|
||||
D: number;
|
||||
Q: number;
|
||||
s: number; // Correctly included
|
||||
aic: number;
|
||||
};
|
||||
searchLog: { p: number; d: number; q: number; P: number; D: number; Q: number; s: number; aic: number }[];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A class containing high-level analysis pipelines that combine
|
||||
* functions from various processing libraries.
|
||||
*/
|
||||
export class AnalysisPipelines {
|
||||
|
||||
/**
|
||||
* A full pipeline to take a raw signal, smooth it to remove noise,
|
||||
* and then decompose it into trend, seasonal, and residual components.
|
||||
* @param series The original time series data.
|
||||
* @param period The seasonal period for STL decomposition.
|
||||
* @param smoothWindow The window size for the initial smoothing (denoising) pass.
|
||||
* @returns An object containing the original, smoothed, and decomposed series.
|
||||
*/
|
||||
static denoiseAndDetrend(series: number[], period: number, smoothWindow: number = 5): DenoiseAndDetrendResult {
|
||||
// Ensure window is odd for symmetry
|
||||
if (smoothWindow > 1 && smoothWindow % 2 === 0) {
|
||||
smoothWindow++;
|
||||
}
|
||||
const smoothed = SignalProcessor.smooth(series, {
|
||||
method: 'gaussian',
|
||||
windowSize: smoothWindow
|
||||
});
|
||||
|
||||
const decomposition = TimeSeriesAnalyzer.stlDecomposition(smoothed, period);
|
||||
|
||||
return {
|
||||
original: series,
|
||||
smoothed: smoothed,
|
||||
decomposition: decomposition,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* [FINAL CORRECTED VERSION] Performs a full grid search to find the optimal SARIMA parameters.
|
||||
* This version now correctly includes 's' in the final result object.
|
||||
* @param series The original time series data.
|
||||
* @param seasonalPeriod The seasonal period of the data (e.g., 7 for weekly, 12 for monthly).
|
||||
* @returns An object containing the best model parameters and a log of the search.
|
||||
*/
|
||||
static findBestArimaParameters(
|
||||
series: number[],
|
||||
seasonalPeriod: number,
|
||||
maxD: number = 1,
|
||||
maxP: number = 2,
|
||||
maxQ: number = 2,
|
||||
maxSeasonalD: number = 1,
|
||||
maxSeasonalP: number = 2,
|
||||
maxSeasonalQ: number = 2
|
||||
): AutoArimaResult {
|
||||
|
||||
const searchLog: any[] = [];
|
||||
let bestModel: any = { aic: Infinity };
|
||||
|
||||
const calculateAIC = (residuals: number[], numParams: number): number => {
|
||||
const n = residuals.length;
|
||||
if (n === 0) return Infinity;
|
||||
const sse = residuals.reduce((sum, r) => sum + r * r, 0);
|
||||
if (sse < 1e-9) return -Infinity; // Perfect fit
|
||||
const logLikelihood = -n / 2 * (Math.log(2 * Math.PI) + Math.log(sse / n)) - n / 2;
|
||||
return 2 * numParams - 2 * logLikelihood;
|
||||
};
|
||||
|
||||
// Grid search over all parameter combinations
|
||||
for (let d = 0; d <= maxD; d++) {
|
||||
for (let p = 0; p <= maxP; p++) {
|
||||
for (let q = 0; q <= maxQ; q++) {
|
||||
for (let D = 0; D <= maxSeasonalD; D++) {
|
||||
for (let P = 0; P <= maxSeasonalP; P++) {
|
||||
for (let Q = 0; Q <= maxSeasonalQ; Q++) {
|
||||
// Skip trivial models where nothing is done
|
||||
if (p === 0 && d === 0 && q === 0 && P === 0 && D === 0 && Q === 0) continue;
|
||||
|
||||
const options = { p, d, q, P, D, Q, s: seasonalPeriod };
|
||||
try {
|
||||
const { residuals } = TimeSeriesAnalyzer.arimaForecast(series, options, 0);
|
||||
const numParams = p + q + P + Q;
|
||||
const aic = calculateAIC(residuals, numParams);
|
||||
|
||||
// Construct the full model info object, ensuring 's' is included
|
||||
const modelInfo = { p, d, q, P, D, Q, s: seasonalPeriod, aic };
|
||||
searchLog.push(modelInfo);
|
||||
|
||||
if (modelInfo.aic < bestModel.aic) {
|
||||
bestModel = modelInfo;
|
||||
}
|
||||
} catch (error) {
|
||||
// Skip invalid parameter combinations that cause errors
|
||||
}
|
||||
} } } } } }
|
||||
|
||||
if (bestModel.aic === Infinity) {
|
||||
throw new Error("Could not find a suitable SARIMA model. The data may be too short or complex.");
|
||||
}
|
||||
|
||||
// Sort the log by AIC for easier reading
|
||||
searchLog.sort((a, b) => a.aic - b.aic);
|
||||
|
||||
return { bestModel, searchLog };
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue