scipyを使用して2次元補間を実行するにはどうすればいいですか? 質問する

scipyを使用して2次元補間を実行するにはどうすればいいですか? 質問する

この Q&A は、scipy を使用した 2 次元 (および多次元) 補間に関する標準的な (標準的な) ものを目指しています。さまざまな多次元補間方法の基本的な構文に関する質問がよく寄せられますが、これらについても明確に説明できればと思います。

散在する 2 次元データ ポイントのセットがあり、できればcontourfまたはplot_surfaceのようなものを使用して、それらをきれいな表面としてプロットしたいと考えていますmatplotlib.pyplot。scipy を使用して 2 次元または多次元データをメッシュに補間するにはどうすればよいですか?

サブパッケージは見つかりましたが、またはまたは(または古い)scipy.interpolateを使用するとエラーが発生し続けます。これらのメソッドの適切な構文は何ですか?interp2dbisplrepgriddataRBFInterpolatorRbf

ベストアンサー1

免責事項: 私は主に構文上の考慮事項と一般的な動作を念頭に置いてこの投稿を書いています。説明されているメソッドのメモリと CPU の側面については詳しくありません。この回答は、補間の品質が考慮すべき主な側面となるような、比較的小さなデータセットを持つユーザーを対象としています。非常に大きなデータセットを扱う場合、パフォーマンスの優れたメソッド (キーワード引数なしgriddata)は実行できない可能性があることを認識しています。RBFInterpolatorneighbors

この回答では新しいRBFInterpolatorクラス導入SciPy1.7.0遺産のためにRbfクラスを見るこの回答の以前のバージョン

3種類の多次元補間法を比較します(interp2d/スプライン、griddataそしてRBFInterpolator) 2 種類の補間タスクと 2 種類の基礎関数 (補間の元となるポイント) を適用します。特定の例では 2 次元補間を示しますが、実行可能な方法は任意の次元に適用できます。各方法にはさまざまな種類の補間が用意されていますが、いずれの場合も 3 次補間 (またはそれに近いもの1 ) を使用します。補間を使用すると、生データと比較してバイアスが導入されること、および使用される特定の方法によって最終的に得られる成果物が影響を受けることに注意することが重要です。常にこの点を認識し、責任を持って補間してください。

2つの補間タスクは

  1. アップサンプリング(入力データは長方形のグリッド上にあり、出力データはより密なグリッド上にあります)
  2. 散在するデータを規則的なグリッド上に補間する

2つの関数(ドメイン上[x, y] in [-1, 1]x[-1, 1])は

  1. スムーズでフレンドリーな機能:cos(pi*x)*sin(pi*y);範囲[-1, 1]
  2. 悪質な(特に非連続な)関数:x*y / (x^2 + y^2)原点付近で0.5の値を持ち、範囲は[-0.5, 0.5]

見た目は次のようになります:

図1: テスト関数

まず、これら 4 つのテストで 3 つのメソッドがどのように動作するかを示し、次に 3 つのメソッドすべての構文を詳しく説明します。メソッドに何を期待すべきかがわかっている場合は、その構文を学習するのに時間を無駄にしたくないかもしれません ( さん、あなたのことですねinterp2d)。

テストデータ

明確にするために、入力データを生成したコードをここに示します。この特定のケースでは、データの基になる関数は当然認識していますが、補間方法の入力を生成するためにのみこれを使用します。利便性のため (主にデータ生成のため) numpy を使用しますが、scipy だけでも十分です。

import numpy as np
import scipy.interpolate as interp

# auxiliary function for mesh generation
def gimme_mesh(n):
    minval = -1
    maxval =  1
    # produce an asymmetric shape in order to catch issues with transpositions
    return np.meshgrid(np.linspace(minval, maxval, n),
                       np.linspace(minval, maxval, n + 1))

# set up underlying test functions, vectorized
def fun_smooth(x, y):
    return np.cos(np.pi*x) * np.sin(np.pi*y)

def fun_evil(x, y):
    # watch out for singular origin; function has no unique limit there
    return np.where(x**2 + y**2 > 1e-10, x*y/(x**2+y**2), 0.5)

# sparse input mesh, 6x7 in shape
N_sparse = 6
x_sparse, y_sparse = gimme_mesh(N_sparse)
z_sparse_smooth = fun_smooth(x_sparse, y_sparse)
z_sparse_evil = fun_evil(x_sparse, y_sparse)

# scattered input points, 10^2 altogether (shape (100,))
N_scattered = 10
rng = np.random.default_rng()
x_scattered, y_scattered = rng.random((2, N_scattered**2))*2 - 1
z_scattered_smooth = fun_smooth(x_scattered, y_scattered)
z_scattered_evil = fun_evil(x_scattered, y_scattered)

