Как реализовать линейную интерполяцию?
Скажем, мне даны следующие данные:
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, но все еще не могу разобраться с ним.
Ответы
Ответ 1
Как я понимаю ваш вопрос, вы хотите написать некоторую функцию 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])
(т.е. dy/dx
).
- Значение
x
теперь имеет значение в x1
плюс наклон, умноженный на расстояние от x1
.
Вам также нужно будет решить, что произойдет, если x
находится за пределами интервала x_values
, либо это ошибка, либо вы можете интерполировать "назад", считая, что наклон совпадает с первым/последним интервалом.
Помогла ли эта помощь, или вам нужны более конкретные советы?
Ответ 2
import scipy.interpolate
y_interp = scipy.interpolate.interp1d(x, y)
print y_interp(5.0)
scipy.interpolate.interp1d
выполняет линейную интерполяцию и может быть настроена для обработки ошибок.
Ответ 3
Я придумал довольно элегантное решение (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 <= 2.7) не будет бить и разрушать вещи, если x1
, x2
, y1
и y2
являются целыми числами для некоторого iterval.
В __getitem__
я пользуюсь тем фактом, что self.x_list сортируется в порядке возрастания, используя bisect_left
(очень) быстро найти индекс наибольшего элемента меньше x
в 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
a IndexError
. Лучше было бы повысить IndexError во всех случаях, но это остается как упражнение для читателя.:)
Ответ 4
Вместо экстраполяции с концов вы можете вернуть экстенты y_list
. В большинстве случаев ваше приложение хорошо себя ведет, а Interpolate[x]
будет в x_list
. (Предположительно) линейные эффекты экстраполяции с концов могут ввести вас в заблуждение, чтобы вы считали, что ваши данные хорошо себя ведут.
-
Возвращая нелинейный результат (ограниченный содержимым x_list
и y_list
), поведение вашей программы может предупредить вас о проблеме для значений, значительно превышающих x_list
. (Линейное поведение становится бананом при заданных нелинейных входах!)
-
Возвращение экстентов y_list
для Interpolate[x]
вне x_list
также означает, что вы знаете диапазон вашего выходного значения. Если вы экстраполируете на основе x
гораздо больше, чем x_list[0]
или 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])
Ответ 5
Ваше решение не работает в 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])
Ответ 6
Опираясь на ответ Lauritz, здесь версия со следующими изменениями
- Обновлен до python3 (карта вызывала у меня проблемы и не нужна)
- Исправлено поведение при краевых значениях
- Вызовите исключение, когда x выходит за пределы
- Используйте
__call__
вместо __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])
Пример использования:
>>> interp = Interpolate([1, 2.5, 3.4, 5.8, 6], [2, 4, 5.8, 4.3, 4])
>>> interp(4)
5.425