-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathZScore.jl
93 lines (79 loc) · 2.52 KB
/
ZScore.jl
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
84
85
86
87
88
89
90
91
92
93
using DataStructures
"""
Calculates the simple moving z-score with a fixed window size in O(1) time.
The z-score is computed as `(x - μ) / σ`, where `x` is the current value, `μ` is the mean,
and `σ` is the standard deviation of the values in the moving window.
# Type parameters
- `In`: Input number type
- `Out`: Output number type
- `corrected`: Boolean flag for variance correction
# Arguments
- `window_size::Int`: Size of the moving window (must be greater than 0)
- `corrected::Bool=true`: Whether to use corrected (unbiased) variance (default is true)
"""
mutable struct ZScore{In<:Number,Out<:Number,corrected} <: StreamOperation
const buffer::CircularBuffer{In}
const window_size::Int
const corrected::Bool
M1::Out # Mean
M2::Out # Variance * (n-1)
current::Out
function ZScore{In,Out}(
window_size::Int
;
corrected::Bool=true
) where {In<:Number,Out<:Number}
@assert window_size > 0 "Window size must be greater than 0"
new{In,Out,corrected}(
CircularBuffer{In}(window_size),
window_size,
corrected,
zero(Out), # M1 (Mean)
zero(Out), # M2 (Variance * (n-1))
zero(Out) # current
)
end
end
@inline function (op::ZScore{In,Out})(executor, value::In) where {In<:Number,Out<:Number}
if isfull(op.buffer)
dropped = popfirst!(op.buffer)
n1 = length(op.buffer)
delta = dropped - op.M1
delta_n = delta / n1
op.M1 -= delta_n
op.M2 -= delta * (dropped - op.M1)
else
n1 = length(op.buffer)
end
n = n1 + 1
push!(op.buffer, value)
# Update mean and variance
delta = value - op.M1
delta_n = delta / n
term1 = delta * delta_n * n1
op.M1 += delta_n
op.M2 += term1
# Calculate z-score
if n > 1
variance = calculate_variance(op, n)
std_dev = sqrt(variance)
op.current = (value - op.M1) / std_dev
else
op.current = zero(Out)
end
nothing
end
@inline function is_valid(op::ZScore{In,Out}) where {In,Out}
isfull(op.buffer)
end
@inline function get_state(op::ZScore{In,Out})::Out where {In,Out}
op.current
end
# Corrected (unbiased) variance calculation
@inline function calculate_variance(op::ZScore{In,Out,true}, n::Int)::Out where {In,Out}
n > 1 ? op.M2 / (n - 1) : zero(Out)
end
# Uncorrected (biased) variance calculation
@inline function calculate_variance(op::ZScore{In,Out,false}, n::Int)::Out where {In,Out}
n > 0 ? op.M2 / n : zero(Out)
end