Na verdade, o objetivo de np.meshgrid
já está mencionado na documentação:
np.meshgrid
Retorne matrizes de coordenadas a partir de vetores de coordenadas.
Faça matrizes de coordenadas ND para avaliações vetorizadas de campos escalares / vetor ND sobre grades ND, considerando matrizes de coordenadas unidimensionais x1, x2, ..., xn.
Portanto, seu principal objetivo é criar matrizes de coordenadas.
Você provavelmente só se perguntou:
Por que precisamos criar matrizes de coordenadas?
O motivo pelo qual você precisa de matrizes de coordenadas com Python / NumPy é que não há relação direta entre coordenadas e valores, exceto quando suas coordenadas começam com zero e são inteiros puramente positivos. Então você pode apenas usar os índices de uma matriz como o índice. No entanto, quando esse não for o caso, você precisará armazenar coordenadas de alguma forma com seus dados. É aí que as redes entram.
Suponha que seus dados sejam:
1 2 1
2 5 2
1 2 1
No entanto, cada valor representa uma região de 2 km de largura horizontalmente e 3 km de vertical. Suponha que sua origem seja o canto superior esquerdo e você deseje matrizes que representem a distância que você poderia usar:
import numpy as np
h, v = np.meshgrid(np.arange(3)*3, np.arange(3)*2)
onde v é:
array([[0, 0, 0],
[2, 2, 2],
[4, 4, 4]])
e h:
array([[0, 3, 6],
[0, 3, 6],
[0, 3, 6]])
Portanto, se você tiver dois índices, digamos x
e y
(é por isso que o valor de retorno meshgrid
é geralmente xx
ou em xs
vez de x
, neste caso, eu escolhi h
horizontalmente!), Então você pode obter a coordenada x do ponto, a coordenada y do ponto e a valor nesse ponto usando:
h[x, y] # horizontal coordinate
v[x, y] # vertical coordinate
data[x, y] # value
Isso facilita muito o controle das coordenadas e (ainda mais importante) você pode transmiti-las para funções que precisam conhecer as coordenadas.
Uma explicação um pouco mais longa
No entanto, np.meshgrid
ele próprio não costuma ser usado diretamente, principalmente se usa apenas objetos semelhantesnp.mgrid
ou np.ogrid
. Aqui np.mgrid
representa o sparse=False
e np.ogrid
o sparse=True
caso (refiro-me ao sparse
argumento de np.meshgrid
). Observe que há uma diferença significativa entre
np.meshgrid
e np.ogrid
e np.mgrid
: Os dois primeiros valores retornados (se houver dois ou mais) são revertidos. Geralmente isso não importa, mas você deve fornecer nomes significativos de variáveis, dependendo do contexto.
Por exemplo, no caso de uma grade 2D e matplotlib.pyplot.imshow
faz sentido nomear o primeiro item retornado np.meshgrid
x
e o segundo y
enquanto é o contrário de np.mgrid
e np.ogrid
.
np.ogrid
e grades esparsas
>>> import numpy as np
>>> yy, xx = np.ogrid[-5:6, -5:6]
>>> xx
array([[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5]])
>>> yy
array([[-5],
[-4],
[-3],
[-2],
[-1],
[ 0],
[ 1],
[ 2],
[ 3],
[ 4],
[ 5]])
Como já foi dito, a saída é revertida quando comparada a np.meshgrid
, é por isso que eu a descompactei como em yy, xx
vez de xx, yy
:
>>> xx, yy = np.meshgrid(np.arange(-5, 6), np.arange(-5, 6), sparse=True)
>>> xx
array([[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5]])
>>> yy
array([[-5],
[-4],
[-3],
[-2],
[-1],
[ 0],
[ 1],
[ 2],
[ 3],
[ 4],
[ 5]])
Isso já parece coordenadas, especificamente as linhas x e y para plotagens 2D.
Visualizado:
yy, xx = np.ogrid[-5:6, -5:6]
plt.figure()
plt.title('ogrid (sparse meshgrid)')
plt.grid()
plt.xticks(xx.ravel())
plt.yticks(yy.ravel())
plt.scatter(xx, np.zeros_like(xx), color="blue", marker="*")
plt.scatter(np.zeros_like(yy), yy, color="red", marker="x")
np.mgrid
e grades densas / polidas
>>> yy, xx = np.mgrid[-5:6, -5:6]
>>> xx
array([[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5],
[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5],
[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5],
[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5],
[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5],
[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5],
[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5],
[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5],
[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5],
[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5],
[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5]])
>>> yy
array([[-5, -5, -5, -5, -5, -5, -5, -5, -5, -5, -5],
[-4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4],
[-3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3],
[-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2],
[-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
[ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4],
[ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5]])
O mesmo se aplica aqui: A saída é revertida em comparação com np.meshgrid
:
>>> xx, yy = np.meshgrid(np.arange(-5, 6), np.arange(-5, 6))
>>> xx
array([[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5],
[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5],
[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5],
[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5],
[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5],
[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5],
[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5],
[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5],
[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5],
[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5],
[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5]])
>>> yy
array([[-5, -5, -5, -5, -5, -5, -5, -5, -5, -5, -5],
[-4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4],
[-3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3],
[-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2],
[-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
[ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4],
[ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5]])
Diferentemente ogrid
dessas matrizes, contém todas as coordenadas xx
e yy
-5 <= xx <= 5; -5 <= aa <= 5 grade.
yy, xx = np.mgrid[-5:6, -5:6]
plt.figure()
plt.title('mgrid (dense meshgrid)')
plt.grid()
plt.xticks(xx[0])
plt.yticks(yy[:, 0])
plt.scatter(xx, yy, color="red", marker="x")
Funcionalidade
Não se limita apenas ao 2D, essas funções funcionam para dimensões arbitrárias (bem, há um número máximo de argumentos dados para funcionar no Python e um número máximo de dimensões que o NumPy permite):
>>> x1, x2, x3, x4 = np.ogrid[:3, 1:4, 2:5, 3:6]
>>> for i, x in enumerate([x1, x2, x3, x4]):
... print('x{}'.format(i+1))
... print(repr(x))
x1
array([[[[0]]],
[[[1]]],
[[[2]]]])
x2
array([[[[1]],
[[2]],
[[3]]]])
x3
array([[[[2],
[3],
[4]]]])
x4
array([[[[3, 4, 5]]]])
>>> # equivalent meshgrid output, note how the first two arguments are reversed and the unpacking
>>> x2, x1, x3, x4 = np.meshgrid(np.arange(1,4), np.arange(3), np.arange(2, 5), np.arange(3, 6), sparse=True)
>>> for i, x in enumerate([x1, x2, x3, x4]):
... print('x{}'.format(i+1))
... print(repr(x))
# Identical output so it's omitted here.
Mesmo que isso também funcione para 1D, existem duas (muito mais comuns) funções de criação de grade 1D:
Além do argumento start
e, stop
ele também suporta o step
argumento (mesmo etapas complexas que representam o número de etapas):
>>> x1, x2 = np.mgrid[1:10:2, 1:10:4j]
>>> x1 # The dimension with the explicit step width of 2
array([[1., 1., 1., 1.],
[3., 3., 3., 3.],
[5., 5., 5., 5.],
[7., 7., 7., 7.],
[9., 9., 9., 9.]])
>>> x2 # The dimension with the "number of steps"
array([[ 1., 4., 7., 10.],
[ 1., 4., 7., 10.],
[ 1., 4., 7., 10.],
[ 1., 4., 7., 10.],
[ 1., 4., 7., 10.]])
Formulários
Você perguntou especificamente sobre o objetivo e, de fato, essas grades são extremamente úteis se você precisar de um sistema de coordenadas.
Por exemplo, se você tiver uma função NumPy que calcula a distância em duas dimensões:
def distance_2d(x_point, y_point, x, y):
return np.hypot(x-x_point, y-y_point)
E você quer saber a distância de cada ponto:
>>> ys, xs = np.ogrid[-5:5, -5:5]
>>> distances = distance_2d(1, 2, xs, ys) # distance to point (1, 2)
>>> distances
array([[9.21954446, 8.60232527, 8.06225775, 7.61577311, 7.28010989,
7.07106781, 7. , 7.07106781, 7.28010989, 7.61577311],
[8.48528137, 7.81024968, 7.21110255, 6.70820393, 6.32455532,
6.08276253, 6. , 6.08276253, 6.32455532, 6.70820393],
[7.81024968, 7.07106781, 6.40312424, 5.83095189, 5.38516481,
5.09901951, 5. , 5.09901951, 5.38516481, 5.83095189],
[7.21110255, 6.40312424, 5.65685425, 5. , 4.47213595,
4.12310563, 4. , 4.12310563, 4.47213595, 5. ],
[6.70820393, 5.83095189, 5. , 4.24264069, 3.60555128,
3.16227766, 3. , 3.16227766, 3.60555128, 4.24264069],
[6.32455532, 5.38516481, 4.47213595, 3.60555128, 2.82842712,
2.23606798, 2. , 2.23606798, 2.82842712, 3.60555128],
[6.08276253, 5.09901951, 4.12310563, 3.16227766, 2.23606798,
1.41421356, 1. , 1.41421356, 2.23606798, 3.16227766],
[6. , 5. , 4. , 3. , 2. ,
1. , 0. , 1. , 2. , 3. ],
[6.08276253, 5.09901951, 4.12310563, 3.16227766, 2.23606798,
1.41421356, 1. , 1.41421356, 2.23606798, 3.16227766],
[6.32455532, 5.38516481, 4.47213595, 3.60555128, 2.82842712,
2.23606798, 2. , 2.23606798, 2.82842712, 3.60555128]])
A saída seria idêntica se alguém passasse em uma grade densa em vez de uma grade aberta. A transmissão do NumPys torna isso possível!
Vamos visualizar o resultado:
plt.figure()
plt.title('distance to point (1, 2)')
plt.imshow(distances, origin='lower', interpolation="none")
plt.xticks(np.arange(xs.shape[1]), xs.ravel()) # need to set the ticks manually
plt.yticks(np.arange(ys.shape[0]), ys.ravel())
plt.colorbar()
E este é também quando NumPys mgrid
e ogrid
tornar-se muito conveniente, pois permite que você altere facilmente a resolução de suas grades:
ys, xs = np.ogrid[-5:5:200j, -5:5:200j]
# otherwise same code as above
No entanto, como imshow
não suporta x
e y
insere, é necessário alterar os ticks manualmente. Seria realmente conveniente se ele aceitasse as coordenadas x
e y
, certo?
É fácil escrever funções com o NumPy que lidam naturalmente com grades. Além disso, existem várias funções no NumPy, SciPy, matplotlib que esperam que você passe na grade.
Eu gosto de imagens, então vamos explorar matplotlib.pyplot.contour
:
ys, xs = np.mgrid[-5:5:200j, -5:5:200j]
density = np.sin(ys)-np.cos(xs)
plt.figure()
plt.contour(xs, ys, density)
Observe como as coordenadas já estão definidas corretamente! Esse não seria o caso se você apenas passasse no density
.
Ou, para dar outro exemplo divertido usando modelos de astropia (desta vez não me importo muito com as coordenadas, apenas as uso para criar uma grade):
from astropy.modeling import models
z = np.zeros((100, 100))
y, x = np.mgrid[0:100, 0:100]
for _ in range(10):
g2d = models.Gaussian2D(amplitude=100,
x_mean=np.random.randint(0, 100),
y_mean=np.random.randint(0, 100),
x_stddev=3,
y_stddev=3)
z += g2d(x, y)
a2d = models.AiryDisk2D(amplitude=70,
x_0=np.random.randint(0, 100),
y_0=np.random.randint(0, 100),
radius=5)
z += a2d(x, y)
Embora isso seja apenas "para a aparência", várias funções relacionadas a modelos funcionais e acessórios (por exemplo scipy.interpolate.interp2d
,
scipy.interpolate.griddata
até mostram exemplos usando np.mgrid
) no Scipy etc. requerem grades. A maioria deles trabalha com grades abertas e grades densas, no entanto, algumas funcionam apenas com uma delas.
xx
eyy
. A parte misteriosa para mim foi por que ele retorna esse par de resultados e como eles são. A resposta de Hai Phan é útil para isso. Eu acho que faz isso por conveniência, já que o enredo quer dois parâmetros assim.