Search code examples
python-3.xfunctionscipyinterpolationlinear-interpolation

Interpolation function for single data point


I'm using the interp1d function for interpolation

from scipy.interpolate import interp1d
x = [0, 3, 6, 10, 15, 20]
y = [1.87, 1.76, 1.27, 1.185, 0.995, 0.855]
f = interp1d(x, y, bounds_error=False)

x_find = [0, 5, 8, 10, 28]
print(f(x_find))

Using bounds_error=False in f = interp1d(x, y, bounds_error=False) returns nan value for x=28 in x_find. Since interp1d raises an error for single datapoints, I tried the following for single datapoint.

x0 = [1.87]
y0 = [0.93]
f0 = lambda x: y0[0] if np.isclose(x, x0[0]) else np.NaN
print(f0(x0[0]))

This doesn't work when I try f0(x_find).

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

Expected output: f0(x_find) returns nan for values of x in x_find not present in x0like how bounds_error works.

Suggestions on how to do this will be really helpful

EDIT:

Question:

Would it be possible to modify the interpolation function of the single datapoint so that we could do just f(x_find), something similar to the one returned by f = interp1d() ?


Solution

  • I just only guess that you are missing a very simple thing: to put a single value to the f0() function using a list comprehension to get all values in case of a list with values.

    Does the following code:

    import numpy as np
    from scipy.interpolate import interp1d
    x = [0, 3, 6, 10, 15, 20]
    y = [1.87, 1.76, 1.27, 1.185, 0.995, 0.855]
    f = interp1d(x, y, bounds_error=False)
    
    x_find = [0, 5, 8, 10, 28]
    print(f(x_find))
    x0 = [1.87]
    y0 = [0.93]
    f0 = lambda x: y0[0] if np.isclose(x, x0[0]) else np.NaN
    print(f0(x0[0]))
    
    print([f0(x) for x in x_find])
    

    which prints:

    [1.87       1.43333333 1.2275     1.185             nan]
    0.93
    [nan, nan, nan, nan, nan]
    

    meet your expectations?

    You can also redefine f0 to cover the case of passing a list of values to it as follows:

    def f0(x): 
        import numpy as np
        x0 = [1.87]
        y0 = [0.93]
        f = lambda x: y0[0] if np.isclose(x, x0[0]) else np.NaN
        if isinstance(x, list):
            return [f(z) for z in x]
        elif isinstance(x, float):
            return f(x)
        else:
            return "f0() accepts only float and lists of floats as parameter"
    print('---')
    print(f0(1.87))
    print(f0(x_find))
    print(f0("5"))
    

    The output of the code above is:

    ---
    0.93
    [nan, nan, nan, nan, nan]
    f0() accepts only float and lists of floats as parameter
    

    FINALLY you can also redefine f0 as f_i which is a bit complex code simulating the behavior of scipy interp1d as follows:

    def f_i(X=[0, 1.87], Y=[1.87, 0.93], bounds_error=False):
        # ToDo: implementation of bounds_error evaluation
    
        def f_interpolate(u):
            assert len(X) > 1
            XY = list(zip(X, Y))
            XY.sort()
    
            if not (XY[0][0] <= u <= XY[-1][0]): 
                return None
            
            x_new = u
            for i in range(len(XY)-1):
                if XY[i][0] <= u <= XY[i+1][0]:
                    x_lo = XY[i  ][0]
                    x_hi = XY[i+1][0]
                    y_lo = XY[i  ][1]
                    y_hi = XY[i+1][1]
                    if x_new == x_lo:
                        return y_lo
                    if x_new == x_hi:
                        return y_hi
                    slope = (y_hi - y_lo) / (x_hi - x_lo)
                    y_new =  y_lo + slope*(x_new - x_lo)
                    return y_new
            return None
    
        def f(v):
            assert len(X) == 1
            if v == X[0]:
                return Y[0]
            else:
                return None
    
        def r_f(w): 
            f_l = f_interpolate if len(X) > 1 else f
            if isinstance(w, list):
                return [f_l(z) for z in w]
            elif isinstance(w, float):
                return f_l(w)
            else:
                return "ValueErrorMessage: param. not float or list of floats"
                
        return r_f
        
    y = [1.87, 1.76, 1.27, 1.185, 0.995, 0.855]
    x = [ 0,    3,    6,     10,    15,    20 ]
    
    y0 = [0.93]
    x0 = [1.87]
    
    print('---')
    f = f_i(x0, y0)
    print(f(1.87))
    
    f = f_i(x, y)
    x_find = [0, 5, 8, 10, 28]
    print(f(x_find))
    print(f("5"))
    

    which gives following output:

    ---
    0.93
    [1.87, 1.4333333333333333, 1.2275, 1.185, None]
    ValueErrorMessage: param. not float or list of floats