Vectorized arithmetic

NumPy · Essentials · 10 min

Vectorized expressions are typically 10-100x faster than loops and read like the maths they implement.

Vectorization means expressing a computation on whole arrays instead of looping over elements. + - * / // % ** and comparisons all work element-wise between arrays of the same shape, or between an array and a scalar.

total = price * qty            # instead of a loop over indexes

The work then happens in a tight C loop over typed memory. For a million elements a Python for loop takes on the order of a hundred milliseconds; the vectorized version takes about a millisecond.

A few semantics to know:

  • / always gives floats; // is floor division, % the matching remainder.
  • Division by zero on floats does not raise; it produces inf or nan and emits a RuntimeWarning. Integer division by zero gives 0 with a warning.
  • Comparisons return boolean arrays: a > 3 is [False, True, ...], not a single bool.
  • Use &, |, ~ to combine boolean arrays, with parentheses: (a > 1) & (a < 5). The keywords and/or try to turn a whole array into one bool and raise ValueError.

Operations return new arrays. To avoid allocating, use augmented assignment (a += 1), which writes in place, or the out= argument of a ufunc. In-place operations keep the array's dtype, so int_array += 0.5 raises a casting error rather than silently truncating.

Shapes must match (or be broadcast-compatible, a rule you will meet soon). Adding shape (3,) to shape (4,) raises ValueError: operands could not be broadcast together.

Example

import numpy as np

price = np.array([9.99, 4.50, 12.00])
qty = np.array([3, 10, 1])
print(price * qty)              # [29.97 45.   12.  ]
print((price * qty).sum())      # 86.97

a = np.arange(6)
print(a ** 2)                   # [ 0  1  4  9 16 25]
print(a / 2)                    # [0.  0.5 1.  1.5 2.  2.5]
print(a // 4, a % 4)            # [0 0 0 0 1 1] [0 1 2 3 0 1]

print((a > 1) & (a < 4))        # [False False  True  True False False]

with np.errstate(divide="ignore"):
    print(np.array([1.0, -1.0]) / 0)   # [ inf -inf]

a += 10                         # in place, no new array
print(a)                        # [10 11 12 13 14 15]

Remember

  • Write whole-array expressions instead of element loops
  • Comparisons give boolean arrays; combine them with &, |, ~ and parentheses
  • Float division by zero yields inf/nan with a warning, not an exception
  • += and friends update in place and keep the dtype

Watch out for

  • Using and/or with arrays, which raises 'truth value of an array is ambiguous'.

Check yourself

  1. Which expression correctly selects values strictly between 2 and 8 in array `x`?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: numpy.org/doc/stable/user/absolute_beginners.html#basic-array-operations