Vectorization

7.4. Vectorization#

We can use for loops to iterate through NumPy arrays and perform calculations. For example, this code will calculate the average value of all the numbers in an array:

a = np.linspace(1, 20, 20)
avg = 0

for x in a:
    avg += x

avg /= len(a)

print("Average is", avg)

Because calculating an average is a common operation, there is a function built into NumPy, np.average() (as you may expect after having seen np.std() and similar functions). Remember, the entire purpose of packages is that you don’t need to reinvent commonly used code.

print("Average is", np.average(a))

This is very handy: it saves us loads of typing. From the function name, it is also easy to understand what it’s doing, making the code cleaner and easier to read. However, the purpose of NumPy functions is not only to save us lots of typing: they also can often perform calculations much faster than if you code the calculation yourself with a for loop.

To show this quantitatively, we will use the time library to calculate the time it takes to find the average of a pretty big array using both techniques (a for loop and np.average()):

# The time() function from the time library will return a floating point number representing 
# the number of seconds since January 1, 1970, 00:00:00 (UTC), with millisecond or even microsecond
# precision
# 
# We will use this to make a note of the starting time and the ending time, 
# and then print out the time difference 
from time import time

# A pretty big array, 50 million random numbers
a = np.random.random(int(50e6))

# Set timer
t1 = time()

# Calculate the average using a for loop
avg = 0
for x in a:
    avg += x    
avg /= len(a)
t2 = time()
t_forloop = t2-t1
print("The 'for' loop took %.3f seconds" % (t2-t1))

# Calculate the average using np.average
t1 = time()
avg = np.average(a)
t2 = time()
t_np = t2-t1
print("Numpy took %.3f seconds" % (t2-t1))

# Now let's compare the two methods
print("\nNumpy was %.1f times faster!" % (t_forloop/t_np))

Why is NumPy so much faster? The reason is that Python is an interpreted language, as we’ve seen earlier. In each of the steps of the for loop, the Python kernel reads in the next step it has to do, translates that into an instruction for your computer processor, asks the computer to perform the step, gets the result back, reads in the next step, translates that into a processor instruction, sends that as an instruction to the computer processor, etc.

If we did the same test in a compiled programing language like C, there would be no difference if we used a library function or if we wrote our own for loop.

When you use smart functions in Python libraries, like (many of) those in NumPy, NumPy will actually use an external library compiled in a language like C or Fortran that is able to send all of the calculation in one step to your computer processor, and in one step, get all the data back. This makes Python nearly as fast as a compiled language like C or Fortran, as long as you are smart in how you use it and avoid having “manual” for loops for large or long calculations.

Note: for small calculations, Python coded for loops are perfectly fine and very handy!

In the language of interpreted programmers, finding smart ways of getting what you need done using compiled library functions is often referred to as vectorization.

Note that even normal mathematical operators are actually vectorized functions when they operate:

# This is actually a vectorized 'for' loop, it involves multiplying 50 million numbers by 5
b = 5*a

Here is a nice example of a vectorized way of counting the number of times the number 5 occurs in a random sample of 100 integers between 0 and 20:

nums = np.random.randint(0, 21, 100)
print("There are %d fives" % np.sum(nums == 5))

To see how this works, we can look at the intermediate steps:

nums = np.random.randint(0, 21, 100)
print(nums)
print(nums == 5)
print("There are %d fives" % np.sum(nums == 5))

Note that in this case, np.sum() will convert the bool value True into 1 and False into 0 for calculating the sum, according to the standard conversion of bool types to int types. You can see this in action using the function astype() that is built into NumPy arrays:

print(nums == 5)
print((nums == 5).astype('int'))
import micropip
await micropip.install("jupyterquiz")
from jupyterquiz import display_quiz
import json

with open("questions6.json", "r") as file:
    questions=json.load(file)
    
display_quiz(questions, border_radius=0)