Haftungsausschluss: Ich schreibe diesen Beitrag hauptsächlich mit syntaktischen Überlegungen und allgemeinem Verhalten. Ich bin mit dem Speicher- und CPU-Aspekt der beschriebenen Methoden nicht vertraut und ziele mit dieser Antwort auf diejenigen ab, die über relativ kleine Datenmengen verfügen, sodass die Qualität der Interpolation der wichtigste zu berücksichtigende Aspekt sein kann. Mir ist bewusst, dass bei der Arbeit mit sehr großen Datenmengen die leistungsstärkeren Methoden (nämlich griddata
und Rbf
) möglicherweise nicht durchführbar sind.
Ich werde drei Arten von mehrdimensionalen Interpolationsmethoden ( interp2d
/ splines griddata
und Rbf
) vergleichen. Ich werde sie zwei Arten von Interpolationsaufgaben und zwei Arten von zugrunde liegenden Funktionen unterwerfen (Punkte, von denen aus interpoliert werden soll). Die spezifischen Beispiele zeigen eine zweidimensionale Interpolation, aber die praktikablen Methoden sind in beliebigen Dimensionen anwendbar. Jede Methode bietet verschiedene Arten der Interpolation. In allen Fällen verwende ich eine kubische Interpolation (oder etwas in der Nähe von 1 ). Es ist wichtig zu beachten, dass Sie bei jeder Interpolation eine Verzerrung im Vergleich zu Ihren Rohdaten einführen und die spezifischen Methoden die Artefakte beeinflussen, mit denen Sie am Ende konfrontiert werden. Seien Sie sich dessen immer bewusst und interpolieren Sie verantwortungsbewusst.
Die zwei Interpolationsaufgaben werden sein
- Upsampling (Eingabedaten befinden sich in einem rechteckigen Raster, Ausgabedaten befinden sich in einem dichteren Raster)
- Interpolation von Streudaten auf ein reguläres Gitter
Die beiden Funktionen (über die Domain [x,y] in [-1,1]x[-1,1]
) werden
- eine reibungslose und freundliche Funktion :
cos(pi*x)*sin(pi*y)
; Bereich in[-1, 1]
- eine böse (und insbesondere nicht kontinuierliche) Funktion:
x*y/(x^2+y^2)
mit einem Wert von 0,5 in der Nähe des Ursprungs; Bereich in[-0.5, 0.5]
So sehen sie aus:
Ich werde zuerst zeigen, wie sich die drei Methoden unter diesen vier Tests verhalten, und dann die Syntax aller drei detailliert beschreiben. Wenn Sie wissen, was Sie von einer Methode erwarten sollten, möchten Sie möglicherweise nicht Ihre Zeit damit verschwenden, ihre Syntax zu lernen (Sie anzusehen interp2d
).
Testdaten
Aus Gründen der Übersichtlichkeit ist hier der Code, mit dem ich die Eingabedaten generiert habe. Während ich in diesem speziellen Fall offensichtlich die den Daten zugrunde liegende Funktion kenne, werde ich diese nur verwenden, um Eingaben für die Interpolationsmethoden zu generieren. Ich benutze numpy aus Bequemlichkeitsgründen (und hauptsächlich zum Generieren der Daten), aber auch scipy allein würde ausreichen.
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)
Reibungslose Funktion und Upsampling
Beginnen wir mit der einfachsten Aufgabe. So funktioniert ein Upsampling von einem Netz aus Form [6,7]
zu einem von [20,21]
für die glatte Testfunktion:
Obwohl dies eine einfache Aufgabe ist, gibt es bereits subtile Unterschiede zwischen den Ausgaben. Auf den ersten Blick sind alle drei Ausgänge sinnvoll. Basierend auf unseren Vorkenntnissen der zugrunde liegenden Funktion sind zwei Merkmale zu beachten: Der mittlere Fall griddata
verzerrt die Daten am meisten. Beachten Sie die y==-1
Grenze des Diagramms (am nächsten zum x
Etikett): Die Funktion sollte streng Null sein (da dies y==-1
eine Knotenlinie für die glatte Funktion ist), dies ist jedoch nicht der Fall griddata
. Beachten Sie auch die x==-1
Grenze der Diagramme (hinten links): Die zugrunde liegende Funktion hat ein lokales Maximum (was einen Gradienten von Null nahe der Grenze impliziert) bei [-1, -0.5]
, aber die griddata
Ausgabe zeigt in diesem Bereich eindeutig einen Gradienten ungleich Null. Der Effekt ist subtil, aber dennoch eine Tendenz. (Die Treue vonRbf
ist sogar noch besser mit der Standardauswahl von Radialfunktionen, synchronisiert multiquadratic
.)
Böse Funktion und Upsampling
Eine etwas schwierigere Aufgabe ist es, ein Upsampling für unsere böse Funktion durchzuführen:
Es zeigen sich deutliche Unterschiede zwischen den drei Methoden. Wenn Sie sich die Oberflächendiagramme ansehen, erscheinen in der Ausgabe von deutliche störende Extrema interp2d
(beachten Sie die beiden Buckel auf der rechten Seite der gezeichneten Oberfläche). Während griddata
und Rbf
auf den ersten Blick ähnliche Ergebnisse zu erzielen scheinen, scheint letzteres ein tieferes Minimum in der Nähe zu erzeugen, das in [0.4, -0.4]
der zugrunde liegenden Funktion fehlt.
Es gibt jedoch einen entscheidenden Aspekt, der Rbf
weit überlegen ist: Er respektiert die Symmetrie der zugrunde liegenden Funktion (was natürlich auch durch die Symmetrie des Probennetzes ermöglicht wird). Die Ausgabe von griddata
unterbricht die Symmetrie der Abtastpunkte, die im glatten Fall bereits schwach sichtbar ist.
Reibungslose Funktion und gestreute Daten
Am häufigsten möchte man eine Interpolation für gestreute Daten durchführen. Aus diesem Grund erwarte ich, dass diese Tests wichtiger sind. Wie oben gezeigt, wurden die Abtastpunkte im interessierenden Bereich pseudo-einheitlich ausgewählt. In realistischen Szenarien kann es bei jeder Messung zu zusätzlichem Rauschen kommen, und Sie sollten überlegen, ob es sinnvoll ist, zunächst Ihre Rohdaten zu interpolieren.
Ausgabe für die Glättungsfunktion:
Jetzt läuft schon eine Horrorshow. Ich habe die Ausgabe ausschließlich zum Plotten von interp2d
bis zwischen abgeschnitten [-1, 1]
, um zumindest eine minimale Menge an Informationen zu erhalten. Es ist klar, dass, während ein Teil der zugrunde liegenden Form vorhanden ist, es riesige verrauschte Bereiche gibt, in denen die Methode vollständig zusammenbricht. Der zweite Fall von griddata
reproduziert die Form ziemlich gut, aber beachten Sie die weißen Bereiche am Rand des Konturdiagramms. Dies liegt an der Tatsache, dass griddata
nur innerhalb der konvexen Hülle der Eingabedatenpunkte funktioniert (mit anderen Worten, es wird keine Extrapolation durchgeführt ). Ich habe den Standard-NaN-Wert für Ausgangspunkte beibehalten, die außerhalb der konvexen Hülle liegen. 2 In Anbetracht dieser Funktionen Rbf
scheint die Leistung am besten zu sein.
Böse Funktion und verstreute Daten
Und der Moment, auf den wir alle gewartet haben:
Es ist keine große Überraschung, die interp2d
aufgibt. Tatsächlich sollten Sie während des Anrufs interp2d
einige freundliche Personen erwarten, die RuntimeWarning
sich über die Unmöglichkeit der Konstruktion des Splines beschweren. Die beiden anderen Methoden Rbf
scheinen die beste Ausgabe zu erzielen, selbst in der Nähe der Grenzen der Domäne, in der das Ergebnis extrapoliert wird.
Lassen Sie mich ein paar Worte zu den drei Methoden in absteigender Reihenfolge der Präferenzen sagen (so dass das Schlimmste von niemandem am wenigsten gelesen wird).
scipy.interpolate.Rbf
Die Rbf
Klasse steht für "radiale Basisfunktionen". Um ehrlich zu sein, habe ich diesen Ansatz erst in Betracht gezogen, als ich anfing, nach diesem Beitrag zu suchen, aber ich bin mir ziemlich sicher, dass ich ihn in Zukunft verwenden werde.
Genau wie bei den Spline-basierten Methoden (siehe später) erfolgt die Verwendung in zwei Schritten: Zuerst wird eine aufrufbare Rbf
Klasseninstanz basierend auf den Eingabedaten erstellt und dann dieses Objekt für ein bestimmtes Ausgabegitter aufgerufen, um das interpolierte Ergebnis zu erhalten. Beispiel aus dem Smooth Upsampling Test:
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
Beachten Sie, dass sowohl Eingabe- als auch Ausgabepunkte in diesem Fall 2D-Arrays waren und die Ausgabe z_dense_smooth_rbf
dieselbe Form wie x_dense
und y_dense
ohne Aufwand hat. Beachten Sie auch, dass Rbf
beliebige Dimensionen für die Interpolation unterstützt werden.
So, scipy.interpolate.Rbf
- erzeugt auch für verrückte Eingabedaten eine gut erzogene Ausgabe
- unterstützt die Interpolation in höheren Dimensionen
- Extrapolation außerhalb der konvexen Hülle der Eingabepunkte (Extrapolation ist natürlich immer ein Glücksspiel, und Sie sollten sich im Allgemeinen überhaupt nicht darauf verlassen)
- erstellt als ersten Schritt einen Interpolator, sodass die Auswertung an verschiedenen Ausgabepunkten weniger zusätzlichen Aufwand bedeutet
- kann Ausgabepunkte beliebiger Form haben (im Gegensatz zur Beschränkung auf rechteckige Netze, siehe später)
- anfällig für die Wahrung der Symmetrie der Eingabedaten
- mehrere Arten von Funktionen für die radialen Schlüsselwort unterstützt
function
: multiquadric
, inverse
, gaussian
, linear
, cubic
, quintic
, thin_plate
und benutzerdefinierte willkürlichen
scipy.interpolate.griddata
Mein früherer Favorit griddata
ist ein allgemeines Arbeitstier für die Interpolation in beliebigen Dimensionen. Es wird keine Extrapolation durchgeführt, die über das Festlegen eines einzelnen voreingestellten Werts für Punkte außerhalb der konvexen Hülle der Knotenpunkte hinausgeht. Da die Extrapolation jedoch eine sehr launische und gefährliche Sache ist, ist dies nicht unbedingt ein Nachteil. Anwendungsbeispiel:
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
Beachten Sie die leicht klobige Syntax. Die Eingabepunkte müssen in einem Array von Formen [N, D]
in D
Abmessungen angegeben werden. Dazu müssen wir zuerst unsere 2D-Koordinaten-Arrays (mit ravel
) reduzieren, dann die Arrays verketten und das Ergebnis transponieren. Es gibt mehrere Möglichkeiten, dies zu tun, aber alle scheinen sperrig zu sein. Die Eingabedaten müssen z
ebenfalls abgeflacht werden. Bei den Ausgabepunkten haben wir etwas mehr Freiheit: Aus irgendeinem Grund können diese auch als Tupel mehrdimensionaler Arrays angegeben werden. Beachten Sie, dass das help
von griddata
irreführend ist, da es darauf hindeutet, dass dies auch für die Eingabepunkte gilt (zumindest für Version 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.
In einer Nussschale, scipy.interpolate.griddata
- erzeugt auch für verrückte Eingabedaten eine gut erzogene Ausgabe
- unterstützt die Interpolation in höheren Dimensionen
- führt keine Extrapolation durch, es kann ein einzelner Wert für die Ausgabe außerhalb der konvexen Hülle der Eingabepunkte festgelegt werden (siehe
fill_value
)
- Berechnet die interpolierten Werte in einem einzigen Aufruf, sodass die Prüfung mehrerer Sätze von Ausgabepunkten von vorne beginnt
- kann Ausgabepunkte beliebiger Form haben
- unterstützt die Interpolation zum nächsten Nachbarn und zur linearen Interpolation in beliebigen Dimensionen, kubisch in 1d und 2d. Nearest-Neighbour- und lineare Interpolation verwenden
NearestNDInterpolator
bzw. LinearNDInterpolator
unter der Haube. Bei der 1d-kubischen Interpolation wird ein Spline verwendet, bei der 2d-kubischen Interpolation wird CloughTocher2DInterpolator
ein kontinuierlich differenzierbarer stückweise kubischer Interpolator erstellt.
- könnte die Symmetrie der Eingabedaten verletzen
scipy.interpolate.interp2d
/.scipy.interpolate.bisplrep
Der einzige Grund, über den ich interp2d
und seine Verwandten spreche, ist, dass es einen trügerischen Namen hat und die Leute es wahrscheinlich versuchen werden. Spoiler-Alarm: Verwenden Sie ihn nicht (ab Scipy-Version 0.17.0). Es ist bereits insofern spezieller als die vorherigen Themen, als es speziell für die zweidimensionale Interpolation verwendet wird, aber ich vermute, dass dies bei weitem der häufigste Fall für die multivariate Interpolation ist.
In Bezug auf die Syntax interp2d
ähnelt dies insofern, als Rbf
zunächst eine Interpolationsinstanz erstellt werden muss, die aufgerufen werden kann, um die tatsächlichen interpolierten Werte bereitzustellen. Es gibt jedoch einen Haken: Die Ausgabepunkte müssen sich auf einem rechteckigen Netz befinden, sodass Eingaben, die in den Aufruf des Interpolators eingehen, 1d-Vektoren sein müssen, die das Ausgabegitter überspannen, als ob von 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
Einer der häufigsten Fehler bei der Verwendung interp2d
ist das Einfügen Ihrer vollständigen 2D-Netze in den Interpolationsaufruf, was zu einem explosiven Speicherverbrauch und hoffentlich zu einem voreiligen führt MemoryError
.
Das größte Problem dabei interp2d
ist, dass es oft nicht funktioniert. Um dies zu verstehen, müssen wir unter die Haube schauen. Es stellt sich heraus, dass dies interp2d
ein Wrapper für die untergeordneten Funktionen bisplrep
+ ist bisplev
, die wiederum Wrapper für FITPACK-Routinen sind (in Fortran geschrieben). Der äquivalente Aufruf zum vorherigen Beispiel wäre
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
Hier ist die Sache mit interp2d
: (in der Scipy-Version 0.17.0) gibt es einen schönen Kommentarinterpolate/interpolate.py
für 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)
und zwar in interpolate/fitpack.py
, in , bisplrep
es gibt einige Setup und schließlich
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)
Und das ist es. Die zugrunde liegenden Routinen interp2d
sind nicht wirklich für die Interpolation gedacht. Sie können für ausreichend gut verhaltene Daten ausreichen, aber unter realistischen Umständen möchten Sie wahrscheinlich etwas anderes verwenden.
Nur zum Schluss, interpolate.interp2d
- kann auch bei gut temperierten Daten zu Artefakten führen
- ist speziell für bivariate Probleme (obwohl es die Begrenzung
interpn
für Eingabepunkte gibt, die in einem Raster definiert sind)
- führt eine Extrapolation durch
- erstellt als ersten Schritt einen Interpolator, sodass die Auswertung an verschiedenen Ausgabepunkten weniger zusätzlichen Aufwand bedeutet
- kann nur eine Ausgabe über ein rechteckiges Gitter erzeugen, für eine gestreute Ausgabe müssten Sie den Interpolator in einer Schleife aufrufen
- unterstützt lineare, kubische und quintische Interpolation
- könnte die Symmetrie der Eingabedaten verletzen
1 Ich bin mir ziemlich sicher, dass die cubic
und die linear
Art der Basisfunktionen von Rbf
nicht genau den anderen gleichnamigen Interpolatoren entsprechen.
2 Diese NaNs sind auch der Grund, warum das Oberflächendiagramm so seltsam erscheint: matplotlib hat historisch gesehen Schwierigkeiten, komplexe 3D-Objekte mit der richtigen Tiefeninformation zu zeichnen. Die NaN-Werte in den Daten verwirren den Renderer, sodass Teile der Oberfläche, die sich hinten befinden sollten, so dargestellt sind, dass sie sich vorne befinden. Dies ist ein Problem bei der Visualisierung und nicht bei der Interpolation.