Skip to content

Commit 7388a8e

Browse files
authored
Allow non-Real eltype with quantile (#981)
This is consistent with `Statistics.quantile` and avoids breakage due to #977. It's particularly useful for `Union{T, Missing}`, e.g. a view of nonmissing entries in a vector. This also allows supporting some types such as `Date`, though currently this only works for some values (would need to implement `type=1`).
1 parent d70c4a2 commit 7388a8e

File tree

2 files changed

+38
-8
lines changed

2 files changed

+38
-8
lines changed

src/weights.jl

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -626,7 +626,7 @@ is strictly superior to ``h``. The weighted ``p`` quantile is given by ``v_k +
626626
with ``γ = (h - S_k)/(S_{k+1} - S_k)``. In particular, when all weights are equal,
627627
the function returns the same result as the unweighted `quantile`.
628628
"""
629-
function quantile(v::AbstractVector{V}, w::AbstractWeights{W}, p::AbstractVector{<:Real}) where {V<:Real,W<:Real}
629+
function quantile(v::AbstractVector{V}, w::AbstractWeights{W}, p::AbstractVector{<:Real}) where {V, W<:Real}
630630
# checks
631631
isempty(v) && throw(ArgumentError("quantile of an empty array is undefined"))
632632
isempty(p) && throw(ArgumentError("empty quantile array"))
@@ -644,6 +644,10 @@ function quantile(v::AbstractVector{V}, w::AbstractWeights{W}, p::AbstractVector
644644
throw(ArgumentError("The values of the vector of `FrequencyWeights` must be numerically" *
645645
"equal to integers. Use `ProbabilityWeights` or `AnalyticWeights` instead."))
646646

647+
# ::Bool is there to prevent JET from reporting a problem on Julia 1.10
648+
any(ismissing, v)::Bool &&
649+
throw(ArgumentError("quantiles are undefined in presence of missing values"))
650+
647651
# remove zeros weights and sort
648652
wsum = sum(w)
649653
nz = .!iszero.(w)
@@ -655,16 +659,19 @@ function quantile(v::AbstractVector{V}, w::AbstractWeights{W}, p::AbstractVector
655659
p = p[ppermute]
656660

657661
# prepare out vector
658-
out = Vector{typeof(zero(V)/1)}(undef, length(p))
662+
v1 = vw[1][1]
663+
out = Vector{typeof(v1 + zero(eltype(p))*zero(W)*zero(v1))}(undef, length(p))
659664
fill!(out, vw[end][1])
660665

666+
# This behavior isn't consistent with Statistics.quantile,
667+
# but preserve it for backward compatibility
661668
for x in v
662-
isnan(x) && return fill!(out, x)
669+
x isa Number && isnan(x) && return fill!(out, x)
663670
end
664671

665672
# loop on quantiles
666673
Sk, Skold = zero(W), zero(W)
667-
vk, vkold = zero(V), zero(V)
674+
vk, vkold = zero(v1), zero(v1)
668675
k = 0
669676

670677
w1 = vw[1][2]
@@ -693,19 +700,19 @@ function quantile(v::AbstractVector{V}, w::AbstractWeights{W}, p::AbstractVector
693700
return out
694701
end
695702

696-
function quantile(v::AbstractVector{<:Real}, w::UnitWeights, p::AbstractVector{<:Real})
703+
function quantile(v::AbstractVector, w::UnitWeights, p::AbstractVector{<:Real})
697704
length(v) != length(w) && throw(DimensionMismatch("Inconsistent array dimension."))
698705
return quantile(v, p)
699706
end
700707

701-
quantile(v::AbstractVector{<:Real}, w::AbstractWeights{<:Real}, p::Number) = quantile(v, w, [p])[1]
708+
quantile(v::AbstractVector, w::AbstractWeights, p::Real) = quantile(v, w, [p])[1]
702709

703710
##### Weighted median #####
704711

705712
"""
706-
median(v::AbstractVector{<:Real}, w::AbstractWeights)
713+
median(v::AbstractVector, w::AbstractWeights)
707714
708715
Compute the weighted median of `v` with weights `w`
709716
(of type `AbstractWeights`). See the documentation for [`quantile`](@ref) for more details.
710717
"""
711-
median(v::AbstractVector{<:Real}, w::AbstractWeights{<:Real}) = quantile(v, w, 0.5)
718+
median(v::AbstractVector, w::AbstractWeights) = quantile(v, w, 0.5)

test/weights.jl

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,21 @@ end
459459
w = [1, 1/3, 1/3, 1/3, 1]
460460
answer = 6.0
461461
@test quantile(data[1], f(w), 0.5) answer atol = 1e-5
462+
463+
# Test non-Real eltype
464+
@test_throws ArgumentError quantile([missing, 1], f([1, 2]), 0.5)
465+
@test quantile(Union{Float64, Missing}[1, 2, 3, 4], f([1, 2, 2, 1]), 0.5) ==
466+
quantile(Any[1, 2, 3, 4], f([1, 2, 2, 1]), 0.5) ==
467+
quantile([1, 2, 3, 4], f([1, 2, 2, 1]), 0.5)
468+
@test quantile([Date(2005, 01, 01), Date(2005, 01, 01)], f([1, 1]), 0.5) ==
469+
Date(2005, 01, 01)
470+
471+
@test_throws ArgumentError quantile([missing, 1], f([1, 2]), [0.5, 0.75])
472+
@test quantile(Union{Float64, Missing}[1, 2, 3, 4], f([1, 2, 2, 1]), [0.5, 0.75]) ==
473+
quantile(Any[1, 2, 3, 4], f([1, 2, 2, 1]), [0.5, 0.75]) ==
474+
quantile([1, 2, 3, 4], f([1, 2, 2, 1]), [0.5, 0.75])
475+
@test quantile(fill(Date(2005, 01, 01), 3), f([1, 1, 1]), [0.5, 0.75]) ==
476+
fill(Date(2005, 01, 01), 2)
462477
end
463478

464479
@testset "Median $f" for f in weight_funcs
@@ -484,6 +499,14 @@ end
484499
data = [4, 3, 2, 1]
485500
wt = [1, 2, 3, 4]
486501
@test median(data, f(wt)) quantile(data, f(wt), 0.5) atol = 1e-5
502+
503+
# Test non-Real eltype
504+
@test_throws ArgumentError median([missing, 1], f([1, 2]))
505+
@test median(Union{Float64, Missing}[1, 2, 3, 4], f([1, 2, 2, 1])) ==
506+
median(Any[1, 2, 3, 4], f([1, 2, 2, 1])) ==
507+
median([1, 2, 3, 4], f([1, 2, 2, 1]))
508+
@test median([Date(2005, 01, 01), Date(2005, 01, 01)], f([1, 1])) ==
509+
Date(2005, 01, 01)
487510
end
488511

489512
@testset "Mismatched eltypes" begin

0 commit comments

Comments
 (0)