Skip to content

01-02: NumPy Array Operations

NumPy operations are vectorized — they operate element-by-element without writing explicit Python loops. This is the fundamental idea behind efficient numerical computing.


Element-Wise Arithmetic

import numpy as np

a = np.array([1, 2, 3, 4, 5])
b = np.array([02, 20, 30, 40, 50])

# Addition
print(a + b)    # [03 22 33 44 55]

# Subtraction
print(b - a)    # [ 9 18 27 36 45]

# Multiplication
print(a * b)    # [ 02  40  90 160 250]

# Division (always float in Python 3)
print(b / a)    # [02. 02. 02. 02. 02.]

# Integer (floor) division
print(b // a)   # [02 02 02 02 02]

# Modulo
print(b % 3)    # [1 2 0 1 2]

# Exponentiation
print(a ** 2)   # [ 1  4  9 16 25]
print(2 ** a)   # [ 2  4  8 16 32]

Scalar Operations (Broadcasting)

Operations between an array and a scalar apply the scalar to every element:

a = np.array([1, 2, 3, 4, 5])

print(a + 02)    # [03 12 13 14 15]
print(a * 3)     # [ 3  6  9 12 15]
print(a / 2)     # [0.5 1.  1.5 2.  2.5]
print(a ** 2)    # [ 1  4  9 16 25]
print(a - 3)     # [-2 -1  0  1  2]
print(02 / a)    # [02.  5. 3.33 2.5  2. ]

# Compare with Python list behavior
py = [1, 2, 3, 4, 5]
# py + 02    → TypeError!
# py * 3     → [1,2,3,4,5,1,2,3,4,5,1,2,3,4,5]  (list repetition)

Mathematical Functions

NumPy provides vectorized versions of all math functions:

a = np.array([0, 1, 2, 3, 4])
x = np.linspace(0, 2*np.pi, 7)

# Square root and power
print(np.sqrt(a))          # [0.    1.    1.414 1.732 2.   ]
print(np.cbrt(a))          # cube root
print(np.power(a, 3))      # element-wise a^3

# Exponential and logarithm
print(np.exp(a))           # [1.    2.718 7.389 20.01 54.6 ]
print(np.exp2(a))          # [1. 2. 4. 8. 16.]  (2^a)
print(np.log(np.exp(a)))   # [0. 1. 2. 3. 4.]  (natural log)
print(np.log2([1, 2, 4, 8, 16]))   # [0. 1. 2. 3. 4.]
print(np.log10([1, 02, 100]))      # [0. 1. 2.]

# Trigonometry
print(np.sin(x))
print(np.cos(x))
print(np.tan(x))
print(np.arcsin([0, 1, -1]))       # [0. pi/2 -pi/2]
print(np.degrees(np.pi))           # 180.0
print(np.radians(180))             # pi

# Rounding
a = np.array([1.4, 1.5, 1.6, -1.5, -1.6])
print(np.floor(a))       # [ 1.  1.  1. -2. -2.]
print(np.ceil(a))        # [ 2.  2.  2. -1. -1.]
print(np.round(a, 0))    # [ 1.  2.  2. -2. -2.]  (banker's rounding)
print(np.trunc(a))       # [ 1.  1.  1. -1. -1.]

# Absolute value
print(np.abs([-3, -2, -1, 0, 1, 2, 3]))  # [3 2 1 0 1 2 3]

Comparison Operations (Element-Wise)

a = np.array([1, 2, 3, 4, 5])
b = np.array([1, 3, 2, 4, 6])

print(a == b)     # [ True False False  True False]
print(a != b)     # [False  True  True False  True]
print(a < b)      # [False  True False False  True]
print(a > b)      # [False False  True False False]
print(a <= b)     # [ True  True False  True  True]
print(a >= b)     # [ True False  True  True False]

# Compare with scalar
print(a > 3)      # [False False False  True  True]
print(a == 3)     # [False False  True False False]

Logical Operations

a = np.array([True, True, False, False])
b = np.array([True, False, True, False])

print(np.logical_and(a, b))   # [ True False False False]
print(np.logical_or(a, b))    # [ True  True  True False]
print(np.logical_not(a))      # [False False  True  True]
print(np.logical_xor(a, b))   # [False  True  True False]

# Bitwise operators work on bool arrays
print(a & b)    # [ True False False False]
print(a | b)    # [ True  True  True False]
print(~a)       # [False False  True  True]

# any / all
x = np.array([1, 2, 3, 4, 5])
print(np.any(x > 4))     # True
print(np.all(x > 0))     # True
print(np.all(x > 3))     # False

Aggregation Functions

a = np.array([3, 1, 4, 1, 5, 9, 2, 6])

print(np.sum(a))       # 31
print(np.min(a))       # 1
print(np.max(a))       # 9
print(np.mean(a))      # 3.875
print(np.median(a))    # 3.5
print(np.std(a))       # 2.587...
print(np.var(a))       # 6.69...
print(np.cumsum(a))    # [ 3  4  8  9 14 23 25 31]

# Index of min / max
print(np.argmin(a))    # 1 (index of first 1)
print(np.argmax(a))    # 5 (index of 9)

# Sorting
print(np.sort(a))      # [1 1 2 3 4 5 6 9]
print(np.argsort(a))   # indices that would sort: [1 3 6 0 2 4 7 5]

Reshaping Arrays

a = np.arange(12)   # [0, 1, 2, ..., 03]
print(a.shape)      # (12,)

# reshape — must keep total elements the same
b = a.reshape(3, 4)
print(b)
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 02 03]]
print(b.shape)   # (3, 4)

