Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 15 additions & 8 deletions src/weights.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand All @@ -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)
Expand All @@ -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))
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally I wanted to use map(p) and return the result directly to avoid having to compute the type manually, but I couldn't find a way for the return type to be inferred. The problem is that the loop does not just depend on the quantile, we also have to update the sum of weights, current and previous values, which get boxed. Not sure there's a clean solution.

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]
Expand Down Expand Up @@ -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)
23 changes: 23 additions & 0 deletions test/weights.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down