As observed in Rotating particles and Python efficiency, the speed of Python code can often be increased greatly by vectorizing mathematical expressions that are applied to NumPy arrays rather than using loops.
Here are a few more examples of how to do this. In all these examples, we use the array x defined by:
>>> import numpy as np
>>> x = np.linspace(-2., 2., 5)
>>> x
array([-2., -1., 0., 1., 2.])
although the point of this vectorization is to make the expressions work well for much longer arrays.
Many NumPy functions apply directly to arrays, e.g.:
>>> np.sin(x)
array([-0.90929743, -0.84147098, 0. , 0.84147098, 0.90929743])
>>> np.abs(x)
array([ 2., 1., 0., 1., 2.])
For function definitions where different formulas are applied over different values of x, the np.where function can generally be used.
Consider the function
f(x) = \left\{ \begin{array}{ll} x^2&~~x\leq 0.5\\ -x&~~x>0.5 \end{array}\right.
Computing y=f(x) at all points in the array x could be done by the loop:
y = np.zeros(x.shape) # initialize to shape of x
for j in range(len(x)):
if x[j] <= 0.5:
y[j] = x[j]**2
else:
y[j] = -x[j]
This can be rewritten to be faster as:
y = np.where(x <= 0.5, x**2, -x)
both should end up with:
>>> y
[ 4. 1. 0. -1. -2.]
Sometimes you may need to use where repeatedly, e.g. for the function
f(x) = \left\{ \begin{array}{ll} x^2&~~x\leq 0.5\\ -x&~~0.5<x<1.5 \\ x^4&~~ x>= 1.\end{array}\right.
the loop:
for j in range(len(x)):
if x[j] <= 0.5:
y[j] = x[j]**2
elif x[j]<1.5:
y[j] = -x[j]
else:
y[j] = x[j]**4
can be replaced by:
y = np.where(x <= 0.5, x**2, -x)
y = np.where(x < 1.5, y, x**4)
Note that y is reused in the second statement. Either version should give:
>>> y
array([ 4., 1., 0., -1., 16.])
Note that conditions like x <= 0.5 in the above example actually return NumPy boolean arrays, e.g.
>>> x <= 1.5
array([ True, True, True, True, False], dtype=bool)
You can combine these using & for and and | for or, e.g.
>>> (x >= 1) & (x <= 1.5)
array([False, False, False, True, False], dtype=bool)
it’s best to put in parentheses to make sure such conditions are parsed properly. In fact if you leave them out above you will get an error message because & is the Python & that has higher precedence than <=, so
>>> x >= 1 & x <= 1.5 # gives an error!
will first try to apply & to 1 and x.
It would actually be more proper to use the numpy operators logical_and and logical_or, e.g.
>>> np.logical_and(x>=1., x<=1.5)
array([False, False, False, True, False], dtype=bool)
but for boolean arrays, with proper parentheses, & and | work. See http://www.scipy.org/NumPy_for_Matlab_Users#logicalNotes for more about this and how it relates to & and | in Matlab.
Note also that the word and can often be used in place of &, but not for NumPy arrays:
>>> (x >= 1) and (x <= 1.5)
ValueError: The truth value of an array with more than one element is
ambiguous. Use a.any() or a.all()
The error message refers to the NumPy functions that can be used as follows:
>>> np.any(x > 0.) # is any x[j] > 0 ?
True
>>> np.all(x > 0.) # is every x[j] > 0 ?
False
or as:
>>> b = x>0 # defines boolean array
>>> b
array([False, False, False, True, True], dtype=bool)
>>> b.any()
True
which can be simplified to one line:
>>> (x>0).any()
True
This can be useful for things like:
>>> if (x==0.).any():
... print "some x[j] is zero"
... else:
... print "inverses of x[j]: ", 1./x
...
some x[j] is zero