From 4f9028c916c6f9385f3a0bdf2d74634424c6e0aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogumi=C5=82=20Kami=C5=84ski?= Date: Tue, 4 Sep 2018 11:29:52 +0200 Subject: [PATCH 1/5] add handling of an empty iterator for mean and var --- stdlib/Statistics/src/Statistics.jl | 6 ++++-- stdlib/Statistics/test/runtests.jl | 7 +++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/stdlib/Statistics/src/Statistics.jl b/stdlib/Statistics/src/Statistics.jl index bc7a857079e88..081a2f1581ec8 100644 --- a/stdlib/Statistics/src/Statistics.jl +++ b/stdlib/Statistics/src/Statistics.jl @@ -57,7 +57,8 @@ julia> mean([√1, √2, √3]) function mean(f::Base.Callable, itr) y = iterate(itr) if y === nothing - throw(ArgumentError("mean of empty collection undefined: $(repr(itr))")) + return Base.mapreduce_empty_iter(f, Base.add_sum, itr, + Base.IteratorEltype(itr)) / 0 end count = 1 value, state = y @@ -148,7 +149,8 @@ var(iterable; corrected::Bool=true, mean=nothing) = _var(iterable, corrected, me function _var(iterable, corrected::Bool, mean) y = iterate(iterable) if y === nothing - throw(ArgumentError("variance of empty collection undefined: $(repr(iterable))")) + T = eltype(iterable) + return typeof((abs2(zero(T)) + abs2(zero(T)))/2)(NaN) end count = 1 value, state = y diff --git a/stdlib/Statistics/test/runtests.jl b/stdlib/Statistics/test/runtests.jl index 8cd4129e9bbbd..8df58151ac294 100644 --- a/stdlib/Statistics/test/runtests.jl +++ b/stdlib/Statistics/test/runtests.jl @@ -86,6 +86,10 @@ end @test ismissing(mean([missing, NaN])) @test isequal(mean([missing 1.0; 2.0 3.0], dims=1), [missing 2.0]) @test mean(skipmissing([1, missing, 2])) === 1.5 + @test isequal(mean(Complex{Float64}[]), NaN+NaN*im) + @test isequal(mean(skipmissing(Complex{Float64}[])), NaN+NaN*im) + @test isequal(mean(abs, Complex{Float64}[]), NaN) + @test isequal(mean(abs, skipmissing(Complex{Float64}[])), NaN) # Check that small types are accumulated using wider type for T in (Int8, UInt8) @@ -245,6 +249,9 @@ end @test ismissing(f([missing, NaN], missing)) @test f(skipmissing([1, missing, 2]), 0) === f([1, 2], 0) end + + @test isequal(mean(Complex{Float64}[]), NaN) + @test isequal(mean(skipmissing(Complex{Float64}[])), NaN) end function safe_cov(x, y, zm::Bool, cr::Bool) From 40b2ea71d4336a59c50c9aa9988486a79b57ee89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogumi=C5=82=20Kami=C5=84ski?= Date: Tue, 4 Sep 2018 16:19:19 +0200 Subject: [PATCH 2/5] additional tests --- stdlib/Statistics/test/runtests.jl | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/stdlib/Statistics/test/runtests.jl b/stdlib/Statistics/test/runtests.jl index 8df58151ac294..228875e4525ba 100644 --- a/stdlib/Statistics/test/runtests.jl +++ b/stdlib/Statistics/test/runtests.jl @@ -60,7 +60,7 @@ end end @testset "mean" begin - @test_throws ArgumentError mean(()) + @test_throws MethodError mean(()) @test mean((1,2,3)) === 2. @test mean([0]) === 0. @test mean([1.]) === 1. @@ -88,8 +88,17 @@ end @test mean(skipmissing([1, missing, 2])) === 1.5 @test isequal(mean(Complex{Float64}[]), NaN+NaN*im) @test isequal(mean(skipmissing(Complex{Float64}[])), NaN+NaN*im) + @test mean(Complex{Float64}[]) isa Complex{Float64} + @test mean(skipmissing(Complex{Float64}[])) isa Complex{Float64} @test isequal(mean(abs, Complex{Float64}[]), NaN) @test isequal(mean(abs, skipmissing(Complex{Float64}[])), NaN) + @test mean(abs, Complex{Float64}[]) isa Float64 + @test mean(abs, skipmissing(Complex{Float64}[])) isa Float64 + @test_throws MethodError mean([]) + @test_throws MethodError mean(skipmissing([])) + @test_throws MethodError mean((1 for i in 2:1)) + @test mean(Int[]) isa Float64 + @test mean(skipmissing(Int[])) isa Float64 # Check that small types are accumulated using wider type for T in (Int8, UInt8) @@ -113,10 +122,10 @@ end @testset "var & std" begin # edge case: empty vector # iterable; this has to throw for type stability - @test_throws ArgumentError var(()) - @test_throws ArgumentError var((); corrected=false) - @test_throws ArgumentError var((); mean=2) - @test_throws ArgumentError var((); mean=2, corrected=false) + @test_throws MethodError var(()) + @test_throws MethodError var((); corrected=false) + @test_throws MethodError var((); mean=2) + @test_throws MethodError var((); mean=2, corrected=false) # reduction @test isnan(var(Int[])) @test isnan(var(Int[]; corrected=false)) @@ -250,8 +259,15 @@ end @test f(skipmissing([1, missing, 2]), 0) === f([1, 2], 0) end - @test isequal(mean(Complex{Float64}[]), NaN) - @test isequal(mean(skipmissing(Complex{Float64}[])), NaN) + @test isequal(var(Complex{Float64}[]), NaN) + @test isequal(var(skipmissing(Complex{Float64}[])), NaN) + @test var(Complex{Float64}[]) isa Float64 + @test var(skipmissing(Complex{Float64}[])) isa Float64 + @test_throws MethodError var([]) + @test_throws MethodError var(skipmissing([])) + @test_throws ArgumentError var((1 for i in 2:1)) + @test var(Int[]) isa Float64 + @test var(skipmissing(Int[])) isa Float64 end function safe_cov(x, y, zm::Bool, cr::Bool) From 9e0b0ba93504c24aa68294e81619e392597e83cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogumi=C5=82=20Kami=C5=84ski?= Date: Tue, 4 Sep 2018 18:18:01 +0200 Subject: [PATCH 3/5] further cleanup of tests --- stdlib/Statistics/test/runtests.jl | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/stdlib/Statistics/test/runtests.jl b/stdlib/Statistics/test/runtests.jl index 228875e4525ba..6f0c4d7bdb1c3 100644 --- a/stdlib/Statistics/test/runtests.jl +++ b/stdlib/Statistics/test/runtests.jl @@ -87,18 +87,20 @@ end @test isequal(mean([missing 1.0; 2.0 3.0], dims=1), [missing 2.0]) @test mean(skipmissing([1, missing, 2])) === 1.5 @test isequal(mean(Complex{Float64}[]), NaN+NaN*im) - @test isequal(mean(skipmissing(Complex{Float64}[])), NaN+NaN*im) @test mean(Complex{Float64}[]) isa Complex{Float64} + @test isequal(mean(skipmissing(Complex{Float64}[])), NaN+NaN*im) @test mean(skipmissing(Complex{Float64}[])) isa Complex{Float64} @test isequal(mean(abs, Complex{Float64}[]), NaN) - @test isequal(mean(abs, skipmissing(Complex{Float64}[])), NaN) @test mean(abs, Complex{Float64}[]) isa Float64 + @test isequal(mean(abs, skipmissing(Complex{Float64}[])), NaN) @test mean(abs, skipmissing(Complex{Float64}[])) isa Float64 - @test_throws MethodError mean([]) - @test_throws MethodError mean(skipmissing([])) - @test_throws MethodError mean((1 for i in 2:1)) + @test isequal(mean(Int[]), NaN) @test mean(Int[]) isa Float64 + @test isequal(mean(skipmissing(Int[])), NaN) @test mean(skipmissing(Int[])) isa Float64 + @test_throws MethodError mean([]) + @test_throws MethodError mean(skipmissing([])) + @test_throws ArgumentError mean((1 for i in 2:1)) # Check that small types are accumulated using wider type for T in (Int8, UInt8) @@ -260,13 +262,15 @@ end end @test isequal(var(Complex{Float64}[]), NaN) - @test isequal(var(skipmissing(Complex{Float64}[])), NaN) @test var(Complex{Float64}[]) isa Float64 + @test isequal(var(skipmissing(Complex{Float64}[])), NaN) @test var(skipmissing(Complex{Float64}[])) isa Float64 @test_throws MethodError var([]) @test_throws MethodError var(skipmissing([])) - @test_throws ArgumentError var((1 for i in 2:1)) + @test_throws MethodError var((1 for i in 2:1)) + @test isequal(var(Int[]), NaN) @test var(Int[]) isa Float64 + @test isequal(var(skipmissing(Int[])), NaN) @test var(skipmissing(Int[])) isa Float64 end From 0487d171de54994c9e449496bedb0b6876b8b8ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogumi=C5=82=20Kami=C5=84ski?= Date: Fri, 14 Sep 2018 02:02:25 +0200 Subject: [PATCH 4/5] change to oftype and define mean of empty range --- stdlib/Statistics/src/Statistics.jl | 8 ++++---- stdlib/Statistics/test/runtests.jl | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/stdlib/Statistics/src/Statistics.jl b/stdlib/Statistics/src/Statistics.jl index 081a2f1581ec8..a2a625c105eb0 100644 --- a/stdlib/Statistics/src/Statistics.jl +++ b/stdlib/Statistics/src/Statistics.jl @@ -131,8 +131,8 @@ mean(A::AbstractArray; dims=:) = _mean(A, dims) _mean(A::AbstractArray{T}, region) where {T} = mean!(Base.reducedim_init(t -> t/2, +, A, region), A) _mean(A::AbstractArray, ::Colon) = sum(A) / length(A) -function mean(r::AbstractRange{<:Real}) - isempty(r) && throw(ArgumentError("mean of an empty range is undefined")) +function Statistics.mean(r::AbstractRange{<:Real}) + isempty(r) && return oftype((first(r) + last(r)) / 2, NaN) (first(r) + last(r)) / 2 end @@ -150,7 +150,7 @@ function _var(iterable, corrected::Bool, mean) y = iterate(iterable) if y === nothing T = eltype(iterable) - return typeof((abs2(zero(T)) + abs2(zero(T)))/2)(NaN) + return oftype((abs2(zero(T)) + abs2(zero(T)))/2, NaN) end count = 1 value, state = y @@ -267,7 +267,7 @@ varm(A::AbstractArray, m; corrected::Bool=true) = _varm(A, m, corrected, :) function _varm(A::AbstractArray{T}, m, corrected::Bool, ::Colon) where T n = length(A) - n == 0 && return typeof((abs2(zero(T)) + abs2(zero(T)))/2)(NaN) + n == 0 && return oftype((abs2(zero(T)) + abs2(zero(T)))/2, NaN) return centralize_sumabs2(A, m) / (n - Int(corrected)) end diff --git a/stdlib/Statistics/test/runtests.jl b/stdlib/Statistics/test/runtests.jl index 6f0c4d7bdb1c3..6c26efd12925f 100644 --- a/stdlib/Statistics/test/runtests.jl +++ b/stdlib/Statistics/test/runtests.jl @@ -119,6 +119,8 @@ end @test f(2:0.1:n) ≈ f([2:0.1:n;]) end end + @test mean(2:1) === NaN + @test mean(big(2):1) isa BigFloat end @testset "var & std" begin From bff28ee9b64cb11271152337b533d0f70728f28c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogumi=C5=82=20Kami=C5=84ski?= Date: Fri, 14 Sep 2018 12:23:34 +0200 Subject: [PATCH 5/5] remove module qualifier --- stdlib/Statistics/src/Statistics.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/Statistics/src/Statistics.jl b/stdlib/Statistics/src/Statistics.jl index a2a625c105eb0..cf1fd8fc97049 100644 --- a/stdlib/Statistics/src/Statistics.jl +++ b/stdlib/Statistics/src/Statistics.jl @@ -131,7 +131,7 @@ mean(A::AbstractArray; dims=:) = _mean(A, dims) _mean(A::AbstractArray{T}, region) where {T} = mean!(Base.reducedim_init(t -> t/2, +, A, region), A) _mean(A::AbstractArray, ::Colon) = sum(A) / length(A) -function Statistics.mean(r::AbstractRange{<:Real}) +function mean(r::AbstractRange{<:Real}) isempty(r) && return oftype((first(r) + last(r)) / 2, NaN) (first(r) + last(r)) / 2 end