次のようなデータが与えられたとします:
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
などに線形補間する関数を設計したいと思います。
this Python tutorial を調べてみましたが、それでも頭を悩ませることはできません。
あなたの質問を理解しているので、いくつかのy
でx
値を与える関数y = interpolate(x_values, y_values, x)
を書きたいですか?基本的な考え方は、次の手順に従います。
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
)でこの間隔の勾配を計算します。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])
x1
、x2
、y1
、およびy2
の場合、整数除算(python <= 2.7)が機能し、台無しにならないように、float
にマップします。いくつかのitervalのすべての整数です。
__getitem__
では、bisect_left
を使用してself.x_listが昇順でソートされるという事実を利用して、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]
for 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
に基づいて外挿する場合、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])
あなたのソリューションは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])
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