Wie kann ich mit scipy eine zweidimensionale Interpolation durchführen?

105

Diese Fragen und Antworten sind als kanonische (-ish) bezüglich zweidimensionaler (und mehrdimensionaler) Interpolation unter Verwendung von Scipy gedacht. Es gibt oft Fragen zur grundlegenden Syntax verschiedener mehrdimensionaler Interpolationsmethoden. Ich hoffe, diese auch zu klären.

Ich habe eine Reihe von verstreuten zweidimensionalen Datenpunkten und möchte sie als schöne Oberfläche darstellen, vorzugsweise mit etwas wie contourfoder plot_surfacein matplotlib.pyplot. Wie kann ich meine zweidimensionalen oder mehrdimensionalen Daten mithilfe von scipy in ein Netz interpolieren?

Ich habe das Unterpaket gefunden scipy.interpolate, erhalte aber immer wieder Fehler, wenn ich interp2doder bisplrepoder griddataoder verwende rbf. Was ist die richtige Syntax dieser Methoden?

Andras Deak
quelle

Antworten:

162

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 griddataund Rbf) möglicherweise nicht durchführbar sind.

Ich werde drei Arten von mehrdimensionalen Interpolationsmethoden ( interp2d/ splines griddataund 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

  1. Upsampling (Eingabedaten befinden sich in einem rechteckigen Raster, Ausgabedaten befinden sich in einem dichteren Raster)
  2. Interpolation von Streudaten auf ein reguläres Gitter

Die beiden Funktionen (über die Domain [x,y] in [-1,1]x[-1,1]) werden

  1. eine reibungslose und freundliche Funktion : cos(pi*x)*sin(pi*y); Bereich in[-1, 1]
  2. 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:

Abb1: Testfunktionen

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:

Abb. 2: Smooth Upsampling

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 griddataverzerrt die Daten am meisten. Beachten Sie die y==-1Grenze des Diagramms (am nächsten zum xEtikett): Die Funktion sollte streng Null sein (da dies y==-1eine Knotenlinie für die glatte Funktion ist), dies ist jedoch nicht der Fall griddata. Beachten Sie auch die x==-1Grenze 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 griddataAusgabe zeigt in diesem Bereich eindeutig einen Gradienten ungleich Null. Der Effekt ist subtil, aber dennoch eine Tendenz. (Die Treue vonRbfist 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:

Abb3: Böses Upsampling

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 griddataund Rbfauf 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 Rbfweit ü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 griddataunterbricht 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:

Abb. 4: Glatte Streuinterpolation

Jetzt läuft schon eine Horrorshow. Ich habe die Ausgabe ausschließlich zum Plotten von interp2dbis 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 griddatareproduziert die Form ziemlich gut, aber beachten Sie die weißen Bereiche am Rand des Konturdiagramms. Dies liegt an der Tatsache, dass griddatanur 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 Rbfscheint die Leistung am besten zu sein.

Böse Funktion und verstreute Daten

Und der Moment, auf den wir alle gewartet haben:

Abb. 5: Böse Streuinterpolation

Es ist keine große Überraschung, die interp2daufgibt. Tatsächlich sollten Sie während des Anrufs interp2deinige freundliche Personen erwarten, die RuntimeWarningsich über die Unmöglichkeit der Konstruktion des Splines beschweren. Die beiden anderen Methoden Rbfscheinen 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 RbfKlasse 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 RbfKlasseninstanz 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_rbfdieselbe Form wie x_denseund y_denseohne Aufwand hat. Beachten Sie auch, dass Rbfbeliebige 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_plateund benutzerdefinierte willkürlichen

scipy.interpolate.griddata

Mein früherer Favorit griddataist 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 DAbmessungen 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 zebenfalls 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 helpvon griddatairrefü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 NearestNDInterpolatorbzw. LinearNDInterpolatorunter der Haube. Bei der 1d-kubischen Interpolation wird ein Spline verwendet, bei der 2d-kubischen Interpolation wird CloughTocher2DInterpolatorein 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 interp2dund 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 Rbfzunä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 interp2dist 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 interp2dist, dass es oft nicht funktioniert. Um dies zu verstehen, müssen wir unter die Haube schauen. Es stellt sich heraus, dass dies interp2dein 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 , bisplrepes 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 interp2dsind 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 interpnfü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 cubicund die linearArt der Basisfunktionen von Rbfnicht 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.

Andras Deak
quelle
2
Rbf kann abhängig von der Anzahl der Datenpunkte und der Anzahl der Dimensionen mehr Speicher als Griddata belegen. Griddata hat auch das zugrunde liegende Objekt LinearNDInterpolator, das wie Rbf in 2 Schritten verwendet werden kann.
Denfromufa
1
Die kubische Interpolation von Griddata ist auf 2 (?) Dimensionen begrenzt. Für höhere Dimensionen sind Smolyak-Sparse-Gitter auf Chebfun-Basis erwägenswert.
Denfromufa
1
Lassen Sie mich meine Kommentare mit diesem Link beenden, wo ich alle Optionen für die verstreute Interpolation untersucht habe: scicomp.stackexchange.com/questions/19137/…
denfromufa
4
Die lineare Interpolation der Griddata ist lokal, die kubische Interpolation der Griddata ist global. Extrapolation wird nicht unterstützt, da ich keine Zeit hatte, um herauszufinden, wie Kontinuität / Differenzierbarkeit erhalten werden kann. Rbf ist für kleine Datensätze in Ordnung, aber um n Datenpunkte zu interpolieren, muss die nxn-Matrix invertiert werden, was schließlich nach n> 5000 unmöglich wird. Rbf kann auch empfindlich auf die Verteilung der Daten reagieren, und Sie müssen möglicherweise die Parameter von Hand anpassen. Es ist möglich, Rbf für große Datenmengen auszuführen, dies ist jedoch in scipy nicht implementiert.
pv.
1
Hier ist Rbf für große Datenmengen: github.com/scipy/scipy/issues/5180
denfromufa