views:

340

answers:

2

I'm trying to port a program which uses a hand-rolled interpolator (developed by a mathematician colleage) over to use the interpolators provided by scipy. I'd like to use or wrap the scipy interpolator so that it has as close as possible behavior to the old interpolator.

A key difference between the two functions is that in our original interpolator - if the input value is above or below the input range, our original interpolator will extrapolate the result. If you try this with the scipy interpolator it raises a ValueError. Consider this program as an example:

import numpy as np
from scipy import interpolate

x = np.arange(0,10)
y = np.exp(-x/3.0)
f = interpolate.interp1d(x, y)

print f(9)
print f(11) # Causes ValueError, because it's greater than max(x)

Is there a sensible way to make it so that instead of crashing, the final line will simply do a linear extrapolate, continuing the gradients defined by the first and last two points to infinity.

Note, that in the real software I'm not actually using the exp function - that's here for illustration only!

+2  A: 

1. Constant extrapolation

You can use interp function from scipy, it extrapolates left and right values as constant beyond the range:

>>> from scipy import interp, arange, exp
>>> x = arange(0,10)
>>> y = exp(-x/3.0)
>>> interp([9,10], x, y)
array([ 0.04978707,  0.04978707])

2. Linear (or other custom) extrapolation

You can write a wrapper around an interpolation function which takes care of linear extrapolation. For example:

from scipy.interpolate import interp1d
from scipy import arange, array, exp

def extrap1d(interpolator):
    xs = interpolator.x
    ys = interpolator.y

    def pointwise(x):
        if x < xs[0]:
            return ys[0]+(x-xs[0])*(ys[1]-ys[0])/(xs[1]-xs[0])
        elif x > xs[-1]:
            return ys[-1]+(x-xs[-1])*(ys[-1]-ys[-2])/(xs[-1]-xs[-2])
        else:
            return interpolator(x)

    def ufunclike(xs):
        return array(map(pointwise, array(xs)))

    return ufunclike

extrap1d takes an interpolation function and returns a function which can also extrapolate. And you can use it like this:

x = arange(0,10)
y = exp(-x/3.0)
f_i = interp1d(x, y)
f_x = extrap1d(f_i)

print f_x([9,10])

Output:

[ 0.04978707  0.03009069]
jetxee
Unfortunately that is not what I need, I want to extrapolate continuing the gradients defined by the first and last two points of the input data.
Salim Fadhley
I updated the answer to include linear extrapolation example.
jetxee
I like it! Did you write that just now, or did you obtain this from a trusted / tested source?
Salim Fadhley
I wrote it just now, but it seems to be correct.
jetxee
+1  A: 

I'm afraid that there is no easy to do this in Scipy to my knowledge. You can, as I'm fairly sure that you are aware, turn off the bounds errors and fill all function values beyond the range with a constant, but that doesn't really help. See this question on the mailing list for some more ideas. Maybe you could use some kind of piecewise function, but that seems like a major pain.

Justin Peel
That's the conclusion I came to, at least with scipy 0.7, however this tutorial written 21 months ago suggests that the interp1d function has a high and low attribute which can be set to "linear", the tutorial is not clear which version of scipy this applies to:http://projects.scipy.org/scipy/browser/branches/Interpolate1D/docs/tutorial.rst?rev=4591
Salim Fadhley
It looks like this is part of a branch that hasn't been assimilated into the main version yet so there might still be some problems with it. The current code for this is at http://projects.scipy.org/scipy/browser/branches/interpolate/interpolate1d.py though you might want to scroll to the bottom of the page and click to download it as plain text. I think that this looks promising though I haven't tried it yet myself.
Justin Peel