En realidad, el propósito de np.meshgrid
ya se menciona en la documentación:
np.meshgrid
Devuelve matrices de coordenadas de vectores de coordenadas.
Realice matrices de coordenadas ND para evaluaciones vectorizadas de campos escalares / vectoriales ND sobre cuadrículas ND, dados los conjuntos de coordenadas unidimensionales x1, x2, ..., xn.
Por lo tanto, su objetivo principal es crear matrices de coordenadas.
Probablemente te hayas preguntado a ti mismo:
¿Por qué necesitamos crear matrices de coordenadas?
La razón por la que necesita matrices de coordenadas con Python / NumPy es que no hay una relación directa de coordenadas a valores, excepto cuando sus coordenadas comienzan con cero y son enteros puramente positivos. Entonces puede usar los índices de una matriz como índice. Sin embargo, cuando ese no es el caso, de alguna manera necesita almacenar coordenadas junto con sus datos. Ahí es donde entran las cuadrículas.
Supongamos que sus datos son:
1 2 1
2 5 2
1 2 1
Sin embargo, cada valor representa una región de 2 kilómetros de ancho horizontalmente y 3 kilómetros verticalmente. Suponga que su origen es la esquina superior izquierda y desea matrices que representen la distancia que podría usar:
import numpy as np
h, v = np.meshgrid(np.arange(3)*3, np.arange(3)*2)
donde v es:
array([[0, 0, 0],
[2, 2, 2],
[4, 4, 4]])
y h:
array([[0, 3, 6],
[0, 3, 6],
[0, 3, 6]])
Entonces, si tiene dos índices, digamos x
y y
(es por eso que el valor de retorno de meshgrid
usualmente xx
o en xs
lugar de x
en este caso elijo h
horizontalmente), entonces puede obtener la coordenada x del punto, la coordenada y del punto y el valor en ese punto usando:
h[x, y] # horizontal coordinate
v[x, y] # vertical coordinate
data[x, y] # value
Eso hace que sea mucho más fácil hacer un seguimiento de las coordenadas y (aún más importante) puede pasarlas a funciones que necesitan conocer las coordenadas.
Una explicación un poco más larga.
Sin embargo, np.meshgrid
a menudo no se usa directamente, en su mayoría solo se usa uno de objetos similaresnp.mgrid
o np.ogrid
. Aquí np.mgrid
representa el sparse=False
y np.ogrid
el sparse=True
caso (me refiero al sparse
argumento del np.meshgrid
). Tenga en cuenta que hay una diferencia significativa entre
np.meshgrid
y np.ogrid
ynp.mgrid
: Los dos primeros valores devueltos (si hay dos o más) se invierten. A menudo, esto no importa, pero debe dar nombres de variables significativos según el contexto.
Por ejemplo, en el caso de una cuadrícula 2D y matplotlib.pyplot.imshow
tiene sentido nombrar el primer elemento devuelto de np.meshgrid
x
y el segundo y
mientras es al revés para np.mgrid
y np.ogrid
.
np.ogrid
y rejillas dispersas
>>> 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 ya se dijo, la salida se invierte en comparación con np.meshgrid
, por eso lo desempaqué como en yy, xx
lugar 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]])
Esto ya parece coordenadas, específicamente las líneas x e y para gráficos 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
y rejillas densas / desarrolladas
>>> 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]])
Lo mismo se aplica aquí: la salida se invierte en comparación con 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]])
A diferencia de ogrid
estas matrices contienen todas xx
y yy
coordenadas en -5 <= xx <= 5; -5 <= aa <= 5 cuadrícula.
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")
Funcionalidad
No solo se limita a 2D, estas funciones funcionan para dimensiones arbitrarias (bueno, hay un número máximo de argumentos dados para funcionar en Python y un número máximo de dimensiones que 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.
Incluso si estos también funcionan para 1D, hay dos funciones de creación de cuadrícula 1D (mucho más comunes):
Además del argumento start
y stop
, también admite el step
argumento (incluso pasos complejos que representan el número de pasos):
>>> 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.]])
Aplicaciones
Usted preguntó específicamente sobre el propósito y, de hecho, estas cuadrículas son extremadamente útiles si necesita un sistema de coordenadas.
Por ejemplo, si tiene una función NumPy que calcula la distancia en dos dimensiones:
def distance_2d(x_point, y_point, x, y):
return np.hypot(x-x_point, y-y_point)
Y quieres saber la distancia de cada punto:
>>> 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]])
La salida sería idéntica si se pasara en una grilla densa en lugar de una grilla abierta. ¡La transmisión de NumPys lo hace posible!
Visualicemos el 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()
Y esto también es cuando NumPys mgrid
y se ogrid
vuelve muy conveniente porque le permite cambiar fácilmente la resolución de sus cuadrículas:
ys, xs = np.ogrid[-5:5:200j, -5:5:200j]
# otherwise same code as above
Sin embargo, dado imshow
que no admite x
e y
ingresa, uno tiene que cambiar los ticks a mano. Sería realmente conveniente si aceptara el x
yy
coordenadas , ¿verdad?
Es fácil escribir funciones con NumPy que tratan naturalmente con cuadrículas. Además, hay varias funciones en NumPy, SciPy, matplotlib que esperan que pase en la cuadrícula.
Me gustan las imágenes, así que exploremos 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 cómo las coordenadas ya están configuradas correctamente! Ese no sería el caso si acabaras de pasar density
.
O para dar otro ejemplo divertido usando modelos de astropía (esta vez no me importan mucho las coordenadas, solo las uso para crear una cuadrícula):
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)
Aunque eso es solo "por el aspecto", varias funciones relacionadas con modelos funcionales y ajuste (por ejemplo scipy.interpolate.interp2d
,
scipy.interpolate.griddata
incluso mostrar ejemplos usando np.mgrid
) en Scipy, etc. requieren cuadrículas. La mayoría de estos funcionan con rejillas abiertas y rejillas densas, sin embargo, algunos solo funcionan con uno de ellos.
xx
yyy
. La parte misteriosa para mí fue por qué devuelve ese par de resultados y cómo se ven. La respuesta de Hai Phan es útil para eso. Supongo que lo hace por conveniencia, ya que plot quiere dos parámetros como ese.