What you show is 'pythonic' in the sense that it uses a Python list and iteration approach. The only use of numpy is in assigning the values, M{i,j] =. Lists don't take that kind of index.

To make most use of numpy, make index grids or arrays, and calculate all values at once, without explicit loop. For example, in your case:

In [333]: N=10
In [334]: I,J = np.ogrid[0:10,0:10]
In [335]: I
Out[335]: 
array([[0],
       [1],
       [2],
       [3],
       [4],
       [5],
       [6],
       [7],
       [8],
       [9]])
In [336]: J
Out[336]: array([[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]])
In [337]: M = 1/((I + 2*J + 1)**2)
In [338]: M
Out[338]: 
array([[ 1.        ,  0.11111111,  0.04      ,  0.02040816,  0.01234568,
         0.00826446,  0.00591716,  0.00444444,  0.00346021,  0.00277008],
 ...
       [ 0.01      ,  0.00694444,  0.00510204,  0.00390625,  0.00308642,
         0.0025    ,  0.00206612,  0.00173611,  0.00147929,  0.00127551]])

ogrid is one of several ways of construction sets of arrays that can be 'broadcast' together. meshgrid is another common function.

In your case, the equation is one that works well with 2 arrays like this. It depends very much on broadcasting rules, which you should study.

If the function only takes scalar inputs, we will have to use some form of iteration. That has been a frequent SO question; search for [numpy] vectorize.

Answer from hpaulj on Stack Overflow
🌐
NumPy
numpy.org › doc › stable › reference › generated › numpy.nested_iters.html
numpy.nested_iters — NumPy v1.26 Manual
June 29, 2020 - numpy.nested_iters(op, axes, flags=None, op_flags=None, op_dtypes=None, order='K', casting='safe', buffersize=0)# Create nditers for use in nested loops · Create a tuple of nditer objects which iterate in nested loops over different axes of the op argument. The first iterator is used in the ...
Discussions

