Skip to content

Commit 0dffd1f

Browse files
authored
Add a function to calculate Cumulants (#810)
1 parent 76a5b64 commit 0dffd1f

File tree

4 files changed

+91
-1
lines changed

4 files changed

+91
-1
lines changed

docs/src/scalarstats.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ harmmean
2323
genmean
2424
```
2525

26-
## Moments
26+
## Moments and cumulants
2727

2828
```@docs
2929
var
@@ -33,6 +33,7 @@ mean_and_std
3333
skewness
3434
kurtosis
3535
moment
36+
cumulant
3637
```
3738

3839
## Measurements of Variation

src/StatsBase.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export
6666
skewness, # (standardized) skewness
6767
kurtosis, # (excessive) kurtosis
6868
moment, # central moment of given order
69+
cumulant, # cumulant of given order
6970
mean_and_var, # (mean, var)
7071
mean_and_std, # (mean, std)
7172
mean_and_cov, # (mean, cov)

src/moments.jl

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,3 +363,41 @@ end
363363

364364
kurtosis(v::RealArray) = kurtosis(v, mean(v))
365365
kurtosis(v::RealArray, wv::AbstractWeights) = kurtosis(v, wv, mean(v, wv))
366+
367+
"""
368+
cumulant(v, k, [wv::AbstractWeights], m=mean(v))
369+
370+
Return the `k`th order cumulant of a real-valued array `v`, optionally
371+
specifying a weighting vector `wv` and a pre-computed mean `m`.
372+
373+
If `k` is a range of `Integer`s, then return all the cumulants of orders in this range as a vector.
374+
375+
This quantity is calculated using a recursive definition on lower-order cumulants and central moments.
376+
377+
Reference: Smith, P. J. 1995. A Recursive Formulation of the Old Problem of Obtaining
378+
Moments from Cumulants and Vice Versa. The American Statistician, 49(2), 217–218.
379+
https://doi.org/10.2307/2684642
380+
"""
381+
function cumulant(v::RealArray, krange::Union{Integer, AbstractRange{<:Integer}}, wv::AbstractWeights,
382+
m::Real=mean(v, wv))
383+
if minimum(krange) <= 0
384+
throw(ArgumentError("Cumulant orders must be strictly positive."))
385+
end
386+
k = maximum(krange)
387+
cmoms = zeros(typeof(m), k)
388+
cumls = zeros(typeof(m), k)
389+
cmoms[1] = 0
390+
cumls[1] = m
391+
for i = 2:k
392+
kn = wv isa UnitWeights ? moment(v, i, m) : moment(v, i, wv, m)
393+
cmoms[i] = kn
394+
for j = 2:i-2
395+
kn -= binomial(i-1, j)*cmoms[j]*cumls[i-j]
396+
end
397+
cumls[i] = kn
398+
end
399+
return cumls[krange]
400+
end
401+
402+
cumulant(v::RealArray, krange::Union{Integer, AbstractRange{<:Integer}}, m::Real=mean(v)) =
403+
cumulant(v, krange, uweights(length(v)), m)

test/moments.jl

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,4 +290,54 @@ end
290290
@test moment(x, 5, w) sum((x2 .- 4).^5) / 5
291291
end
292292

293+
@testset "Cumulants with $f" for f in weight_funcs
294+
x = collect(2.0:8.0)
295+
@test cumulant(x, 2) moment(x, 2)
296+
@test cumulant(x, 3) moment(x, 3)
297+
@test cumulant(x, 4) moment(x, 4) - 3*moment(x, 2)^2
298+
@test cumulant(x, 5) moment(x, 5) - 10*moment(x, 3)*moment(x, 2)
299+
@test cumulant(x, 6)
300+
moment(x, 6) - 15*moment(x, 4)*moment(x, 2) - 10*moment(x, 3)^2 + 30*moment(x, 2)^3
301+
302+
@test cumulant(x, 1:6) == [cumulant(x, i) for i in 1:6]
303+
304+
@test cumulant(x, 2, 4.0) moment(x, 2, 4.0)
305+
@test cumulant(x, 3, 4.0) moment(x, 3, 4.0)
306+
@test cumulant(x, 4, 4.0) moment(x, 4, 4.0) - 3*moment(x, 2, 4.0)^2
307+
@test cumulant(x, 5, 4.0) moment(x, 5, 4.0) - 10*moment(x, 3, 4.0)*moment(x,2, 4.0)
308+
@test cumulant(x, 6, 4.0)
309+
moment(x, 6, 4.0) - 15*moment(x, 4, 4.0)*moment(x,2, 4.0) -
310+
10*moment(x, 3, 4.0)^2 + 30*moment(x, 2, 4.0)^3
311+
312+
@test cumulant(x, 1:6, 4.0) == [cumulant(x, i, 4.0) for i in 1:6]
313+
314+
w1 = f([1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0])
315+
x2 = collect(2.0:6.0)
316+
@test cumulant(x, 2, w) moment(x2, 2)
317+
@test cumulant(x, 3, w) moment(x2, 3)
318+
@test cumulant(x, 4, w) moment(x2, 4) - 3*moment(x2, 2)^2
319+
@test cumulant(x, 5, w) moment(x2, 5) - 10*moment(x2,3)*moment(x2,2)
320+
@test cumulant(x, 6, w)
321+
moment(x2, 6) - 15*moment(x2,4)*moment(x2,2) + 10*moment(x2,3)^2 + 30*moment(x2,2)^3
322+
323+
@test cumulant(x, 1:6, w) == [cumulant(x2, i) for i in 1:6]
324+
325+
x3 = collect(2:8)
326+
@test cumulant(x3, 6)
327+
moment(x3, 6) - 15*moment(x3, 4)*moment(x3, 2) - 10*moment(x3, 3)^2 +
328+
30*moment(x3, 2)^3
329+
330+
w2 = f([1, 1, 1, 1, 1, 0, 0])
331+
x4 = collect(2:6)
332+
@test cumulant(x3, 6, w)
333+
moment(x4, 6) - 15*moment(x4, 4)*moment(x4, 2) +
334+
10*moment(x4, 3)^2 + 30*moment(x4, 2)^3
335+
336+
@test_throws ArgumentError cumulant(x, -1)
337+
@test_throws ArgumentError cumulant(x, 0)
338+
@test_throws ArgumentError cumulant(x, 0:3)
339+
@test_throws ArgumentError cumulant(x, -1:3)
340+
@test_throws ArgumentError cumulant(x, 1:0)
341+
end
342+
293343
end # @testset "StatsBase.Moments"

0 commit comments

Comments
 (0)