Как реализовать линейную интерполяцию? - программирование
Подтвердить что ты не робот

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

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

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, но все еще не могу разобраться с ним.

4b9b3361

Ответ 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