Python for Data Science & Automation · Module 2: Numerical Computing with NumPy · Lesson 9 of 34

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.

In this lesson, you will learn:
  • 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.

Key Idea:

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.]
Important:

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]
Important:

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))
Exam Tip:

* → 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.

Exam Tip:

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:

  1. They are equal.
  2. One of them is 1.
  3. One of the arrays does not have that dimension.
Core Rule:

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?

  1. for x in arr: print(x * 2)
  2. arr * 2
  3. while arr: pass
  4. print(arr[0])

Answer: B — arr * 2

Q2. Which operator performs element-wise multiplication?

  1. @
  2. *
  3. **
  4. //

Answer: B — *

Q3. Which operator performs matrix multiplication?

  1. *
  2. +
  3. @
  4. %

Answer: C — @

Q4. What is the output?

arr = np.array([1, 2, 3])

print(arr * 2)
  1. [1 2 3]
  2. [2 4 6]
  3. [3 4 5]
  4. [1 4 9]

Answer: B — [2 4 6]

Q5. Which function calculates the square root element-wise?

  1. np.root()
  2. np.sqrt()
  3. np.square_root()
  4. np.sq()

Answer: B — np.sqrt()

Q6. Are shapes (2, 3) and (3,) broadcast-compatible?

  1. Yes
  2. No

Answer: A — Yes

Q7. Which condition allows two corresponding dimensions to broadcast?

  1. They must always be different.
  2. They must both be greater than 1.
  3. They must be equal or one must be 1.
  4. They must both be zero.

Answer: C — They must be equal or one must be 1.

Short Answer Questions

  1. Define vectorization in NumPy.
  2. State two advantages of vectorized operations.
  3. What is a Universal Function or ufunc?
  4. Differentiate between * and @.
  5. Explain broadcasting with an example.
  6. State the basic broadcasting rules.
  7. What happens if two arrays have incompatible shapes?
  8. 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.

  1. Create a NumPy array containing at least 10 product prices.
  2. Increase every price by 5%.
  3. Apply a 10% discount using vectorized arithmetic.
  4. Round the resulting prices to two decimal places.
  5. Find all prices above a specified threshold using Boolean comparison.
  6. Calculate the square root of the prices using np.sqrt().
  7. Create a 2D array containing monthly sales.
  8. Create a 1D array containing monthly adjustment values.
  9. Use broadcasting to apply the adjustment values across the dataset.
  10. Calculate the total and average using NumPy aggregation functions.

43. Common Mistakes to Avoid

  1. Confusing * with @: * is element-wise multiplication; @ is matrix multiplication.
  2. Using Python and for array conditions: Use element-wise operators such as & and |.
  3. Ignoring array shapes: Broadcasting only works when dimensions are compatible.
  4. Forgetting parentheses: Write (arr > 10) & (arr < 50).
  5. 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.
  6. Assuming all NumPy functions are aggregations: Functions such as np.sqrt() operate element-wise, while functions such as np.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(), and np.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.
Golden Rule:

Vectorize whenever practical, understand your array shapes, and remember that broadcasting allows compatible arrays to interact without manually repeating values.