Come posso eseguire l'interpolazione bidimensionale usando scipy?


105

Questa domanda e risposta è intesa come una canonica (-ish) riguardante l'interpolazione bidimensionale (e multidimensionale) usando scipy. Ci sono spesso domande riguardanti la sintassi di base di vari metodi di interpolazione multidimensionale, spero di mettere in chiaro anche questi.

Ho una serie di punti dati bidimensionali sparsi e vorrei tracciarli come una bella superficie, preferibilmente usando qualcosa come contourfo plot_surfacein matplotlib.pyplot. Come posso interpolare i miei dati bidimensionali o multidimensionali su una mesh utilizzando scipy?

Ho trovato il scipy.interpolatesottopacchetto, ma continuo a ricevere errori durante l'utilizzo di interp2do bisplrepo griddatao rbf. Qual è la sintassi corretta di questi metodi?

Risposte:


163

Disclaimer: sto scrivendo principalmente questo post con considerazioni sintattiche e comportamenti generali in mente. Non ho familiarità con l'aspetto della memoria e della CPU dei metodi descritti, e rivolgo questa risposta a coloro che hanno set di dati ragionevolmente piccoli, in modo tale che la qualità dell'interpolazione possa essere l'aspetto principale da considerare. Sono consapevole del fatto che quando si lavora con set di dati molto grandi, i metodi con prestazioni migliori (vale a dire griddatae Rbf) potrebbero non essere fattibili.

Metterò a confronto tre tipi di metodi di interpolazione multidimensionale ( interp2d/ splines griddatae Rbf). Li sottoporrò a due tipi di attività di interpolazione e due tipi di funzioni sottostanti (punti da cui interpolare). Gli esempi specifici dimostreranno l'interpolazione bidimensionale, ma i metodi praticabili sono applicabili in dimensioni arbitrarie. Ciascun metodo fornisce vari tipi di interpolazione; in tutti i casi userò l'interpolazione cubica (o qualcosa di simile 1 ). È importante notare che ogni volta che si utilizza l'interpolazione si introduce un pregiudizio rispetto ai dati grezzi e i metodi specifici utilizzati influenzano gli artefatti che si otterranno. Sii sempre consapevole di questo e interpola in modo responsabile.

I due compiti di interpolazione saranno

  1. sovracampionamento (i dati di input sono su una griglia rettangolare, i dati di output sono su una griglia più densa)
  2. interpolazione di dati sparsi su una griglia regolare

Le due funzioni (sul dominio [x,y] in [-1,1]x[-1,1]) saranno

  1. una superficie liscia e la funzione friendly: cos(pi*x)*sin(pi*y); gamma in[-1, 1]
  2. una funzione cattiva (e in particolare non continua): x*y/(x^2+y^2)con un valore di 0,5 vicino all'origine; gamma in[-0.5, 0.5]

Ecco come appaiono:

fig1: funzioni di test

Per prima cosa dimostrerò come si comportano i tre metodi in questi quattro test, quindi descriverò in dettaglio la sintassi di tutti e tre. Se sai cosa aspettarti da un metodo, potresti non voler sprecare il tuo tempo ad apprenderne la sintassi (guardandoti interp2d).

Dati di test

Per ragioni di chiarezza, ecco il codice con cui ho generato i dati di input. Anche se in questo caso specifico sono ovviamente consapevole della funzione sottostante i dati, la userò solo per generare input per i metodi di interpolazione. Uso numpy per comodità (e principalmente per generare i dati), ma anche scipy da solo sarebbe sufficiente.

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)

Funzione regolare e sovracampionamento

Cominciamo con il compito più semplice. Ecco come funziona un sovracampionamento da una mesh di forma [6,7]a una di [20,21]per la funzione di test regolare:

fig2: sovracampionamento fluido

