web-dev-qa-db-ja.com

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

このQ&Aは、scipyを使用した2次元(および多次元)補間に関する標準的な(-ish)を目的としています。さまざまな多次元補間法の基本的な構文に関する質問がよくあります。 。

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

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

98
Andras Deak

免責事項:私は主に構文上の考慮事項と一般的な行動を念頭に置いてこの投稿を書いています。説明した方法のメモリとCPUの側面に精通していないため、この回答は、補間の品質が考慮すべき主要な側面になるように、かなり小さなデータセットを持つ人々を対象としています。非常に大きなデータセットを操作する場合、パフォーマンスの良いメソッド(つまりgriddataおよびRbf)が実行できない可能性があることを認識しています。

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

2つの補間タスクは

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

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

  1. スムーズで使いやすい関数:cos(pi*x)*sin(pi*y); [-1, 1]の範囲
  2. 悪の(特に、非連続の)関数:Originの近くの値が0.5のx*y/(x^2+y^2); [-0.5, 0.5]の範囲

外観は次のとおりです。

fig1: test functions

最初に、これら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
x_scattered,y_scattered = np.random.Rand(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]のメッシュから[20,21]のいずれかへのアップサンプリングがどのように機能するかを以下に示します。

fig2: smooth upsampling

これは単純な作業ですが、出力にはすでに微妙な違いがあります。一見すると、3つの出力はすべて妥当です。基礎となる関数の事前知識に基づいて、注意すべき2つの機能があります。griddataの中間のケースは、データを最も歪めます。プロットのy==-1境界(xラベルに最も近い)に注意してください:関数は厳密にゼロでなければなりません(y==-1はスムーズ関数の節線であるため)が、griddataには当てはまりません。また、プロットのx==-1境界(背後、左)にも注意してください:基になる関数は[-1, -0.5]で極大(境界付近のゼロ勾配を意味します)ですが、griddata出力はこの領域で明らかにゼロ以外の勾配を示します。効果は微妙ですが、それでもバイアスです。 (Rbfの忠実度は、multiquadraticと呼ばれる放射状関数のデフォルトの選択によりさらに向上します。)

邪悪な機能とアップサンプリング

少し難しいタスクは、邪悪な機能のアップサンプリングを実行することです。

fig3: evil upsampling

3つの方法の間に明らかな違いが現れ始めています。表面プロットを見ると、interp2dからの出力に明らかな偽の極値があります(プロットされた表面の右側の2つのこぶに注意してください)。 griddataRbfは一見類似の結果を生成するように見えますが、後者は基礎となる関数にない[0.4, -0.4]の近くでより深い最小値を生成するようです。

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

スムーズな機能と分散データ

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

スムーズ機能の出力:

fig4: smooth scattered interpolation

現在、ホラーショーが行われています。少なくとも最小限の情報を保持するために、プロット専用にinterp2dからの出力を[-1, 1]の間にクリップしました。基礎となる形状の一部は存在しますが、メソッドが完全に壊れる大きなノイズのある領域があることは明らかです。 griddataの2番目のケースは、形状をかなり良く再現しますが、等高線プロットの境界にある白い領域に注意してください。これは、griddataが入力データポイントの凸包の内部でのみ機能するという事実によるものです(つまり、extrapolationを実行しません) 。凸包の外側にある出力ポイントのデフォルトのNaN値を保持しました。2 これらの機能を考慮すると、Rbfのパフォーマンスが最高のようです。

邪悪な機能と散在するデータ

そして、私たち全員が待っていた瞬間:

fig5: evil scattered interpolation

interp2dが放棄することは大きな驚きではありません。実際、interp2dの呼び出し中に、構築するスプラインの不可能性について不平を言っている友好的なRuntimeWarningsを期待する必要があります。他の2つの方法については、結果が外挿されるドメインの境界の近くであっても、Rbfが最適な出力を生成するようです。


それでは、3つの方法について、優先順位の降順でいくつかの言葉を話しましょう(だから、最悪の事態は誰にも読まれないように)。

scipy.interpolate.Rbf

Rbfクラスは「放射基底関数」の略です。正直に言うと、この投稿の調査を開始するまでこのアプローチを検討したことはありませんでしたが、今後これらを使用することになると確信しています。

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

import scipy.interpolate as interp
zfun_smooth_rbf = interp.Rbf(x_sparse, y_sparse, z_sparse_smooth, function='cubic', smooth=0)  # default smooth=0 for interpolation
z_dense_smooth_rbf = zfun_smooth_rbf(x_dense, y_dense)  # not really a function, but a callable class instance

この場合、入力ポイントと出力ポイントは両方とも2D配列であり、出力z_dense_smooth_rbfは、x_denseおよびy_denseと同じ形状であり、手間がかからないことに注意してください。また、Rbfは補間のために任意の次元をサポートすることに注意してください。

