Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | 1x 25x 25x 25x 25x 25x 25x 25x 25x 25x 25x 25x 25x 25x 25x 26x 20x 20x 26x 18x 26x 16x 20x 9x 16x 7x 26x 25x 9x 9x 8x 9x 184x 2218x 2218x 1469x 2218x 184x 8x 9x 9x 25x 16x 16x 13x 13x 16x 11x 16x 33x 27x 33x 971x 971x 27x 27x 27x 11x 16x 25x | /**
* AutoModelSelector — selects the best forecasting model based on data characteristics.
*
* Analyzes trend (Mann-Kendall τ), seasonality (ACF peak), length, and intermittency.
* Thresholds derived from forecasting literature (Assimakopoulos 2010, Syntetos 2005).
*/
import type { DataPoint } from '../types.js'
export class AutoModelSelector {
private enabled: boolean
private shortThreshold: number
private seasonalityThreshold: number
private trendThreshold: number
private intermittencyThreshold: number
constructor(enabled = true, opts?: {
shortThreshold?: number; seasonalityThreshold?: number
trendThreshold?: number; intermittencyThreshold?: number
}) {
this.enabled = enabled
this.shortThreshold = opts?.shortThreshold ?? 20
this.seasonalityThreshold = opts?.seasonalityThreshold ?? 0.3
this.trendThreshold = opts?.trendThreshold ?? 0.5
this.intermittencyThreshold = opts?.intermittencyThreshold ?? 0.3
}
select(context: DataPoint[]): string {
if (!this.enabled || context.length === 0) return 'AutoForecaster'
const values = context.map(p => p.value)
const n = values.length
if (n < this.shortThreshold) return 'ThetaForecaster'
const zeroRatio = values.filter(v => v === 0 || Math.abs(v) < 1e-10).length / n
if (zeroRatio > this.intermittencyThreshold) return 'CrostonForecaster'
const seasonality = this.computeSeasonality(values)
if (seasonality > this.seasonalityThreshold) return 'AutoETSForecaster'
const trend = this.computeTrend(values)
if (Math.abs(trend) > this.trendThreshold) return 'AutoARIMAForecaster'
return 'AutoForecaster'
}
/** Compute trend strength via Mann-Kendall τ simplified. */
private computeTrend(values: number[]): number {
const n = values.length
if (n < 3) return 0
let concordant = 0; let discordant = 0
for (let i = 0; i < n - 1; i++) {
for (let j = i + 1; j < Math.min(i + 20, n); j++) {
const diff = values[j]! - values[i]!
if (diff > 0) concordant++
else if (diff < 0) discordant++
}
}
const total = concordant + discordant
return total > 0 ? (concordant - discordant) / total : 0
}
/** Compute seasonality strength via ACF at candidate lags. */
private computeSeasonality(values: number[]): number {
const n = values.length
if (n < 15) return 0
const mean = values.reduce((a, b) => a + b, 0) / n
const variance = values.reduce((s, v) => s + (v - mean) ** 2, 0) / n
if (variance < 1e-10) return 0
let maxAcf = 0
for (const lag of [7, 12, 24]) {
if (lag >= n / 2) continue
let acf = 0
for (let i = 0; i < n - lag; i++) {
acf += (values[i]! - mean) * (values[i + lag]! - mean)
}
acf /= (n - lag) * variance
maxAcf = Math.max(maxAcf, Math.abs(acf))
}
return maxAcf
}
}
|