Scroll back to top

    NumPy tutorial

    Code outputs are displayed below code boxes.

    We'll go through some examples here to refresh the basics of numpy. There are also plenty of other guides online:

    First, to use a package we need to import it.

    Text successfully copied to clipboard!

        
            import numpy as np
        
    

    Motivation

    Question: Why bother with numpy?

    Answer: Numpy arrays are faster and more efficient than lists when working with numerical data.

    Example: pure Python vs Numpy

    Let's compare the running time for a basic operation in pure python and numpy. The following code blocks create two random matrices \( A \) and \( B \), then compute \( C = A B \) (you don't need to worry about understanding the code at this time).

    Pure Python

    The first code block is with pure python using lists (no numpy).

    Text successfully copied to clipboard!

        
            import random
          import time
    
          def create_rand_matrix(n):
              # Create random matrix without numpy
              M = []
              for i in range(n):
                  row = []
                  for j in range(n):
                      row.append(random.random())
                  M.append(row)
              return M
              
          start = time.perf_counter()
    
          n = 100
    
          A = create_rand_matrix(n)
          B = create_rand_matrix(n)
              
          # Compute C = AB
          C = []
          for i in range(n):
              row = []
              for j in range(n):
                  sum = 0
                  for k in range(n):
                      sum += A[i][k] * B[k][j]
                  row.append(sum)
              C.append(row)
              
          stop = time.perf_counter()
          print(stop - start)
        
    
    0.3338270999993256
        

    Numpy

    The same thing using numpy arrays.

    Text successfully copied to clipboard!

        
            start = time.perf_counter()
    
          n = 100
    
          A = np.random.random((n, n))
          B = np.random.random((n, n))
          C = A @ B
    
          stop = time.perf_counter()
          print(stop - start)
        
    
    0.04226900000139722
        

    Creating arrays

    Numerous ways of creating arrays are available.

    Creating arrays from a list

    Text successfully copied to clipboard!

        
            vals_list = [1, 3, 2, 8]
          vals_array = np.array(vals_list)
    
          print("vals_list: ", vals_list)
          print("vals_array: ", vals_array)
        
    
    vals_list: [1, 3, 2, 8]
    vals_array: [1 3 2 8]
        

    Creating arrays using built-in functions

    np.arrange()

    Evenly spaced numbers in a interval (meant for use with an integer step size). Examples:

    Text successfully copied to clipboard!

        
            print(np.arange(10)) # stop
          print(np.arange(4,12)) # start and stop
          print(np.arange(4,12,2)) # start, stop, and step
        
    
    [0 1 2 3 4 5 6 7 8 9]
    [ 4  5  6  7  8  9 10 11]
    [ 4  6  8 10]
        

    np.linspace()

    Evenly spaced numbers in an interval. Examples:

    Text successfully copied to clipboard!

        
            print(np.linspace(0,1,11)) #start, stop, step
          print(np.linspace(0,10,11))
        
    
    [0.  0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1. ]
    [ 0.  1.  2.  3.  4.  5.  6.  7.  8.  9. 10.]
        

    Note that unlike np.arange(), here the last number is included, and the third parameter is the number of steps.

    Other special vectors

    Often need to initialize to zeros or vector of all 1s

    Text successfully copied to clipboard!

        
            print(np.zeros(5))
          print(np.ones(4))
        
    
    print(np.zeros(5))
    print(np.ones(4))
        

    Data types

    Recall, Python is dynamically typed. Types are changed automatically as needed. And, lists can hold anything. A single list could hold strings and integers.

    What about arrays? Numpy arrays are statically typed.

    So, what are the data types of the arrays we created above? What are the available datatypes? How do we specify what datatype we want?

    Text successfully copied to clipboard!

        
            vals_list = [1,3,2,8]
          vals_array = np.array(vals_list)
          vals_arrayf = np.array(vals_list, dtype=float)
    
          print("vals_array: ", vals_array)
          print("vals_arrayf: ", vals_arrayf)
    
          print(type(vals_list))
          print(type(vals_array))
          print(type(vals_arrayf))
          print(vals_array.dtype)
          print(vals_arrayf.dtype)
        
    
    vals_array:  [1 3 2 8]
    vals_arrayf:  [1. 3. 2. 8.]
    <class 'list'>
    <class 'numpy.ndarray'>
    <class 'numpy.ndarray'>
    int32
    float64
        

    The dtype argument is valid for most array-creation functions, including numpy.zeros, np.ones, and np.arange.

    In Python3, the dtype of an array that results from mathematical operations will automatically adjust to whatever is sensible.

    Text successfully copied to clipboard!

        
            print('integers: ', vals_array)
          print('more integers: ', vals_array * 3)
          print('floats: ', vals_array / 3)
        
    
    integers:  [1 3 2 8]
    more integers:  [ 3  9  6 24]
    floats:  [0.33333333 1.         0.66666667 2.66666667]
        

    You can also copy an array and change the dtype.

    Text successfully copied to clipboard!

        
            arr = np.arange(10.0)
          x = arr.astype(int)
          print('arr: ', arr)
          print('x: ', x)
        
    
    arr:  [0. 1. 2. 3. 4. 5. 6. 7. 8. 9.]
    x:  [0 1 2 3 4 5 6 7 8 9]
        

    Accessing array elements

    Now that we actually have arrays, how do we get things from them? Indexed from 0, bracket notation for accessing

    Text successfully copied to clipboard!

        
            print(vals_arrayf)
          print(vals_arrayf[0])
        
    
    [1. 3. 2. 8.]
    1.0
        

    Negative indexing is also allowed. An index of -1 will return the last element, an index of -2 returns the second to last, and so on.

    Text successfully copied to clipboard!

        
            print(vals_arrayf)
          print("last element: ", vals_arrayf[-1])
          print("second to last element: ", vals_arrayf[-2])
        
    
    [1. 3. 2. 8.]
    last element:  8.0
    second to last element:  2.0
        

    What if I want a section of an array? Array slicing.

    Text successfully copied to clipboard!

        
            print(vals_arrayf[1:3]) #starting index (inclusive): stopping index (exclusive)
          print(vals_arrayf[1:2])
        
    
    [3. 2.]
    [3.]
        

    In addition to a start and end, you can also choose a step for the slice.

    Text successfully copied to clipboard!

        
            print(vals_arrayf)
          print(vals_arrayf[::2])  # even indices 
          print(vals_arrayf[1::2])  # odd indices
          print(vals_arrayf[::-1])  # handy way to reverse an array
        
    
    [1. 3. 2. 8.]
    [1. 2.]
    [3. 8.]
    [8. 2. 3. 1.]
        

    Copies vs. views (accidentally changing your array)

    You need to be careful with numpy arrays if you are

    • trying to copy part of an array, or
    • passing an array to a function

    You might be in for a nasty surprise if you change an element.

    Text successfully copied to clipboard!

        
            simple = np.arange(5)
          small = simple[:2]
          print(simple)
          print('')
          print(small)
          print('')
    
          small[0] = 7
          print(small)
          print('')
          print(simple)  # shouldn't have changed, right?
        
    
    [0 1 2 3 4]
    
    [0 1]
    
    [7 1]
    
    [7 1 2 3 4]
        

    This happens because small is something called a "view" of simple, rather than a copy. This helps numpy save memory and speed up your program, but it can lead to tricky bugs if it is not your intent. In general, it can be difficult to tell whether something will be a view or a copy.

    Functions also do not make copies of their input arrays.

    Text successfully copied to clipboard!

        
            def foo(x):  # notice that x is not returned
              x[0] = 100
    
    
          foo(simple)
          print(simple)
        
    
    [100   1   2   3   4]
        

    If you think you are accidentally changing your array elsewhere in your code, you can copy it to be on the safe side. This will be slow your program down and use more memory, but it can help debugging and save a lot of headaches.

    Text successfully copied to clipboard!

        
            simple = np.arange(5)
          print('before:')
          print(simple)
    
          my_copy = simple[:2].copy()
          my_copy[1] = 10
    
          foo(simple.copy())
    
          print('after:')
          print(simple)
        
    
    before:
    [0 1 2 3 4]
    after:
    [0 1 2 3 4]
        

    Numpy function

    There are trigonometric functions we will use that are built into Numpy

    Text successfully copied to clipboard!

        
            theta = np.pi/4
    
          print(f"sin(pi/4) = {np.sin(theta):.3f}")
          print(f"cos(pi/4) = {np.cos(theta):.3f}")
          print(f"tan(pi/4) = {np.tan(theta):.3f}")
    
          #You can also call inverse functions
    
          x = 1e-3
    
          y = np.arcsin(x) #returns y in radians
    
          x1 = -4
          y1 = -3
    
          theta1 = np.arctan(y1/x1) #does not choose correct quadrant
          theta2 = np.arctan2(y1,x1) #chooses correct quadrant
    
          print(f"Note that these angles are in different quadrants: {theta1:.5f}, {theta2:.5f} (rds)")
        
    
    sin(pi/4) = 0.707
    cos(pi/4) = 0.707
    tan(pi/4) = 1.000
    Note that these angles are in different quadrants: 0.64350, -2.49809 (rds)
        

    You've probably noticed by now that Numpy's trigonometric functions either input/output angles in radians. There are multiple ways to convert an angle to radians (and back to degrees)

    Text successfully copied to clipboard!

        
            alpha_deg = 180
    
          #code converts to radians, comments show revertion to degrees
          alpha_rd1 = np.deg2rad(alpha_deg)   #np.rad2deg()
          alpha_rd2 = np.radians(alpha_deg)   #np.degrees()
          alpha_rd3 = alpha_deg*np.pi/180   #*180/np.pi
        
    

    You can also use Numpy for vector arithmetics...

    Text successfully copied to clipboard!

        
            vector_a = np.array([1,0,0])
          vector_b = np.array([0,1,0,])
    
          #c = a ● b
          vector_c = np.dot(vector_a, vector_b) #recall that would be the same as np.dot(vector_b, vector_a)
    
          #d = a x b
          vector_d = np.cross(vector_a, vector_b) #order matters here
        
    

    ... or linear algebra. Here's an example of solving a system of linear equations using two different methods.

    Text successfully copied to clipboard!

        
            A = np.array([[1, 1, 0],
                      [0, -3, 1], 
                      [2, 1, -3]])
    
          b = np.array([6, 7, 5])
    
          #Ax = b
          x1 = np.linalg.solve(A,b)
    
          #x = A^-1 b
          x2 = np.linalg.inv(A) @ b
    
          #or finding the magnitude/length of a vector
          b_magnitude = np.linalg.norm(b)