diff --git a/src/weights.jl b/src/weights.jl index 59c9aae4..c7164b33 100644 --- a/src/weights.jl +++ b/src/weights.jl @@ -626,7 +626,7 @@ is strictly superior to ``h``. The weighted ``p`` quantile is given by ``v_k + with ``γ = (h - S_k)/(S_{k+1} - S_k)``. In particular, when all weights are equal, the function returns the same result as the unweighted `quantile`. """ -function quantile(v::AbstractVector{V}, w::AbstractWeights{W}, p::AbstractVector{<:Real}) where {V<:Real,W<:Real} +function quantile(v::AbstractVector{V}, w::AbstractWeights{W}, p::AbstractVector{<:Real}) where {V, W<:Real} # checks isempty(v) && throw(ArgumentError("quantile of an empty array is undefined")) isempty(p) && throw(ArgumentError("empty quantile array")) @@ -644,6 +644,10 @@ function quantile(v::AbstractVector{V}, w::AbstractWeights{W}, p::AbstractVector throw(ArgumentError("The values of the vector of `FrequencyWeights` must be numerically" * "equal to integers. Use `ProbabilityWeights` or `AnalyticWeights` instead.")) + # ::Bool is there to prevent JET from reporting a problem on Julia 1.10 + any(ismissing, v)::Bool && + throw(ArgumentError("quantiles are undefined in presence of missing values")) + # remove zeros weights and sort wsum = sum(w) nz = .!iszero.(w) @@ -655,16 +659,19 @@ function quantile(v::AbstractVector{V}, w::AbstractWeights{W}, p::AbstractVector p = p[ppermute] # prepare out vector - out = Vector{typeof(zero(V)/1)}(undef, length(p)) + v1 = vw[1][1] + out = Vector{typeof(v1 + zero(eltype(p))*zero(W)*zero(v1))}(undef, length(p)) fill!(out, vw[end][1]) + # This behavior isn't consistent with Statistics.quantile, + # but preserve it for backward compatibility for x in v - isnan(x) && return fill!(out, x) + x isa Number && isnan(x) && return fill!(out, x) end # loop on quantiles Sk, Skold = zero(W), zero(W) - vk, vkold = zero(V), zero(V) + vk, vkold = zero(v1), zero(v1) k = 0 w1 = vw[1][2] @@ -693,19 +700,19 @@ function quantile(v::AbstractVector{V}, w::AbstractWeights{W}, p::AbstractVector return out end -function quantile(v::AbstractVector{<:Real}, w::UnitWeights, p::AbstractVector{<:Real}) +function quantile(v::AbstractVector, w::UnitWeights, p::AbstractVector{<:Real}) length(v) != length(w) && throw(DimensionMismatch("Inconsistent array dimension.")) return quantile(v, p) end -quantile(v::AbstractVector{<:Real}, w::AbstractWeights{<:Real}, p::Number) = quantile(v, w, [p])[1] +quantile(v::AbstractVector, w::AbstractWeights, p::Real) = quantile(v, w, [p])[1] ##### Weighted median ##### """ - median(v::AbstractVector{<:Real}, w::AbstractWeights) + median(v::AbstractVector, w::AbstractWeights) Compute the weighted median of `v` with weights `w` (of type `AbstractWeights`). See the documentation for [`quantile`](@ref) for more details. """ -median(v::AbstractVector{<:Real}, w::AbstractWeights{<:Real}) = quantile(v, w, 0.5) +median(v::AbstractVector, w::AbstractWeights) = quantile(v, w, 0.5) diff --git a/test/weights.jl b/test/weights.jl index 7065025e..c99f31c9 100644 --- a/test/weights.jl +++ b/test/weights.jl @@ -459,6 +459,21 @@ end w = [1, 1/3, 1/3, 1/3, 1] answer = 6.0 @test quantile(data[1], f(w), 0.5) ≈ answer atol = 1e-5 + + # Test non-Real eltype + @test_throws ArgumentError quantile([missing, 1], f([1, 2]), 0.5) + @test quantile(Union{Float64, Missing}[1, 2, 3, 4], f([1, 2, 2, 1]), 0.5) == + quantile(Any[1, 2, 3, 4], f([1, 2, 2, 1]), 0.5) == + quantile([1, 2, 3, 4], f([1, 2, 2, 1]), 0.5) + @test quantile([Date(2005, 01, 01), Date(2005, 01, 01)], f([1, 1]), 0.5) == + Date(2005, 01, 01) + + @test_throws ArgumentError quantile([missing, 1], f([1, 2]), [0.5, 0.75]) + @test quantile(Union{Float64, Missing}[1, 2, 3, 4], f([1, 2, 2, 1]), [0.5, 0.75]) == + quantile(Any[1, 2, 3, 4], f([1, 2, 2, 1]), [0.5, 0.75]) == + quantile([1, 2, 3, 4], f([1, 2, 2, 1]), [0.5, 0.75]) + @test quantile(fill(Date(2005, 01, 01), 3), f([1, 1, 1]), [0.5, 0.75]) == + fill(Date(2005, 01, 01), 2) end @testset "Median $f" for f in weight_funcs @@ -484,6 +499,14 @@ end data = [4, 3, 2, 1] wt = [1, 2, 3, 4] @test median(data, f(wt)) ≈ quantile(data, f(wt), 0.5) atol = 1e-5 + + # Test non-Real eltype + @test_throws ArgumentError median([missing, 1], f([1, 2])) + @test median(Union{Float64, Missing}[1, 2, 3, 4], f([1, 2, 2, 1])) == + median(Any[1, 2, 3, 4], f([1, 2, 2, 1])) == + median([1, 2, 3, 4], f([1, 2, 2, 1])) + @test median([Date(2005, 01, 01), Date(2005, 01, 01)], f([1, 1])) == + Date(2005, 01, 01) end @testset "Mismatched eltypes" begin