2.4 Vectorized Operations in NumPy
NumPy Vectorized Operations, ufuncs & Broadcasting
One of the major strengths of NumPy is its ability to perform mathematical operations on complete arrays without explicitly writing Python loops.
This technique is called vectorization. NumPy also provides highly optimized Universal Functions (ufuncs) for performing element-wise mathematical operations.
Another important NumPy feature is broadcasting, which allows arrays with compatible shapes to participate in arithmetic operations.
- What vectorization means
- Why vectorized operations are useful
- Element-wise arithmetic operations
- Addition, subtraction, multiplication and division
- Power and remainder operations
- Comparison operations
- Universal Functions (ufuncs)
- Mathematical ufuncs
- Aggregation functions
- Broadcasting
- Broadcasting rules
- Scalar and array operations
- 1D and 2D broadcasting
- Common broadcasting errors
- Practical Data Science examples
1. What Is Vectorization?
Vectorization means applying an operation to an entire collection of values at once rather than manually processing each value using a Python loop.
For example, suppose we want to double every value:
Traditional Python Approach
numbers = [10, 20, 30, 40]
result = []
for number in numbers:
result.append(number * 2)
print(result)
Output:
[20, 40, 60, 80]
NumPy Vectorized Approach
import numpy as np
numbers = np.array([10, 20, 30, 40])
result = numbers * 2
print(result)
Output:
[20 40 60 80]
The NumPy expression operates on the entire array directly.
Instead of telling Python "repeat this operation for every element", vectorized NumPy code expresses the operation on the entire array.
2. Why Is Vectorization Important?
Vectorized operations are particularly useful when working with large numerical datasets.
| Traditional Loop | NumPy Vectorization |
|---|---|
| Requires explicit iteration. | Operates on arrays directly. |
| Usually produces more Python-level code. | Usually produces concise code. |
| Python performs the iteration. | NumPy performs optimized numerical operations. |
| Can be slower for large numerical workloads. | Often much faster for large numerical workloads. |
Vectorization is one of the reasons NumPy is widely used as a foundation for Python-based Data Science and scientific computing.
3. Element-Wise Arithmetic Operations
When two compatible NumPy arrays are operated on, arithmetic operators generally work element by element.
import numpy as np
a = np.array([10, 20, 30])
b = np.array([1, 2, 3])
print(a + b)
print(a - b)
print(a * b)
print(a / b)
Output:
[11 22 33]
[ 9 18 27]
[10 40 90]
[10. 10. 10.]
These operations are element-wise. The first
element of a is combined with the first element
of b, the second with the second, and so on.
4. Array Addition
a = np.array([10, 20, 30])
b = np.array([5, 10, 15])
result = a + b
print(result)
Output:
[15 30 45]
The calculation is:
10 + 5 = 15
20 + 10 = 30
30 + 15 = 45
5. Array Subtraction
a = np.array([100, 200, 300])
b = np.array([10, 20, 30])
result = a - b
print(result)
Output:
[ 90 180 270]
6. Array Multiplication
a = np.array([2, 3, 4])
b = np.array([5, 6, 7])
result = a * b
print(result)
Output:
[10 18 28]
The * operator performs
element-wise multiplication.
It is not matrix multiplication.
7. Element-Wise Multiplication vs Matrix Multiplication
NumPy distinguishes element-wise multiplication from matrix multiplication.
Element-Wise Multiplication
a = np.array([
[1, 2],
[3, 4]
])
b = np.array([
[5, 6],
[7, 8]
])
print(a * b)
Output:
[[ 5 12]
[21 32]]
Matrix Multiplication
print(a @ b)
Output:
[[19 22]
[43 50]]
Matrix multiplication can also be performed using
np.matmul().
print(np.matmul(a, b))
* → element-wise multiplication
@ → matrix multiplication
8. Array Division
a = np.array([10, 20, 30])
b = np.array([2, 4, 5])
result = a / b
print(result)
Output:
[5. 5. 6.]
NumPy's true division operator generally produces floating-point results.
9. Floor Division
The // operator performs floor division.
a = np.array([10, 11, 12])
b = np.array([3, 3, 5])
print(a // b)
Output:
[3 3 2]
For integer-valued examples, the result represents the floor of each quotient.
10. Power Operation
The ** operator performs element-wise exponentiation.
numbers = np.array([2, 3, 4])
result = numbers ** 2
print(result)
Output:
[ 4 9 16]
11. Modulo Operation
The % operator calculates the remainder element by
element.
numbers = np.array([10, 11, 12, 13])
print(numbers % 2)
Output:
[0 1 0 1]
This is useful for identifying even and odd values.
12. Operations Between an Array and a Scalar
A scalar is a single value. NumPy can apply the scalar operation to every element of an array.
prices = np.array([100, 200, 300])
discounted = prices * 0.9
print(discounted)
Output:
[ 90. 180. 270.]
Conceptually, NumPy performs:
100 × 0.9
200 × 0.9
300 × 0.9
This is an example of broadcasting, discussed in detail later in this lesson.
13. Vectorized Comparison Operations
Comparison operators can also be applied element by element.
numbers = np.array([10, 20, 30, 40])
print(numbers > 25)
print(numbers == 20)
print(numbers != 30)
Output:
[False False True True]
[False True False False]
[ True True False True]
These Boolean arrays can then be used for filtering.
14. What Are Universal Functions (ufuncs)?
A Universal Function, commonly called a ufunc, is a NumPy function designed to operate element by element on array data.
Examples include:
np.sqrt()np.exp()np.log()np.sin()np.cos()np.abs()np.round()np.floor()np.ceil()
These functions provide efficient element-wise mathematical operations over NumPy arrays.
15. np.sqrt()
np.sqrt() calculates the square root of each element.
numbers = np.array([1, 4, 9, 16, 25])
result = np.sqrt(numbers)
print(result)
Output:
[1. 2. 3. 4. 5.]
16. np.abs()
np.abs() returns the absolute value of each element.
numbers = np.array([-10, -5, 0, 5, 10])
print(np.abs(numbers))
Output:
[10 5 0 5 10]
17. np.round()
values = np.array([
2.345,
4.678,
8.912
])
print(np.round(values, 2))
Output:
[2.35 4.68 8.91]
The second argument specifies the number of decimal places.
18. np.floor() and np.ceil()
values = np.array([
1.2,
2.7,
3.1,
4.9
])
print(np.floor(values))
print(np.ceil(values))
Output:
[1. 2. 3. 4.]
[2. 3. 4. 5.]
floor() moves toward negative infinity, while
ceil() moves toward positive infinity.
19. np.exp()
np.exp() calculates the exponential function
element-wise.
values = np.array([0, 1, 2])
print(np.exp(values))
This corresponds to:
e⁰, e¹, e²
20. np.log()
np.log() calculates the natural logarithm
element-wise.
values = np.array([1, np.e, np.e ** 2])
print(np.log(values))
The results are approximately:
[0. 1. 2.]
21. Trigonometric ufuncs
NumPy provides trigonometric functions such as
sin(), cos(), and tan().
angles = np.array([
0,
np.pi / 2,
np.pi
])
print(np.sin(angles))
print(np.cos(angles))
NumPy's trigonometric functions use radians.
np.pi / 2 represents 90° when working with
NumPy's trigonometric functions.
22. Aggregation Operations
NumPy also provides functions that summarize an entire array or an axis of an array.
Common aggregation functions include:
np.sum()np.mean()np.min()np.max()np.std()np.var()
numbers = np.array([10, 20, 30, 40, 50])
print(np.sum(numbers))
print(np.mean(numbers))
print(np.min(numbers))
print(np.max(numbers))
Output:
150
30.0
10
50
These operations will be studied more extensively in the next NumPy statistics lesson.
23. What Is Broadcasting?
Broadcasting is NumPy's mechanism for performing arithmetic operations on arrays with different but compatible shapes.
The simplest example is an array combined with a scalar.
numbers = np.array([10, 20, 30])
result = numbers + 5
print(result)
Output:
[15 25 35]
NumPy effectively applies the scalar to every element:
10 + 5
20 + 5
30 + 5
The scalar does not need to be manually converted into an array of the same size.
24. Broadcasting with a 1D Array
Broadcasting becomes particularly useful when operating on a multidimensional array with a smaller compatible array.
matrix = np.array([
[10, 20, 30],
[40, 50, 60]
])
values = np.array([1, 2, 3])
result = matrix + values
print(result)
Output:
[[11 22 33]
[41 52 63]]
The 1D array is effectively applied across each row.
25. Understanding the Previous Example
Matrix:
10 20 30
40 50 60
Values:
1 2 3
Conceptually:
10 + 1 20 + 2 30 + 3
40 + 1 50 + 2 60 + 3
Result:
11 22 33
41 52 63
26. Broadcasting Rules
NumPy compares array shapes from the rightmost dimension toward the left.
Two dimensions are compatible when:
- They are equal.
-
One of them is
1. - One of the arrays does not have that dimension.
Broadcasting works when corresponding dimensions are equal or one of them is 1.
27. Broadcasting Shape Example
Consider:
A.shape = (2, 3)
B.shape = (3,)
NumPy aligns the shapes from the right:
(2, 3)
(3)
The dimensions are compatible because the final dimensions are both 3.
Therefore, broadcasting is possible.
28. Broadcasting Down a Column
A column-shaped array can be broadcast across the rows.
matrix = np.array([
[10, 20, 30],
[40, 50, 60],
[70, 80, 90]
])
values = np.array([
[1],
[2],
[3]
])
result = matrix + values
print(result)
Output:
[[11 21 31]
[42 52 62]
[73 83 93]]
The shape of values is (3, 1), which is
compatible with (3, 3).
29. When Broadcasting Fails
Consider:
A.shape = (2, 3)
B.shape = (2,)
The shapes are effectively:
(2, 3)
( 2)
The rightmost dimensions are 3 and 2. They are not equal, and neither is 1.
Therefore, the shapes are incompatible for direct broadcasting.
A = np.array([
[1, 2, 3],
[4, 5, 6]
])
B = np.array([10, 20])
result = A + B
This raises a broadcasting-related
ValueError.
30. Fixing a Broadcasting Shape
If the intention is to add one value to each row, reshape the smaller array to a column.
A = np.array([
[1, 2, 3],
[4, 5, 6]
])
B = np.array([10, 20])
result = A + B.reshape(2, 1)
print(result)
Output:
[[11 12 13]
[24 25 26]]
Now the shapes are:
A.shape = (2, 3)
B.shape = (2, 1)
The second dimension can broadcast because it is 1.
31. Scalar Broadcasting
Scalars can broadcast across arrays of any compatible shape.
matrix = np.array([
[10, 20],
[30, 40]
])
result = matrix * 10
print(result)
Output:
[[100 200]
[300 400]]
The scalar 10 is applied to every element.
32. Broadcasting Compatibility Examples
| Shape A | Shape B | Compatible? | Reason |
|---|---|---|---|
(2, 3) |
(3,) |
Yes | Last dimensions match. |
(3, 3) |
(3, 1) |
Yes | One dimension is 1. |
(2, 3) |
(2, 1) |
Yes | Last dimension can broadcast. |
(2, 3) |
(2,) |
No | 3 and 2 are incompatible. |
(4, 3) |
(3,) |
Yes | Last dimensions match. |
(4, 3) |
(4,) |
No | 3 and 4 are incompatible. |
33. Combining Row and Column Vectors
Broadcasting can produce a grid from a column vector and a row vector.
rows = np.array([
[1],
[2],
[3]
])
columns = np.array([
10, 20, 30, 40
])
result = rows + columns
print(result)
Output:
[[11 21 31 41]
[12 22 32 42]
[13 23 33 43]]
Here:
rows.shape = (3, 1)
columns.shape = (4,)
Broadcasting produces:
(3, 4)
34. Data Science Example — Applying a Discount
Suppose a dataset contains product prices.
prices = np.array([
500,
800,
1200,
1500
])
discount_rate = 0.10
discounted_prices = prices * (
1 - discount_rate
)
print(discounted_prices)
Output:
[ 450. 720. 1080. 1350.]
A scalar discount rate is automatically applied to every price.
35. Data Science Example — Standardizing Values
Vectorized operations can also be used in data preprocessing.
values = np.array([
10,
20,
30,
40,
50
])
mean = np.mean(values)
std = np.std(values)
standardized = (
values - mean
) / std
print(standardized)
Here subtraction and division are applied element by element.
This is conceptually related to standardization used in Machine Learning preprocessing.
36. Data Science Example — Temperature Conversion
NumPy can convert an entire collection of temperatures without a loop.
celsius = np.array([
0,
10,
20,
30,
40
])
fahrenheit = (
celsius * 9 / 5
) + 32
print(fahrenheit)
Output:
[ 32. 50. 68. 86. 104.]
37. Important NumPy ufuncs
| Function | Purpose | Example |
|---|---|---|
np.abs() |
Absolute value. |
np.abs(arr)
|
np.sqrt() |
Square root. |
np.sqrt(arr)
|
np.exp() |
Exponential. |
np.exp(arr)
|
np.log() |
Natural logarithm. |
np.log(arr)
|
np.sin() |
Sine. |
np.sin(arr)
|
np.cos() |
Cosine. |
np.cos(arr)
|
np.round() |
Round values. |
np.round(arr, 2)
|
np.floor() |
Floor operation. |
np.floor(arr)
|
np.ceil() |
Ceiling operation. |
np.ceil(arr)
|
38. NumPy Arithmetic Operators Cheatsheet
| Operator | Operation | Example |
|---|---|---|
+ |
Addition | a + b |
- |
Subtraction | a - b |
* |
Element-wise multiplication | a * b |
/ |
Division | a / b |
// |
Floor division | a // b |
** |
Power | a ** 2 |
% |
Remainder | a % 2 |
@ |
Matrix multiplication | a @ b |
39. NumPy Comparison Operators
| Operator | Meaning | Example |
|---|---|---|
== |
Equal to | a == b |
!= |
Not equal to | a != b |
> |
Greater than | a > b |
< |
Less than | a < b |
>= |
Greater than or equal to | a >= b |
<= |
Less than or equal to | a <= b |
40. NumPy Interview Questions
Q1. What is vectorization in NumPy?
View Answer
Vectorization is the technique of applying an operation to an entire array without explicitly writing a Python loop for each element.
Q2. Why is vectorized NumPy code generally faster than equivalent Python loops for large numerical workloads?
View Answer
NumPy performs many numerical operations through optimized compiled implementations, reducing the amount of element-by-element work handled directly by the Python interpreter.
Q3. What is a ufunc?
View Answer
A ufunc, or Universal Function, is a NumPy function designed to perform element-wise operations on array data.
Q4. What is broadcasting?
View Answer
Broadcasting is NumPy's mechanism for performing operations on arrays with compatible but potentially different shapes.
Q5. What are the basic broadcasting compatibility rules?
View Answer
Dimensions are compatible when they are equal, when one of them is 1, or when one array does not have that dimension. Shapes are compared from the rightmost dimension.
Q6. What is the difference between * and @ in NumPy?
View Answer
* performs element-wise multiplication,
whereas @ performs matrix multiplication.
Q7. What happens when two arrays have incompatible broadcasting shapes?
View Answer
NumPy raises a ValueError because the arrays
cannot be aligned according to the broadcasting rules.
41. Examination Questions
Multiple Choice Questions
Q1. Which of the following represents vectorized NumPy code?
-
for x in arr: print(x * 2) -
arr * 2 -
while arr: pass -
print(arr[0])
Answer: B — arr * 2
Q2. Which operator performs element-wise multiplication?
@***//
Answer: B — *
Q3. Which operator performs matrix multiplication?
*+@%
Answer: C — @
Q4. What is the output?
arr = np.array([1, 2, 3])
print(arr * 2)
[1 2 3][2 4 6][3 4 5][1 4 9]
Answer: B — [2 4 6]
Q5. Which function calculates the square root element-wise?
np.root()np.sqrt()np.square_root()np.sq()
Answer: B — np.sqrt()
Q6. Are shapes (2, 3) and (3,)
broadcast-compatible?
- Yes
- No
Answer: A — Yes
Q7. Which condition allows two corresponding dimensions to broadcast?
- They must always be different.
- They must both be greater than 1.
- They must be equal or one must be 1.
- They must both be zero.
Answer: C — They must be equal or one must be 1.
Short Answer Questions
- Define vectorization in NumPy.
- State two advantages of vectorized operations.
- What is a Universal Function or ufunc?
-
Differentiate between
*and@. - Explain broadcasting with an example.
- State the basic broadcasting rules.
- What happens if two arrays have incompatible shapes?
- Write a NumPy statement to calculate the square root of every element in an array.
42. Practical Challenge
Build a Vectorized Data Processing Program
Create a NumPy program that processes a collection of product prices.
- Create a NumPy array containing at least 10 product prices.
- Increase every price by 5%.
- Apply a 10% discount using vectorized arithmetic.
- Round the resulting prices to two decimal places.
- Find all prices above a specified threshold using Boolean comparison.
-
Calculate the square root of the prices using
np.sqrt(). - Create a 2D array containing monthly sales.
- Create a 1D array containing monthly adjustment values.
- Use broadcasting to apply the adjustment values across the dataset.
- Calculate the total and average using NumPy aggregation functions.
43. Common Mistakes to Avoid
-
Confusing
*with@:*is element-wise multiplication;@is matrix multiplication. -
Using Python
andfor array conditions: Use element-wise operators such as&and|. - Ignoring array shapes: Broadcasting only works when dimensions are compatible.
-
Forgetting parentheses:
Write
(arr > 10) & (arr < 50). - Assuming broadcasting means copying data: Broadcasting describes how NumPy logically aligns values; it does not mean that a full repeated array must always be physically created.
-
Assuming all NumPy functions are aggregations:
Functions such as
np.sqrt()operate element-wise, while functions such asnp.sum()reduce data.
44. Key Takeaways
- Vectorization allows NumPy to perform operations on complete arrays without explicit Python loops.
-
Arithmetic operators such as
+,-,*, and/generally operate element by element on compatible arrays. -
*performs element-wise multiplication. -
@performs matrix multiplication. - ufuncs provide optimized element-wise mathematical operations.
-
Examples of ufuncs include
np.sqrt(),np.exp(),np.log(),np.sin(), andnp.abs(). - Broadcasting allows operations between arrays with compatible shapes.
- Broadcasting compares dimensions from the rightmost side.
- Corresponding dimensions are compatible when they are equal or one of them is 1, or when a dimension is absent from the smaller shape.
-
Incompatible shapes result in a
ValueError. - Broadcasting is fundamental to concise and efficient numerical Data Science code.
Vectorize whenever practical, understand your array shapes, and remember that broadcasting allows compatible arrays to interact without manually repeating values.