En fait, le but de np.meshgrid
est déjà mentionné dans la documentation:
np.meshgrid
Renvoie les matrices de coordonnées des vecteurs de coordonnées.
Faire des tableaux de coordonnées ND pour des évaluations vectorisées de champs scalaires / vectoriels ND sur des grilles ND, étant donné les tableaux de coordonnées unidimensionnels x1, x2, ..., xn.
Son objectif principal est donc de créer une matrice de coordonnées.
Vous vous êtes probablement demandé:
Pourquoi devons-nous créer des matrices de coordonnées?
La raison pour laquelle vous avez besoin de matrices de coordonnées avec Python / NumPy est qu'il n'y a pas de relation directe entre les coordonnées et les valeurs, sauf lorsque vos coordonnées commencent par zéro et sont des entiers purement positifs. Ensuite, vous pouvez simplement utiliser les indices d'un tableau comme index. Cependant, lorsque ce n'est pas le cas, vous devez en quelque sorte stocker les coordonnées à côté de vos données. C'est là qu'interviennent les grilles.
Supposons que vos données soient:
1 2 1
2 5 2
1 2 1
Cependant, chaque valeur représente une région de 2 kilomètres de large horizontalement et de 3 kilomètres verticalement. Supposons que votre origine soit le coin supérieur gauche et que vous vouliez des tableaux qui représentent la distance que vous pourriez utiliser:
import numpy as np
h, v = np.meshgrid(np.arange(3)*3, np.arange(3)*2)
où v est:
array([[0, 0, 0],
[2, 2, 2],
[4, 4, 4]])
et h:
array([[0, 3, 6],
[0, 3, 6],
[0, 3, 6]])
Donc, si vous avez deux indices, disons x
et y
(c'est pourquoi la valeur de retour de meshgrid
est généralement xx
ou xs
au lieu de x
dans ce cas j'ai choisi h
horizontalement!), Alors vous pouvez obtenir la coordonnée x du point, la coordonnée y du point et le valeur à ce point en utilisant:
h[x, y] # horizontal coordinate
v[x, y] # vertical coordinate
data[x, y] # value
Cela facilite grandement le suivi des coordonnées et (plus important encore) vous pouvez les transmettre à des fonctions qui ont besoin de connaître les coordonnées.
Une explication un peu plus longue
Cependant, np.meshgrid
lui-même n'est pas souvent utilisé directement, la plupart du temps, on utilise simplement l'un des objets similairesnp.mgrid
ou np.ogrid
. Ici np.mgrid
représente le sparse=False
et np.ogrid
le sparse=True
cas (je me réfère à l' sparse
argument de np.meshgrid
). Notez qu'il existe une différence significative entre
np.meshgrid
et np.ogrid
et np.mgrid
: Les deux premières valeurs renvoyées (s'il y en a deux ou plus) sont inversées. Souvent, cela n'a pas d'importance, mais vous devez donner des noms de variables significatifs en fonction du contexte.
Par exemple, dans le cas d'une grille 2D et matplotlib.pyplot.imshow
il est logique de nommer le premier élément retourné de np.meshgrid
x
et le second y
alors que c'est l'inverse pour np.mgrid
et np.ogrid
.
np.ogrid
et grilles clairsemées
>>> 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]])
Comme déjà dit, la sortie est inversée par rapport à np.meshgrid
, c'est pourquoi je l'ai décompressée au yy, xx
lieu 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]])
Cela ressemble déjà à des coordonnées, en particulier les lignes x et y pour les tracés 2D.
Visualisé:
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
et grilles denses / étoffées
>>> 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]])
Il en va de même ici: la sortie est inversée par rapport à 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]])
Contrairement à ogrid
ces tableaux contiennent tous xx
et yy
coordonnées dans le -5 <= xx <= 5; -5 <= yy <= 5 grille.
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")
Fonctionnalité
Ce n'est pas seulement limité à la 2D, ces fonctions fonctionnent pour des dimensions arbitraires (enfin, il y a un nombre maximum d'arguments donnés pour fonctionner en Python et un nombre maximum de dimensions que NumPy permet):
>>> 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.
Même si ceux-ci fonctionnent également pour 1D, il existe deux fonctions de création de grille 1D (beaucoup plus courantes):
Outre l' argument start
et stop
, il prend également en charge l' step
argument (même les étapes complexes qui représentent le nombre d'étapes):
>>> 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.]])
Applications
Vous avez spécifiquement demandé à propos de l'objectif et en fait, ces grilles sont extrêmement utiles si vous avez besoin d'un système de coordonnées.
Par exemple, si vous avez une fonction NumPy qui calcule la distance en deux dimensions:
def distance_2d(x_point, y_point, x, y):
return np.hypot(x-x_point, y-y_point)
Et vous voulez connaître la distance de chaque point:
>>> 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 sortie serait identique si l'on passait dans une grille dense au lieu d'une grille ouverte. La diffusion NumPys rend cela possible!
Visualisons le résultat:
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()
Et cela est aussi quand NumPys mgrid
et ogrid
deviennent très pratique car il vous permet de changer facilement la résolution de vos grilles:
ys, xs = np.ogrid[-5:5:200j, -5:5:200j]
# otherwise same code as above
Toutefois, étant donné que imshow
ne prend pas en charge x
et les y
entrées on doit changer les tiques à la main. Ce serait vraiment pratique s'il acceptait les coordonnées x
et y
, n'est-ce pas?
Il est facile d'écrire des fonctions avec NumPy qui traitent naturellement des grilles. De plus, il existe plusieurs fonctions dans NumPy, SciPy, matplotlib qui s'attendent à ce que vous passiez dans la grille.
J'aime les images alors explorons 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)
Notez comment les coordonnées sont déjà correctement définies! Ce ne serait pas le cas si vous veniez de passer le density
.
Ou pour donner un autre exemple amusant en utilisant des modèles d'astropie (cette fois, je ne me soucie pas beaucoup des coordonnées, je les utilise juste pour créer une grille):
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)
Bien que ce soit juste "pour l'apparence", plusieurs fonctions liées aux modèles fonctionnels et à l'ajustement (par exemple scipy.interpolate.interp2d
,
scipy.interpolate.griddata
même montrer des exemples d'utilisation np.mgrid
) dans Scipy, etc. nécessitent des grilles. La plupart d'entre eux fonctionnent avec des grilles ouvertes et des grilles denses, mais certains ne fonctionnent qu'avec l'une d'entre elles.
xx
etyy
. La partie mystérieuse pour moi était pourquoi elle renvoie cette paire de résultats, et à quoi ils ressemblent. La réponse de Hai Phan est pratique pour cela. Je suppose que cela fait cela pour plus de commodité, car l'intrigue veut deux paramètres comme ça.