このQ&Aは、scipyを使用した2次元(および多次元)補間に関する標準的な(-ish)を目的としています。さまざまな多次元補間法の基本的な構文に関する質問がよくあります。 。
散乱した2次元データポイントのセットがあり、contourf
またはplot_surface
in matplotlib.pyplot
などのようなものを使用して、それらをNiceサーフェスとしてプロットしたいと思います。 scipyを使用して2次元データまたは多次元データをメッシュに補間するにはどうすればよいですか?
scipy.interpolate
サブパッケージを見つけましたが、interp2d
またはbisplrep
またはgriddata
またはrbf
を使用するとエラーが発生し続けます。これらのメソッドの適切な構文は何ですか?
免責事項:私は主に構文上の考慮事項と一般的な行動を念頭に置いてこの投稿を書いています。説明した方法のメモリとCPUの側面に精通していないため、この回答は、補間の品質が考慮すべき主要な側面になるように、かなり小さなデータセットを持つ人々を対象としています。非常に大きなデータセットを操作する場合、パフォーマンスの良いメソッド(つまりgriddata
およびRbf
)が実行できない可能性があることを認識しています。
3種類の多次元補間法を比較します( interp2d
/splines、 griddata
および Rbf
)。 2種類の補間タスクと2種類の基礎となる関数(補間されるポイント)を適用します。特定の例では2次元補間を示しますが、実行可能な方法は任意の次元に適用できます。各方法は、さまざまな種類の補間を提供します。すべての場合において、3次補間(またはそれに近いもの)を使用します1)。補間を使用するときは常に、生データと比較してバイアスを導入し、使用される特定の方法が最終的に生じるアーティファクトに影響することに注意することが重要です。これを常に意識し、責任を持って補間してください。
2つの補間タスクは
(ドメイン[x,y] in [-1,1]x[-1,1]
上の)2つの関数は
cos(pi*x)*sin(pi*y)
; [-1, 1]
の範囲x*y/(x^2+y^2)
; [-0.5, 0.5]
の範囲外観は次のとおりです。
最初に、これら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]
のいずれかへのアップサンプリングがどのように機能するかを以下に示します。
これは単純な作業ですが、出力にはすでに微妙な違いがあります。一見すると、3つの出力はすべて妥当です。基礎となる関数の事前知識に基づいて、注意すべき2つの機能があります。griddata
の中間のケースは、データを最も歪めます。プロットのy==-1
境界(x
ラベルに最も近い)に注意してください:関数は厳密にゼロでなければなりません(y==-1
はスムーズ関数の節線であるため)が、griddata
には当てはまりません。また、プロットのx==-1
境界(背後、左)にも注意してください:基になる関数は[-1, -0.5]
で極大(境界付近のゼロ勾配を意味します)ですが、griddata
出力はこの領域で明らかにゼロ以外の勾配を示します。効果は微妙ですが、それでもバイアスです。 (Rbf
の忠実度は、multiquadratic
と呼ばれる放射状関数のデフォルトの選択によりさらに向上します。)
少し難しいタスクは、邪悪な機能のアップサンプリングを実行することです。
3つの方法の間に明らかな違いが現れ始めています。表面プロットを見ると、interp2d
からの出力に明らかな偽の極値があります(プロットされた表面の右側の2つのこぶに注意してください)。 griddata
とRbf
は一見類似の結果を生成するように見えますが、後者は基礎となる関数にない[0.4, -0.4]
の近くでより深い最小値を生成するようです。
ただし、Rbf
がはるかに優れている1つの重要な側面があります。それは、基になる関数の対称性を尊重します(もちろん、サンプルメッシュの対称性によっても可能になります)。 griddata
からの出力は、サンプルポイントの対称性を破ります。これは、スムーズなケースではすでに弱く見えています。
多くの場合、散布データの補間を実行したいと考えています。このため、これらのテストがより重要になると期待しています。上記に示したように、サンプルポイントは、対象のドメイン内で疑似均一に選択されました。現実的なシナリオでは、各測定で追加のノイズが発生する可能性があるため、元のデータを補間することが理にかなっているかどうかを検討する必要があります。
スムーズ機能の出力:
現在、ホラーショーが行われています。少なくとも最小限の情報を保持するために、プロット専用にinterp2d
からの出力を[-1, 1]
の間にクリップしました。基礎となる形状の一部は存在しますが、メソッドが完全に壊れる大きなノイズのある領域があることは明らかです。 griddata
の2番目のケースは、形状をかなり良く再現しますが、等高線プロットの境界にある白い領域に注意してください。これは、griddata
が入力データポイントの凸包の内部でのみ機能するという事実によるものです(つまり、extrapolationを実行しません) 。凸包の外側にある出力ポイントのデフォルトのNaN値を保持しました。2 これらの機能を考慮すると、Rbf
のパフォーマンスが最高のようです。
そして、私たち全員が待っていた瞬間:
interp2d
が放棄することは大きな驚きではありません。実際、interp2d
の呼び出し中に、構築するスプラインの不可能性について不平を言っている友好的なRuntimeWarning
sを期待する必要があります。他の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
の複数の種類の放射状関数をサポート:multiquadric
、inverse
、gaussian
、linear
、cubic
、quintic
、thin_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
データもフラット化する必要があります。出力ポイントに関しては、もう少し自由があります。何らかの理由で、これらを多次元配列のタプルとして指定することもできます。 help
のgriddata
は、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
を参照)NearestNDInterpolator
およびLinearNDInterpolator
を使用します。 1d 3次補間はスプラインを使用し、2d 3次補間はCloughTocher2DInterpolator
を使用して連続微分可能な区分的3次補間を作成します。scipy.interpolate.interp2d
/scipy.interpolate.bisplrep
interp2d
とその親類について説明している唯一の理由は、名前が偽装されており、人々がそれを使用しようとすることです。ネタバレ警告:使用しないでください(scipyバージョン0.17.0以降)。これは、特に2次元補間に使用されるという点で、以前の主題よりも特別ですが、多変量補間の最も一般的なケースであると思われます。
構文に関する限り、interp2d
はRbf
に似ていますが、実際に補間された値を提供するために呼び出すことができる補間インスタンスを最初に構築する必要があります。ただし、キャッチがあります。出力ポイントは長方形メッシュ上に配置する必要があるため、補間への呼び出しに入る入力は、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.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
があります)1cubic
の基底関数のlinear
とRbf
の種類が、同じ名前の他のインターポレーターに正確に対応していないことは確かです。
2これらのNaNは、表面プロットがそれほど奇妙に見える理由でもあります。matplotlibは、歴史的に、適切な深さ情報を持つ複雑な3Dオブジェクトをプロットするのが困難でした。データ内のNaN値はレンダラーを混乱させるため、背面にあるはずのサーフェスの部分が前面にあるようにプロットされます。これは視覚化の問題であり、補間ではありません。