Как реализовать линейную интерполяцию?

Я довольно новичок в программировании и подумал, что попробую написать функцию линейной интерполяции.

скажем, мне даны следующие данные:

x = [1, 2.5, 3.4, 5.8, 6]
y = [2, 4, 5.8, 4.3, 4]

Я хочу создать функцию, которая будет интерполироваться линейно между 1 и 2.5, 2.5 до 3.4 и т. д. С помощью Python.

Я пробовал смотреть через Учебник Python, но я все еще не могу понять это.

6 ответов


как я понимаю ваш вопрос, вы хотите написать функцию y = interpolate(x_values, y_values, x), который y значение x? Затем основная идея следует следующим шагам:

  1. найти индексы значений в x_values, которые определяют интервал, содержащий x. Например, для x=3 С вашими списками примеров, содержащий интервал будет [x1,x2]=[2.5,3.4], и индексы будут i1=1, i2=2
  2. вычислить наклон на этом интервале по (y_values[i2]-y_values[i1])/(x_values[i2]-x_values[i1]) (ie dy/dx).
  3. значение x теперь значение в x1 плюс наклон, умноженный на расстояние от x1.

вам дополнительно нужно будет решить, что произойдет, если x находится за пределами интервала x_values, либо это ошибка, либо вы можете интерполировать "назад", предполагая, что наклон совпадает с первым/последним интервалом.

это помогло, или вам нужен более конкретный совет?


import scipy.interpolate
y_interp = scipy.interpolate.interp1d(x, y)
print y_interp(5.0)

scipy.interpolate.interp1d делает линейную интерполяцию мимо и может быть подгоняно для регуляции условий ошибки.


я придумал довольно элегантное решение (IMHO), поэтому не могу удержаться от публикации:

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

я карту float Так что целочисленное деление (python x1, x2, y1 и y2 являются целыми числами для некоторых iterval.

на __getitem__ Я использую тот факт, что я.x_list сортируется по возрастанию с помощью bisect_left (очень) быстро найти индекс самого большого элемента меньше, чем x in self.x_list.

используйте класс следующим образом:

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]

я вообще не занимался пограничными условиями здесь, для простоты. Как есть,i[x] на x < 1 будет работать так, как если бы линия от (2.5, 4) до (1, 2) была расширена до минус бесконечности, в то время как i[x] на x == 1 или x > 6 выдает IndexError. Лучше было бы поднять IndexError во всех случаях, но это оставлено как упражнение для читателя. :)


вместо экстраполяции концов вы можете вернуть экстенты y_list. Большую часть времени ваше приложение хорошо себя ведет, и Interpolate[x] будет x_list. (Предположительно) линейные влияния экстраполяции с концов могут ввести вас в заблуждение, полагая, что ваши данные хорошо себя ведут.

  • возврат нелинейного результата (ограниченного содержимым x_list и y_list) поведение вашей программы может предупредить вас о проблеме для значений за пределами x_list. (Линейное поведение идет бананами, когда заданы нелинейные входы!)

  • возвращение границ y_list на Interpolate[x] за пределами x_list также означает, что вы знаете диапазон выходного значения. Если вы экстраполируете на основе x гораздо, гораздо меньше!--11--> или x гораздо, гораздо больше, чем x_list[-1], ваш результат возврата может находиться вне диапазона значений, которые вы ожидали.

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

ваше решение не работает в Python 2.7. Произошла ошибка при проверке порядка x элементов. Мне пришлось изменить код на этот, чтобы заставить его работать:

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

вы можете использовать эту функцию.

import numpy as np
def linear_spline_interpolation_1d(x_data, y_data, x):

    #check x_data and y_data are numpy arrays
    if type(x_data).__module__ != np.__name__ and \
      type(x_data).__module__ != np.__name__:
        x_data = np.asarray(x_data)
        y_data = np.asarray(y_data)

    #check x_data is in an ascending order
    if all(np.diff(x_data) >=0) == False:
        raise ValueError("x_data must be in strictly\
                     ascending order!")

    #check x_data and y_data are of same length
    if len(x_data) != len(y_data):
        raise ValueError("x_data and y_data must be\
           of same length")

    #check x_data contains at least two data points
    if len(x_data) < 2:
        raise ValueError("cannot interpolate from one data point")

    # convert x into np array to become iterable object
    x = np.asarray(x) 
    y = np.zeros_like(x)

    for indx, xi in enumerate(x):
        #check x is between x_data.min() and x_data.max()
        if xi < x_data.min() or xi > x_data.max():
            raise ValueError("x must be within x_data range")

            i = np.searchsorted(x_data, xi) 

            y[indx] = ((xi-x_data[i-1])/(x_data[i]-x_data[i-1]))*y_data[i] + \
                   ((x_data[i] - xi)/(x_data[i]-x_data[i-1]))*y_data[i-1]

    return y

x = np.linspace(0, 10, num=11, endpoint=True)
y = np.cos(-x**2/9.0)

xnew = np.linspace(0, 10, num=41, endpoint=True)

ynew = linear_spline_interpolation_1d(x, y, xnew)


import matplotlib.pyplot as plt
plt.plot(x, y, 'o', xnew, ynew, '-')
plt.legend(['data', 'linear'], loc='best')
plt.show()

enter image description here