code wiki / _hdl_build / nx_timeseries.nx
nx_timeseries.nx source
↩ module page · 79 lines · 2915 B
1// nx_timeseries.nx -- LIB: TIME-SERIES analytics over an ordered i64 column (rounds out the analytics
2// primitive set: aggregation + profiling + correlation + NOW time). Windowed rolling mean, least-squares
3// LINEAR TREND (slope + direction class), and overall growth. Integer/fixed-point (bit-reproducible).
4// Composes nx_dataframe. license_tier: ORIGINAL
5import "nx_syscalls.nx"
6import "_hdl_build/nx_dataframe.nx"
7
8const TS_FLAT: i64 = 0
9const TS_RISING: i64 = 1
10const TS_FALLING: i64 = 2
11
12// rolling (trailing) mean of window w into out[0..n): out[i] = mean(col[max(0,i-w+1)..i]). out must hold n.
13func ts_rolling_mean(col: *i64, n: i64, w: i64, out: *i64) -> i64 {
14 if w < 1 { return 0 - 1 }
15 var i: i64 = 0
16 while i < n {
17 var lo: i64 = i - w + 1
18 if lo < 0 { lo = 0 }
19 var s: i64 = 0
20 var j: i64 = lo
21 while j <= i { s = s + col[j]; j = j + 1 }
22 let cnt: i64 = i - lo + 1
23 out[i] = s / cnt
24 i = i + 1
25 }
26 return 0
27}
28
29// least-squares linear trend of col vs index (0..n-1). Writes slope*1000 to out_slope_milli and the integer
30// intercept to out_intercept. slope = (n*Sxy - Sx*Sy)/(n*Sxx - Sx^2), x=index. Returns 0 ok, -1 degenerate.
31func ts_linear_trend(col: *i64, n: i64, out_slope_milli: *i64, out_intercept: *i64) -> i64 {
32 if n < 2 { out_slope_milli[0] = 0; out_intercept[0] = 0; return 0 - 1 }
33 var sx: i64 = 0
34 var sy: i64 = 0
35 var sxy: i64 = 0
36 var sxx: i64 = 0
37 var i: i64 = 0
38 while i < n {
39 sx = sx + i
40 sy = sy + col[i]
41 sxy = sxy + i * col[i]
42 sxx = sxx + i * i
43 i = i + 1
44 }
45 let denom: i64 = n * sxx - sx * sx
46 if denom == 0 { out_slope_milli[0] = 0; out_intercept[0] = 0; return 0 - 1 }
47 let num: i64 = n * sxy - sx * sy
48 out_slope_milli[0] = (num * 1000) / denom
49 // intercept = (sy - slope*sx)/n ; use slope_milli to keep precision then /1000
50 let sm: i64 = out_slope_milli[0]
51 out_intercept[0] = (sy * 1000 - sm * sx) / (n * 1000)
52 return 0
53}
54
55// classify a slope_milli into direction. `flat_band_milli` = |slope*1000| below which we call it flat.
56func ts_trend_class(slope_milli: i64, flat_band_milli: i64) -> i64 {
57 var a: i64 = slope_milli
58 if a < 0 { a = 0 - a }
59 if a <= flat_band_milli { return TS_FLAT }
60 if slope_milli > 0 { return TS_RISING }
61 return TS_FALLING
62}
63
64func ts_class_str(cls: i64) -> *u8 {
65 if cls == TS_RISING { return "rising" as *u8 }
66 if cls == TS_FALLING { return "falling" as *u8 }
67 return "flat" as *u8
68}
69
70// overall growth in permil from the first value to the last: (last-first)*1000/|first|. first==0 -> sentinel.
71func ts_growth_permil(col: *i64, n: i64) -> i64 {
72 if n < 2 { return 0 }
73 let first: i64 = col[0]
74 let last: i64 = col[n - 1]
75 if first == 0 { return 0 - 1 }
76 var f: i64 = first
77 if f < 0 { f = 0 - f }
78 return ((last - first) * 1000) / f
79}