Как реализовать линейную интерполяцию?
Я довольно новичок в программировании и подумал, что попробую написать функцию линейной интерполяции.
скажем, мне даны следующие данные:
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
? Затем основная идея следует следующим шагам:
- найти индексы значений в
x_values
, которые определяют интервал, содержащийx
. Например, дляx=3
С вашими списками примеров, содержащий интервал будет[x1,x2]=[2.5,3.4]
, и индексы будутi1=1
,i2=2
- вычислить наклон на этом интервале по
(y_values[i2]-y_values[i1])/(x_values[i2]-x_values[i1])
(iedy/dx
). - значение
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()