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 griddata
e Rbf
) potrebbero non essere fattibili.
Metterò a confronto tre tipi di metodi di interpolazione multidimensionale ( interp2d
/ splines griddata
e 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
- sovracampionamento (i dati di input sono su una griglia rettangolare, i dati di output sono su una griglia più densa)
- interpolazione di dati sparsi su una griglia regolare
Le due funzioni (sul dominio [x,y] in [-1,1]x[-1,1]
) saranno
- una superficie liscia e la funzione friendly:
cos(pi*x)*sin(pi*y)
; gamma in[-1, 1]
- 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:
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:
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 griddata
distorce maggiormente i dati. Nota il y==-1
confine del grafico (più vicino x
all'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==-1
confine 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' griddata
output 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:
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 griddata
e Rbf
sembrano 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 griddata
rompe 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:
Ora c'è già un po 'di uno spettacolo dell'orrore in corso. Ho ritagliato l'output da interp2d
a 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 griddata
riproduce la forma abbastanza bene, ma si notino le regioni bianche al bordo del grafico di contorno. Ciò è dovuto al fatto che griddata
funziona 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, Rbf
sembra funzionare al meglio.
Funzione malvagia e dati sparsi
E il momento che tutti stavamo aspettando:
Non è una grande sorpresa che si interp2d
arrenda. Infatti, durante la chiamata a interp2d
voi dovreste aspettarvi che alcuni amichevoli si RuntimeWarning
lamentino dell'impossibilità di costruire la spline. Come per gli altri due metodi, Rbf
sembra 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 Rbf
classe 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 Rbf
un'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_rbf
ha la stessa forma x_dense
e y_dense
senza alcuno sforzo. Si noti inoltre che Rbf
supporta 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_plate
e 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 D
dimensioni. 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 z
dati 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 help
of 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
NearestNDInterpolator
e LinearNDInterpolator
sotto il cofano, rispettivamente. L'interpolazione cubica 1d utilizza una spline, l'interpolazione cubica 2d utilizza CloughTocher2DInterpolator
per 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 interp2d
e 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 Rbf
in 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 bisplrep
c'è 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 interp2d
non 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
interpn
per 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 cubic
e le linear
funzioni di base di Rbf
non 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.