J'essaie de porter un programme qui utilise un interpolateur roulé à la main (développé par un collègue mathématicien) pour utiliser les interpolateurs fournis par scipy. Je voudrais utiliser ou envelopper l'interpolateur Scipy afin qu'il ait le comportement le plus proche possible de l'ancien interpolateur.
Une différence essentielle entre les deux fonctions réside dans le fait que, dans notre interpolateur d'origine, si la valeur d'entrée est supérieure ou inférieure à la plage d'entrée, notre interpolateur d'origine extrapolera le résultat. Si vous essayez ceci avec l'interpolateur scipy, il génère une ValueError
. Considérez ce programme comme un exemple:
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-t-il un moyen judicieux de faire en sorte qu'au lieu de s’écraser, la dernière ligne effectue simplement une extrapolation linéaire en continuant les gradients définis par les deux premiers et derniers points à l’infini.
Notez que dans le vrai logiciel, je n’utilise pas réellement la fonction exp, c’est là uniquement pour illustration!
Vous pouvez utiliser la fonction interp
de scipy, elle extrapole les valeurs gauche et droite en tant que constantes au-delà de la plage:
>>> 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])
Vous pouvez écrire un wrapper autour d'une fonction d'interpolation qui prend en charge l'extrapolation linéaire. Par exemple:
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
prend une fonction d'interpolation et retourne une fonction qui peut également extrapoler. Et vous pouvez l'utiliser comme ceci:
x = arange(0,10)
y = exp(-x/3.0)
f_i = interp1d(x, y)
f_x = extrap1d(f_i)
print f_x([9,10])
Sortie:
[ 0.04978707 0.03009069]
Vous pouvez consulter InterpolatedUnivariateSpline
Voici un exemple d'utilisation:
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()
Depuis la version 0.17.0 de SciPy, une nouvelle option pour scipy.interpolate.interp1d autorise l'extrapolation. Définissez simplement fill_value = 'extrapolate' dans l'appel. La modification de votre code de cette manière donne:
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)
et le résultat est:
0.0497870683679
0.010394302658
Qu'en est-il de scipy.interpolate.splrep (avec degré 1 et sans lissage):
>> 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
Il semble faire ce que vous voulez, puisque 34 = 25 + (25 - 16).
Voici une méthode alternative qui utilise uniquement le paquet numpy. Il tire parti des fonctions de tableau de numpy et peut donc être plus rapide lors de l'interpolation/extrapolation de grands tableaux:
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)
Edit: Mark Mikofski a suggéré de modifier la fonction "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
Il peut être plus rapide d’utiliser index booléen avec grands ensembles de données , puisque l’algorithme vérifie si tous les points sont en dehors de l’intervalle, alors que l’indexation booléenne permet une comparaison plus facile et plus rapide.
Par exemple:
# 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])
Dans mon cas, avec un ensemble de données de 300 000 points, cela signifie une vitesse de 25,8 à 0,094 secondes, ce qui est plus de 250 fois plus rapide .
Je l'ai fait en ajoutant un point à mes tableaux initiaux. De cette façon, j'évite de définir des fonctions que nous avons créées et l'extrapolation linéaire (dans l'exemple ci-dessous: extrapolation à droite) semble correcte.
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)
Je crains qu'il ne soit pas facile de faire cela dans Scipy à ma connaissance. Vous pouvez, comme je suis à peu près sûr que vous en êtes conscient, désactiver les erreurs de limites et renseigner toutes les valeurs de fonction en dehors de la plage avec une constante, mais cela ne vous aide pas vraiment. Voir cette question sur la liste de diffusion pour quelques idées supplémentaires. Vous pourriez peut-être utiliser une sorte de fonction par morceau, mais cela semble être une douleur majeure.
Standard interpoler + linéaire extrapoler:
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)
Le code ci-dessous vous donne le module d'extrapolation simple. k est la valeur à laquelle le jeu de données y doit être extrapolé en fonction du jeu de données x. Le module numpy
est requis.
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)