# dense output mesh, 20x21 in shape
N_dense = 20
x_dense, y_dense = gimme_mesh(N_dense)

スムーズ機能とアップサンプリング

最も簡単なタスクから始めましょう。スムーズ テスト関数で、シェイプのメッシュからシェイプ[6, 7]の 1 つへのアップサンプリングがどのように機能するかを次に示します。[20, 21]

図2: スムーズなアップサンプリング

これは単純なタスクですが、出力にはすでに微妙な違いがあります。一見すると、3 つの出力はすべて妥当です。基礎関数に関する事前の知識に基づくと、注目すべき特徴が 2 つあります。 の中間のケースでは、griddataデータが最も歪んでいます。y == -1プロットの境界 (xラベルに最も近い) に注意してください。関数は厳密にゼロになるはずです ( はy == -1滑らかな関数のノード線であるため) が、 の場合はそうではありませんgriddata。また、x == -1プロットの境界 (後ろ、左側) にも注意してください。基礎関数は で局所的最大値を持ちます (境界付近で勾配がゼロであることを意味します) が[-1, -0.5]griddata出力はこの領域で明らかにゼロ以外の勾配を示しています。影響は微妙ですが、それでもバイアスであることに変わりはありません。

悪の機能とアップサンプリング

少し難しいタスクは、evil 関数でアップサンプリングを実行することです。

図3: 悪質なアップサンプリング

3 つの方法の間には明らかな違いが現れ始めています。表面プロットを見ると、出力に明らかな偽の極値が現れていますinterp2d(プロットされた表面の右側にある 2 つの山に注目してください)。一見するとgriddata、 と は同様の結果を生成するように見えますが、その付近に局所的最小値を生成する機能は、基礎となる関数には存在しません。RBFInterpolator[0.4, -0.4]

しかし、 にははるかに優れている重要な側面が 1 つありますRBFInterpolator。それは、基礎となる関数の対称性を尊重していることです (もちろん、サンプル メッシュの対称性によっても可能になります)。 からの出力はgriddata、滑らかなケースですでに弱く見えるサンプル ポイントの対称性を破壊します。

滑らかな関数と散在したデータ

ほとんどの場合、散在したデータに対して補間を実行する必要があります。このため、これらのテストがより重要になると考えています。上記のように、サンプル ポイントは対象ドメイン内で擬似的に均一に選択されました。現実的なシナリオでは、測定ごとに追加のノイズが発生する可能性があるため、生のデータを補間することが理にかなっているかどうかを検討する必要があります。

スムーズ関数の出力:

図4: 滑らかな散布補間

すでにちょっとしたホラーショーが起こっています。私は、最低限の情報量を残すために、プロット専用interp2dに から までの出力を切り取りました。明らかに、基礎となる形状の一部は存在しますが、この方法が完全に機能しない大きなノイズ領域があります。 の2番目のケースは形状をかなりうまく再現しますが、等高線プロットの境界に白い領域があることに注意してください。これは、 が入力データポイントの凸包内でのみ機能するという事実によるものです(言い換えれば、[-1, 1]griddatagriddata外挿)。凸包の外側にある出力ポイントについては、デフォルトの NaN 値を維持しました。2これらの機能を考慮すると、RBFInterpolator最もパフォーマンスが良いようです。

悪意のある機能と散在するデータ

そして私たち全員が待ち望んでいた瞬間がやってきました。

図5: 悪意のある散在補間

が諦めるのは、それほど驚くことではありませんinterp2d。実際、 の呼び出し中に、スプラインを構築できないと文句を言うinterp2dフレンドリーな がいくつかあることを覚悟しておく必要があります。RuntimeWarning他の 2 つの方法に関しては、RBFInterpolator結果が外挿されるドメインの境界付近でも、 が最良の出力を生成するようです。


それでは、優先順位の高い順に 3 つの方法について少し説明しましょう (最悪の方法は誰にも読まれにくいものになります)。

scipy.interpolate.RBFInterpolator

クラス名の RBF はRBFInterpolator「ラジアル基底関数」の略です。正直に言うと、この投稿の調査を始めるまでこのアプローチを検討したことはありませんでしたが、将来的にはこれを使用することになるでしょう。

スプラインベースの方法 (後述) と同様に、使用方法は 2 つのステップに分かれています。まず、RBFInterpolator入力データに基づいて呼び出し可能なクラス インスタンスを作成し、次に指定された出力メッシュに対してこのオブジェクトを呼び出して補間結果を取得します。スムーズ アップサンプリング テストの例:

import scipy.interpolate as interp

sparse_points = np.stack([x_sparse.ravel(), y_sparse.ravel()], -1)  # shape (N, 2) in 2d
dense_points = np.stack([x_dense.ravel(), y_dense.ravel()], -1)  # shape (N, 2) in 2d

