NumPy arithmetic operations apply directly to entire arrays element-by-element, without explicit loops. This vectorized model is the core productivity advantage of NumPy over plain Python lists — clearer code and dramatically better performance.
Scalar arithmetic
A single scalar can be applied to every element of an array:
import numpy as np
arr = np.array([1, 2, 3, 4])
arr + 10 # [11, 12, 13, 14]
arr * 2 # [2, 4, 6, 8]
arr ** 2 # [1, 4, 9, 16]
arr / 2 # [0.5, 1.0, 1.5, 2.0]
arr - 1 # [0, 1, 2, 3]The scalar is broadcast across the array — applied to each element individually. Returns a new array; the original is unchanged unless you assign back.
Vectorized math functions
NumPy provides math functions (np.sin, np.exp, np.log, np.sqrt, etc.) that operate element-wise:
np.sin(arr) # [sin(1), sin(2), sin(3), sin(4)]
np.exp(arr) # [e, e², e³, e⁴]
np.sqrt(arr) # [1.0, 1.414..., 1.732..., 2.0]
np.log(arr) # [ln(1), ln(2), ln(3), ln(4)]These are dramatically faster than Python math.sin in a loop — internally implemented in C with SIMD vectorization where supported.
Element-wise operations between arrays
Arrays of compatible shape can be combined element-wise:
a = np.array([1, 2, 3])
b = np.array([10, 20, 30])
a + b # [11, 22, 33]
a * b # [10, 40, 90]
a / b # [0.1, 0.1, 0.1]
a ** b # [1, 4, 27] — slightly contrived but element-wise stillThe two arrays must have compatible shapes — same shape, or shapes that NumPy can broadcast.
Broadcasting rules in brief
Two shapes are broadcast-compatible when, comparing them right-to-left dimension by dimension, every pair of dimensions either:
- matches exactly, or
- has one of the two equal to 1 (the size-1 axis is “stretched” to match the other), or
- is missing on one side (treated as if size 1, padding on the left).
The result shape is the element-wise maximum of the two padded shapes.
Examples:
(3, 4) + (4,)→ the second array is padded to(1, 4), then broadcast to(3, 4). Result(3, 4). Each row gets the same(4,)vector added.(3, 1) + (1, 4)→ both stretch: result(3, 4). This is the “outer product” pattern — each row paired with each column .(3, 4) + (3,)→ fails. The trailing dimensions4and3don’t match and neither is 1, and right-alignment is the rule. To make this work you’d reshape the right operand to(3, 1).
The most common bug is shape mismatch you don’t notice because broadcasting silently does something but not what you wanted. Print arr.shape for both operands before combining if the result looks wrong.
For 2D arrays:
m1 = np.array([[1, 2], [3, 4]])
m2 = np.array([[10, 20], [30, 40]])
m1 + m2 # [[11, 22], [33, 44]]
m1 * m2 # [[10, 40], [90, 160]] — element-wise, NOT matrix multiplicationFor matrix multiplication, use np.dot(m1, m2) or the @ operator: m1 @ m2.
Comparison operators
Apply comparisons element-wise to produce a boolean array:
arr = np.array([1, 5, 3, 8, 2])
arr > 3 # [False, True, False, True, False]
arr == 5 # [False, True, False, False, False]
arr >= 3 # [False, True, True, True, False]The result has the same shape as the input. Use it as a mask for filtering — see NumPy advanced indexing.
For comparisons between two arrays:
a = np.array([1, 2, 3])
b = np.array([3, 2, 1])
a == b # [False, True, False]
a < b # [True, False, False]Aggregation
Reduce an array to a single value:
arr = np.array([1, 2, 3, 4, 5])
arr.sum() # 15
arr.mean() # 3.0
arr.min() # 1
arr.max() # 5
arr.std() # standard deviation
arr.var() # variance
np.prod(arr) # 120For multi-dimensional arrays, you can aggregate along specific axes:
m = np.array([[1, 2], [3, 4]])
m.sum(axis=0) # [4, 6] — sum each column
m.sum(axis=1) # [3, 7] — sum each row
m.sum() # 10 — sum everythingaxis=0 reduces along the first axis (collapses rows); axis=1 reduces along the second (collapses columns).
Why vectorize
Vectorized NumPy code is:
- Faster: 10-100× speedup over equivalent Python loops, due to optimized C implementation.
- More concise:
arr + 10reads cleaner than aforloop. - More expressive: matches mathematical notation (linear algebra, statistics).
- Less error-prone: fewer index variables to manage.
The cost: arrays must be created and have compatible types. Transitioning between Python lists and NumPy arrays has overhead — for small one-shot calculations, plain Python may be faster.
For the array creation and basic indexing, see NumPy arrays. For slicing and submatrix extraction, see NumPy array slicing. For boolean filtering, see NumPy advanced indexing.