Disons que je reçois les données comme suit:
x = [1, 2.5, 3.4, 5.8, 6]
y = [2, 4, 5.8, 4.3, 4]
Je veux concevoir une fonction qui interpolera linéairement entre 1
et 2.5
, 2.5
à 3.4
, et ainsi de suite en utilisant Python.
J'ai essayé de regarder à travers ce Python , mais je ne parviens toujours pas à m'en sortir.
Si je comprends bien votre question, vous voulez écrire une fonction y = interpolate(x_values, y_values, x)
, qui vous donnera la valeur y
à un certain x
? L'idée de base suit alors ces étapes:
x_values
qui définit un intervalle contenant x
. Par exemple, pour x=3
avec vos exemples de listes, l'intervalle contenant serait [x1,x2]=[2.5,3.4]
, et les indices seraient i1=1
, i2=2
(y_values[i2]-y_values[i1])/(x_values[i2]-x_values[i1])
(c'est à dire dy/dx
).x
est maintenant la valeur à x1
plus la pente multipliée par la distance de x1
.Vous devrez en outre décider de ce qui se passe si x
est en dehors de l'intervalle de x_values
, soit c'est une erreur, soit vous pouvez interpoler "en arrière", en supposant que la pente est la même que le premier/dernier intervalle.
Cela vous a-t-il aidé ou avez-vous eu besoin de conseils plus spécifiques?
import scipy.interpolate
y_interp = scipy.interpolate.interp1d(x, y)
print y_interp(5.0)
scipy.interpolate.interp1d
effectue une interpolation linéaire par et peut être personnalisé pour gérer les conditions d'erreur.
J'ai pensé à une solution plutôt élégante (à mon humble avis), donc je ne peux pas résister à la poster:
from bisect import bisect_left
class Interpolate(object):
def __init__(self, x_list, y_list):
if any(y - x <= 0 for x, y in Zip(x_list, x_list[1:])):
raise ValueError("x_list must be in strictly ascending order!")
x_list = self.x_list = map(float, x_list)
y_list = self.y_list = map(float, y_list)
intervals = Zip(x_list, x_list[1:], y_list, y_list[1:])
self.slopes = [(y2 - y1)/(x2 - x1) for x1, x2, y1, y2 in intervals]
def __getitem__(self, x):
i = bisect_left(self.x_list, x) - 1
return self.y_list[i] + self.slopes[i] * (x - self.x_list[i])
Je mappe sur float
pour que la division entière (python <= 2.7) ne se déclenche pas et ne ruine pas les choses si x1
, x2
, y1
et y2
sont tous des entiers pour un iterval.
Dans __getitem__
Je profite du fait que self.x_list est trié par ordre croissant en utilisant bisect_left
pour trouver (très) rapidement l'indice du plus grand élément plus petit que x
dans self.x_list
.
Utilisez la classe comme ceci:
i = Interpolate([1, 2.5, 3.4, 5.8, 6], [2, 4, 5.8, 4.3, 4])
# Get the interpolated value at x = 4:
y = i[4]
Je n'ai pas du tout traité des conditions frontalières ici, par souci de simplicité. Tel quel, i[x]
pour x < 1
fonctionnera comme si la ligne de (2,5, 4) à (1, 2) avait été étendue à moins l'infini, tandis que i[x]
pour x == 1
ou x > 6
lèvera un IndexError
. Il serait préférable de déclencher une IndexError dans tous les cas, mais cela reste un exercice pour le lecteur. :)
Au lieu d'extrapoler les extrémités, vous pouvez renvoyer les étendues de y_list
. La plupart du temps, votre application se comporte bien et le Interpolate[x]
sera dans le x_list
. Les effets linéaires (vraisemblablement) de l'extrapolation des extrémités peuvent vous induire en erreur en pensant que vos données se comportent bien.
Renvoyer un résultat non linéaire (délimité par le contenu de x_list
et y_list
) le comportement de votre programme peut vous alerter d'un problème de valeurs largement en dehors de x_list
. (Le comportement linéaire devient banane lorsque des entrées non linéaires sont fournies!)
Renvoyer l'étendue du y_list
pour Interpolate[x]
en dehors de x_list
signifie également que vous connaissez la plage de votre valeur de sortie. Si vous extrapolez sur la base de x
beaucoup, beaucoup moins que x_list[0]
ou x
beaucoup, beaucoup plus que x_list[-1]
, votre résultat de retour pourrait être en dehors de la plage de valeurs que vous attendiez.
def __getitem__(self, x):
if x <= self.x_list[0]:
return self.y_list[0]
Elif x >= self.x_list[-1]:
return self.y_list[-1]
else:
i = bisect_left(self.x_list, x) - 1
return self.y_list[i] + self.slopes[i] * (x - self.x_list[i])
Votre solution n'a pas fonctionné en Python 2.7. Il y a eu une erreur lors de la vérification de l'ordre des éléments x. J'ai dû changer de code pour le faire fonctionner:
from bisect import bisect_left
class Interpolate(object):
def __init__(self, x_list, y_list):
if any([y - x <= 0 for x, y in Zip(x_list, x_list[1:])]):
raise ValueError("x_list must be in strictly ascending order!")
x_list = self.x_list = map(float, x_list)
y_list = self.y_list = map(float, y_list)
intervals = Zip(x_list, x_list[1:], y_list, y_list[1:])
self.slopes = [(y2 - y1)/(x2 - x1) for x1, x2, y1, y2 in intervals]
def __getitem__(self, x):
i = bisect_left(self.x_list, x) - 1
return self.y_list[i] + self.slopes[i] * (x - self.x_list[i])
S'appuyant sur la réponse de Lauritz, voici une version avec les modifications suivantes
__call__
au lieu de __getitem__
from bisect import bisect_right
class Interpolate:
def __init__(self, x_list, y_list):
if any(y - x <= 0 for x, y in Zip(x_list, x_list[1:])):
raise ValueError("x_list must be in strictly ascending order!")
self.x_list = x_list
self.y_list = y_list
intervals = Zip(x_list, x_list[1:], y_list, y_list[1:])
self.slopes = [(y2 - y1) / (x2 - x1) for x1, x2, y1, y2 in intervals]
def __call__(self, x):
if not (self.x_list[0] <= x <= self.x_list[-1]):
raise ValueError("x out of bounds!")
if x == self.x_list[-1]:
return self.y_list[-1]
i = bisect_right(self.x_list, x) - 1
return self.y_list[i] + self.slopes[i] * (x - self.x_list[i])
Exemple d'utilisation:
>>> interp = Interpolate([1, 2.5, 3.4, 5.8, 6], [2, 4, 5.8, 4.3, 4])
>>> interp(4)
5.425