# Range (statistics)

**URL:** <https://discourse.julialang.org/t/range-statistics/92518>\
**Category:** Statistics\
**Tags:** function\
**Created:** [January 4, 2023, 6:59pm UTC](https://discourse.julialang.org/t/range-statistics/92518 "2023-01-04T18:59:09Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![rafael.guerra](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rafael.guerra/32/216610_2.png) [@rafael.guerra](https://discourse.julialang.org/u/rafael.guerra)\
**Post date:** [January 4, 2023, 6:59pm UTC](https://discourse.julialang.org/t/range-statistics/92518/1 "2023-01-04T18:59:09Z")

</div>

I often need to compute a [statistical range](https://en.wikipedia.org/wiki/Range_(statistics)) (difference between maximum and minimum).  
Is there a function in the `Statistics` standard library to compute the range? Or should we use: ` maximum(x) - minimum(x)`?

---

<div class="post-metadata">

**Author:** ![tbeason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tbeason/32/15898_2.png) [@tbeason](https://discourse.julialang.org/u/tbeason)\
**Post date:** [January 4, 2023, 7:08pm UTC](https://discourse.julialang.org/t/range-statistics/92518/2 "2023-01-04T19:08:02Z")

</div>

Since `extrema` is slightly faster than calling both functions separately, you could do this

`rangeof(x) = (tmp = extrema(x); return tmp[2] - tmp[1])`

or

`rangeof(x) = ((low, high) = extrema(x); return high - low)`

---

<div class="post-metadata">

**Author:** ![stillyslalom](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stillyslalom/32/45687_2.png) [@stillyslalom](https://discourse.julialang.org/u/stillyslalom)\
**Post date:** [January 4, 2023, 7:08pm UTC](https://discourse.julialang.org/t/range-statistics/92518/3 "2023-01-04T19:08:13Z")

</div>

```julia
julia> using BenchmarkTools

julia> datarange(x) = -(-(extrema(x)...))
datarange (generic function with 1 method)

julia> @btime datarange($x)
  161.395 ns (0 allocations: 0 bytes)
9.91438063817977

julia> @btime maximum($x) - minimum($x)
  257.181 ns (0 allocations: 0 bytes)
9.91438063817977

```

---

<div class="post-metadata">

**Author:** ![juliohm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/juliohm/32/215266_2.png) [@juliohm](https://discourse.julialang.org/u/juliohm)\
**Post date:** [January 4, 2023, 7:15pm UTC](https://discourse.julialang.org/t/range-statistics/92518/4 "2023-01-04T19:15:19Z")

</div>

> [@rafael.guerra](#):
>
> I often need to compute a [statistical range](https://en.wikipedia.org/wiki/Range_(statistics)) (difference between maximum and minimum).

Is this computation done for scaling a given set of samples? If yes, you may be interested in using TableTransforms.jl’s MinMax or Scale:

> **[Transforms · TableTransforms.jl](https://juliaml.github.io/TableTransforms.jl/stable/transforms/#MinMax)**
>
> Documentation for TableTransforms.jl.

---

<div class="post-metadata">

**Author:** ![rafael.guerra](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rafael.guerra/32/216610_2.png) [@rafael.guerra](https://discourse.julialang.org/u/rafael.guerra)\
**Post date:** [January 4, 2023, 7:28pm UTC](https://discourse.julialang.org/t/range-statistics/92518/5 "2023-01-04T19:28:21Z")

</div>

Thanks, so I guess I better define one of the above range functions in my startup.jl, if there is nothing ready-made.

---

<div class="post-metadata">

**Author:** ![rafael.guerra](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rafael.guerra/32/216610_2.png) [@rafael.guerra](https://discourse.julialang.org/u/rafael.guerra)\
**Post date:** [January 5, 2023, 3:14pm UTC](https://discourse.julialang.org/t/range-statistics/92518/6 "2023-01-05T15:14:17Z")

</div>

Fyi, I found this [minmax algorithm here](https://qr.ae/pryUgs), which seems to perform better than the previous solutions:

```julia
function range2(x)
    min = max = first(x)
    for xi in view(x, 2:length(x))
        min > xi ? min = xi : max < xi && (max = xi)
    end
    return max - min
end

x = 10 * rand(100)
@btime range2($x) # 123 ns (0 allocs: 0 bytes)

```

Since range is one of the important measures of variability, an optimized version should be available in the standard library, IMHO.

---

<div class="post-metadata">

**Author:** ![Dan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dan/32/42581_2.png) [@Dan](https://discourse.julialang.org/u/Dan)\
**Post date:** [January 5, 2023, 3:37pm UTC](https://discourse.julialang.org/t/range-statistics/92518/7 "2023-01-05T15:37:37Z")

</div>

For performance, branches (as generated by `? :` operator) are bad. An alternate version:

```julia
function range3(x)
    min = typemax(eltype(x))
    max = typemin(eltype(x))
    for xi in x
        min = ifelse(min > xi, xi, min)
        max = ifelse(max < xi, xi, max)
    end
    return max - min
end

```

is faster on my machine.

```julia
julia> @btime range3($x) # 123
  98.743 ns (0 allocations: 0 bytes)

```

---

<div class="post-metadata">

**Author:** ![rafael.guerra](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rafael.guerra/32/216610_2.png) [@rafael.guerra](https://discourse.julialang.org/u/rafael.guerra)\
**Post date:** [January 5, 2023, 3:39pm UTC](https://discourse.julialang.org/t/range-statistics/92518/8 "2023-01-05T15:39:07Z")

</div>

Thanks @Dan for the insights, I see 40% speedup here!

---

<div class="post-metadata">

**Author:** ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)\
**Post date:** [January 5, 2023, 4:25pm UTC](https://discourse.julialang.org/t/range-statistics/92518/9 "2023-01-05T16:25:26Z")

</div>

This just replicates the functionality of `extrema` followed by a subtraction. If this is faster than `extrema`, that means there’s something wrong with the latter.

---

<div class="post-metadata">

**Author:** ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)\
**Post date:** [January 5, 2023, 5:32pm UTC](https://discourse.julialang.org/t/range-statistics/92518/10 "2023-01-05T17:32:02Z")

</div>

Hmm, I amend my statement a bit. `extrema` handles `NaN`s, which `range3` does not (nor does it handle empty collections):

```julia
julia> range3([1.0, NaN, 2.0])
1.0

julia> extrema([1.0, NaN, 2.0])
(NaN, NaN)

julia> range3(Float64[])
-Inf

julia> range3(Int[])
1

```

If you want this to be in a stdlib, it should be robust against things like this.

---

<div class="post-metadata">

**Author:** ![Dan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dan/32/42581_2.png) [@Dan](https://discourse.julialang.org/u/Dan)\
**Post date:** [January 5, 2023, 10:52pm UTC](https://discourse.julialang.org/t/range-statistics/92518/12 "2023-01-05T22:52:34Z")

</div>

> [@DNF](#):
>
> `extrema` handles `NaN`s, which `range3` does not (nor does it handle empty collections):

Fixing this does not entail so much overhead:

```julia
function range4(x)
    isempty(x) && error("Does not support empty vectors")
    min = typemax(eltype(x))
    max = typemin(eltype(x))
    hasnan = false
    for xi in x
        hasnan |= isnan(xi)
        min = ifelse(min > xi, xi, min)
        max = ifelse(max < xi, xi, max)
    end
    hasnan && error("Does not support NaNs in vectors")
    return max - min
end

```

```julia
julia> x = 10 * rand(100);

julia> y = 10 * rand(1000000);

julia> @btime range4($x);
  107.822 ns (0 allocations: 0 bytes)

julia> @btime extrema($x);
  318.175 ns (0 allocations: 0 bytes)

julia> @btime range4($y);
  1.397 ms (0 allocations: 0 bytes)

julia> @btime extrema($y);
  3.285 ms (0 allocations: 0 bytes)

```

There is a ~3X gap here (persisting into large vectors), and this trivial (and popular) operation could probably be optimized further (SIMD, threads etc.)

---

<div class="post-metadata">

**Author:** ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)\
**Post date:** [January 5, 2023, 11:04pm UTC](https://discourse.julialang.org/t/range-statistics/92518/13 "2023-01-05T23:04:28Z")

</div>

Can you take a look at `Base.extrema` and see if you can tell what, if anything, is wrong with it, performance-wise?

Because I really think the right approach is to use `extrema` for this, and fix any problems there.

---

<div class="post-metadata">

**Author:** ![rafael.guerra](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rafael.guerra/32/216610_2.png) [@rafael.guerra](https://discourse.julialang.org/u/rafael.guerra)\
**Post date:** [January 5, 2023, 11:06pm UTC](https://discourse.julialang.org/t/range-statistics/92518/14 "2023-01-05T23:06:11Z")

</div>

Dan, sorry to ask, but is `hasnan` required?  
Could we write `range4()` as follows:

```julia
function range4b(x)
    isempty(x) && error("Does not support empty vectors")
    min = typemax(eltype(x))
    max = typemin(eltype(x))
    for xi in x
        isnan(xi) && error("Does not support NaNs in vectors")
        min = ifelse(min > xi, xi, min)
        max = ifelse(max < xi, xi, max)
    end
    return max - min
end

```

---

<div class="post-metadata">

**Author:** ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)\
**Post date:** [January 5, 2023, 11:07pm UTC](https://discourse.julialang.org/t/range-statistics/92518/15 "2023-01-05T23:07:22Z")

</div>

You probably want to avoid branches.

---

<div class="post-metadata">

**Author:** ![Dan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dan/32/42581_2.png) [@Dan](https://discourse.julialang.org/u/Dan)\
**Post date:** [January 5, 2023, 11:16pm UTC](https://discourse.julialang.org/t/range-statistics/92518/16 "2023-01-05T23:16:12Z")

</div>

> [@rafael.guerra](#):
>
> ` any(isnan, x) && error("Does not support NaNs in vectors")`

You wouldn’t want to read the vector from memory twice. The memory transfers are critical for performance.

Whoops, the code changed… but now DNF is right, the branch is bad.

---

<div class="post-metadata">

**Author:** ![rafael.guerra](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rafael.guerra/32/216610_2.png) [@rafael.guerra](https://discourse.julialang.org/u/rafael.guerra)\
**Post date:** [January 5, 2023, 11:17pm UTC](https://discourse.julialang.org/t/range-statistics/92518/17 "2023-01-05T23:17:39Z")

</div>

Sorry, you seem to have captured a temporary change in the code, the original snippet is restored.

---

<div class="post-metadata">

**Author:** ![Dan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dan/32/42581_2.png) [@Dan](https://discourse.julialang.org/u/Dan)\
**Post date:** [January 5, 2023, 11:19pm UTC](https://discourse.julialang.org/t/range-statistics/92518/18 "2023-01-05T23:19:45Z")

</div>

But benchmarking is the best disinfectant. You are welcome to post results (my machine’s results are up there already).

---

<div class="post-metadata">

**Author:** ![Dan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dan/32/42581_2.png) [@Dan](https://discourse.julialang.org/u/Dan)\
**Post date:** [January 5, 2023, 11:24pm UTC](https://discourse.julialang.org/t/range-statistics/92518/19 "2023-01-05T23:24:12Z")

</div>

> [@DNF](#):
>
> Can you take a look at `Base.extrema` and see if you can tell what, if anything, is wrong with it, performance-wise?

Had a quick glance, but will look some more. Basically, there is an attempt to capture many reduce-like operations in one template, and perhaps the overhead of `reduce` with support to `dims` option is taking a toll.

On the NaN / empty-vec front, I think actually a vector type which prohibits NaNs can be useful (i.e. throws when operations generate a NaN). With this type, specialized cases can be dispatched.

---

<div class="post-metadata">

**Author:** ![rafael.guerra](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rafael.guerra/32/216610_2.png) [@rafael.guerra](https://discourse.julialang.org/u/rafael.guerra)\
**Post date:** [January 5, 2023, 11:26pm UTC](https://discourse.julialang.org/t/range-statistics/92518/20 "2023-01-05T23:26:21Z")

</div>

> [@Dan](#):
>
> But benchmarking is the best disinfectant.

In your examples which do not contain `NaN`, for the small array case, `range4b()` is ~5% faster, and for the large array case, `range4()` is ~2% faster.

---

<div class="post-metadata">

**Author:** ![Dan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dan/32/42581_2.png) [@Dan](https://discourse.julialang.org/u/Dan)\
**Post date:** [January 5, 2023, 11:29pm UTC](https://discourse.julialang.org/t/range-statistics/92518/21 "2023-01-05T23:29:54Z")

</div>

…and compared to `extrema`? is it still 3X?

[Next page](https://discourse.julialang.org/t/range-statistics/92518.md?page=2)
