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.
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.
np.fromfunction is intended for that :
def f(i,j) : return 1/((i+2*j+1)**2)
M = np.fromfunction(f,(N,N))
it's slighty slower that the 'hand made' vectorised way , but easy to understand.
How can I rewrite a nested for loop using numpy arrays
python - nested loop with array indexing in numpy - Stack Overflow
python - How to loop through a nested numpy array to obtain column-wise values - Stack Overflow
python - Create 2D numpy array through nested loop - Stack Overflow
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?
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.)
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.
You can loop through the arrays and append to the new ones:
array1 = []
array2 = []
for array in my_array:
for nested_array in array:
# nested_array is of form [[ 1 10]] here, you need to index it
# first with [0] then with the element you want to access [0] or [1]
array1.append(nested_array[0][0])
array2.append(nested_array[0][1])
You just have to think about the structure of the input data and how to get the values you need in the order that you need.
The output:
>>> array1
[1, 2, 3, 4, 5, 6, 7, 8]
>>> array2
[10, 11, 12, 13, 14, 15, 16, 17]
You can try this:
>>> from numpy import array
>>> import numpy as np
>>> my_array = [array([[[1, 10]],
[[2, 11]]], dtype='int32'), array([[[3, 12]],
[[4, 13]],
[[5, 14]]], dtype='int32'), array([[[6, 15]],
[[7, 16]],
[[8, 17]]], dtype='int32')]
# One way
>>> np.concatenate(my_array,axis=0)[...,0] # [...,1] would give the other one
array([[1],
[2],
[3],
[4],
[5],
[6],
[7],
[8]], dtype=int32)
# Other way:
>>> np.concatenate(my_array,axis=0)[...,0].reshape(-1,) # [...,1].reshape(-1,0) would be the other one
array([1, 2, 3, 4, 5, 6, 7, 8], dtype=int32)
The numpy array you are defining is not the right shape for the loop you are using. b = np.array([]) gives you an array of shape (0,)
You can use something like np.zeros to define your 2D array.
import numpy as np
b = np.zeros((3,6))
for i in range(2):
for j in range(5):
b[i][j]=i+j
print(b)
The output will be
[[0. 1. 2. 3. 4. 0.]
[1. 2. 3. 4. 5. 0.]
[0. 0. 0. 0. 0. 0.]]
Another option is to make a 2D list, fill it up in for loops, and convert to numpy array later
import numpy as np
#2D array
b = [ [0 for _ in range(6)] for _ in range(3)]
#Fill up array with for loops
for i in range(2):
for j in range(5):
b[i][j]=i+j
#Convert to numpy array
print(np.array(b))
The output will be
[[0 1 2 3 4 0]
[1 2 3 4 5 0]
[0 0 0 0 0 0]]
I understand you asked how to do this with nested loops, so apologies if you don't find this useful. I'd just thought I'd share how I normally do this:
b = np.arange(10).reshape(2,5)
print(b)
[[0 1 2 3 4]
[5 6 7 8 9]]
You can use broadcasting for this:
a = np.array([
[1,1,1,1],
[2,2,2,2],
[3,3,3,3]
])
b = np.array([
[1,1,1,1],
[2,2,2,2]
])
def euc_distance(array1, array2):
return np.sqrt(np.sum((array1 - array2)**2, axis = -1))
print(euc_distance(a[None, :, :], b[:, None, :]))
# [[0. 2. 4.]
# [2. 0. 2.]]
Comparing the times for a dataset of your size:
a = np.random.rand(700000, 4)
b = np.random.rand(4, 4)
c = euc_distance(a[None, :, :], b[:, None, :])
d = np.array([euc_distance(a, val) for val in b])
e = np.array([euc_distance(val, b) for val in a]).T
np.allclose(c, d)
# True
np.allclose(d, e)
# True
%timeit euc_distance(a[None, :, :], b[:, None, :])
# 113 ms ± 4.56 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit np.array([euc_distance(a, val) for val in b])
# 115 ms ± 4.32 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit np.array([euc_distance(val, b) for val in a])
# 7.03 s ± 216 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Couldn't test it, but this should get you there assuming normalised positive data. np.argmax(np.matmul(a, b.T), axis=1)
Little elaboration of my previous post. If performance is still an issue, instead of your approach you can use this:
b = np.tile(b, (a.shape[0], 1, 1))
a = np.tile(a, (1, 1, b.shape[1])).reshape(b.shape)
absolute_dist = np.sqrt(np.sum(np.square(a - b), axis=2))
It produces the exact same result but runs about 20 times faster on 600,000 lines than the generator.
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:
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.
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.
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)
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.
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.
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.
Maybe you could replace the inner loop and your function with these matrix operations?
for i in range(307200):
temp = A[:,i,np.newaxis] - B[:]
C[i,:] = np.linalg.norm(temp, axis=0)
For smaller arrays, I got about 20 times lower running times. Perhaps you gain even more. In any case, plese make sure that you are receiving good results (on a smaller sets).
Update: With OP's update and clarifications things get much simpler:
>>> def f_pp(A, B):
... return np.sqrt(np.add.outer(np.einsum('ij,ij->j', A, A), np.einsum('il,il->l', B, B)) - 2*np.einsum('ij,il->jl', A, B))
...
end Update
You could use np.einsum and real arithmetic for a massive speedup:
>>> def f_pp(A, B):
... Ar = A.view(float).reshape(*A.shape, 2)
... Br = B.view(float).reshape(*B.shape, 2)
... return np.sqrt(np.add.outer(np.einsum('ijk,ijk->j', Ar, Ar), np.einsum('ilk,ilk->l', Br, Br)) - 2*np.einsum('ijk,ilk->jl', Ar, Br))
...
For shapes (1024, 3072) and (1024, 50) I get a factor of about 40.
Some explanation:
Real arithmetic: Unless numpy does some unbelievably smart optimizations I would expect a complex product like x*x.conj() to use 4 real multiplications. Knowing that the result is real we save two of those.
Writing |A-B|^2 as |A|^2 + |B|^2 - 2|A*B|. This saves memory by avoiding the huge intermediate A-B of shape (1024, 3072, 50) ((1024, 307200, 50) in the full example) that a direct broadcasting approach would use.