だから、scipy.interpolate.Rbf

  • クレイジーな入力データに対しても適切に動作する出力を生成します
  • 高次元での補間をサポート
  • 入力点の凸包の外側に外挿します(もちろん外挿は常にギャンブルであり、通常はこれにまったく依存しないでください)
  • 最初のステップとして補間器を作成するため、さまざまな出力ポイントで補間器を評価するのに追加の手間がかかりません
  • 任意の形状の出力ポイントを持つことができます(長方形のメッシュに制約されるのではなく、後述します)
  • 入力データの対称性を維持する傾向がある
  • キーワードfunctionの複数の種類の放射状関数をサポート:multiquadricinversegaussianlinearcubicquinticthin_plate、およびユーザー定義の任意

scipy.interpolate.griddata

私の以前のお気に入り、griddataは、任意の次元での補間の一般的な主力製品です。節点の凸包の外側の点に単一のプリセット値を設定する以外の外挿は実行しませんが、外挿は非常に気まぐれで危険なものなので、これは必ずしも詐欺ではありません。使用例:

z_dense_smooth_griddata = interp.griddata(np.array([x_sparse.ravel(),y_sparse.ravel()]).T,
                                          z_sparse_smooth.ravel(),
                                          (x_dense,y_dense), method='cubic')   # default method is linear

わずかにぎこちない構文に注意してください。入力ポイントは、D次元の形状[N, D]の配列で指定する必要があります。このために、最初に2次元座標配列を平坦化し(ravelを使用)、配列を連結して結果を転置する必要があります。これを行うには複数の方法がありますが、それらはすべてかさばるようです。入力zデータもフラット化する必要があります。出力ポイントに関しては、もう少し自由があります。何らかの理由で、これらを多次元配列のタプルとして指定することもできます。 helpgriddataは、inputポイント(少なくともバージョン0.17.0)にも同じことが当てはまることを示唆しているため、誤解を招くことに注意してください。

griddata(points, values, xi, method='linear', fill_value=nan, rescale=False)
    Interpolate unstructured D-dimensional data.

    Parameters
    ----------
    points : ndarray of floats, shape (n, D)
        Data point coordinates. Can either be an array of
        shape (n, D), or a Tuple of `ndim` arrays.
    values : ndarray of float or complex, shape (n,)
        Data values.
    xi : ndarray of float, shape (M, D)
        Points at which to interpolate data.

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

  • クレイジーな入力データに対しても適切に動作する出力を生成します
  • 高次元での補間をサポート
  • 外挿を実行しません。入力ポイントの凸包の外側の出力に単一の値を設定できます(fill_valueを参照)
  • 単一の呼び出しで補間値を計算するため、出力ポイントの複数のセットの調査は最初から開始されます
  • 任意の形状の出力ポイントを持つことができます
  • 任意の次元での最近傍および線形補間、1dおよび2dでの3次補間をサポートします。最近傍および線形補間は、それぞれフードの下でNearestNDInterpolatorおよびLinearNDInterpolatorを使用します。 1d 3次補間はスプラインを使用し、2d 3次補間はCloughTocher2DInterpolatorを使用して連続微分可能な区分的3次補間を作成します。
  • 入力データの対称性に違反する可能性があります

scipy.interpolate.interp2d/scipy.interpolate.bisplrep

interp2dとその親類について説明している唯一の理由は、名前が偽装されており、人々がそれを使用しようとすることです。ネタバレ警告:使用しないでください(scipyバージョン0.17.0以降)。これは、特に2次元補間に使用されるという点で、以前の主題よりも特別ですが、多変量補間の最も一般的なケースであると思われます。

構文に関する限り、interp2dRbfに似ていますが、実際に補間された値を提供するために呼び出すことができる補間インスタンスを最初に構築する必要があります。ただし、キャッチがあります。出力ポイントは長方形メッシュ上に配置する必要があるため、補間への呼び出しに入る入力は、numpy.meshgridからの場合のように、出力グリッドにまたがる1dベクトルでなければなりません。

# 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

interp2dを使用する際の最も一般的な間違いの1つは、完全な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バージョン0.17.0で)Nice comment in interpolate/interpolate.py for 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.pybisplrepにはいくつかのセットアップがあり、最終的には

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があります)
  • 外挿を実行します
  • 最初のステップとして補間器を作成するため、さまざまな出力ポイントで補間器を評価するのに追加の手間がかかりません
  • 長方形のグリッド上でのみ出力を生成できます。散在する出力の場合、ループで補間器を呼び出す必要があります。
  • 線形補間、3次補間、5次補間をサポート
  • 入力データの対称性に違反する可能性があります

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

149
Andras Deak