3

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.

Community
  • 1
  • 1
oschoudhury
  • 1,086
  • 12
  • 17
  • oh, asked a month ago, and edited it now - can you post a bit of code, like what your dataset looks like, and what you use now, what it gives as result, and what you want it to look like? – usethedeathstar Jan 31 '14 at 10:25

4 Answers4

5

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)
  • I like the idea but your `__call__` method is sort of an all or nothing solution. My guess is that OP wants to do interpolation where possible, filling in the resulting edge NaNs with nearest values. Still, this is a great starting point. – medley56 Nov 11 '20 at 19:55
4

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.

JPG
  • 2,224
  • 2
  • 15
  • 15
2

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
Christo
  • 21
  • 2
2

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

GabCaz
  • 99
  • 1
  • 11