How can I rewrite a nested for loop using numpy arrays
Not an easy question, unfortunately — you sort of have to unravel your image array into a flat array and re-ravel it into a new matrix with depth and is ordered s.t. when you cut it with a machete, the slices look like the tiles you want. Here’s a 4 x 9 example >>> A = np.arange(36).reshape(4,9) [[0, 1, 2, 3, 4, 5, 6, 7, 8], [9, 10,11,12,13,14,15,16,17], [18,19,20,21,22,23,24,25,26], [27,28,29,30,31,32,33,34,35]] Suppose I want to get the 2 x 3 tiles of this matrix, i.e. [[[0,1,2], [[3, 4, 5], ... [[24,25,26], [9,10,11]] , [12,13,14] ... [33,34,35]]] My goal is to get a 6 x 2 x 3 matrix with all those tiles along the first axis. To understand how to accomplish this, we need to know how numpy does reshape— it unravels A into the 1D array [0,1,2,...] and then fills an empty matrix with the new dimensions such that the last axes change fastest. i.e. if you think about d x m x n array as “pages, rows, and columns”, I flip my book to page 0, fill the columns of the first row, then the columns of the second row, ..., then flip to page 1, fill the columns of first row, then columns of second row, then flip to page 2 and continue. The trick here is, while we clearly want to fill the columns of our tiles first, we can’t fill the tiles’ rows after. Or else our first tile, for instance, would be [[0,1,2],[3,4,5]]. So we have to insert a dummy axis that’ll gobble up enough entries until we can take the second row of the first tile. The number of things that axis needs to accept is simply the number of tiles we can horizontally stack — i.e. n // win_cols, where nis the # of columns of the original and win_cols is the width of your tile. So this reshape should ravel the matrix into a suitable order for us (note the first dimension is there to “catch” the 3D blocks formed from the 2-row groups of the original). B = A.reshape(m//win_rows, win_rows, n//win_cols, win_cols) Now we want to exchange the middle dimensions so the B[i,j,:,:] for various i,j yield desired tiles. C = B.transpose((0,2,1,3)) # perm, transposes axis 1 and 2 Finally, we can reshape C into our desired output: D = C.reshape((m*n)//(win_rows*win_cols), win_rows, win_cols) Note this requires our tiles to evenly fit into the array size. You’ll need to pad your array or change how you’re tiling it if that isn’t the case. Note also that you probably shouldn’t store all the intermediate steps above. More on reddit.com
🌐 r/learnpython
3
3
February 18, 2021
python - nested loop with array indexing in numpy - Stack Overflow
Did you mean to use j in the most inner loop as well? And consider using xrange(n) instead of np.arange(n). The latter creates an array and wastes memory which you do not seem to be using. The former is simply an iterator. ... Also, you may have formatted your code wrong, because there is a triple nested ... More on stackoverflow.com
🌐 stackoverflow.com
python - How to loop through a nested numpy array to obtain column-wise values - Stack Overflow
Communities for your favorite technologies. Explore all Collectives · Stack Overflow for Teams is now called Stack Internal. Bring the best of human thought and AI automation together at your work More on stackoverflow.com
🌐 stackoverflow.com
python - Create 2D numpy array through nested loop - Stack Overflow
Except with numpy arrays, this may be costly as numpy arrays are immutable for the most part. I.e. appending the result into the array will create a new copy; and for this copy to persist, it needs to be assigned. Potentially, a lot of arrays will be created and thrown away. Nevertheless, your answer isn't entirely wrong. 2019-05-06T03:54:32.993Z+00:00 ... This is just a random initialization trick with for loop ... More on stackoverflow.com
🌐 stackoverflow.com
June 29, 2019
🌐
NumPy
numpy.org › devdocs › reference › generated › numpy.nested_iters.html
numpy.nested_iters — NumPy v2.6.dev0 Manual
numpy.nested_iters(op, axes, flags=None, op_flags=None, op_dtypes=None, order='K', casting='safe', buffersize=0)# Create nditers for use in nested loops · Create a tuple of nditer objects which iterate in nested loops over different axes of the op argument. The first iterator is used in the ...
🌐
Reddit
reddit.com › r/learnpython › how can i rewrite a nested for loop using numpy arrays
r/learnpython on Reddit: How can I rewrite a nested for loop using numpy arrays
February 18, 2021 -

I'm new to python and have heard that for loops are slow in comparison to arrays. I have a programme that analyses a spectrogram (2D image) by splitting it into tiles and checks whether the range of values in the tile meets a certain peak threshold (constant* standard dev). The code works but runs slow so I'm looking to remove the for loops and take advantage of arrays.

def f(image, winx, winy, k):
    result = np.empty_like(image)
    gamma = np.std(image)  
    for y in np.arange(0, image.shape[0], winy):
        for x in np.arange(0, image.shape[1], winx):
            window = abs(image[y: y + winy, x: x + winy])
            ac = np.max(window) - np.min(window)
            
            if ac >= k*gamma:  # Peak present
                result[y: y + winy, x: x + winy] = image[y: y + winy, x: x + winy]
            else:
                result[y: y + winy, x: x + winy] = 0

    return result 

So the question, How can I split a 2D array into many small arrays to perform the calculation, and return a new array containing data corresponding to peaks, and 0 otherwise without using for loops?

Top answer
1 of 1
2
Not an easy question, unfortunately — you sort of have to unravel your image array into a flat array and re-ravel it into a new matrix with depth and is ordered s.t. when you cut it with a machete, the slices look like the tiles you want. Here’s a 4 x 9 example >>> A = np.arange(36).reshape(4,9) [[0, 1, 2, 3, 4, 5, 6, 7, 8], [9, 10,11,12,13,14,15,16,17], [18,19,20,21,22,23,24,25,26], [27,28,29,30,31,32,33,34,35]] Suppose I want to get the 2 x 3 tiles of this matrix, i.e. [[[0,1,2], [[3, 4, 5], ... [[24,25,26], [9,10,11]] , [12,13,14] ... [33,34,35]]] My goal is to get a 6 x 2 x 3 matrix with all those tiles along the first axis. To understand how to accomplish this, we need to know how numpy does reshape— it unravels A into the 1D array [0,1,2,...] and then fills an empty matrix with the new dimensions such that the last axes change fastest. i.e. if you think about d x m x n array as “pages, rows, and columns”, I flip my book to page 0, fill the columns of the first row, then the columns of the second row, ..., then flip to page 1, fill the columns of first row, then columns of second row, then flip to page 2 and continue. The trick here is, while we clearly want to fill the columns of our tiles first, we can’t fill the tiles’ rows after. Or else our first tile, for instance, would be [[0,1,2],[3,4,5]]. So we have to insert a dummy axis that’ll gobble up enough entries until we can take the second row of the first tile. The number of things that axis needs to accept is simply the number of tiles we can horizontally stack — i.e. n // win_cols, where nis the # of columns of the original and win_cols is the width of your tile. So this reshape should ravel the matrix into a suitable order for us (note the first dimension is there to “catch” the 3D blocks formed from the 2-row groups of the original). B = A.reshape(m//win_rows, win_rows, n//win_cols, win_cols) Now we want to exchange the middle dimensions so the B[i,j,:,:] for various i,j yield desired tiles. C = B.transpose((0,2,1,3)) # perm, transposes axis 1 and 2 Finally, we can reshape C into our desired output: D = C.reshape((m*n)//(win_rows*win_cols), win_rows, win_cols) Note this requires our tiles to evenly fit into the array size. You’ll need to pad your array or change how you’re tiling it if that isn’t the case. Note also that you probably shouldn’t store all the intermediate steps above.
Top answer
1 of 3
1

There is a possibility to (ab)use scipy.ndimage.convolve for this kind of problem. Maybe using some filter methods in scipy might work too and be better as it does not rely on scipy.ndimage.convolve working in place (and I could imagine this changing in some far future). (EDIT: first wrote scipy.signal.convolve which like the numpy.convolve, cannot do this)

The trick is that this convolve function can be used in-place so the double for loop:

for i in xrange(1, flux.shape[0]):
    for j in xrange(1, flux.shape[1]):
        flux[i,j] = 0.5*(flux[i-1,j] + bz[i-1,j]*dx) + 0.5*(flux[i,j-1] - bx[i,j-1]*dz)

Can be replaced by (sorry, for the need of so many temporary arrays...):

from scipy.ndimage import convolve
_flux = np.zeros((flux.shape[0]+1, flux.shape[1]+1), dtype=flux.dtype)
temp_bx = np.zeros((bx.shape[0]+1, bx.shape[1]+1), dtype=bx.dtype)
temp_bz = np.zeros((bz.shape[0]+1, bz.shape[1]+1), dtype=bz.dtype)

_flux[:-1,:-1] = flux
convolve(_flux[:-1,:-1], [[0, 0.5], [0.5, 0]], _flux[1:,1:])

temp_bz[1:,1:-1] = bz[:,1:]*dx
temp_bx[1:-1,1:] = bx[1:,:]*dz

conv_b = np.array([[0.0, 0.5], [0.5, 0.5]])
convolve(temp_bz[:-1,:-1], [[0.5, 0.5], [0.5, 0.]], temp_bz[1:,1:])
convolve(temp_bx[:-1,:-1], [[-0.5, 0.5], [0.5, 0.]], temp_bx[1:,1:])

flux = _flux[:-1,:-1] + temp_by[:-1,:-1] + temp_bx[:-1,:-1]

Unfortunatly it means we need to fiddle apart how bx, bz go into the final results, but this approach avoids creating large powers of two and should be noticeably faster then the previous answer as well.

(Note that the numpy convolve function does not allow for this inplace usage.)

2 of 3
1

The inner loop is reasonably simple to vectorize. You have a basic equation that looks like this:

X[n] = a * X[n-1] + b[n]

This equation can be expanded and rewritten without any dependency on X[n-1]:

X[n] = a^n * X[0] + a^(n-1) * b[0] + a^(n-2) * b[1] + ... + a^0 * b[n]

So if the original code looked like this:

for i in np.arange(1,nx+1):
    for j in np.arange(1,nz+1):
        flux2[i,j] = 0.5*(flux2[i-1,j] + bz[i-1,j]*dx) \
                   + 0.5*(flux2[i,j-1] - bx[i,j-1]*dz)

You could get rid of the inner loop like this:

a = 0.5
aexp = np.arange(nz).reshape(nz, 1) - np.arange(nz).reshape(1, nz)
abcoeff = a**aexp
abcoeff[aexp<0] = 0
for i in np.arange(1,nx+1):
    b = 0.5*flux2[i-1, 1:] + 0.5*bz[i-1, 1:]*dx - 0.5*bx[i,:-1]*dz
    bvals = (abcoeff * b.reshape(1, nz)).sum(axis=1)
    n = np.arange(1, nz+1)
    x0 = flux2[i, 0]
    flux2[i, 1:] = a**n * x0 + bvals

The values won't come out exactly the same due to floating point errors, but close enough. I guess in theory you could apply the same procedure to get rid of both loops, but it would become quite complex and, depending on the shape of your arrays, might not offer much performance benefit.

Find elsewhere
🌐
NumPy
numpy.org › doc › 1.23 › reference › generated › numpy.nested_iters.html
numpy.nested_iters — NumPy v1.23 Manual
numpy.nested_iters(op, axes, flags=None, op_flags=None, op_dtypes=None, order='K', casting='safe', buffersize=0)# Create nditers for use in nested loops · Create a tuple of nditer objects which iterate in nested loops over different axes of the op argument. The first iterator is used in the ...
🌐
NumPy
numpy.org › doc › 2.1 › reference › generated › numpy.nested_iters.html
numpy.nested_iters — NumPy v2.1 Manual
numpy.nested_iters(op, axes, flags=None, op_flags=None, op_dtypes=None, order='K', casting='safe', buffersize=0)# Create nditers for use in nested loops · Create a tuple of nditer objects which iterate in nested loops over different axes of the op argument. The first iterator is used in the ...
🌐
NumPy
numpy.org › doc › 2.2 › reference › generated › numpy.nested_iters.html
numpy.nested_iters — NumPy v2.2 Manual
numpy.nested_iters(op, axes, flags=None, op_flags=None, op_dtypes=None, order='K', casting='safe', buffersize=0)# Create nditers for use in nested loops · Create a tuple of nditer objects which iterate in nested loops over different axes of the op argument. The first iterator is used in the ...
🌐
NumPy
numpy.org › doc › 2.0 › reference › generated › numpy.nested_iters.html
numpy.nested_iters — NumPy v2.0 Manual
numpy.nested_iters(op, axes, flags=None, op_flags=None, op_dtypes=None, order='K', casting='safe', buffersize=0)# Create nditers for use in nested loops · Create a tuple of nditer objects which iterate in nested loops over different axes of the op argument. The first iterator is used in the ...
🌐
SciPy
docs.scipy.org › doc › numpy-1.15.1 › reference › generated › numpy.nested_iters.html
numpy.nested_iters — NumPy v1.15 Manual
numpy.nested_iters()¶ · Create nditers for use in nested loops · Create a tuple of nditer objects which iterate in nested loops over different axes of the op argument. The first iterator is used in the outermost loop, the last in the innermost loop. Advancing one will change the subsequent ...
Top answer
1 of 1
1

With a numpy tag I assume 'optimize' means cast these loops as compiled numpy array expressions. I don't think there's a way to do without first collecting all the data as numpy arrays, which will require the same loops.

You have a list of n variables. Each variable has a list of ? parameters. Each parameter has 3 or 4 attributes.

So

rates = np.array([v.rate for v in variables])
values = np.array([[p.value for p in v.parameters] for v in variables]
index1s = <dito>   (?,n) array
index2s = <dita>   (?,n) array

self.state.num is already a 2d array, size compatible with the range of values in index1s and index2s.

Given those 1 and 2d arrays we should be able to derive mat1 and mat2 with whole array operations. If ? is small relative to m and the range of values in index1 and index2 it might be worth do this. I don't have a realistic feel for your data.

===========

You mention the map function, comprehension expressions, and a bit of itertools and generators. These can make the code look a little cleaner but don't make much difference in speed.

I demonstrated the use of list comprehensions. Those expression can be more elaborate, but often at the cost of readability. I like writing helper functions to hide details. A generator comprehension can replace a list comprehension that feeds another. Maps cover the same functionality.

Since variables and parameters have attributes I assume you have defined them in classes. You could write methods that extract those attributes as simple lists or arrays.

class Variable(....):
   ....
   def get_values(self):
       return [p.value for p in self.parameters]
   def get_rate(self, state):
       rate = self.rate
       for parameter in self.parameters:
            if parameter.value < 0 and                
                state.num[parameter.index1, parameter.index2] <= 0:
            rate = 0
       return rate

values = [v.get_values() for v in variables]
rates = [v.get_rate(self.state) for v in variables]

You could even write these helper function without the class structure.

That doesn't speed up anything; it just hides some details in the object.

Top answer
1 of 3
94

This is a common conceptual difficulty when learning to use NumPy effectively. Normally, data processing in Python is best expressed in terms of iterators, to keep memory usage low, to maximize opportunities for parallelism with the I/O system, and to provide for reuse and combination of parts of algorithms.

But NumPy turns all that inside out: the best approach is to express the algorithm as a sequence of whole-array operations, to minimize the amount of time spent in the slow Python interpreter and maximize the amount of time spent in fast compiled NumPy routines.

Here's the general approach I take:

  1. Keep the original version of the function (which you are confident is correct) so that you can test it against your improved versions both for correctness and speed.

  2. Work from the inside out: that is, start with the innermost loop and see if can be vectorized; then when you've done that, move out one level and continue.

  3. Spend lots of time reading the NumPy documentation. There are a lot of functions and operations in there and they are not always brilliantly named, so it's worth getting to know them. In particular, if you find yourself thinking, "if only there were a function that did such-and-such," then it's well worth spending ten minutes looking for it. It's usually in there somewhere.

There's no substitute for practice, so I'm going to give you some example problems. The goal for each problem is to rewrite the function so that it is fully vectorized: that is, so that it consists of a sequence of NumPy operations on whole arrays, with no native Python loops (no for or while statements, no iterators or comprehensions).

Problem 1

def sumproducts(x, y):
    """Return the sum of x[i] * y[j] for all pairs of indices i, j.

    >>> sumproducts(np.arange(3000), np.arange(3000))
    20236502250000

    """
    result = 0
    for i in range(len(x)):
        for j in range(len(y)):
            result += x[i] * y[j]
    return result

Problem 2

def countlower(x, y):
    """Return the number of pairs i, j such that x[i] < y[j].

    >>> countlower(np.arange(0, 200, 2), np.arange(40, 140))
    4500

    """
    result = 0
    for i in range(len(x)):
        for j in range(len(y)):
            if x[i] < y[j]:
                result += 1
    return result

Problem 3

def cleanup(x, missing=-1, value=0):
    """Return an array that's the same as x, except that where x ==
    missing, it has value instead.

    >>> cleanup(np.arange(-3, 3), value=10)
    ... # doctest: +NORMALIZE_WHITESPACE
    array([-3, -2, 10, 0, 1, 2])

    """
    result = []
    for i in range(len(x)):
        if x[i] == missing:
            result.append(value)
        else:
            result.append(x[i])
    return np.array(result)

Spoilers below. You'll get much the best results if you have a go yourself before looking at my solutions!

Answer 1

np.sum(x) * np.sum(y)

Answer 2

np.sum(np.searchsorted(np.sort(x), y))

Answer 3

np.where(x == missing, value, x)

2 of 3
8

To make things faster you have to read up on your data structures and use the ones appropriate.

For non trivial sizes of small array and big array (let's say small=100 elements and big=10.000 elements) one way to do it is to sort the small array, then iterate over big-array and use a binary search to find matching elements in the small array.

This would make the maximum time complexity, O(N log N) (and for small small-arrays and very big big-arrays it's closer to O(N)) where your nested loop solution is O(N^2)

However. what data structures are most efficient is highly dependent on the actual problem.

Top answer
1 of 2
3

The first simple thing you can do is use broadcasting to turn three calculations into one:

xg[p]=(i * xv[0] + j * xv[1] + k * xv[2] + l * xv[3])/n

The next is to note that the division by n can be moved to the very end:

return xg / n

Then, we can split apart the four multiplies and store the results separately, then combine them at the end. Now we have:

xg = np.empty((nsize,4)) # n.b. zeros not required
p = 0

for i in range ( 0, n + 1 ):
    for j in range ( 0, n + 1 - i ):
        for k in range ( 0, n + 1 - i - j ):
            l = n - i - j - k
            xg[p,0] = i
            xg[p,1] = j
            xg[p,2] = k
            xg[p,3] = l
            p = p + 1

return (xg[:,:,None] * xv).sum(1) / n

The trick of xg[:,:,None] at the bottom is to broadcast (nsize,n) * (n,3) - we expand the (nsize,n) xg to (nsize,n,3) because i,j,k,l do not depend on which column of xv is being multiplied with.

We can skip computing l in the loop and instead do it all at once right before the return:

xg[:,3] = n - xg[:,0:3].sum(1)

Now all you need to do is figure out how to create i,j,k in a vectorized way according to p.

As a general note, I find it easiest to work these problems from the "inside out," looking at the code in the innermost loop and pushing as much as possible outside of as many loops as possible. Do this over and over, and eventually there will be no loops.

2 of 2
1

You can get rid of your dependent nested loop, but at a cost of waste in memory (more so than usual in vectorization problems). You're addressing a small subset of a 3d box in your for loop. If you don't mind generating (n+1)^3 items for using only (n+1)(n+2)(n+3)/6 ones, and you can fit into memory, the vectorized version will indeed be likely much faster.

My suggestion, explanation below:

import numpy as np

def tet_vect(n):

    xv = np.array([
        [-1.,-1.,-1.],
        [ 1.,-1.,-1.],
        [-1., 1.,-1.],
        [-1.,-1., 1.],
        ])

    # spanning arrays of a 3d grid according to range(0,n+1)
    ii,jj,kk = np.ogrid[:n+1,:n+1,:n+1]
    # indices of the triples which fall inside the original for loop
    inds = (jj < n+1-ii) & (kk < n+1-ii-jj)
    # the [i,j,k] indices of the points that fall inside the for loop, in the same order
    combs = np.vstack(np.where(inds)).T
    # combs is now an (nsize,3)-shaped array

    # compute "l" column too
    lcol = n - combs.sum(axis=1)
    combs = np.hstack((combs,lcol[:,None]))
    # combs is now an (nsize,4)-shaped array

    # all we need to do now is to take the matrix product of combs and xv, divide by n in the end
    xg = np.matmul(combs,xv)/n

    return xg

The key step is generating the ii,jj,kk indices which span a 3d grid. This step is memory-efficient, since np.ogrid creates spanning arrays that can be used in broadcasting operations to refer to your full grid. We only generate a full (n+1)^3-sized array in the next step: the inds boolean array selects those points in the 3d box that lie inside your region of interest (and it does so by making use of array broadcasting). In the following step np.where(inds) picks out the truthy elements of this large array, and we end up with the smaller number of nsize elements. The single memory-wasting step is thus the creation of inds.

The rest is straightforward: we need to generate an additional column for the array that contains the [i,j,k] indices in each row, this can be done by summing over the columns of the array (again a vectorized operation). Once we have the (nsize,4)-shaped auxiliary array that contains each (i,j,k,l) in its rows: we need to perform a matrix multiplication of this object with xv, and we're done.

Testing with small n sizes suggests that the above function produces the same results as yours. Timing for n = 100: 1.15 s original, 19 ms vectorized.