zfun_smooth_rbf = interp.RBFInterpolator(sparse_points, z_sparse_smooth.ravel(),
                                         smoothing=0, kernel='cubic')  # explicit default smoothing=0 for interpolation
z_dense_smooth_rbf = zfun_smooth_rbf(dense_points).reshape(x_dense.shape)  # not really a function, but a callable class instance

zfun_evil_rbf = interp.RBFInterpolator(sparse_points, z_sparse_evil.ravel(),
                                       smoothing=0, kernel='cubic')  # explicit default smoothing=0 for interpolation
z_dense_evil_rbf = zfun_evil_rbf(dense_points).reshape(x_dense.shape)  # not really a function, but a callable class instance

の API をRBFInterpolator満足させるために、配列構築の体操をしなければならなかったことに注意してください。2 次元のポイントを shape の配列として渡す必要があるため(N, 2)、入力グリッドを平坦化し、平坦化された 2 つの配列を積み重ねる必要があります。構築された補間器もこの形式のクエリ ポイントを想定しており、結果は shape の 1 次元配列になります(N,)。これをプロット用の 2 次元グリッドに一致するように再形成する必要があります。 はRBFInterpolator入力ポイントの次元数について想定しないため、補間には任意の次元をサポートします。

それで、scipy.interpolate.RBFInterpolator

  • 入力データがおかしくても、適切な出力を生成します
  • 高次元での補間をサポート
  • 入力ポイントの凸包の外側を外挿します(もちろん外挿は常にギャンブルであり、一般的にはまったく頼るべきではありません)
  • 最初のステップとして補間器を作成するので、さまざまな出力ポイントで評価するのに追加の労力はかかりません。
  • 任意の形状の出力点配列を持つことができる(長方形のメッシュに制限されるのではなく、後述)
  • 入力データの対称性を維持する可能性が高くなる
  • キーワードに対して複数の種類のラジアル関数をサポートしますkernel: multiquadric、、、、、、、、 (デフォルト)。SciPy 1.7.0 の時点では、技術的な理由により、クラスはカスタムinverse_multiquadric呼び出し可能オブジェクトを渡すことを許可していませんが、これは将来のバージョンで追加される可能性があります。inverse_quadraticgaussianlinearcubicquinticthin_plate_spline
  • smoothingパラメータを増やすことで不正確な補間を行うことができる

RBF 補間の欠点の 1 つは、Nデータ ポイントの補間に行列の反転が含まれることですN x N。この 2 次的な複雑さにより、多数のデータ ポイントに必要なメモリが急速に膨れ上がります。ただし、新しいクラスでは、各ラジアル基底関数の計算を最も近い近傍に制限するキーワード パラメータRBFInterpolatorもサポートされており、これによりメモリの必要性が軽減されます。neighborsk

scipy.interpolate.griddata

以前私が愛用していた は、griddata任意の次元での補間によく使われる汎用的なツールです。 は、節点の凸包の外側の点に単一のプリセット値を設定する以外に外挿は実行しませんが、外挿は非常に不安定で危険なものなので、これは必ずしも欠点ではありません。 使用例:

sparse_points = np.stack([x_sparse.ravel(), y_sparse.ravel()], -1)  # shape (N, 2) in 2d
z_dense_smooth_griddata = interp.griddata(sparse_points, z_sparse_smooth.ravel(),
                                          (x_dense, y_dense), method='cubic')  # default method is linear

入力配列には、 の場合と同じ配列変換が必要であることに注意してください。入力ポイントは、次元の形状のRBFInterpolator配列で指定するか、1 次元配列のタプルとして指定する必要があります。[N, D]D

z_dense_smooth_griddata = interp.griddata((x_sparse.ravel(), y_sparse.ravel()),
                                          z_sparse_smooth.ravel(), (x_dense, y_dense), method='cubic')

出力ポイント配列は、任意の次元の配列のタプルとして指定できます (上記の両方のスニペットのように)。これにより、柔軟性が向上します。

