30

What is a good way to produce a numpy array containing the values of a function evaluated on an n-dimensional grid of points?

For example, suppose I want to evaluate the function defined by

def func(x, y):
    return <some function of x and y>

Suppose I want to evaluate it on a two dimensional array of points with the x values going from 0 to 4 in ten steps, and the y values going from -1 to 1 in twenty steps. What's a good way to do this in numpy?

P.S. This has been asked in various forms on StackOverflow many times, but I couldn't find a concisely stated question and answer. I posted this to provide a concise simple solution (below).

DanielSank
  • 3,303
  • 3
  • 24
  • 42
  • Do you want me to improve anything more on my answer? If not, can you accept it as answer? – usethedeathstar Apr 02 '14 at 12:21
  • @usethedeathstar: The one thing I don't like about your method is that you have to explicitly type things like x[:,None,:]. That's not scalable or scriptable. On the other hand, with meshgrid you just put as many arguments as you want and it does exactly the right thing. If you could just modify your answer to handle that case I'll definitely accept it. – DanielSank Apr 09 '14 at 05:06
  • Modified my answer to make it work as you said. This method will also work in numpy before 1.8, while meshgrid does not work in more than 2 dimensions in numpy versions before 1.8 – usethedeathstar Apr 09 '14 at 07:29

5 Answers5

35

shorter, faster and clearer answer, avoiding meshgrid:

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,:])

This will be faster in memory if you get something like x^2+y as function, since than x^2 is done on a 1D array (instead of a 2D one), and the increase in dimension only happens when you do the "+". For meshgrid, x^2 will be done on a 2D array, in which essentially every row is the same, causing massive time increases.

Edit: the "x[:,None]", makes x to a 2D array, but with an empty second dimension. This "None" is the same as using "x[:,numpy.newaxis]". The same thing is done with Y, but with making an empty first dimension.

Edit: in 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,:])

This way you can easily extend to n dimensions if you wish, using as many None or : as you have dimensions. Each : makes a dimension, and each None makes an "empty" dimension. The next example shows a bit more how these empty dimensions work. As you can see, the shape changes if you use None, showing that it is a 3D object in the next example, but the empty dimensions only get filled up whenever you multiply with an object that actually has something in those dimensions (sounds complicated, but the next example shows what i mean)

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: without needing to type the None yourself

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)

This way, you make the None-slicing to create the extra empty dimensions, by making the first argument you give to ndm as the first full dimension, the second as second full dimension etc- it does the same as the 'hardcoded' None-typed syntax used before.

Short explanation: doing x2, y2, z2 = ndm(xaxis, yaxis, zaxis) is the same as doing

x2 = xaxis[:,None,None]
y2 = yaxis[None,:,None]
z2 = zaxis[None,None,:]

but the ndm method should also work for more dimensions, without needing to hardcode the None-slices in multiple lines like just shown. This will also work in numpy versions before 1.8, while numpy.meshgrid only works for higher than 2 dimensions if you have numpy 1.8 or higher.

usethedeathstar
  • 2,219
  • 1
  • 19
  • 30
  • Could you explain the indexing that is going on here `func(xaxis[:,None], yaxis[None,:])`? – rerx Apr 01 '14 at 06:51
  • @rerx added explanation in the answer – usethedeathstar Apr 01 '14 at 12:45
  • This definitely better performace than what I proposed. Is there a way to generalize this to many dimensions? – DanielSank Apr 02 '14 at 06:13
  • @DanielSank updated to show a bit more in 3 dimensions. to go to 4 or more, it is similar. – usethedeathstar Apr 02 '14 at 07:23
  • @usethedeathstar how can someone estimate with this method a function with the length of `N` using the method you have suggested, while one of the inputs is a two-dimensional array of coordinates with the size of `Nx2` and the other variable is an array with the size of `N`? – Dalek May 09 '15 at 13:57
  • @Dalek Can you make a different question out of this? Otherwise i have to add in an answer to a question which is in comments. – usethedeathstar May 11 '15 at 08:59
  • @usethedeathstar I was trying to compute a function with two variable on the grid points. I posted my question [here](http://stackoverflow.com/questions/30140966/broadcasting-a-function-on-a-2-dimensional-numpy-array) but can't quite apply you method for my application. The calculation process is tediously slow and even using a machine with 16 core, takes more than 10 hours. I am looking for a way to speed up the calculation. – Dalek May 11 '15 at 10:53
  • [`np.newaxis`](https://docs.scipy.org/doc/numpy-dev/reference/arrays.indexing.html#numpy.newaxis) exists to make this more clear. – endolith Aug 28 '17 at 03:47
  • And shouldn't `result = func(x[:,None], y[None,:])` be `result = func(xaxis[:, None], yaxis[None, :])`? – endolith Aug 28 '17 at 03:49
  • `newaxis` creates a new size 1 dimension, not an "empty" one. By `broadcasting` rules, size 1 dimensions will be adjusted to match the corresponding dimension of the other variable (that adjustment may be 'down' to 0!). `np.meshgrid` takes a `sparse` parameter, in which case the arrays will look just like ones created with `newaxis`. – hpaulj Apr 08 '21 at 00:40
13
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)
DanielSank
  • 3,303
  • 3
  • 24
  • 42
6

I use this function to get X, Y, Z values ready for plotting:

def npmap2d(fun, xs, ys, doPrint=False):
  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, np.linspace(0, 0.5, 21), np.linspace(0.6, 0.4, 41))

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_wireframe(X, Y, Z)

The same result can be achieved using 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)
grabantot
  • 2,111
  • 20
  • 31
3

In the case your function actually takes a tuple of d elements, i.e. f((x1,x2,x3,...xd)) (for example the scipy.stats.multivariate_normal function), and you want to evaluate f on N^d combinations/grid of N variables, you could also do the following (2D case):

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))

Here np.dstack(np.meshgrid(x,y)) creates an 10x10 "matrix" (technically a 10x10x2 numpy array) whose entries are the 2-dimensional tuples to be evaluated by f.

Yibo Yang
  • 2,353
  • 4
  • 27
  • 40
  • How does `f()` work if it is expecting the tuple `(x1,x2,x3,...,xd)`? `np.dstack()` will return all grid coordinates, not an individual point. – hertzsprung Jan 08 '19 at 16:22
  • For this to work with an arbitrary function, I found it necessary to first vectorize the function, e.g. `np.vectorize(f, signature='(d)->()')(np.dstack(np.meshgrid(x, y)))` assuming that `f()` returns a scalar. – hertzsprung Jan 08 '19 at 16:34
0

My two cents:

    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)
rainman
  • 237
  • 3
  • 9