web-dev-qa-db-ja.com

線形補間を実装する方法は?

次のようなデータが与えられたとします:

x = [1, 2.5, 3.4, 5.8, 6]
y = [2, 4, 5.8, 4.3, 4]

Pythonを使用して、12.52.5から3.4などに線形補間する関数を設計したいと思います。

this Python tutorial を調べてみましたが、それでも頭を悩ませることはできません。

27
Helpless

あなたの質問を理解しているので、いくつかのyx値を与える関数y = interpolate(x_values, y_values, x)を書きたいですか?基本的な考え方は、次の手順に従います。

  1. xを含む間隔を定義するx_valuesの値のインデックスを見つけます。たとえば、サンプルリストのx=3の場合、含まれる間隔は[x1,x2]=[2.5,3.4]になり、インデックスはi1=1i2=2になります
  2. (y_values[i2]-y_values[i1])/(x_values[i2]-x_values[i1])(つまりdy/dx)でこの間隔の勾配を計算します。
  3. xの値は、x1の値に傾きにx1からの距離を掛けた値になります。

さらに、xx_valuesの間隔外にある場合、エラーであるか、傾きが最初/最後と同じであると仮定して「後方」に補間する場合に何が起こるかを決定する必要があります。間隔。

これは役に立ちましたか、またはより具体的なアドバイスが必要ですか?

17
carlpett
import scipy.interpolate
y_interp = scipy.interpolate.interp1d(x, y)
print y_interp(5.0)

scipy.interpolate.interp1d は線形補間を行い、エラー条件を処理するためにカスタマイズできます。

38
Dave

私はかなりエレガントなソリューション(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])

x1x2y1、および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 > 6IndexErrorを発生させます。すべての場合にIndexErrorを発生させる方が良いでしょうが、これは読者の演習として残しておきます。 :)

14

両端から外挿する代わりに、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])
    
2

あなたのソリューションは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])
1
Leo

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
0
Karl Bartel