假设我有以下数据:
x = [1, 2.5, 3.4, 5.8, 6]
y = [2, 4, 5.8, 4.3, 4]
我希望设计一个函数,使用Python进行线性插值,从1
到2.5
,从2.5
到3.4
等等。
我已经尝试查阅这个Python教程,但是仍然无法理解。
假设我有以下数据:
x = [1, 2.5, 3.4, 5.8, 6]
y = [2, 4, 5.8, 4.3, 4]
我希望设计一个函数,使用Python进行线性插值,从1
到2.5
,从2.5
到3.4
等等。
我已经尝试查阅这个Python教程,但是仍然无法理解。
import scipy.interpolate
y_interp = scipy.interpolate.interp1d(x, y)
print y_interp(5.0)
scipy.interpolate.interp1d
通过进行线性插值,并可以自定义处理错误条件。
根据我理解你的问题,你想编写一个函数 y = interpolate(x_values, y_values, x)
,它会在一些 x
值上给出相应的 y
值? 那么基本思路如下:
x
的值所定义区间的 x_values
索引。例如,在你的样例列表中,对于 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
)。x1
值处的值加上与 x1
的距离乘以斜率即可得到 x
处的值。此外,你还需要决定如果 x
在 x_values
的区间之外会发生什么情况,这可能是一个错误,或者你可以进行反向插值,假设斜率与第一个/最后一个区间相同。
这有帮助吗?还是你需要更具体的建议?
我想出了一个相当优雅的解决方案(在我看来),所以我忍不住要发帖:
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
,以便在某个区间中如果x1
、x2
、y1
和y2
都是整数,则整数除法(Python <= 2.7)不会触发并破坏计算。
在__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]
为了简单起见,我在这里没有处理边界条件。当前情况下,对于 x < 1
的情况,i[x]
会像从 (2.5, 4) 到 (1, 2) 的线段已经延伸至负无穷一样工作,而对于 x == 1
或 x > 6
的情况,i[x]
会引发一个 IndexError
。更好的做法是在所有情况下都引发 IndexError
,但这留给读者作为练习。 :)
__call__
而不是 __getitem__
,因为它通常是一个插值函数。 - Davedef interpolate(x1: float, x2: float, y1: float, y2: float, x: float):
"""Perform linear interpolation for x between (x1,y1) and (x2,y2) """
return ((y2 - y1) * x + x2 * y1 - x1 * y2) / (x2 - x1)
lerp
函数,它是纯Python编写的,甚至还有一个文档字符串 - 完美的答案。而我却不得不滚动才能看到它。 - Ahmed Fasih在Lauritz的回答基础上,以下是带有以下更改的版本:
__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
不要对两端进行外推,你可以返回 y_list
的范围。大多数情况下,你的应用程序表现良好,Interpolate[x]
将在 x_list
中。在两端外推时产生的(可能是)线性影响可能会让你误以为数据表现良好。
Returning a non-linear result (bounded by the contents of x_list
and y_list
) your program's behavior may alert you to an issue for values greatly outside x_list
. (Linear behavior goes bananas when given non-linear inputs!)
Returning the extents of the y_list
for Interpolate[x]
outside of x_list
also means you know the range of your output value. If you extrapolate based on x
much, much less than x_list[0]
or x
much, much greater than x_list[-1]
, your return result could be outside of the range of values you expected.
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])
__call__
比使用__getitem__
更为可取,因为它通常是一个插值函数。 - Davefrom 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])