This is a gentle introduction to people who have some experience with programming to get started with python. We used this boot camp for EE 16A at Berkeley. Source files: ee16a_python_bootcamp.zip
EE 16A Python Bootcamp
EECS 16A: Designing Information Devices and Systems I, Spring 2017
Table of Contents
- Miscellaneous Functions
This mini-lab serves as an introduction to IPython and a couple important packages we will be using throughout the semester. The lab aims to teach you proper usage of certain commands and can serve as a reference doc in the future. Even if you are a python wizard already, we recommend that you at least look through the lab to get re-acquainted with the functions we will be using a lot.
This lab is separated into two main parts: Guide and Questions. The Guide portion walks you through frequently used Python and IPython code, functions, and techniques. The Guide is supplemented with numerous blocks of example code to showcase concepts. The Questions portion of the lab is a collection of 10 problems meant to test your understanding of this guide. If you can answer these questions, then you have the knowledge to complete any and all programming tasks in EE 16A.
Both the labs and the homeworks in this course will require you to write some Python code. If you’re new to programming, have no fear, as the assignments don’t require more than just the fundamentals; this is not round 2 of 61A.
Control Flow in Python
Programming languages usually contain statements that can be used to direct or “control the flow” of execution. This includes (but is not limited to) conditional statements such as
elif, and loop-control statements such as
Conditional Statements: (if, else, elif)
# Example 1: Simple if/else x = 16 if x > 20: # Asking the question, "Is x greater than 20?" print('if condition is True!') else: print('if condition is False!')
# Example 2: Introducing elif x = 16 if x > 20: # Asking the question, "Is x greater than 20?" print('first if condition is True!') elif x > 10 and x < 20: # Asking the question, "Is x greater than 10 AND less than 20?" print('first if condition is False and second if condition is True!') else: print('Neither if condition was True!')
Loop-Control Statements: (while, for)
# Example 3: while i = 0 while i < 5: # Check if i < 5 every iteration. Stop looping if the condition is false, i.e. if i >= 5. print('i:',i) i += 1 # increment i by 1
Unlike while loops, which can theoretically run “forever” given the right condition, for loops serve a different purpose – iterating a fixed number of times. For loops in Python expect an iterable object – something similar to a list – to control the number of iterations. The example below is “equivalent” to the while loop in the previous example.
# Example 4: for (pun intended) for i in range(0,5): # read about range() here: http://pythoncentral.io/pythons-range-function-explained/ . print('i:',i) # Notice no i += 1 statement!
# Example 5: Iterating through lists char_list = [1, 6, 'a'] word = '' for element in char_list: word += str(element) print('word:',word)
All of the loop examples so far have terminated with some sort of stopping condition (i < 5, i in range(0,5), element in char_list). But what if we wanted to exit a loop early? Or, what if we wanted to immediately go to the next loop iteration? These two changes can be applied using the break and continue statements, respectively.
# Example 6: The break statement candies = ['Skittles', 'Snickers', '3 Musketeers', 'Twizzlers', 'Kit-Kat', 'Twix', 'Almond Joy'] print('Loop without break statement.') for candy in candies: print(candy) print('\nLoop with break statement.') for candy in candies: print(candy) if candy == 'Kit-Kat': break
# Example 7: The continue statement candies = ['Skittles', 'Snickers', '3 Musketeers', 'Twizzlers', 'Kit-Kat', 'Twix', 'Almond Joy'] print('Same Loop as above but with continue instead of break statement.') for candy in candies: print(candy) if candy == 'Kit-Kat': continue print('\nLoop that skips over every-other candy.') for i in range(len(candies)): if i % 2 == 1: # if i is odd. The "%" symbol is the modulo operator: https://en.wikipedia.org/wiki/Modulo_operation continue print(candies[i])
Notice how the continue statement enabled us to skip every-other candy.
There are multiple ways of creating lists in Python. A list is a mutable array of data. They can be created using square brackets [ ]. Elements in the list are separated by commas. Elements can be of any type (int, string, float, etc.).
Important Python List functions:
'+'joins lists and creates a new list.
len(x)to get the length of list
Next, we will explore the idea of a list comprehension, which is a compact way of creating a list from a for-loop in a single line. Please keep in mind that list comprehension is just a style suggestion; any list comprehension can be expanded to a fully fleshed out control-loop block. However, the advantage of list comprehensions is their compact yet expressive syntax.
For the example below, our goal is to create a list of the squares of each even integer in the range from 0 to 10 (inclusive).
# Example 8a: for-loop list construction # Expected output: [0*0, 2*2, 4*4, 6*6, 8*8, 10*10], which equals: [0, 4, 16, 36, 64, 100] lst =  for i in range(11): # iterate over numbers 0 through 10 (inclusive) if i % 2 == 0: # see example 7 for explanation of "%" symbol. lst += [i**2] # '**' is Python syntax for raising the power. Alternatively: lst.append(i**2) print(lst)
# Example 8b: List Comprehension lst = [i**2 for i in range(11) if i % 2 == 0] # one-liner magic print(lst)
The syntax for a list comprehension is as follows:
list = ** **[function(ITEM) for ITEM in ITERABLE_OBJECT if condition(ITEM)]
In example 8b above:
ITEM = i
ITERABLE_OBJECT = range(11)
function() = raise ITEM to the second power
condition() = is ITEM even?
A couple notes: list comprehensions DO NOT require a condition, list comprehensions can have nested for-loops.
From the NumPy website, “NumPy is the fundamental package for scientific computing with Python. It contains among other things: a powerful N-dimensional array object.” For the purposes of this course, we primarily use NumPy for its fast and fancy matrix functions. In general, Python list operations are slow; NumPy functions exploit the NumPy array object to “vectorize” the code, which usually improves the runtime of matrix calculations. As a general rule of thumb, if a task involves vectors or matrices, you should resort to NumPy. In addition to speeding up basic operations, NumPy contains an enormous library of matrix functions, so if you ever need to manipulate a vector or matrix, NumPy most likely already has a function implemented to suit your needs.
Quintessential NumPy Documentation: http://docs.scipy.org/doc/numpy/reference/index.html
Run the cell below to import the packages needed to complete this lab.
import numpy as np # from now on, we can access numpy functions by referencing "np" instead of numpy from numpy import linalg # import the linalg package, which includes useful matrix operations
Creating a NumPy array object
NumPy is centered around the
numpy.array() class. This array object is extremely useful, however, it is often confused with built-in Python lists, particularly when trying to represent vectors. NumPy arrays and Python Lists are NOT synonymous; you cannot simply apply functions to NumPy arrays as if they were Python Lists.
# Example 9: Going from Python list to NumPy array py_lst = [1,2,3,4] np_arr = np.array(py_lst) print('Python list:',py_lst) print('NumPy array:',np_arr)
# Example 10: Populating an empty NumPy array np_arr = np.empty([4,4], dtype=np.int) # An empty 4x4 numpy array for i in range(4): for j in range(4): np_arr[i,j] = i+j print(np_arr)
# Example 11: Creating a NumPy array of zeros and the Identity matrix np_zeros = np.zeros([5,5]) # 5x5 NumPy array of all zeros np_id = np.eye(5) # 5x5 Identity array print('np_zeros:\n',np_zeros) print('\nnp_id:\n',np_id)
# Example 12: Creating a NumPy array that spans a certain set/list of numbers """numpy.linspace() is useful when you know the number of divisions over a certain range you want, i.e., you want to divide the range [0-9] into 10 equal divisions. """ np_arr1 = np.linspace(0, 9, 10) # args for linspace(): (start, stop, num_divisions) print('np_arr1:',np_arr1) """numpy.arange() is useful when you know how far away each division is from one another, a.k.a. the step size. You want to start at 0 and get every number that is 1 away from the previous number until you get to 9. """ np_arr2 = np.arange(0, 10, 1) # args for arange(): (start, stop, step) print('np_arr2:',np_arr2)
NumPy array vs. Python List
Most arithmetic operations apply to NumPy arrays in an element-wise fashion. This is in contrast with arithmetic operations for Python lists, which apply via concatenation.
# Example 13: NumPy array vs. Python list lst = [1,2,3] arr = np.eye(3) lst2 = lst + lst arr2 = arr + arr print('lst:',lst) print('lst + lst =',lst2) print('\narr:\n',arr) print('arr + arr =\n',arr2)
NumPy array slicing
Array slicing is a technique in Python (and other languages) that programmers use to extract specific index-based information from an array. Array slicing answers queries such as, “What are the first/last n elements in this array?”, “What are the elements in the first r rows and first c columns of this matrix?”, “What is every nth element in this array?”
# Example 14: Basic vector/list slicing simple_arr = np.arange(0,100,1) print('\nFirst ten elements of simple_arr:',simple_arr[:10]) print('\nLast ten elements of simple_arr:',simple_arr[-10:]) # you should be aware that in Python, # requesting a negative index (-n) from list a is the same as requesting is equivalent to requesting a[len(a)-n]. print('\nElements 16-26 of simple_arr:',simple_arr[16:26]) # Notice slicing includes the first index and excludes that last index.
Slicing includes the start index and excludes the end index, i.e.
simple_arr[16:26] means to extract the values in
simple_arr at indexes in the range
[16,26) which is the same as
[16,25] since indexes can only be integers.
# Example 15: Some fancy vector/list slicing simple_arr = np.arange(0,20,1) print('\nEvery-other element of simple_arr, starting from 0:',simple_arr[::2]) print('\nEvery-third element of simple_arr, starting from 0:',simple_arr[::3]) print('\nEvery-other element of simple_arr, starting from 10-16:',simple_arr[10:16:2])
# Example 16: Slicing NumPy arrays i = np.array(range(25), dtype=np.int).reshape([5,5]) # numpy.reshape() will be introduced in the next cell print('i:\n',i) print('\nFirst row of i:',i) print('\nFirst column of i:',i[:,0]) print('\nRows 1-3 of i:\n',i[1:4]) print('\nColumns 1-3 of i:\n',i[:,1:4]) print('\nTop left 3x3 of i:\n',i[:3,:3]) print('\nEvery-other column of i:\n',i[:,::2])
NumPy array reshaping
Reshaping is useful when you want to do something such as turn a vector into a matrix or vice-versa. We want to be able to do this because it is often easier to construct the desired array as a vector then reshape the vector into a matrix.
# Example 17: Determining the shape of a NumPy array test_arr = np.zeros([15,189]) print('Shape of test_arr:',test_arr.shape) # Notice .shape is a NumPy array property NOT a function, i.e. no parenthesis. print('Number of elements in test_arr:',test_arr.size)
# Example 18: Using reshape() test_arr = np.array(range(16), dtype=np.int) print('\ntest_arr:',test_arr) print('Shape of test_arr:',test_arr.shape) test_arr_4x4 = test_arr.reshape([4,4]) # Notice reshape() is called on the array object, i.e. array.reshape(dimensions) NOT np.reshape(arr, dimensions)! print('\nReshaped test_arr:\n',test_arr_4x4) print('Shape of test_arr_4x4:',test_arr_4x4.shape) test_arr_vec = test_arr_4x4.reshape(test_arr_4x4.size) # Use array.flatten() instead. This is just to show array.reshape works in both directions. print('\ntest_arr back as a vector:',test_arr_vec) print('Shape of test_arr_vec:',test_arr_vec.shape)
Useful NumPy functions: (transpose(), linalg.inv(), dot(), concatenate(), vstack(), hstack(), max(), argmax())
Quintessential NumPy Documentation: http://docs.scipy.org/doc/numpy/reference/index.html
# Example 19: numpy.transpose() norm = np.array(range(16), dtype=np.int).reshape([4,4]) print('\nnorm:\n',norm) norm_transpose = np.transpose(norm) print('\nnorm_transpose:\n',norm_transpose) print('\nnorm easy transpose:\n',norm.T) # numpy.transpose(arr) == arr.T
# Example 20: numpy.linalg.inv (finds the inverse of a matrix) i = np.eye(4) print('\ni:\n',i) i_inv = np.linalg.inv(i) # Notice .inv() is a function in the linalg library of NumPy. print('\ni_inv:\n',i_inv) print('\nAs expected, i == inv(i).')
# Example 21a: numpy.dot() (how to do matrix multiplication in NumPy!) a = np.array([[2,3],[4,5]]) print('\na:\n',a) b = np.array([[1,2],[0,2]]) print('\nb:\n',b) print('\nMatrix multiplication.') c = np.dot(a,b) print('a*b:\n',c) print('\nOrder matters in numpy.dot()!') d = np.dot(b,a) print('b*a:\n',d) print('Notice a*b != b*a.') e = np.array([2,2]) print('\ne:',e) print('\nnumpy.dot() can be used to multiply an array and vector too.') f = np.dot(a,e) print('a*e:',f)
Instead of using
numpy.dot() to perform matrix multiplication, NumPy provides an alternative using the
* operator. Up until now, we’ve been exclusively dealing with NumPy
arrays; but there is another NumPy class called
matrix. A NumPy
matrix is just a 2-dimensional NumPy
array, except it has a few additional features. In particular, we can use the
* operator to perform multiplication of two NumPy
matrices (we CANNOT use
* when multiplying NumPy
# Example 21b: NumPy matrix multiplication a = np.matrix([[2,3],[4,5]]) print('\na:\n',a) b = np.matrix([[1,2],[0,2]]) print('\nb:\n',b) print('\nMatrix multiplication using * operator.') c = a*b print('a*b:\n',c)
# Example 22: numpy.concatenate() (how to append/attach multiple arrays.) a = np.array([[2,3],[4,5]]) print('\na:\n',a) b = np.array([[1,2],[0,2]]) print('\nb:\n',b) c = np.concatenate([a,b], axis=0) # axis controls how to concatenate the arrays. axis=0 attach vertically, axis=1 attach horizontally. print('\nAppend b to the "bottom" of a:\n',c) d = np.concatenate([a,b], axis=1) print('\nAppend b to the "right" of a:\n',d)
# Example 23: numpy.vstack() and numpy.hstack() a = np.array([[2,3],[4,5]]) print('\na:\n',a) b = np.array([[1,2],[0,2]]) print('\nb:\n',b) c = np.vstack([a,b]) print('\nvstack a and b:\n',c) print('Notice this is equivalent to concatenate with axis=0.') d = np.hstack([a,b]) print('\nhstack a and b:\n',d) print('Notice this is equivalent to concatenate with axis=1.')
# Example 24: np.floor(), np.ceil() a = 16.5 print('a:',a) print('floor of a:',np.floor(a)) print('ceiling of a:',np.ceil(a))
# Example 25: np.max(), np.min(), np.argmax(), np.argmin() a = np.array([0,1,2,3,16,3,2,1,0]) print('a:',a) print('max of a =',np.max(a)) print('min of a =',np.min(a)) print('index of max value of a =',np.argmax(a)) print('index of min value of a =',np.argmin(a))
These questions are in no particular order (except for question 0, do that one first). The questions range in difficulty; some are one-liners, others require a lot more thinking. Don’t be discouraged if you hit a roadblock. Talk to your neighbors and ask for help from the lab staff.
In order to test your code, please run the cell below to load the autograder. There is a cell after each question that you can run in order to check your answer. The autograder is purposefully not very verbose.
Search the NumPy documentation and/or the web for a NumPy function that can solve a system of linear equations of the form
Ax=b. Once you’ve found the package and function, insert those names into the
function placeholders in the cell below.
# find the missing package and function func = np.your.function # Do not modify the code below def q1(A,b): return func(A,b)
Given NumPy array A, return an array that consists of every entry of A that is in an even row and in an odd column.
def q2(A): """ Input: A - MxN NumPy array Output: Returns a NumPy array that consists of every entry of A that has an even row index and has an odd column index. Example: A = np.array([[ 1 2 3 4 5] [ 6 7 8 9 10] [11 12 13 14 15] [16 17 18 19 20] [21 22 23 24 25]]) Output = np.array([[ 2 4] [12 14] [22 24]]) """ # YOUR CODE HERE
Given an MxN NumPy array, first find the indices of the maximum value in each row of the array, then return the largest of the indices.
def q3(A): """ Input: A - MxN NumPy array Output: Return the maximum index of maximum row values of A. Example: A = np.array([[0 1 0 0] [1 0 0 0] [0 0 0 0] [0 0 1 0]]) Output = 2 """ # YOUR CODE HERE return
Given two MxN NumPy arrays, copy every-other column of array A to the right side of array B.
def q4(A, B): """ Inputs: A - MxN NumPy array B - MxP NumPy array Output: Returns an Mx(P+(N/2)) NumPy array where every-other column of A is added to the right side of B in order, starting from index 0. Example: A = np.array([[1,0,0,2] [0,1,0,2] [0,0,1,2]]) B = np.array([[1,2,3] [4,5,6] [7,8,9]]) Output = np.array([[1,2,3,1,0] [4,5,6,0,0] [7,8,9,0,1]]) """ # YOUR CODE HERE return
Given vectors u = [1,2,3,…,N] and v = [2017,2018,2019,…,2017+N-1], create a vector that contains the following sequence: [1
def q5(N): """ Input: N - the number of elements in u and v. Output: Returns the sequence: np.array([1*2017,2*2018,...,N*(2017+N-1)]) Example: N = 5 Output = np.array([ 2017 4036 6057 8080 10105]) """ u = # YOUR CODE HERE v = # YOUR CODE HERE # YOUR CODE HERE return
Given a NumPy vector v, shift all of the elements in v by n steps to the right; values that “fall off” the right end of v get inserted at the beginning of v, thus the length of v is preserved. You can either attempt to implement this on your own, or, (hint hint) try searching for a related NumPy function that does some/all of the work for you…
def q6(v, N=10): """ Input: v = NumPy vector N = number of steps to shift v to the right Output: Returns v shifted to the right by N steps. Example: v = np.array([0,1,2,3,4,5]) N = 3 Output = np.array([3,4,5,0,1,2]) """ # YOUR CODE HERE return
Given an MxM identity matrix, convert this to an (M-N)x(M-N) identity matrix WITHOUT using numpy.eye().
def q7(I=np.eye(10), N=4): """ Input: I - MxM NumPy array representing the identity matrix N - number of rows and columns to cut from I Output: Returns an (M-N)x(M-N) NumPy identity array. Example: I = np.eye(10) N = 8 Output = np.array([[1,0] [0,1]]) """ # YOUR CODE HERE; REMEMBER, YOU CANNOT USE np.eye()! return
Given a square NxN NumPy array A, return a Python list of the values along the diagonal of A, sorted in descending order.
def q8(A): """ Input: A - NxN NumPy array Output: Returns a Python list containing the diagonal of A sorted in descending order. Example: A = np.array([[1,2,3] [4,5,6] [7,8,9]]) Output = [9,5,1] """ # YOUR CODE HERE return
Given two differently sized matrices, “pad” the matrices with the smaller dimensions with rows/columns of zeros until they are the same size as one another. Add the padding to the bottom (if adding rows) and to the right (if adding columns). Hint: there might be a NumPy function that does something similar/exactly to this, but it’s good practice to try this yourself.
def q9(A,B): """ Input: A - MxN NumPy array B - YxZ NumPy array Output: Returns the zero-padded versions of each array such that they are of equivalent dimensions. Padding is added to the bottom and right. Example: A = np.array([[1,2,3] [4,5,6]]) B = np.array([[1,1] [1,1] [1,1]]) Output = np.array([[1,2,3] [4,5,6] [0,0,0]]), np.array([[1,1,0] [1,1,0] [1,1,0]]) """ # YOUR CODE HERE return A, B
Given an MxN matrix, A, and an NxM matrix, B, concatenate (side-by-side) the first p rows of A with the transpose of the last p columns of B.
def q10(A, B, p): """ Input: A - MxN NumPy array B - NxM NumPy array p - the number of rows from A to concatenate with the number of columns from B Output: Returns the side-by-side concatenation of the first p rows of A with the transpose of the last p columns of B. Example: A = np.array([[1,1,1] [1,1,1] [1,1,1] [1,1,1]]) B = np.array([[1,2,3,4] [5,6,7,8] [9,10,11,12]]) p = 2 Output = np.array([[1,1,1,3,7,11] [1,1,1,4,8,12]]) """ # YOUR CODE HERE return