¿Cómo hacer que scipy.interpolate dé un resultado extrapolado más allá del rango de entrada?

82

Estoy intentando portar un programa que usa un interpolador hecho a mano (desarrollado por un colega de matemáticos) para usar los interpoladores proporcionados por scipy. Me gustaría usar o envolver el interpolador scipy para que tenga un comportamiento lo más parecido posible al antiguo interpolador.

Una diferencia clave entre las dos funciones es que en nuestro interpolador original, si el valor de entrada está por encima o por debajo del rango de entrada, nuestro interpolador original extrapolará el resultado. Si intenta esto con el interpolador scipy, genera un ValueError. Considere este programa como ejemplo:

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)

¿Existe una forma sensata de hacerlo de modo que en lugar de fallar, la línea final simplemente haga una extrapolación lineal, continuando los gradientes definidos por los dos primeros y dos últimos puntos hasta el infinito?

Tenga en cuenta que en el software real no estoy usando la función exp, ¡eso está aquí solo para ilustración!

Salim Fadhley
fuente
2
scipy.interpolate.UnivariateSplineparece extrapolar sin problemas.
heltonbiker

Respuestas:

37

1. Extrapolación constante

Puede usar la interpfunción de scipy, extrapola los valores izquierdo y derecho como constantes más allá del rango:

>>> 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. Extrapolación lineal (u otra personalizada)

Puede escribir una envoltura alrededor de una función de interpolación que se encarga de la extrapolación lineal. Por ejemplo:

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(list(map(pointwise, array(xs))))

    return ufunclike

extrap1dtoma una función de interpolación y devuelve una función que también puede extrapolar. Y puedes usarlo así:

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

print f_x([9,10])

Salida:

[ 0.04978707  0.03009069]
Sastanin
fuente
1
En Python 3.6, tuve que agregar lista la devolución: return array(list(map(pointwise, array(xs)))) para resolver el iterador.
user21387
Esta solución es más flexible que la opción fill_value = "extrapolate". Pude adaptar la función interna 'puntual' a mis necesidades, secundo el comentario anterior e inserto la lista cuando sea necesario. Habiendo dicho eso, a veces es posible que solo desee tener un generador.
Wilmer E. Henao
1
Tenga en cuenta que la primera solución basada en scipy.interpya no se recomienda porque está obsoleta y desaparecerá en SciPy 2.0.0. Recomiendan usar en su numpy.interplugar, pero como se indica en la pregunta, no funcionará aquí
Yosko
86

Puede echar un vistazo a InterpolatedUnivariateSpline

Aquí un ejemplo usándolo:

import matplotlib.pyplot as plt
import numpy as np
from scipy.interpolate import InterpolatedUnivariateSpline

# given values
xi = np.array([0.2, 0.5, 0.7, 0.9])
yi = np.array([0.3, -0.1, 0.2, 0.1])
# positions to inter/extrapolate
x = np.linspace(0, 1, 50)
# spline order: 1 linear, 2 quadratic, 3 cubic ... 
order = 1
# do inter/extrapolation
s = InterpolatedUnivariateSpline(xi, yi, k=order)
y = s(x)

# example showing the interpolation for linear, quadratic and cubic interpolation
plt.figure()
plt.plot(xi, yi)
for order in range(1, 4):
    s = InterpolatedUnivariateSpline(xi, yi, k=order)
    y = s(x)
    plt.plot(x, y)
plt.show()
Joma
fuente
2
Esta es la mejor respuesta. Eso fue lo que hice. I used k=1 (order), por lo que se convierte en una interpolación lineal, yI used bbox=[xmin-w, xmax+w] where w is my tolerance
eusoubrasileiro
76

A partir de la versión 0.17.0 de SciPy, hay una nueva opción para scipy.interpolate.interp1d que permite la extrapolación. Simplemente configure fill_value = 'extrapolate' en la llamada. Modificar su código de esta manera da:

import numpy as np
from scipy import interpolate

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

print f(9)
print f(11)

y la salida es:

