Isenção de responsabilidade: estou escrevendo este post principalmente com considerações sintáticas e comportamento geral em mente. Não estou familiarizado com o aspecto de memória e CPU dos métodos descritos, e pretendo esta resposta para aqueles que têm conjuntos de dados razoavelmente pequenos, de forma que a qualidade da interpolação pode ser o principal aspecto a ser considerado. Estou ciente de que, ao trabalhar com conjuntos de dados muito grandes, os métodos de melhor desempenho (ou seja, griddata
e Rbf
) podem não ser viáveis.
Vou comparar três tipos de métodos de interpolação multidimensional ( interp2d
/ splines griddata
e Rbf
). Vou submetê-los a dois tipos de tarefas de interpolação e dois tipos de funções subjacentes (pontos a partir dos quais devem ser interpolados). Os exemplos específicos irão demonstrar a interpolação bidimensional, mas os métodos viáveis são aplicáveis em dimensões arbitrárias. Cada método fornece vários tipos de interpolação; em todos os casos, usarei interpolação cúbica (ou algo próximo a 1 ). É importante observar que sempre que você usa a interpolação, você introduz um viés em comparação com seus dados brutos, e os métodos específicos usados afetam os artefatos que você terá. Esteja sempre ciente disso e interpole com responsabilidade.
As duas tarefas de interpolação serão
- upsampling (os dados de entrada estão em uma grade retangular, os dados de saída estão em uma grade mais densa)
- interpolação de dados dispersos em uma grade regular
As duas funções (sobre o domínio [x,y] in [-1,1]x[-1,1]
) serão
- a suavizar e função friendly:
cos(pi*x)*sin(pi*y)
; alcance em[-1, 1]
- uma função má (e em particular, não contínua):
x*y/(x^2+y^2)
com um valor de 0,5 próximo à origem; alcance em[-0.5, 0.5]
Veja como eles são:
Primeiro demonstrarei como os três métodos se comportam nesses quatro testes e, em seguida, detalharei a sintaxe de todos os três. Se você sabe o que deve esperar de um método, talvez não queira perder seu tempo aprendendo sua sintaxe (olhando para você interp2d
).
Dados de teste
Para fins de clareza, aqui está o código com o qual gerei os dados de entrada. Embora, neste caso específico, esteja obviamente ciente da função subjacente aos dados, usarei isso apenas para gerar entrada para os métodos de interpolação. Eu uso o numpy por conveniência (e principalmente para gerar os dados), mas o scipy sozinho também seria suficiente.
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)
Função suave e upsampling
Vamos começar com a tarefa mais fácil. Veja como um upsampling de uma malha de forma [6,7]
para outra [20,21]
funciona para a função de teste suave:
Embora seja uma tarefa simples, já existem diferenças sutis entre os resultados. À primeira vista, todas as três saídas são razoáveis. Há dois recursos a serem observados, com base em nosso conhecimento prévio da função subjacente: o caso do meio de griddata
distorce mais os dados. Observe o y==-1
limite do gráfico (mais próximo do x
rótulo): a função deve ser estritamente zero (uma vez que y==-1
é uma linha nodal para a função suave), mas este não é o caso griddata
. Observe também o x==-1
limite dos gráficos (atrás, à esquerda): a função subjacente tem um máximo local (implicando em gradiente zero próximo ao limite) [-1, -0.5]
, mas a griddata
saída mostra um gradiente claramente diferente de zero nesta região. O efeito é sutil, mas ainda assim é um viés. (A fidelidade deRbf
é ainda melhor com a escolha padrão de funções radiais, dublado multiquadratic
.)
Função do mal e upsampling
Uma tarefa um pouco mais difícil é realizar upsampling em nossa função maligna:
Diferenças claras estão começando a aparecer entre os três métodos. Olhando para os gráficos de superfície, há nítidos extremos espúrios aparecendo na saída de interp2d
(observe as duas saliências no lado direito da superfície plotada). Embora griddata
e Rbf
pareça produzir resultados semelhantes à primeira vista, o último parece produzir um mínimo mais profundo próximo [0.4, -0.4]
que está ausente da função subjacente.
No entanto, há um aspecto crucial em que Rbf
é muito superior: ele respeita a simetria da função subjacente (o que, obviamente, também é possível pela simetria da malha da amostra). A saída de griddata
quebra a simetria dos pontos de amostra, que já é fracamente visível no caso suave.
Função suave e dados dispersos
Na maioria das vezes, deseja-se realizar a interpolação em dados dispersos. Por esse motivo, espero que esses testes sejam mais importantes. Conforme mostrado acima, os pontos da amostra foram escolhidos de forma pseudo-uniformemente no domínio de interesse. Em cenários realistas, você pode ter ruído adicional com cada medição e deve considerar se faz sentido interpolar seus dados brutos para começar.
Saída para a função suave:
Agora já está acontecendo um pouco de um show de terror. Cortei a saída de interp2d
para entre [-1, 1]
exclusivamente para plotagem, a fim de preservar pelo menos uma quantidade mínima de informações. É claro que, embora algumas das formas subjacentes estejam presentes, há grandes regiões ruidosas onde o método falha completamente. O segundo caso de griddata
reproduz a forma muito bem, mas observe as regiões brancas na borda do gráfico de contorno. Isso se deve ao fato de que griddata
só funciona dentro do casco convexo dos pontos de dados de entrada (ou seja, não realiza nenhuma extrapolação ). Eu mantive o valor NaN padrão para pontos de saída situados fora do casco convexo. 2 Considerando esses recursos, Rbf
parece ter o melhor desempenho.
Função do mal e dados dispersos
E o momento que todos nós estávamos esperando:
Não é nenhuma grande surpresa que interp2d
desista. Na verdade, durante a ligação, interp2d
você deve esperar alguns amigos RuntimeWarning
reclamando da impossibilidade de construção do spline. Quanto aos outros dois métodos, Rbf
parece produzir o melhor resultado, mesmo próximo às fronteiras do domínio onde o resultado é extrapolado.
Portanto, deixe-me dizer algumas palavras sobre os três métodos, em ordem decrescente de preferência (de modo que o pior seja o menos provável de ser lido por alguém).
scipy.interpolate.Rbf
A Rbf
classe significa "funções de base radial". Para ser honesto, nunca considerei essa abordagem até começar a pesquisar para este post, mas tenho certeza de que vou usá-la no futuro.
Assim como os métodos baseados em spline (veja mais adiante), o uso vem em duas etapas: primeiro, uma cria uma Rbf
instância de classe que pode ser chamada com base nos dados de entrada e, em seguida, chama esse objeto para uma determinada malha de saída para obter o resultado interpolado. Exemplo do teste de upsampling suave:
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
Observe que os pontos de entrada e saída eram matrizes 2d neste caso, e a saída z_dense_smooth_rbf
tem o mesmo formato x_dense
e y_dense
sem nenhum esforço. Observe também que Rbf
suporta dimensões arbitrárias para interpolação.
Assim, scipy.interpolate.Rbf
- produz uma saída bem comportada, mesmo para dados de entrada malucos
- suporta interpolação em dimensões superiores
- extrapola fora do casco convexo dos pontos de entrada (é claro que a extrapolação é sempre uma aposta, e você geralmente não deve confiar nela)
- cria um interpolador como uma primeira etapa, portanto, avaliá-lo em vários pontos de saída é menos esforço adicional
- pode ter pontos de saída de forma arbitrária (em oposição a ser restrito a malhas retangulares, veja mais tarde)
- propenso a preservar a simetria dos dados de entrada
- suporta vários tipos de funções radiais para palavra-chave
function
: multiquadric
, inverse
, gaussian
, linear
, cubic
, quintic
, thin_plate
e arbitrário definido pelo usuário
scipy.interpolate.griddata
Meu antigo favorito, griddata
é um burro de carga geral para interpolação em dimensões arbitrárias. Ele não executa extrapolação além de definir um único valor predefinido para pontos fora do casco convexo dos pontos nodais, mas como a extrapolação é uma coisa muito instável e perigosa, isso não é necessariamente um trapaceiro. Exemplo de uso:
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
Observe a sintaxe um pouco confusa. Os pontos de entrada devem ser especificados em uma matriz de formas [N, D]
em D
dimensões. Para isso, primeiro temos que nivelar nossos arrays de coordenadas 2d (usando ravel
), depois concatenar os arrays e transpor o resultado. Existem várias maneiras de fazer isso, mas todas parecem ser volumosas. Os z
dados de entrada também devem ser achatados. Temos um pouco mais de liberdade quando se trata dos pontos de saída: por algum motivo, eles também podem ser especificados como uma tupla de matrizes multidimensionais. Observe que o help
de griddata
é enganoso, pois sugere que o mesmo é verdadeiro para os pontos de entrada (pelo menos para a versão 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.
Em poucas palavras, scipy.interpolate.griddata
- produz uma saída bem comportada, mesmo para dados de entrada malucos
- suporta interpolação em dimensões superiores
- não executa extrapolação, um único valor pode ser definido para a saída fora do casco convexo dos pontos de entrada (consulte
fill_value
)
- calcula os valores interpolados em uma única chamada, portanto, a sondagem de vários conjuntos de pontos de saída começa do zero
- pode ter pontos de saída de forma arbitrária
- suporta vizinho mais próximo e interpolação linear em dimensões arbitrárias, cúbico em 1d e 2d. O vizinho mais próximo e a interpolação linear usam
NearestNDInterpolator
e LinearNDInterpolator
sob o capô, respectivamente. A interpolação 1d cúbica usa um spline, a interpolação 2d cúbica usa CloughTocher2DInterpolator
para construir um interpolador cúbico por partes continuamente diferenciável.
- pode violar a simetria dos dados de entrada
scipy.interpolate.interp2d
/scipy.interpolate.bisplrep
A única razão pela qual estou discutindo interp2d
e seus parentes é que ele tem um nome enganoso e é provável que as pessoas tentem usá-lo. Alerta de spoiler: não o use (a partir da versão 0.17.0 do scipy). Já é mais especial do que os assuntos anteriores, pois é especificamente usado para interpolação bidimensional, mas suspeito que este seja de longe o caso mais comum para interpolação multivariada.
No que diz respeito à sintaxe, interp2d
é semelhante a Rbf
porque primeiro precisa construir uma instância de interpolação, que pode ser chamada para fornecer os valores interpolados reais. Há um porém: os pontos de saída devem estar localizados em uma malha retangular, então as entradas que vão para a chamada ao interpolador devem ser 1d vetores que abrangem a grade de saída, como se fossem de 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
Um dos erros mais comuns ao usar interp2d
é colocar suas malhas 2D completas na chamada de interpolação, o que leva a um consumo explosivo de memória e, com sorte, a um apressado MemoryError
.
Agora, o maior problema com interp2d
é que muitas vezes não funciona. Para entender isso, temos que examinar os bastidores. Acontece que interp2d
é um invólucro para as funções de nível inferior bisplrep
+ bisplev
, que por sua vez são invólucros para rotinas FITPACK (escritas em Fortran). A chamada equivalente ao exemplo anterior seria
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
Agora, aqui está o seguinte interp2d
: (na versão scipy 0.17.0) há um bom comentáriointerpolate/interpolate.py
para 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, de fato interpolate/fitpack.py
, bisplrep
há alguma configuração e, finalmente,
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 é isso. As rotinas subjacentes interp2d
não têm o objetivo de realizar interpolação. Eles podem ser suficientes para dados suficientemente bem comportados, mas em circunstâncias realistas você provavelmente desejará usar outra coisa.
Só para concluir, interpolate.interp2d
- pode levar a artefatos, mesmo com dados bem temperados
- é especificamente para problemas bivariados (embora haja o limite
interpn
para pontos de entrada definidos em uma grade)
- realiza extrapolação
- cria um interpolador como uma primeira etapa, portanto, avaliá-lo em vários pontos de saída é menos esforço adicional
- só pode produzir saída em uma grade retangular, para saída espalhada você teria que chamar o interpolador em um loop
- suporta interpolação linear, cúbica e quíntica
- pode violar a simetria dos dados de entrada
1 Estou bastante certo de que o cubic
e linear
tipo de funções de base de Rbf
não correspondem exatamente aos outros interpoladores com o mesmo nome.
2 Esses NaNs também são a razão pela qual a plotagem de superfície parece tão estranha: matplotlib historicamente tem dificuldades em plotar objetos 3d complexos com informações de profundidade adequadas. Os valores NaN nos dados confundem o renderizador, de modo que partes da superfície que deveriam estar na parte de trás são plotadas para ficar na frente. Este é um problema de visualização, não de interpolação.