edit2:
The tweak suggested by Henrik Schumacher in Carl Woll's answer is so far a clear winner ($\times800$ over the benchmark); it's almost disturbing to contemplate how 'slow'-relatively speaking-something like -(list - center)
is...
edit1:
So far this is the best alternative (without compiling anything yet)
negativeSemivariance5 = Function[{list, center, length},
Total[Ramp[-(list - center)]^2]/(length - 1)
]
Evaluating the testing code snippet (simply replace negativeSemivariance4
with negativeSemivariance5
) returns
{190., True}
which indicates a $\times190$ improvement on the specified test case.
original:
Is there a better way to write a semivariance function?
This is my benchmark definition
negativeSemivariance0[list_, center_, length_] := Module[{sum = 0.},
Scan[If[# < center, sum += (# - center)^2] &, list];
sum/length
]
and this is the best version I can think of
negativeSemivariance4[list_, center_, length_] :=
With[{first = First[list]},
Fold[
With[{d = center - #2},
#1 + Ramp[d]^2
] &,
Ramp[center - first]^2,
Rest[list]
]/length
]
I use the following code for testing:
Through[{Divide @@ Part[#, All, 1] &, Equal @@ Part[#, All, -1] &}[BlockRandom[
With[{n = 5000},
With[{rand = RandomReal[{-1, 1}, n]},
With[{mean = Mean[rand]},
{negativeSemivariance0[rand, mean, n] // RepeatedTiming,
negativeSemivariance4[rand, mean, n] // RepeatedTiming}
]
]
], RandomSeeding -> 132456987]]]
Evaluating it, returns
{20., True}
which can be interpreted as saying that the second version is $\times 20$ faster than the first and both functions return the same result.
Ps.This function will be frequently called on many different lists of same length. The size of the list used for testing-n=5000
-is an average problem instance size.