Skip to content

Commit 97a6bd4

Browse files
authored
Merge pull request #839 from LilithHafner/patch-2
Combine and optimize `mad!` and `mad`
2 parents 0572db4 + 1569471 commit 97a6bd4

File tree

3 files changed

+13
-31
lines changed

3 files changed

+13
-31
lines changed

Project.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,12 @@ StatsAPI = "1.2"
2626
julia = "1"
2727

2828
[extras]
29+
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
2930
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
3031
DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab"
3132
OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
3233
StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3"
3334
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
3435

3536
[targets]
36-
test = ["Dates", "DelimitedFiles", "OffsetArrays", "StableRNGs", "Test"]
37+
test = ["BenchmarkTools", "Dates", "DelimitedFiles", "OffsetArrays", "StableRNGs", "Test"]

src/scalarstats.jl

Lines changed: 7 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -532,41 +532,14 @@ If `normalize` is set to `true`, the MAD is multiplied by
532532
of the standard deviation under the assumption that the data is normally distributed.
533533
"""
534534
function mad(x; center=nothing, normalize::Union{Bool, Nothing}=nothing, constant=nothing)
535-
isempty(x) && throw(ArgumentError("mad is not defined for empty arrays"))
536-
T = eltype(x)
537-
# Knowing the eltype allows allocating a single array able to hold both original values
538-
# and differences from the center, instead of two arrays
539-
S = isconcretetype(T) ? promote_type(T, typeof(middle(zero(T)))) : T
540-
x2 = x isa AbstractArray ? copyto!(similar(x, S), x) : collect(S, x)
541-
c = center === nothing ? median!(x2) : center
542-
if isconcretetype(T)
543-
x2 .= abs.(x2 .- c)
544-
else
545-
x2 = abs.(x2 .- c)
546-
end
547-
m = median!(x2)
548-
if normalize isa Nothing
549-
Base.depwarn("the `normalize` keyword argument will be false by default in future releases: set it explicitly to silence this deprecation", :mad)
550-
normalize = true
551-
end
552-
if !isa(constant, Nothing)
553-
Base.depwarn("keyword argument `constant` is deprecated, use `normalize` instead or apply the multiplication directly", :mad)
554-
m * constant
555-
elseif normalize
556-
m * mad_constant
557-
else
558-
m
559-
end
535+
mad!(Base.copymutable(x); center=center, normalize=normalize, constant=constant)
560536
end
561537

562538
"""
563539
StatsBase.mad!(x; center=median!(x), normalize=true)
564540
565541
Compute the median absolute deviation (MAD) of array `x` around `center`
566542
(by default, around the median), overwriting `x` in the process.
567-
`x` must be able to hold values of generated by calling `middle` on its elements
568-
(for example an integer vector is not appropriate since `middle` can produce
569-
non-integer values).
570543
571544
If `normalize` is set to `true`, the MAD is multiplied by
572545
`1 / quantile(Normal(), 3/4) ≈ 1.4826`, in order to obtain a consistent estimator
@@ -577,8 +550,12 @@ function mad!(x::AbstractArray;
577550
normalize::Union{Bool,Nothing}=true,
578551
constant=nothing)
579552
isempty(x) && throw(ArgumentError("mad is not defined for empty arrays"))
580-
x .= abs.(x .- center)
581-
m = median!(x)
553+
c = center === nothing ? median!(x) : center
554+
T = promote_type(typeof(c), eltype(x))
555+
U = eltype(x)
556+
x2 = U == T ? x : isconcretetype(U) && isconcretetype(T) && sizeof(U) == sizeof(T) ? reinterpret(T, x) : similar(x, T)
557+
x2 .= abs.(x .- c)
558+
m = median!(x2)
582559
if normalize isa Nothing
583560
Base.depwarn("the `normalize` keyword argument will be false by default in future releases: set it explicitly to silence this deprecation", :mad)
584561
normalize = true

test/scalarstats.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ using StatsBase
22
using Test
33
using DelimitedFiles
44
using Statistics
5+
using BenchmarkTools: @benchmark
56

67
##### Location
78

@@ -212,6 +213,9 @@ x = sort!(vcat([5:-1:i for i in 1:5]...))
212213
@test mad(Any[1, 2.1], normalize=false) 0.55
213214
@test mad(Union{Int,Missing}[1, 2], normalize=false) 0.5
214215
@test_throws ArgumentError mad(Int[], normalize = true)
216+
@test mad(Iterators.repeated(4, 10)) == 0
217+
@test mad(Integer[1,2,3,4]) === mad(1:4)
218+
@test (@benchmark mad((i for i in 1:10000))).allocs < 200
215219

216220
# Issue 197
217221
@test mad(1:2, normalize=true) 0.7413011092528009

0 commit comments

Comments
 (0)