web-dev-qa-db-fra.com

Comment implémenter l'interpolation linéaire?

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.

27
Helpless

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:

  1. Trouvez les indices des valeurs dans 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
  2. Calculez la pente sur cet intervalle par (y_values[i2]-y_values[i1])/(x_values[i2]-x_values[i1]) (c'est à dire dy/dx).
  3. La valeur à 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?

17
carlpett
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.

38
Dave

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. :)

14
Lauritz V. Thaulow

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])
    
2
robertberrington

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])
1
Leo

S'appuyant sur la réponse de Lauritz, voici une version avec les modifications suivantes

  • Mis à jour en python3 (la carte me causait des problèmes et n'est pas nécessaire)
  • Comportement fixe aux valeurs Edge
  • Déclenche une exception lorsque x est hors limites
  • Utilisation __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
0
Karl Bartel