0.0497870683679
0.010394302658
Discutible
fuente
¿El tipo de extrapolación es similar al tipo de interpolación? Por ejemplo, ¿podemos tener interpolación lineal con extrapolación del punto más cercano?
a.sam
Si kind = 'cubic', fill_value = 'extrapolate' no funciona.
vlmercado
@ a.sam: No estoy seguro de lo que quieres decir ... Presumiblemente, si usas kind = 'linear' con fill_value = 'interpolation', obtienes una interpolación lineal, y si la usas con fill_value = 'extrapolation' entonces obtienes una extrapolación lineal, ¿no?
Moot
@vlmercado: ¿puedes explicar de qué manera no funciona? Intenté ejecutar el ejemplo anterior con la adición de kind = 'cubic', y funciona bien para mí.
Moot
@Moot, usando scipy 0.18.1, obtengo lo siguiente: ValueError: La extrapolación no funciona con kind = spline
vlmercado
8

¿Qué pasa con scipy.interpolate.splrep (con grado 1 y sin suavizado):

>> tck = scipy.interpolate.splrep([1, 2, 3, 4, 5], [1, 4, 9, 16, 25], k=1, s=0)
>> scipy.interpolate.splev(6, tck)
34.0

Parece hacer lo que quieras, ya que 34 = 25 + (25 - 16).

subnivean
fuente
7

Aquí hay un método alternativo que usa solo el paquete numpy. Aprovecha las funciones de matriz de numpy, por lo que puede ser más rápido al interpolar / extrapolar matrices grandes:

import numpy as np

def extrap(x, xp, yp):
    """np.interp function with linear extrapolation"""
    y = np.interp(x, xp, yp)
    y = np.where(x<xp[0], yp[0]+(x-xp[0])*(yp[0]-yp[1])/(xp[0]-xp[1]), y)
    y = np.where(x>xp[-1], yp[-1]+(x-xp[-1])*(yp[-1]-yp[-2])/(xp[-1]-xp[-2]), y)
    return y

x = np.arange(0,10)
y = np.exp(-x/3.0)
xtest = np.array((8.5,9.5))

print np.exp(-xtest/3.0)
print np.interp(xtest, x, y)
print extrap(xtest, x, y)

Editar: modificación sugerida por Mark Mikofski de la función "extrap":

def extrap(x, xp, yp):
    """np.interp function with linear extrapolation"""
    y = np.interp(x, xp, yp)
    y[x < xp[0]] = yp[0] + (x[x<xp[0]]-xp[0]) * (yp[0]-yp[1]) / (xp[0]-xp[1])
    y[x > xp[-1]]= yp[-1] + (x[x>xp[-1]]-xp[-1])*(yp[-1]-yp[-2])/(xp[-1]-xp[-2])
    return y
Ryggyr
fuente
2
+1 para un ejemplo real, pero también puede usar la indexación booleana y aquí y[x < xp[0]] = fp[0] + (x[x < xp[0]] - xp[0]) / (xp[1] - xp[0]) * (fp[1] - fp[0]) y en y[x > xp[-1]] = fp[-1] + (x[x > xp[-1]] - xp[-1]) / (xp[-2] - xp[-1]) * (fp[-2] - fp[-1])lugar de np.where, ya que la Falseopción yno cambia.
Mark Mikofski
6

Puede ser más rápido usar la indexación booleana con grandes conjuntos de datos , ya que el algoritmo verifica si cada punto está fuera del intervalo, mientras que la indexación booleana permite una comparación más fácil y rápida.

Por ejemplo:

# Necessary modules
import numpy as np
from scipy.interpolate import interp1d

# Original data
x = np.arange(0,10)
y = np.exp(-x/3.0)

# Interpolator class
f = interp1d(x, y)

# Output range (quite large)
xo = np.arange(0, 10, 0.001)

# Boolean indexing approach

# Generate an empty output array for "y" values
yo = np.empty_like(xo)

# Values lower than the minimum "x" are extrapolated at the same time
low = xo < f.x[0]
yo[low] =  f.y[0] + (xo[low]-f.x[0])*(f.y[1]-f.y[0])/(f.x[1]-f.x[0])

