Extrapolate with LinearNDInterpolator

5.2k views Asked by At

I've got a 3D dataset that I want to interpolate AND extrapolate linearly. The interpolation can be done easily with scipy.interpolate.LinearNDInterpolator. The module can only fill in a constant/nan for values outside the parameter range, but I don't see why it would not offer an option to turn on extrapolation.

Looking at the code, I see that the module is written in cython. With no experience in cython, it is hard to play around with the code to implement extrapolation. I can write it in pure python code, but maybe someone else here has a better idea? My particular case involves a constant xy-grid, but the z-values keep changing a lot (-100,000) and therefore the interpolation must be fast as the interpolation will be runned for each time the z-values change.

To give a basic example, as requested, lets say that I have a grid like

xyPairs = [[-1.0, 0.0], [-1.0, 4.0],
           [-0.5, 0.0], [-0.5, 4.0],
           [-0.3, 0.0], [-0.3, 4.0],
           [+0.0, 0.0], [+0.0, 4.0],
           [+0.2, 0.0], [+0.2, 4.0]]

and lets say I want to calculate values at x = -1.5, -0.8, +0.5 and y = -0.2, +0.2, +0.5. Currently, I am performing 1d interpolation/extrapolation along the x-axis for each y-value and then along the y-axis for each x-value. The extrapolation is done by the second function in ryggyr's answer.

4

There are 4 answers

2
JPG On BEST ANSWER

I propose a method, the code is awful but I hope it will help you. The idea is, if you know by advance the bounds in which you will have to extrapolate, you can add extra columns/rows at the edge of your arrays with linearly extrapolated values, and then interpolate on the new array. Here is an example with some data that will be extrapolated until x=+-50 and y=+-40:

import numpy as np
x,y=np.meshgrid(np.linspace(0,6,7),np.linspace(0,8,9)) # create x,y grid
z=x**2*y # and z values
# create larger versions with two more columns/rows
xlarge=np.zeros((x.shape[0]+2,x.shape[1]+2))
ylarge=np.zeros((x.shape[0]+2,x.shape[1]+2))
zlarge=np.zeros((x.shape[0]+2,x.shape[1]+2))
xlarge[1:-1,1:-1]=x # copy data on centre
ylarge[1:-1,1:-1]=y
zlarge[1:-1,1:-1]=z
# fill extra columns/rows
xmin,xmax=-50,50
ymin,ymax=-40,40
xlarge[:,0]=xmin;xlarge[:,-1]=xmax # fill first/last column
xlarge[0,:]=xlarge[1,:];xlarge[-1,:]=xlarge[-2,:] # copy first/last row
ylarge[0,:]=ymin;ylarge[-1,:]=ymax
ylarge[:,0]=ylarge[:,1];ylarge[:,-1]=ylarge[:,-2]
# for speed gain: store factor of first/last column/row
first_column_factor=(xlarge[:,0]-xlarge[:,1])/(xlarge[:,1]-xlarge[:,2]) 
last_column_factor=(xlarge[:,-1]-xlarge[:,-2])/(xlarge[:,-2]-xlarge[:,-3])
first_row_factor=(ylarge[0,:]-ylarge[1,:])/(ylarge[1,:]-ylarge[2,:])
last_row_factor=(ylarge[-1,:]-ylarge[-2,:])/(ylarge[-2,:]-ylarge[-3,:])
# extrapolate z; this operation only needs to be repeated when zlarge[1:-1,1:-1] is updated
zlarge[:,0]=zlarge[:,1]+first_column_factor*(zlarge[:,1]-zlarge[:,2]) # extrapolate first column
zlarge[:,-1]=zlarge[:,-2]+last_column_factor*(zlarge[:,-2]-zlarge[:,-3]) # extrapolate last column
zlarge[0,:]=zlarge[1,:]+first_row_factor*(zlarge[1,:]-zlarge[2,:]) # extrapolate first row
zlarge[-1,:]=zlarge[-2,:]+last_row_factor*(zlarge[-2,:]-zlarge[-3,:]) #extrapolate last row

Then you can interpolate on (xlarge,ylarge,zlarge). Since all operations are numpy slices operations, I hope it will be fast enough for you. When z data are updated, copy them in zlarge[1:-1,1:-1] and re-execute the 4 last lines.

0
Christo On

I modified @Keith Williams answer slightly, which worked well for me (noting it does not extrapolate linearly - it only uses nearest neighbour):

import numpy as np
from scipy.interpolate import LinearNDInterpolator as linterp
from scipy.interpolate import NearestNDInterpolator as nearest

class LinearNDInterpolatorExt(object):
    def __init__(self, points, values):
        self.funcinterp = linterp(points, values)
        self.funcnearest = nearest(points, values)
    
    def __call__(self, *args):
        z = self.funcinterp(*args)
        chk = np.isnan(z)
        if chk.any():
            return np.where(chk, self.funcnearest(*args), z)
        else:
            return z
0
GabCaz On

Thank you @Keith William!

A small edit can also make it work with np.ndarray:

from typing import Union

import numpy as np
import scipy.interpolate


class LinearNDInterpolatorExtrapolator:

    def __init__(self, points: np.ndarray, values: np.ndarray):
        """ Use ND-linear interpolation over the convex hull of points, and nearest neighbor outside (for
            extrapolation)

            Idea taken from https://stackoverflow.com/questions/20516762/extrapolate-with-linearndinterpolator
        """
        self.linear_interpolator = scipy.interpolate.LinearNDInterpolator(points, values)
        self.nearest_neighbor_interpolator = scipy.interpolate.NearestNDInterpolator(points, values)

    def __call__(self, *args) -> Union[float, np.ndarray]:
        t = self.linear_interpolator(*args)
        t[np.isnan(t)] = self.nearest_neighbor_interpolator(*args)[np.isnan(t)]
        if t.size == 1:
            return t.item(0)
        return t

1
Keith Williams On

use combination of nearest and linear interpolation. LinearNDInterpolator returns np.nan if it fails to interpolate otherwise it returns an array size(1) NearestNDInterpolator returns a float

import scipy.interpolate
import numpy
class LinearNDInterpolatorExt(object):
  def __init__(self, points,values):
    self.funcinterp=scipy.interpolate.LinearNDInterpolator(points,values)
    self.funcnearest=scipy.interpolate.NearestNDInterpolator(points,values)
  def __call__(self,*args):
    t=self.funcinterp(*args)
    if not numpy.isnan(t):
      return t.item(0)
    else:
      return self.funcnearest(*args)