Anche se questo è un compito semplice, ci sono già sottili differenze tra gli output. A prima vista tutte e tre le uscite sono ragionevoli. Ci sono due caratteristiche da notare, in base alla nostra conoscenza precedente della funzione sottostante: il caso centrale griddatadistorce maggiormente i dati. Nota il y==-1confine del grafico (più vicino xall'etichetta): la funzione dovrebbe essere rigorosamente zero (poiché y==-1è una linea nodale per la funzione liscia), ma questo non è il caso di griddata. Notare anche il x==-1confine dei grafici (dietro, a sinistra): la funzione sottostante ha un massimo locale (che implica un gradiente zero vicino al confine) a [-1, -0.5], tuttavia l' griddataoutput mostra chiaramente un gradiente diverso da zero in questa regione. L'effetto è sottile, ma è comunque un pregiudizio. (La fedeltà diRbfè ancora meglio con la scelta predefinita delle funzioni radiali, doppiata multiquadratic.)

Funzione malvagia e sovracampionamento

Un compito un po 'più difficile è eseguire l'upsampling sulla nostra funzione malvagia:

fig3: sovracampionamento malvagio

Cominciano a manifestarsi chiare differenze tra i tre metodi. Guardando i grafici di superficie, ci sono chiari estremi spuri che appaiono nell'output di interp2d(notare le due gobbe sul lato destro della superficie tracciata). Mentre griddatae Rbfsembrano produrre risultati simili a prima vista, quest'ultimo sembra produrre un minimo più profondo vicino [0.4, -0.4]che è assente dalla funzione sottostante.

Tuttavia, c'è un aspetto cruciale in cui Rbfè di gran lunga superiore: rispetta la simmetria della funzione sottostante (che è ovviamente resa possibile anche dalla simmetria della mesh campione). L'output di griddatarompe la simmetria dei punti campione, che è già debolmente visibile nel caso liscio.

Funzione regolare e dati sparsi

Molto spesso si desidera eseguire l'interpolazione su dati sparsi. Per questo motivo mi aspetto che questi test siano più importanti. Come mostrato sopra, i punti campione sono stati scelti in modo pseudouniforme nel dominio di interesse. In scenari realistici potresti avere rumore aggiuntivo con ogni misurazione e dovresti considerare se ha senso interpolare i tuoi dati grezzi per cominciare.

Uscita per la funzione liscia:

fig4: interpolazione dispersa uniforme

Ora c'è già un po 'di uno spettacolo dell'orrore in corso. Ho ritagliato l'output da interp2da tra [-1, 1]esclusivamente per la stampa, in modo da preservare almeno una minima quantità di informazioni. È chiaro che mentre una parte della forma sottostante è presente, ci sono enormi regioni rumorose in cui il metodo si rompe completamente. Il secondo caso di griddatariproduce la forma abbastanza bene, ma si notino le regioni bianche al bordo del grafico di contorno. Ciò è dovuto al fatto che griddatafunziona solo all'interno dello scafo convesso dei punti dati di input (in altre parole, non esegue alcuna estrapolazione ). Ho mantenuto il valore NaN predefinito per i punti di output che si trovano all'esterno dello scafo convesso. 2 Considerando queste caratteristiche, Rbfsembra funzionare al meglio.

Funzione malvagia e dati sparsi

E il momento che tutti stavamo aspettando:

fig5: interpolazione malvagia dispersa

Non è una grande sorpresa che si interp2darrenda. Infatti, durante la chiamata a interp2dvoi dovreste aspettarvi che alcuni amichevoli si RuntimeWarninglamentino dell'impossibilità di costruire la spline. Come per gli altri due metodi, Rbfsembra produrre il miglior output, anche vicino ai bordi del dominio dove il risultato viene estrapolato.


Quindi lasciatemi dire alcune parole sui tre metodi, in ordine decrescente di preferenza (in modo che il peggio sia il meno probabile che venga letto da qualcuno).

scipy.interpolate.Rbf

La Rbfclasse sta per "funzioni di base radiale". Ad essere onesti non ho mai considerato questo approccio fino a quando non ho iniziato a fare ricerche per questo post, ma sono abbastanza sicuro che li userò in futuro.

Proprio come i metodi basati su spline (vedere più avanti), l'utilizzo avviene in due passaggi: il primo crea Rbfun'istanza di classe richiamabile basata sui dati di input, quindi chiama questo oggetto per una data mesh di output per ottenere il risultato interpolato. Esempio dal test di sovracampionamento regolare:

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

Si noti che in questo caso sia i punti di input che quelli di output erano array 2d e l'output z_dense_smooth_rbfha la stessa forma x_densee y_densesenza alcuno sforzo. Si noti inoltre che Rbfsupporta dimensioni arbitrarie per l'interpolazione.

Così, scipy.interpolate.Rbf

  • produce un output ben educato anche per dati di input pazzi
  • supporta l'interpolazione in dimensioni superiori
  • estrapola al di fuori dello scafo convesso dei punti di input (ovviamente l'estrapolazione è sempre un azzardo, e generalmente non dovresti fare affidamento su di essa)
  • crea un interpolatore come primo passaggio, quindi valutarlo in vari punti di output è uno sforzo aggiuntivo minore
  • può avere punti di output di forma arbitraria (invece di essere vincolato a mesh rettangolari, vedere più avanti)
  • incline a preservare la simmetria dei dati di input
  • supporta più tipi di funzioni radiali per parola chiave function: multiquadric, inverse, gaussian, linear, cubic, quintic, thin_platee arbitraria definita dall'utente

scipy.interpolate.griddata

Il mio precedente preferito, griddataè un cavallo di battaglia generale per l'interpolazione in dimensioni arbitrarie. Non esegue l'estrapolazione oltre l'impostazione di un singolo valore preimpostato per i punti al di fuori dello scafo convesso dei punti nodali, ma poiché l'estrapolazione è una cosa molto volubile e pericolosa, questa non è necessariamente una truffa. Esempio di utilizzo:

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

Notare la sintassi leggermente confusa. I punti di input devono essere specificati in una matrice di forma [N, D]nelle Ddimensioni. Per questo dobbiamo prima appiattire i nostri array di coordinate 2d (usando ravel), quindi concatenare gli array e trasporre il risultato. Ci sono diversi modi per farlo, ma sembrano tutti ingombranti. Anche i zdati di input devono essere appiattiti. Abbiamo un po 'più di libertà quando si tratta dei punti di output: per qualche motivo questi possono anche essere specificati come una tupla di array multidimensionali. Si noti che helpof griddataè fuorviante, poiché suggerisce che lo stesso vale per i punti di input (almeno per la versione 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 poche parole, scipy.interpolate.griddata

  • produce un output ben educato anche per dati di input pazzi
  • supporta l'interpolazione in dimensioni superiori
  • non esegue estrapolazioni, è possibile impostare un unico valore per l'uscita fuori dallo scafo convesso dei punti di ingresso (vedi fill_value)
  • calcola i valori interpolati in una singola chiamata, quindi l'analisi di più set di punti di output inizia da zero
  • può avere punti di uscita di forma arbitraria
  • supporta il vicino più vicino e l'interpolazione lineare in dimensioni arbitrarie, cubiche in 1d e 2d. Uso del vicino più vicino e dell'interpolazione lineare NearestNDInterpolatore LinearNDInterpolatorsotto il cofano, rispettivamente. L'interpolazione cubica 1d utilizza una spline, l'interpolazione cubica 2d utilizza CloughTocher2DInterpolatorper costruire un interpolatore cubico a tratti differenziabili in modo continuo.
  • potrebbe violare la simmetria dei dati di input

scipy.interpolate.interp2d/scipy.interpolate.bisplrep

L'unico motivo di cui sto discutendo interp2de i suoi parenti è che ha un nome ingannevole e le persone probabilmente cercheranno di usarlo. Avviso spoiler: non usarlo (a partire dalla versione 0.17.0 di scipy). È già più speciale dei soggetti precedenti in quanto è specificamente utilizzato per l'interpolazione bidimensionale, ma sospetto che questo sia di gran lunga il caso più comune per l'interpolazione multivariata.

Per quanto riguarda la sintassi, interp2dè simile a Rbfin quanto deve prima costruire un'istanza di interpolazione, che può essere chiamata per fornire i valori interpolati effettivi. C'è un problema, tuttavia: i punti di output devono essere posizionati su una mesh rettangolare, quindi gli input che entrano nella chiamata all'interpolatore devono essere vettori 1d che si estendono sulla griglia di output, come se da 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

Uno degli errori più comuni durante l'utilizzo interp2dè inserire le mesh 2d complete nella chiamata di interpolazione, che porta a un consumo di memoria esplosivo e, si spera, a un frettoloso MemoryError.

Ora, il problema più grande interp2dè che spesso non funziona. Per capirlo, dobbiamo guardare sotto il cofano. Si scopre che interp2dè un wrapper per le funzioni di livello inferiore bisplrep+ bisplev, che sono a loro volta wrapper per le routine FITPACK (scritte in Fortran). La chiamata equivalente all'esempio precedente sarebbe

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

Ora, ecco il punto interp2d: (nella versione 0.17.0 di scipy) c'è un bel commentointerpolate/interpolate.py per 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)

e in effetti interpolate/fitpack.py, in bisplrepc'è un po 'di configurazione e alla fine

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)                 

E questo è tutto. Le routine sottostanti interp2dnon hanno lo scopo di eseguire l'interpolazione. Potrebbero essere sufficienti per dati sufficientemente ben comportati, ma in circostanze realistiche probabilmente vorrai usare qualcos'altro.

Giusto per concludere, interpolate.interp2d

  • può portare ad artefatti anche con dati ben temperati
  • è specifico per problemi bivariati (anche se c'è il limite interpnper i punti di input definiti su una griglia)
  • esegue l'estrapolazione
  • crea un interpolatore come primo passaggio, quindi valutarlo in vari punti di output è uno sforzo aggiuntivo minore
  • può produrre solo output su una griglia rettangolare, per output sparsi dovresti chiamare l'interpolatore in un ciclo
  • supporta l'interpolazione lineare, cubica e quintica
  • potrebbe violare la simmetria dei dati di input

1 Sono abbastanza certo che il tipo cubice le linearfunzioni di base di Rbfnon corrispondono esattamente agli altri interpolatori con lo stesso nome.
2 Questi NaN sono anche la ragione per cui il grafico della superficie sembra così strano: matplotlib storicamente ha difficoltà a tracciare oggetti 3D complessi con informazioni di profondità adeguate. I valori NaN nei dati confondono il renderer, quindi le parti della superficie che dovrebbero essere nella parte posteriore vengono tracciate per essere nella parte anteriore. Questo è un problema con la visualizzazione e non con l'interpolazione.


2
Rbf può consumare più memoria dei griddata a seconda del numero di punti dati e del numero di dimensioni. Anche griddata ha l'oggetto sottostante LinearNDInterpolator che può essere utilizzato come Rbf in 2 passaggi.
denfromufa

1
L'interpolazione cubica di Griddata è limitata a 2 (?) Dimensioni. Per dimensioni superiori, vale la pena considerare le griglie sparse smolyak basate su chebfun.
denfromufa

1
lasciami terminare i miei commenti con questo link, dove ho ricercato tutte le opzioni di interpolazione sparse: scicomp.stackexchange.com/questions/19137/…
denfromufa

4
l'interpolazione lineare griddata è locale, l'interpolazione cubica griddata è globale. L'estrapolazione non è supportata, perché non ho avuto il tempo di capire come preservare la continuità / differenziabilità. Rbf va bene per piccoli insiemi di dati, ma per interpolare n punti di dati è necessario invertire la matrice nxn, che alla fine diventa impossibile dopo n> 5000. Rbf può anche essere sensibile alla distribuzione dei dati e potrebbe essere necessario regolare i suoi parametri manualmente. È possibile eseguire Rbf per set di dati di grandi dimensioni, ma questo non è implementato in scipy.
pv.

1
ecco rbf per set di dati di grandi dimensioni: github.com/scipy/scipy/issues/5180
denfromufa
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.