Quel est un bon moyen de produire un tableau numpy contenant les valeurs d'une fonction évaluée sur une grille de points à n dimensions?
Par exemple, supposons que je veuille évaluer la fonction définie par
def func(x, y):
return <some function of x and y>
Supposons que je veuille l'évaluer sur un tableau de points à deux dimensions, les valeurs x allant de 0 à 4 en dix étapes et les valeurs y allant de -1 à 1 en vingt étapes. Quelle est la bonne façon de faire cela numpy?
P.S. Cela a été demandé à plusieurs reprises sur StackOverflow sous différentes formes, mais je n’ai pas trouvé de question ni de réponse concises. J'ai posté ceci pour fournir une solution simple concise (ci-dessous).
réponse plus courte, plus rapide et plus claire, en évitant le maillage:
import numpy as np
def func(x, y):
return np.sin(y * x)
xaxis = np.linspace(0, 4, 10)
yaxis = np.linspace(-1, 1, 20)
result = func(xaxis[:,None], yaxis[None,:])
Ce sera plus rapide en mémoire si vous obtenez quelque chose comme x ^ 2 + y comme fonction, car x ^ 2 est fait sur un tableau 1D (au lieu d'un 2D), et l'augmentation de la dimension ne se produit que lorsque vous faites " + ". Pour meshgrid, x ^ 2 sera effectué sur un tableau 2D, dans lequel toutes les lignes sont identiques, ce qui entraîne une augmentation considérable du temps.
Edit: le "x [:, None]", transforme x en un tableau 2D, mais avec une seconde dimension vide. Ce "Aucun" revient à utiliser "x [:, numpy.newaxis]". La même chose est faite avec Y, mais avec une première dimension vide.
Edit: en 3 dimensions:
def func2(x, y, z):
return np.sin(y * x)+z
xaxis = np.linspace(0, 4, 10)
yaxis = np.linspace(-1, 1, 20)
zaxis = np.linspace(0, 1, 20)
result2 = func2(xaxis[:,None,None], yaxis[None,:,None],zaxis[None,None,:])
De cette façon, vous pouvez facilement étendre à n dimensions si vous le souhaitez, en utilisant autant de None
ou :
que vous avez de dimensions. Chaque :
crée une dimension et chaque None
crée une dimension "vide". L'exemple suivant montre un peu plus comment fonctionnent ces dimensions vides. Comme vous pouvez le constater, la forme change si vous utilisez None
, ce qui montre qu’il s’agit d’un objet 3D dans l’exemple suivant, mais que les dimensions vides ne se remplissent que lorsque vous multipliez avec un objet qui contient réellement quelque chose dans ces dimensions mais l'exemple suivant montre ce que je veux dire)
In [1]: import numpy
In [2]: a = numpy.linspace(-1,1,20)
In [3]: a.shape
Out[3]: (20,)
In [4]: a[None,:,None].shape
Out[4]: (1, 20, 1)
In [5]: b = a[None,:,None] # this is a 3D array, but with the first and third dimension being "empty"
In [6]: c = a[:,None,None] # same, but last two dimensions are "empty" here
In [7]: d=b*c
In [8]: d.shape # only the last dimension is "empty" here
Out[8]: (20, 20, 1)
edit: sans avoir besoin de taper le None vous-même
def ndm(*args):
return [x[(None,)*i+(slice(None),)+(None,)*(len(args)-i-1)] for i, x in enumerate(args)]
x2,y2,z2 = ndm(xaxis,yaxis,zaxis)
result3 = func2(x2,y2,z2)
De cette façon, vous faites le découpage None
- pour créer les dimensions vides supplémentaires, en faisant le premier argument que vous donnez à ndm en tant que première dimension complète, le second en tant que seconde dimension complète etc. syntaxe typée utilisée auparavant.
Brève explication: faire x2, y2, z2 = ndm(xaxis, yaxis, zaxis)
est la même chose que faire
x2 = xaxis[:,None,None]
y2 = yaxis[None,:,None]
z2 = zaxis[None,None,:]
mais la méthode ndm devrait également fonctionner pour plus de dimensions, sans qu'il soit nécessaire de coder en dur les tranches None
- sur plusieurs lignes, comme indiqué ci-dessus. Cela fonctionnera également dans les versions numpy antérieures à 1.8, alors que numpy.meshgrid ne fonctionne que pour des dimensions supérieures à 2 dimensions si numpy est 1.8 ou supérieur.
import numpy as np
def func(x, y):
return np.sin(y * x)
xaxis = np.linspace(0, 4, 10)
yaxis = np.linspace(-1, 1, 20)
x, y = np.meshgrid(xaxis, yaxis)
result = func(x, y)
Dans le cas où votre fonction prend en réalité un tuple d'éléments d
, c'est-à-dire f((x1,x2,x3,...xd))
(par exemple, scipy.stats.multivariate_normal function ) et que vous souhaitez évaluer f
sur N ^ combinaisons/grille de N variables, procédez également comme suit (cas 2D):
x=np.arange(-1,1,0.2) # each variable is instantiated N=10 times
y=np.arange(-1,1,0.2)
Z=f(np.dstack(np.meshgrid(x,y))) # result is an NxN (10x10) matrix, whose entries are f((xi,yj))
Ici, np.dstack(np.meshgrid(x,y))
crée une "matrice" 10x10 (techniquement, un tableau numpy 10x10x2) dont les entrées sont les n-uplets bidimensionnels à évaluer par f
.
J'utilise cette fonction pour que les valeurs X, Y, Z soient prêtes pour le traçage:
def npmap2d(fun, x_spec, y_spec, doPrint=False):
xs = np.linspace(*x_spec)
ys = np.linspace(*y_spec)
Z = np.empty(len(xs) * len(ys))
i = 0
for y in ys:
for x in xs:
Z[i] = fun(x, y)
if doPrint: print([i, x, y, Z[i]])
i += 1
X, Y = np.meshgrid(xs, ys)
Z.shape = X.shape
return X, Y, Z
Usage:
def f(x, y):
# ...some function that can't handle numpy arrays
X, Y, Z = npmap2d(f, (0, 0.5, 21), (0.6, 0.4, 41))
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_wireframe(X, Y, Z)
Le même résultat peut être obtenu en utilisant map:
xs = np.linspace(0, 4, 10)
ys = np.linspace(-1, 1, 20)
X, Y = np.meshgrid(xs, ys)
Z = np.fromiter(map(f, X.ravel(), Y.ravel()), X.dtype).reshape(X.shape)
Mes deux centimes:
import numpy as np
x = np.linspace(0, 4, 10)
y = np.linspace(-1, 1, 20)
[X, Y] = np.meshgrid(x, y, indexing = 'ij', sparse = 'true')
def func(x, y):
return x*y/(x**2 + y**2 + 4)
# I have defined a function of x and y.
func(X, Y)