Rescale a matrix between two limits

Dear all,

I’m learning Julia and I would like to rescale a matriz between [-1 and 1].
In matlab I’m using rescale but i’m not sure it works in Julia.
Do you have a clue of what can I use?

Thanks in advance.

How do you want rescale it ? by rows ? by cols? overall ?

If overall:

julia> x = rand(3,4)
3×4 Matrix{Float64}:
 0.721988  0.849659  0.173933  0.277249
 0.946924  0.470537  0.686905  0.0554813
 0.113508  0.736393  0.378827  0.614165

julia> xl,xu = extrema(x)
(0.05548134618346434, 0.9469235535692043)

julia> xscaled = (x .- xl) .* (2  /  (xu-xl)) .+ -1
3×4 Matrix{Float64}:
  0.495345   0.78178    -0.734247  -0.502453
  1.0       -0.0687999   0.416633  -1.0
 -0.869815   0.527663   -0.274557   0.253439

If by column either adapt the code above by col or use a package:

julia> using BetaML

julia> x = rand(3,4)
3×4 Matrix{Float64}:
 0.154214  0.229241  0.537544  0.639791
 0.595099  0.505548  0.231598  0.409011
 0.746998  0.637075  0.392721  0.270853

julia> xscaled = fit!(Scaler(MinMaxScaler(outputRange=(-1,1))),x)
3×4 Matrix{Float64}:
 -1.0       -1.0        1.0         1.0
  0.487506   0.354995  -1.0        -0.251052
  1.0        1.0        0.0532762  -1.0

if m,M = extrema(X)

1 Like

m, M extrema(A). This composition maps [m, M]\to[-1,1]:
t\in[m, M]\to (t-m)/(M-m)\in[0,1]\to -1+2(t-m)/(M-m)\in[-1,1]

The first map is normalization. Hence the rescaled matrix is:

-1 .+ 2(A .-m)/(M-m)

More general, mapping an interval [a,b] onto [c,d] is realized by:
f(t)=c+(d-c)(t-a)/(b-a)

Hello Sylvaticus,

First of all, thank you for your help, but I’ve been testing your proposal and it gives me completely different results between Julia and Matlab.
In matlab i’m doing:
X = 1:5;
R = rescale(X,-1,1) which gives me the following result:
-1.0000 -0.5000 0 0.5000 1.0000

In Julia, with your proposal the result gives me the following result:
(-1.0, -1.6, -2.2, -2.8, -3.4)

which is completely different.

What I need is a similar function than rescale function in matlab. In matlab this function scales the entries of X, lets say, the overal matrix to the interval [a,b], which in my case a and b are respectivelly -1 and 1.

e.g.

function rescale(X, a, b)
    min, max = extrema(X)
    return @. (X - min) * ((b - a) * $(inv(max - min))) + a
end

which gives e.g.

julia> rescale(1:5, -1, 1)
-1.0:0.5:1.0

(Note that it will still work if a and/or b are arrays rather than scalars, e.g. to scale each row or column to a different minimum/maximum, similar to Matlab. I used $(inv(max - min)) to hoist the 1/(max-min) out of the broadcast loop, for efficiency, though maybe the compiler can do that?)

In actual practice, you’re likely to combine this kind of rescaling with other operations. (Fusing loops is good.) But in any case it’s a good learning exercise to figure out how to implement simple functions like rescale yourself. (Your own code can be just as fast as something “built in”, unlike Matlab.)

@sylvaticus’s answer seems correct to me:

julia> X = 1:5;

julia> xl,xu = extrema(X)
(1, 5)

julia> xscaled = (X .- xl) .* (2  /  (xu-xl)) .+ -1
-1.0:0.5:1.0

Thank you @stevengj . It works perfectly!

In some cases, materializing another array is not needed. MappedArrays package allows doing the following:

using MappedArrays

function rescalemap(A,m = 0.0, M = 1.0)
    amin, amax = extrema(A)
    let k = inv(amax-amin)*(M-m)
        mappedarray(x-> k*(x-amin)+m, A)
    end
end

and then the following uses this function:

julia> A = rand(3,4)
3×4 Matrix{Float64}:
 0.288335  0.891559  0.244806  0.50186
 0.49325   0.54083   0.597688  0.739514
 0.278386  0.688678  0.14086   0.790771

julia> rescalemap(A, -1.0, 1.0)
3×4 mappedarray(var"#9#10"{Float64, Float64, Float64}...
 -0.607099   1.0       -0.72307   -0.0382319
 -0.0611698  0.065593   0.217073   0.594923
 -0.633607   0.459486  -1.0        0.73148

Hello, the method that I posted you should work:


julia> x = 1:5
1:5
julia> xl,xu = extrema(x)
(1, 5)
julia> xscaled = (x .- xl) .* (2  /  (xu-xl)) .+ -1
-1.0:0.5:1.0

Note that when x is a range (like in your example), julia is able to keep the whole operation as a range, so the output remains a range. If you want to materialise it as as an array (most times you don’t need it) just call collect on it:

julia> xscaled_collected = collect(xscaled)
5-element Vector{Float64}:
 -1.0
 -0.5
  0.0
  0.5
  1.0

The interesting thing is that the trick of @stevengj somehow forces a collection of the array.
For small arrays like in the example, the efficiency gain of keeping the 1/( max-min) over the broadcasting loop prevails over the materialisation of the array, but for bigger arrays this is not the case:

julia> using BenchmarkTools

julia> @btime ($x .- minimum($x)) .* (2  /  (maximum($x)-minimum($x))) .+ -1
  139.246 ns (0 allocations: 0 bytes)
-1.0:0.5:1.0

julia> function rescale(X, a, b)
           min, max = extrema(X)
           return @. (X - min) * ((b - a) * $(inv(max - min))) + a
       end
rescale (generic function with 1 method)

julia> @btime rescale($x,-1,1)
  38.231 ns (1 allocation: 96 bytes)
5-element Vector{Float64}:
 -1.0
 -0.5
  0.0
  0.5
  1.0

julia> x = 1:100000
1:100000

julia> @btime ($x .- minimum($x)) .* (2  /  (maximum($x)-minimum($x))) .+ -1
  169.485 ns (0 allocations: 0 bytes)
-1.0:2.000020000200002e-5:1.0

julia> @btime rescale($x,-1,1)
  73.164 μs (2 allocations: 781.30 KiB)
100000-element Vector{Float64}:
 -1.0
 -0.999979999799998
 -0.999959999599996
 -0.999939999399994
 -0.999919999199992
 -0.99989999899999
 -0.999879998799988
 -0.999859998599986
 -0.999839998399984
 -0.999819998199982
  ⋮
  0.9998399983999839
  0.9998599985999859
  0.999879998799988
  0.9998999989999899
  0.9999199991999919
  0.999939999399994
  0.9999599995999959
  0.9999799997999981
  1.0