Numpy Exercises#

Most of your answers to this notebook will be in markdown, so we won’t use the autograder. When completed, please export to HTML, print to PDF, and upload the resulting PDF.

Because most answers will be written in markdown, formatting will be especially important, so be sure to review the guidelines for writing good notebooks!

Exercise 1#

First, lets make a common array to work with.

import numpy as np

# Seed insures results are stable.
np.random.seed(21)
random_integers = np.random.randint(1, high=500000, size=(20, 5))
random_integers
array([[ 80842, 333008, 202553, 140037,  81969],
       [ 63857,  42105, 261540, 481981, 176739],
       [489984, 326386, 110795, 394863,  25024],
       [ 38317,  49982, 408830, 485118,  16119],
       [407675, 231729, 265455, 109413, 103399],
       [174677, 343356, 301717, 224120, 401101],
       [140473, 254634, 112262,  25063, 108262],
       [375059, 406983, 208947, 115641, 296685],
       [444899, 129585, 171318, 313094, 425041],
       [188411, 335140, 141681,  59641, 211420],
       [287650,   8973, 477425, 382803, 465168],
       [  3975,  32213, 160603, 275485, 388234],
       [246225,  56174, 244097,   9350, 496966],
       [225516, 273338,  73335, 283013, 212813],
       [ 38175, 282399, 318413, 337639, 379802],
       [198049, 101115, 419547, 260219, 325793],
       [148593, 425024, 348570, 117968, 107007],
       [ 52547, 180346, 178760, 305186, 262153],
       [ 11835, 449971, 494184, 472031, 353049],
       [476442,  35455, 191553, 384154,  29917]])

Exercise 2#

What is the average value of the second column (to one decimal place).

Exercise 3#

What is the average value of the first 5 rows of the third and fourth columns (to one decimal place)?

Exercise 4#

Without using Python, read the following code and predict the result of the last print statement (print(first_matrix + second_matrix)):

first_matrix = np.array([[1, 2, 3], [4, 5, 6]])
print(first_matrix)
[[1 2 3]
 [4 5 6]]
second_matrix = np.array([1, 2, 3])
print(second_matrix)
[1 2 3]
print(first_matrix + second_matrix)

Please write your result in LaTeX in a markdown cell. You can write matrices in Markdown in Jupyter Notebooks using the following syntax, where the & symbols separate columns and \\ is used to end a row, and you have an empty row above and below this block:

\begin{bmatrix}
1 & 2 & 3 \\ 
4 & 5 & 6
\end{bmatrix}

This will be rendered as:

\begin{bmatrix} 1 & 2 & 3 \ 4 & 5 & 6 \end{bmatrix}

Execise 5#

Still without Python! Evaluate the following code. Write your prediction of the final print statement in markdown:

my_vector = np.array([1, 2, 3, 4, 5, 6])
selection = my_vector % 2 == 0
print(my_vector[selection])

Exercise 6#

Now open python and check your answers to Exercises 4 and 5. How did your answers compare with what Python generates? What errors did you make?

Working with Views#

One of the nuances of numpy can can easily lead to problems is that when one takes a slice of an array, one does not actually get a new array; rather, one is given a “view” on the original array, meaning they are sharing the same underlying data.

Views exist because they are more memory efficient (a view doesn’t require making a new copy of data) and faster (again, no copying required). And if you’re doing super-computer simulations where every milisecond counts, or working with truely huge datasets, this is important. But for most data scientists, I tend to see it as a a trap waiting to get you in trouble.

This is especially since there’s no reliable way to check if two arrays are views of one another except by modifying one and seeing if the other changes. (You may find people saying otherwise; don’t trust them!). The way we use is in regular Python to see if two variables point at the same object doesn’t work for numpy arrays. Thus its on you to remember the rules.

My advice on copies: UNLESS YOU REALLY NEED A VIEW AND ARE BEING SUPER CAREFUL: don’t use views for anything but looking at data. If you ever want to modify or work with a sub-array, just make a copy to be safe. Computers are fast enough and ram is plentiful enough that for most applications, it’s almost never a problem.

Exercise 7#

Without Python Let’s try and work out a few problems in our heads to test our understanding of numpy views. Let’s start with the following array:

my_array = np.array([[1, 2, 3], [4, 5, 6]])
print(my_array)
[[1 2 3]
 [4 5 6]]

Now, in markdown write down the result of this code:

my_array = np.array([[1, 2, 3], [4, 5, 6]])
my_slice = my_array[:, 1:3]
print(my_slice)

Exercise 8#

Now suppose we run the code:

my_array = np.array([[1, 2, 3], [4, 5, 6]])
my_slice = my_array[:, 1:3]
my_array[:, :] = my_array * 2
print(my_slice)

Now what does my_slice look like? Again, show your result in markdown.

Exercise 9#

Now suppose we run the following code:

my_array = np.array([[1, 2, 3], [4, 5, 6]])
my_slice = my_array[:, 1:3]
my_array = my_array * 2
print(my_slice)

What does my_slice look like?

Exercise 10#

Using Python, run the preceding code. Were your predictions correct? If not, why not?

Exercise 11#

OK, let’s close Python again and go back to markdown. Let’s also reset my_array and start over with the following code:

my_array = np.array([[1, 2, 3], [4, 5, 6]])
my_slice = my_array[:, 1:3].copy()
my_array[:, :] = my_array * 2
print(my_slice)

What does my_slice look like?

Exercise 12#

Arrays aren’t the only data structure in Python with the slice syntax (i.e., the ability to subset like this: v[1:3]). Close Python and, in markdown, try and predict the result of this code:

x = [1, 2, 3]
y = x[0:2]
y[0] = "a change"
print(y)

Exercise 13#

Now what is the output if we subsequently run print(x)?

Exercise 14#

Now open Python and check your answer. Were you correct? Why or why not?

Note: Don’t trust my_array.base#

Before we wrap up this section, and important note about views: You will find some tutorials online that suggest you can test if one array is a view of another with the code my_slice.base is my_array. The problem is… this doesn’t always work. It does sometimes:

my_array = np.array([1, 2, 3])
my_slice = my_array[1:3]
my_slice.base is my_array
True

But not always. Here’s an example where my_array and my_slice point to the same data, but my_slice.base is my_array returns false.

my_array = np.array([1, 2, 3])
my_array = my_array[1:4]
my_slice = my_array[1:3]
my_slice.base is my_array
False
my_slice
array([3])
my_array
array([2, 3])
# But a change to `my_slice` still impacts `my_array`.
my_slice[0] = -1
my_array
array([ 2, -1])

(The reason is that the .base property can be defined recursively. In this case, the slicing of my_array made my_array a view on data you can no longer access, so they actually do both point to the same data, but that data is not my_array, it’s my_array.base. So:

my_slice.base is my_array.base
True

In practice, you can get infinite chains of .base.base....

And yes, if this is making your head hurt, that’s because you’re doing it right. :)