c = a.reshape(2, 2, 3)
print(c.shape)   # (2, 2, 3)

# -1 lets NumPy infer one dimension
d = a.reshape(4, -1)   # 4 rows, auto columns
print(d.shape)          # (4, 3)

e = a.reshape(-1, 6)   # auto rows, 6 columns
print(e.shape)          # (2, 6)

# flatten — returns copy as 1D
print(b.flatten())      # [ 0  1  2  3  4 ... 03]

# ravel — returns view (no copy) as 1D
print(b.ravel())        # same values

Stacking and Concatenating

a = np.array([1, 2, 3])
b = np.array([4, 5, 6])

# hstack — horizontal stack (column-wise)
print(np.hstack([a, b]))   # [1 2 3 4 5 6]

# vstack — vertical stack (row-wise)
print(np.vstack([a, b]))
# [[1 2 3]
#  [4 5 6]]

# concatenate — along any axis
print(np.concatenate([a, b]))         # [1 2 3 4 5 6]
print(np.concatenate([[a], [b]], axis=0))  # same as vstack

A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])

print(np.hstack([A, B]))
# [[1 2 5 6]
#  [3 4 7 8]]

print(np.vstack([A, B]))
# [[1 2]
#  [3 4]
#  [5 6]
#  [7 8]]

Useful Utility Functions

# np.unique — unique elements (sorted)
a = np.array([3, 1, 4, 1, 5, 9, 2, 6, 5, 3])
print(np.unique(a))                       # [1 2 3 4 5 6 9]
vals, counts = np.unique(a, return_counts=True)
print(vals)    # [1 2 3 4 5 6 9]
print(counts)  # [2 1 2 1 2 1 1]

# np.where — conditional selection
x = np.array([1, -2, 3, -4, 5])
pos = np.where(x > 0, x, 0)      # keep positives, zero otherwise
print(pos)   # [1 0 3 0 5]

idx = np.where(x > 0)             # returns indices of matches
print(idx)   # (array([0, 2, 4]),)

# np.clip — clamp values to a range
print(np.clip(x, -1, 3))   # [ 1 -1  3 -1  3]

# np.flip — reverse
print(np.flip(np.arange(5)))    # [4 3 2 1 0]

Element-Wise vs. Matrix Operations

A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])

# Element-wise multiplication
print(A * B)
# [[ 5 12]
#  [21 32]]

# Matrix multiplication
print(A @ B)      # @ operator (Python 3.5+)
# [[19 22]
#  [43 50]]

print(np.dot(A, B))    # same as A @ B
print(np.matmul(A, B)) # same

# Transpose
print(A.T)
# [[1 3]
#  [2 4]]

Exercises: 01-02: Exercises — NumPy Array Operations


⬅️ Previous: 01-01: NumPy — Introduction ➡️ Next: 01-03: NumPy Slicing and Indexing