# Values higher than the maximum "x" are extrapolated at same time
high = xo > f.x[-1]
yo[high] = f.y[-1] + (xo[high]-f.x[-1])*(f.y[-1]-f.y[-2])/(f.x[-1]-f.x[-2])

# Values inside the interpolation range are interpolated directly
inside = np.logical_and(xo >= f.x[0], xo <= f.x[-1])
yo[inside] = f(xo[inside])

En mi caso, con un conjunto de datos de 300000 puntos, esto significa una aceleración de 25,8 a 0,094 segundos, esto es más de 250 veces más rápido .

Iñigo Hernáez Corres
fuente
Esto es bueno, pero no funciona si x0 es un flotante, si y [0] es np.nan, o si y [-1] es np.nan.
Estirar el
2

Lo hice agregando un punto a mis matrices iniciales. De esta manera evito definir funciones de creación propia, y la extrapolación lineal (en el siguiente ejemplo: extrapolación a la derecha) se ve bien.

import numpy as np  
from scipy import interp as itp  

xnew = np.linspace(0,1,51)  
x1=xold[-2]  
x2=xold[-1]  
y1=yold[-2]  
y2=yold[-1]  
right_val=y1+(xnew[-1]-x1)*(y2-y1)/(x2-x1)  
x=np.append(xold,xnew[-1])  
y=np.append(yold,right_val)  
f = itp(xnew,x,y)  
Giovanni
fuente
1

Me temo que no es fácil hacer esto en Scipy que yo sepa. Puede, como estoy bastante seguro de que lo sabe, desactivar los errores de límites y completar todos los valores de función más allá del rango con una constante, pero eso realmente no ayuda. Consulte esta pregunta en la lista de correo para obtener más ideas. Tal vez podría usar algún tipo de función por partes, pero eso parece un gran problema.

Justin Peel
fuente
Esa es la conclusión a la que llegué, al menos con scipy 0.7, sin embargo, este tutorial escrito hace 21 meses sugiere que la función interp1d tiene un atributo alto y bajo que se puede configurar como "lineal", el tutorial no está claro qué versión de scipy es esta. se aplica a: projects.scipy.org/scipy/browser/branches/Interpolate1D/docs/…
Salim Fadhley
Parece que esto es parte de una rama que aún no se ha asimilado a la versión principal, por lo que aún puede haber algunos problemas. El código actual para esto está en projects.scipy.org/scipy/browser/branches/interpolate/… aunque es posible que desee desplazarse hasta el final de la página y hacer clic para descargarlo como texto sin formato. Creo que parece prometedor, aunque todavía no lo he probado.
Justin Peel
1

El siguiente código le brinda el módulo de extrapolación simple. k es el valor al que establecer los datos y tiene que ser extrapolados basado en el conjunto de datos x. El numpymódulo es obligatorio.

 def extrapol(k,x,y):
        xm=np.mean(x);
        ym=np.mean(y);
        sumnr=0;
        sumdr=0;
        length=len(x);
        for i in range(0,length):
            sumnr=sumnr+((x[i]-xm)*(y[i]-ym));
            sumdr=sumdr+((x[i]-xm)*(x[i]-xm));

        m=sumnr/sumdr;
        c=ym-(m*xm);
        return((m*k)+c)
Kashyap Narasimha
fuente
0

Interpolación estándar + extrapolación lineal:

    def interpola(v, x, y):
        if v <= x[0]:
            return y[0]+(y[1]-y[0])/(x[1]-x[0])*(v-x[0])
        elif v >= x[-1]:
            return y[-2]+(y[-1]-y[-2])/(x[-1]-x[-2])*(v-x[-2])
        else:
            f = interp1d(x, y, kind='cubic') 
            return f(v)
Federico González Tamez
fuente
1
¡Oye Federico! Si se pregunta por qué recibió una votación negativa, tenga en cuenta que al responder preguntas, debe explicar realmente cómo resuelve el problema. Esta respuesta, tal como está, es solo un volcado de código y debe tener al menos algunas oraciones que expliquen por qué y / o cómo es útil. ¡Gracias!
Félix Gagnon-Grenier