For use running incremental statistics over windowed data, I have some structs that share struct-function-like methods. Here is an example that keeps track of the maximum value encountered as it runs over a windowed data stream.
abstract type Accumulator{T} end
mutable struct AccumMax{T} <: Accumulator{T}
max::T
AccumMax(::Type{T}=Float64) where {T} =
(T <: Integer) ? new{T}(typemin(T)) : new{T}(floatmin(T))
end
(accum::AccumMax{T})() where {T} = (accum.max)
(accum::AccumMax{T})(x) where {T} =
(accum.max = ifelse(accum.max < x, T(x), accum.max))
julia> accum = AccumMax(Float32)
AccumMax{Float32}(1.1754944f-38)
julia> accum(17.0f0) # the one-arg form accumulates the value
17.0f0
jula> accum(5.0f0);
julia> accum() # the no-args form retrieves the accumulated value
17.0f0
I want to offer AccumMaxAbs
that does what AccumMax
does using abs(x)
in place of x
. This is not too hard to do in a way that keeps interoperability with e.g. AccumMin
so AccumMinAbs
can be realized the same way.
(accum::AccumMax{T})(x, fx::F=identity) where {T,F<:Function} =
(x = fx(x); accum.max = ifelse(accum.max < x, T(x), accum.max))
[currently I recode each explicitly … there are more than two]
It would be great if there were a way to auto-generate the Abs
versions; I am not concerned so much about making Abs
versions by hand – I am opaque as to how one approaches modifying the internal calculation in the third statement in a more detailed accumulator. I would like to turn the following into an exponentially weighted version of itself knowing how the modification should be, without entirely recoding it (I would like to offer an exponentially weighted variant of most Accumulators).
mutable struct AccumMeanVariance{T} <: Accumulator{T}
n::Int
mean::T
svar::T
AccumMeanVariance(::Type{T}=Float64) where {T} = new{T}(0, zero(T), zero(T))
end
function (accum::AccumMeanVariance{T})() where {T}
unbiased_var = accum.svar / (accum.n - 1)
(a.mean, unbiased_var)
end
function (accum::AccumMeanVariance{T})(x) where {T}
if !iszero(accum.n)
oldmean = accum.mean
accum.mean = oldmean + (x - oldmean) / accum.n
accum.svar = accum.svar + (x - oldmean) * (x - accum.mean)
else
acccum.mean = x # svar is zero already
end
end
the exponentially weighted version of this is
mutable struct AccumExpWMeanVariance{T} <: Accumulator{T}
n::Int
alpha::T
mean::T
svar::T
AccumExpWMeanVariance(::Type{T}=Float64; alpha::T=0.5) where {T} =
new{T}(0, alpha, zero(T), zero(T))
end
function (accum::AccumExpWMeanVariance{T})() where {T}
unbiased_var = accum.svar / (accum.n = 1)
(accum.mean, unbiased_var)
end
function (accum::AccumExpWMeanVariance{T})(x) where {T}
accum.n += 1
diff = x - accum.mean
incr = accum.alpha * diff
accum.mean += accum.alpha * (x - a.mean)
accum.svar = (one(T) - accum.alpha) * (accum.svar + diff * incr)
end