12
\$\begingroup\$

As I've described in a StackOverflow question, I'm trying to fit a NumPy array into a certain range.

Here is the solution I currently use:

import numpy as np

def scale_array(dat, out_range=(-1, 1)):
    domain = [np.min(dat, axis=0), np.max(dat, axis=0)]

    def interp(x):
        return out_range[0] * (1.0 - x) + out_range[1] * x

    def uninterp(x):
        b = 0
        if (domain[1] - domain[0]) != 0:
            b = domain[1] - domain[0]
        else:
            b =  1.0 / domain[1]
        return (x - domain[0]) / b

    return interp(uninterp(dat))

print(scale_array(np.array([-2, 0, 2], dtype=np.float)))
# Gives: [-1., 0., 1.]
print(scale_array(np.array([-3, -2, -1], dtype=np.float)))
# Gives: [-1., 0., 1.]

Is there a way to make this code cleaner? Is there a built-in function in NumPy or scikit-learn? This feels like a really common data pre-processing step and it feels weird that I keep re-implementing it.

\$\endgroup\$
15
\$\begingroup\$

NumPy provides numpy.interp for 1-dimensional linear interpolation. In this case, where you want to map the minimum element of the array to −1 and the maximum to +1, and other elements linearly in-between, you can write:

np.interp(a, (a.min(), a.max()), (-1, +1))

For more advanced kinds of interpolation, there's scipy.interpolate.

\$\endgroup\$
1
\$\begingroup\$

What you need here are basically two rescalings. The first is to rescale the data to be symmetric around 0 and the second is to shift and scale it to the out_range. Both can be simply written down, there is no need for your inner functions and their special cases.

def scale(x, out_range=(-1, 1)):
    domain = np.min(x), np.max(x)
    y = (x - (domain[1] + domain[0]) / 2) / (domain[1] - domain[0])
    return y * (out_range[1] - out_range[0]) + (out_range[1] + out_range[0]) / 2

Note that I removed the axis=0 arguments to np.min and np.max. By default they run over all axes. If that is not what you want, but you want to rescale only some axis, I would make this a parameter of the scale function to give the user full control:

def scale(x, out_range=(-1, 1), axis=None):
    domain = np.min(x, axis), np.max(x, axis)
    y = (x - (domain[1] + domain[0]) / 2) / (domain[1] - domain[0])
    return y * (out_range[1] - out_range[0]) + (out_range[1] + out_range[0]) / 2

This function behaves the same as yours, even with out_range = (-1, -1).

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.