一言で言えば、scipy.interpolate.griddata

  • 入力データがおかしくても、適切な出力を生成します
  • 高次元での補間をサポート
  • 外挿を行わない場合、入力点の凸包の外側の出力に単一の値を設定できます(を参照fill_value
  • 補間された値は1回の呼び出しで計算されるため、複数の出力ポイントセットの調査は最初から開始されます。
  • 任意の形状の出力ポイントを持つことができる
  • 任意の次元での最近傍補間と線形補間、1D と 2D での 3 次補間をサポートします。最近傍補間と線形補間は、それぞれ内部でNearestNDInterpolatorとを使用します。1D 3 次補間ではスプラインを使用し、2D 3 次補間では連続的に微分可能な区分 3 次補間を構築するためにを使用します。LinearNDInterpolatorCloughTocher2DInterpolator
  • 入力データの対称性に違反する可能性がある

scipy.interpolate.interp2d/scipy.interpolate.bisplrep

私がこの用語とその類似語について議論している唯一の理由は、interp2dこの用語の名前が紛らわしく、人々がこれを使おうとする可能性が高いからです。ネタバレ注意: 使用しないでください。interp2dSciPyバージョン1.10で非推奨となり、SciPy 1.12で削除される予定です。。 見る詳細についてはこのメーリングリストのディスカッションをご覧くださいまた、これは 2 次元補間に特化している点で、これまでの主題よりも特殊ですが、多変量補間ではこれが最も一般的なケースであると思われます。

構文に関しては、 は、最初に補間インスタンスを構築する必要がある点interp2dで と似ていますRBFInterpolator。補間インスタンスは、実際の補間値を提供するために呼び出すことができます。ただし、注意点があります。出力ポイントは長方形のメッシュ上に配置する必要があるため、補間器への呼び出しに入る入力は、 からのように、出力グリッドにまたがる 1 次元ベクトルである必要がありますnumpy.meshgrid

# reminder: x_sparse and y_sparse are of shape [6, 7] from numpy.meshgrid
zfun_smooth_interp2d = interp.interp2d(x_sparse, y_sparse, z_sparse_smooth, kind='cubic')   # default kind is 'linear'
# reminder: x_dense and y_dense are of shape (20, 21) from numpy.meshgrid
xvec = x_dense[0,:] # 1d array of unique x values, 20 elements
yvec = y_dense[:,0] # 1d array of unique y values, 21 elements
z_dense_smooth_interp2d = zfun_smooth_interp2d(xvec, yvec)   # output is (20, 21)-shaped array

を使用する際の最も一般的な間違いの 1 つはinterp2d、完全な 2D メッシュを補間呼び出しに入れることです。これにより、爆発的なメモリ消費が発生し、うまくいけば、急いで が実行される可能性がありますMemoryError

さて、最大の問題は、interp2dそれがうまく動作しないことが多いことです。これを理解するには、内部を調べる必要があります。それは、interp2d低レベルの関数のラッパーであることがわかります。bisplrep+bisplevは、FITPACKルーチン(Fortranで記述)のラッパーです。前の例と同等の呼び出しは次のようになります。

kind = 'cubic'
if kind == 'linear':
    kx = ky = 1
elif kind == 'cubic':
    kx = ky = 3
elif kind == 'quintic':
    kx = ky = 5
# bisplrep constructs a spline representation, bisplev evaluates the spline at given points
bisp_smooth = interp.bisplrep(x_sparse.ravel(), y_sparse.ravel(),
                              z_sparse_smooth.ravel(), kx=kx, ky=ky, s=0)
z_dense_smooth_bisplrep = interp.bisplev(xvec, yvec, bisp_smooth).T  # note the transpose

さて、ここで問題なのはinterp2d:(scipyバージョン1.7.0では)コメントするinterpolate/interpolate.pyのためにinterp2d

if not rectangular_grid:
    # TODO: surfit is really not meant for interpolation!
    self.tck = fitpack.bisplrep(x, y, z, kx=kx, ky=ky, s=0.0)

そして実際、 ではinterpolate/fitpack.py、 にはbisplrepいくつかの設定があり、最終的には

tx, ty, c, o = _fitpack._surfit(x, y, z, w, xb, xe, yb, ye, kx, ky,
                                task, s, eps, tx, ty, nxest, nyest,
                                wrk, lwrk1, lwrk2)                 

以上です。基礎となるルーチンは、interp2d実際には補間を実行するためのものではありません。十分に適切に動作するデータであれば十分かもしれませんが、現実的な状況では、おそらく他のものを使用したほうがよいでしょう。

最後に、interpolate.interp2d

  • 適切に調整されたデータであってもアーティファクトが生じる可能性がある
  • interpn二変量問題に特化しています(ただし、グリッド上に定義された入力ポイントには制限があります)
  • 外挿を行う
  • 最初のステップとして補間器を作成するので、さまざまな出力ポイントで評価するのに追加の労力はかかりません。
  • 長方形のグリッド上の出力しか生成できないため、散在出力の場合はループ内で補間器を呼び出す必要があります。
  • 線形、三次、五次補間をサポート
  • 入力データの対称性に違反する可能性がある

1およびの基底関数の種類は、cubic同じ名前の他の補間関数と正確には対応していないことはほぼ確実です。 2これらの NaN は、表面プロットが非常に奇妙に見える理由でもあります。matplotlib は歴史的に、適切な深度情報を持つ複雑な 3D オブジェクトをプロットするのに問題があります。データ内の NaN 値はレンダラーを混乱させるため、背面にあるべき表面の部分が前面にプロットされます。これは視覚化の問題であり、補間の問題ではありません。linearRBFInterpolator

おすすめ記事