{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Exercise: Linear algebra operations with NumPy" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[NumPy](https://numpy.org/) is the library that gives Python its ability to work with numerical data at speed. This exercise will demonstrate how to use NumPy for common tasks such as the creation and manipulation of arrays, linear algebra operations, and statistical operations. Some of the content is inspired by one (out of many) [cheat sheets](https://github.com/Nothingaholic/python-cheat-sheet) found on [github](https://github.com)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The structure of this note:\n", "\n", "1. N-dimensional arrays\n", "1. Array shape manipulations\n", "1. Linear algebra and numerical operations\n", "1. Array manipulations routines ( select and split)\n", "1. Statistical operations" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import numpy as np" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## N dimensional arrays" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### What are arrays?\n", "Arrays are a data structure for storing elements of the same type. Each item stored in an array is called an element. Each location of an element in an array has a numerical index which is used to identify the element. Arrays (and the indices) can be of more than one dimension." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1D vs 2D Array\n", "1D array (i.e., single dimensional array) stores a list of variables of the same data type. It is possible to access each variable using the index.\n", "\n", "2D array (i.e, multi-dimensional array) stores data in a format consisting of rows and columns. Each index will then be two numbers." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Arrays vs Lists\n", "- Arrays use less memory than lists\n", "- Arrays have significantly more functionality\n", "- Arrays require data to be homogeneous; lists do not\n", "- Arithmetic on arrays operates like matrix multiplication\n", "- NumPy is used to work with arrays. The array object in NumPy is called `ndarray`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Create Arrays" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([1, 2, 3])" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Create 1 dimensional array (vector)\n", "np.array([1,2,3]) # Create vector as a row" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[1],\n", " [2],\n", " [3]])" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.array([[1],[2],[3]]) #Create vector as a column. This is now a two-dimensional array." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[1, 2, 3],\n", " [4, 5, 6]])" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.array([(1,2,3),(4,5,6)]) # Two-dimensional array mith multiple rows and columns." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`````{admonition} Zero-based indexing\n", ":class: tip\n", "Note that elements in `ndarrays` are accessed with zero-based indexing, meaning that the numbering starts with zero.\n", "`````" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`````{admonition} Row-major order\n", ":class: tip\n", "Note that elements in `ndarrays` are stored in memory using *row-major order*.\n", "\n", "In computing, row-major order and column-major order are methods for storing multidimensional arrays in linear storage such as random access memory.\n", "\n", "The difference between the orders lies in which elements of an array are contiguous in memory. In row-major order, the consecutive elements of a row reside next to each other, whereas the same holds true for consecutive elements of a column in column-major order. \n", "\n", "Data layout is critical for correctly passing arrays between programs written in different programming languages. It is also important for performance when traversing an array because modern CPUs process sequential data more efficiently than nonsequential data.\n", "`````" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Creating a Sparse Matrix\n", "A sparse matrix is a matrix in which most of the elements are zero. Sparse matrix data formats only store nonzero elements and assume all other values will be zero, leading to significant computational savings.\n", "\n", "Let’ say, we want to create a NumPy array with two nonzero values, then converted it into a sparse matrix. If we view the sparse matrix, we can see that only the nonzero values are stored:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " (1, 1)\t1\n", " (2, 0)\t3\n" ] } ], "source": [ "from scipy import sparse\n", "matrix_large = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n", " [0, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n", " [3, 0, 0, 0, 0, 0, 0, 0, 0, 0]])\n", "# Create compressed sparse row (CSR) matrix\n", "matrix_large_sparse = sparse.csr_matrix(matrix_large)\n", "print(matrix_large_sparse)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the example above, (1, 1) and (2, 0) represent the indices of the non-zero values 1 and 3, respectively. For example, the element 1 is in the second row and second column (remember that numbering starts at zero!)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Create special `ndarray`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here are some examples of `numpy` methods that can be used to create special arrays." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([0., 0., 0.])" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Create 1d array of zeros, length 3\n", "np.zeros(3) " ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[0., 0., 0.],\n", " [0., 0., 0.]])" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# 2x3 array of zeros\n", "np.zeros((2,3))" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[1., 1., 1., 1.],\n", " [1., 1., 1., 1.],\n", " [1., 1., 1., 1.]])" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Create array of ones, 3x4 (3 rows, 4 columns)\n", "np.ones((3,4))" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[1., 0., 0., 0., 0.],\n", " [0., 1., 0., 0., 0.],\n", " [0., 0., 1., 0., 0.],\n", " [0., 0., 0., 1., 0.],\n", " [0., 0., 0., 0., 1.]])" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# 5x5 array of 0 with 1 on diagonal (Identity matrix)\n", "np.eye(5)" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([ 0., 20., 40., 60., 80., 100.])" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Array of 6 evenly divided values from 0 to 100\n", "np.linspace(0,100,6)" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([0, 3, 6, 9])" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Array of values from 0 to less than 10 with step 3 \n", "np.arange(0,10,3)" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[5, 5, 5],\n", " [5, 5, 5]])" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# 2x3 array with all values 5\n", "np.full((2,3),5)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[0.44182313, 0.19051442, 0.83397729],\n", " [0.93012309, 0.33034137, 0.27159291]])" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# 2x3 array of random floats between 0–1\n", "np.random.rand(2,3)" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[65.82836754, 31.77520274, 86.3442516 ],\n", " [28.89289403, 16.22230131, 10.99366733]])" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# 2x3 array of random floats between 0–100\n", "np.random.rand(2,3)*100" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[0, 1, 0],\n", " [2, 3, 1]])" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# 2x3 array with random ints between 0–4\n", "np.random.randint(5,size=(2,3))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Remember that useful `shift-Tab-Tab` shortcut in Jupyter notebooks to quickly find the docstring of a function. You can try it on any of the functions shown above.\n", "\n", "You can also use the `np.info` function." ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " eye(N, M=None, k=0, dtype=, order='C')\n", "\n", "Return a 2-D array with ones on the diagonal and zeros elsewhere.\n", "\n", "Parameters\n", "----------\n", "N : int\n", " Number of rows in the output.\n", "M : int, optional\n", " Number of columns in the output. If None, defaults to `N`.\n", "k : int, optional\n", " Index of the diagonal: 0 (the default) refers to the main diagonal,\n", " a positive value refers to an upper diagonal, and a negative value\n", " to a lower diagonal.\n", "dtype : data-type, optional\n", " Data-type of the returned array.\n", "order : {'C', 'F'}, optional\n", " Whether the output should be stored in row-major (C-style) or\n", " column-major (Fortran-style) order in memory.\n", "\n", " .. versionadded:: 1.14.0\n", "\n", "Returns\n", "-------\n", "I : ndarray of shape (N,M)\n", " An array where all elements are equal to zero, except for the `k`-th\n", " diagonal, whose values are equal to one.\n", "\n", "See Also\n", "--------\n", "identity : (almost) equivalent function\n", "diag : diagonal 2-D array from a 1-D array specified by the user.\n", "\n", "Examples\n", "--------\n", ">>> np.eye(2, dtype=int)\n", "array([[1, 0],\n", " [0, 1]])\n", ">>> np.eye(3, k=1)\n", "array([[0., 1., 0.],\n", " [0., 0., 1.],\n", " [0., 0., 0.]])\n" ] } ], "source": [ "np.info(np.eye) # View documentation for np.eye" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Array shape manipulations" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Shape, size, length and data type\n", "It will be valuable to check the shape and size of an array both for further calculations and simply as a sanity check after some operation.\n", "\n", "NumPy arrays have an attribute called shape that returns a tuple with each index having the number of corresponding elements." ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(2, 3)" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "arr = np.array([(1,2,3),(4,5,6)])\n", "arr.shape # Returns dimensions of arr (rows,columns)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the example above, (2, 3) means that the array has 2 dimensions, and each dimension has 3 elements." ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "6" ] }, "execution_count": 34, "metadata": {}, "output_type": "execute_result" } ], "source": [ "arr.size # Return number of elements in arr" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "2" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "len(arr) # Number of array dimension" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "dtype('int64')" ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "source": [ "arr.dtype # Return type of elements in arr" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'int64'" ] }, "execution_count": 37, "metadata": {}, "output_type": "execute_result" } ], "source": [ "arr.dtype.name # Name of data type" ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "dtype('float64')" ] }, "execution_count": 40, "metadata": {}, "output_type": "execute_result" } ], "source": [ "arr_int = arr.astype(float) # Convert an array to a different type\n", "arr_int.dtype" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Reshape\n", "It is important to know how to reshape the NumPy arrays so that our data meets the expectation of specific Python libraries. For example, Scikit- learn requires a one-dimensional array of output variables to be shaped like a two-dimensional array with one column and outcomes for each row.\n", "\n", "Other algorithms might require input to be specified as a three-dimensional array. One example is the Long Short-Term Memory recurrent neural network in Keras that expects a three-dimensional input of samples, timesteps, and features.\n", "\n", "`np.reshape()` allows us to restructure an array so that we maintain the same data but it is organized as a different number of rows and columns.\n", "\n", "Note: The shape of the original and new matrix contains the same number of elements (i.e, same size)." ] }, { "cell_type": "code", "execution_count": 188, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "arr1 = [ 1 2 3 4 5 6 7 8 9 10]\n", "\n", "The shape of array `arr1` is: (10,)\n", "\n", "Reshaping the array into:\n", "[[ 1 2 3 4 5]\n", " [ 6 7 8 9 10]]\n", "The shape is now: (2, 5)\n", "\n" ] } ], "source": [ "# Reshaping\n", "arr1 = np.arange(1, 11) # numbers 1 to 10\n", "print(f'arr1 = {arr1}\\n')\n", "print(f'The shape of array `arr1` is: {arr1.shape}\\n') # Prints a tuple for the one dimension.\n", "\n", "arr1_2d = arr1.reshape(2, 5)\n", "print(f'Reshaping the array into:\\n{arr1_2d}')\n", "print(f'The shape is now: {arr1_2d.shape}\\n')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `reshape` method has a useful option of specifying `-1` as one of the shape elements. This will then automatically become the required number to maintain the array size." ] }, { "cell_type": "code", "execution_count": 189, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[ 1, 2, 3, 4, 5],\n", " [ 6, 7, 8, 9, 10]])" ] }, "execution_count": 189, "metadata": {}, "output_type": "execute_result" } ], "source": [ "arr1.reshape(2, 5)\n", "arr1.reshape(-1, 5) # same as above: arr1.reshape(2, 5)\n", "arr1.reshape(2, -1) # same as above: arr1.reshape(2, 5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Transposing a matrix (rows <=> columns) or flattening into a one-dimensional array. " ] }, { "cell_type": "code", "execution_count": 239, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[ 1, 6],\n", " [ 2, 7],\n", " [ 3, 8],\n", " [ 4, 9],\n", " [ 5, 10]])" ] }, "execution_count": 239, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Transpose\n", "arr1_2d.T" ] }, { "cell_type": "code", "execution_count": 190, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])" ] }, "execution_count": 190, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Flattening a Matrix\n", "arr1_2d.flatten()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that the default of flatten is to use the row-major order. Can you find the argument to flatten with column-major instead?" ] }, { "cell_type": "code", "execution_count": 54, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[ 1, 2, 3, 4],\n", " [ 5, 6, 7, 8],\n", " [ 9, 10, 1, 2]])" ] }, "execution_count": 54, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Resize arr1_2d to 3 rows, 4 columns \n", "np.resize(arr1_2d, (3,4))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Array to/from List conversion" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Arrays need to be declared whereas lists do not need declaration because they are a part of Python’s syntax. This is the reason lists are more often used than arrays. But in case of performing some arithmetic function to our list, we should go with arrays instead.\n", "\n", "It is possible to convert between lists and arrays (although list to array conversion requires the object to be of a single type)." ] }, { "cell_type": "code", "execution_count": 191, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[1 2 3]\n", " [4 5 6]]\n" ] }, { "data": { "text/plain": [ "numpy.ndarray" ] }, "execution_count": 191, "metadata": {}, "output_type": "execute_result" } ], "source": [ "arr = np.array([(1,2,3),(4,5,6)])\n", "print(arr)\n", "type(arr)" ] }, { "cell_type": "code", "execution_count": 192, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[1, 2, 3], [4, 5, 6]]\n" ] }, { "data": { "text/plain": [ "list" ] }, "execution_count": 192, "metadata": {}, "output_type": "execute_result" } ], "source": [ "arr_to_list = arr.tolist() # Convert arr to a Python list\n", "print(arr_to_list)\n", "type(arr_to_list)" ] }, { "cell_type": "code", "execution_count": 193, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[1 2 3]\n", " [4 5 6]]\n" ] }, { "data": { "text/plain": [ "numpy.ndarray" ] }, "execution_count": 193, "metadata": {}, "output_type": "execute_result" } ], "source": [ "arr_from_list = np.array(arr_to_list) # Convert list to array\n", "print(arr_from_list)\n", "type(arr_from_list)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The following operation will not work with a list, but does work with an array." ] }, { "cell_type": "code", "execution_count": 194, "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "unsupported operand type(s) for /: 'list' and 'int'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0marr_to_list\u001b[0m\u001b[0;34m/\u001b[0m\u001b[0;36m4\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mTypeError\u001b[0m: unsupported operand type(s) for /: 'list' and 'int'" ] } ], "source": [ "arr_to_list/4" ] }, { "cell_type": "code", "execution_count": 195, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[0.25, 0.5 , 0.75],\n", " [1. , 1.25, 1.5 ]])" ] }, "execution_count": 195, "metadata": {}, "output_type": "execute_result" } ], "source": [ "arr_from_list/4" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Linear algebra and numerical operations" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's explore some of the numerical operations that can be performed with matrices. We will often use methods from the [`np.linalg`](https://numpy.org/doc/stable/reference/routines.linalg.html) module. Note that these NumPy linear algebra functions rely on BLAS and LAPACK to provide efficient low level implementations of standard linear algebra algorithms. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Trace\n", "The trace is the sum of all the diagonal elements of a square matrix." ] }, { "cell_type": "code", "execution_count": 247, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "6" ] }, "execution_count": 247, "metadata": {}, "output_type": "execute_result" } ], "source": [ "arr = np.array([[2, 0, 0], [0, 2, 0], [0, 0, 2]])\n", "np.trace(arr)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Inverse" ] }, { "cell_type": "code", "execution_count": 55, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[-2. , 1. ],\n", " [ 1.5, -0.5]])" ] }, "execution_count": 55, "metadata": {}, "output_type": "execute_result" } ], "source": [ "matrix = np.array([[1, 2],\n", " [3, 4]])\n", "# Calculate inverse of matrix\n", "np.linalg.inv(matrix)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Determinant" ] }, { "cell_type": "code", "execution_count": 249, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0.0" ] }, "execution_count": 249, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Determinant\n", "matrix = np.array([[1, 2, 3],\n", " [2, 4, 6],\n", " [3, 8, 9]])\n", "# Return determinant of matrix\n", "np.linalg.det(matrix)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Rank" ] }, { "cell_type": "code", "execution_count": 58, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3" ] }, "execution_count": 58, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Rank of a matrix\n", "matrix = np.array([[1, 1, 3],\n", " [1, 2, 4],\n", " [1, 3, 0]])\n", "# Return matrix rank\n", "np.linalg.matrix_rank(matrix)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Question: Why is the rank of the 3x3 matrix below not equal to 3?" ] }, { "cell_type": "code", "execution_count": 196, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "2" ] }, "execution_count": 196, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Rank of a matrix\n", "matrix = np.array([[1, 7, 3],\n", " [1, 9, 4],\n", " [1, 1, 0]])\n", "# Return matrix rank\n", "np.linalg.matrix_rank(matrix)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Eigenvalues and eigenvectors" ] }, { "cell_type": "code", "execution_count": 65, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Eigenvalues: [ 1.33484692e+01 -1.34846923e+00 -1.26963291e-15]\n", "Eigenvectors: [[ 0.16476382 0.79969966 0.40824829]\n", " [ 0.50577448 0.10420579 -0.81649658]\n", " [ 0.84678513 -0.59128809 0.40824829]]\n" ] } ], "source": [ "# Eigenvalues and Eigenvectors\n", "matrix = np.array([[0, 1, 2],\n", " [3, 4, 5],\n", " [6, 7, 8]])\n", "# Calculate eigenvalues and eigenvectors\n", "eigenvalues, eigenvectors = np.linalg.eig(matrix)\n", "print(f'Eigenvalues: {eigenvalues}')\n", "print(f'Eigenvectors: {eigenvectors}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`````{admonition} Operating on matrices with special properties\n", ":class: tip\n", "Note that there are methods that are targeted to work on matrices with certain properties, such as `np.linalg.eigh` for complex Hermitian (conjugate symmetric) or a real symmetric matrix. Be careful, however, as it can produce incorrect results if applied to an object that does not have the required property.\n", "`````" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Scalar operations\n", "When we add, subtract, multiply or divide a matrix by a number, this is called the scalar operation. During scalar operations, the scalar value is applied to each element in the array, therefore, the function returns a new matrix with the same number of rows and columns." ] }, { "cell_type": "code", "execution_count": 69, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[ 1, 32, 243],\n", " [1024, 3125, 7776]])" ] }, "execution_count": 69, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Scalar Operations\n", "new_arr = np.arange(1,10)\n", "\n", "# Add 1 to each array element\n", "np.add(new_arr,1)\n", "\n", "np.subtract(arr,2) # Subtract 2 from each array element\n", "np.multiply(arr,3) # Multiply each array element by 3\n", "np.divide(arr,4) # Divide each array element by 4 (returns np.nan for division by zero)\n", "np.power(arr,5) # Raise each array element to the 5th power" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Matrix Operations\n", "A matrix can only be added to (or subtracted from) another matrix if the two matrices have the same dimensions, that is, they must have the same number of rows and columns.\n", "\n", "When multiplying matrices, we take rows of the first matrix and multiply them by the corresponding columns of the second matrix." ] }, { "cell_type": "code", "execution_count": 90, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "arr1 =\n", " [[1 2]\n", " [3 4]]\n", "arr2 =\n", " [[2 2]\n", " [2 2]]\n" ] } ], "source": [ "arr1 = np.array([[1,2],[3,4]])\n", "arr2 = np.ones_like(arr1)*2\n", "print('arr1 =\\n',arr1)\n", "print('arr2 =\\n',arr2)" ] }, { "cell_type": "code", "execution_count": 87, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Elementwise add arr2 to arr1:\n", " [[3 4]\n", " [5 6]]\n", "Elementwise subtract arr2 from arr1:\n", " [[-1 0]\n", " [ 1 2]]\n", "Elementwise multiply arr1 by arr2:\n", " [[2 4]\n", " [6 8]]\n", "Elementwise divide arr1 by arr2:\n", " [[0.5 1. ]\n", " [1.5 2. ]]\n", "Elementwise raise arr1 to the power of arr2:\n", " [[ 1 4]\n", " [ 9 16]]\n", "Return True if the arrays have the same elements and shape: False\n" ] } ], "source": [ "print('Elementwise add arr2 to arr1:\\n',np.add(arr1,arr2)) \n", "print('Elementwise subtract arr2 from arr1:\\n',np.subtract(arr1,arr2)) \n", "print('Elementwise multiply arr1 by arr2:\\n',np.multiply(arr1,arr2))\n", "print('Elementwise divide arr1 by arr2:\\n',np.divide(arr1,arr2))\n", "print('Elementwise raise arr1 to the power of arr2:\\n',np.power(arr1,arr2))\n", "print('Return True if the arrays have the same elements and shape:',np.array_equal(arr1,arr2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Other math operations" ] }, { "cell_type": "code", "execution_count": 91, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[1, 2, 3],\n", " [4, 5, 6]])" ] }, "execution_count": 91, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.sqrt(arr) # Square root of each element in the array\n", "np.sin(arr) # Sine of each element in the array\n", "np.log(arr) # Natural log of each element in the array\n", "np.abs(arr) # Absolute value of each element in the array\n", "np.ceil(arr) # Rounds up to the nearest int\n", "np.floor(arr) # Rounds down to the nearest int\n", "np.round(arr) # Rounds to the nearest int" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Dot product versus elementwise multiplication" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The dot product of two matrices can be computed with the `np.dot` function. Note that it is not the same as the arithmetic `∗` operation that performs elementwise multiplication (Hadamard product)." ] }, { "cell_type": "code", "execution_count": 96, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "A =\n", " [[1 2]\n", " [3 4]]\n", "The dot product A \\cdot A:\n", "[[ 7 10]\n", " [15 22]]\n", "Element-wise product A * A:\n", "[[ 1 4]\n", " [ 9 16]]\n" ] } ], "source": [ "A = np.array([[1,2],[3,4]])\n", "print('A =\\n',A)\n", "print(r'The dot product A \\cdot A:')\n", "AA = np.dot(A,A)\n", "print(AA)\n", "print(r'Element-wise product A * A:')\n", "print(A*A)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The dot product of a matrix by its inverse returns the identity matrix (with small floating point errors). **Verify that this is true.**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`````{admonition} Further examples with numpy\n", ":class: tip\n", "The [NumPy tutorial](https://www.numpy.org/devdocs/user/quickstart.html) is a good resource.\n", "`````" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Array Manipulation Routines" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Adding/removing elements\n", "The `np.append()` function is used to append values to the end of a given array." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Adding/removing Elements\n", "np.append ([0, 1, 2], [[3, 4, 5], [6, 7, 8]])\n", "np.append([[0, 1, 2], [3, 4, 5]],[[6, 7, 8]], axis=0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The axis along which values are appended. If the axis is not given, both array and values are flattened before use." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`np.insert()'` is used to insert the element before the given index of the array." ] }, { "cell_type": "code", "execution_count": 99, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([ 1, 2, 10, 3, 4, 5, 6])" ] }, "execution_count": 99, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.insert(arr,2,10) # Inserts 10 into arr before index 2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "While `np.delete()` can be used to delete any row and column from the `ndarray`." ] }, { "cell_type": "code", "execution_count": 101, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "arr =\n", " [[ 0 1 2 3]\n", " [ 4 5 6 7]\n", " [ 8 9 10 11]]\n" ] }, { "data": { "text/plain": [ "array([[ 0, 1, 2],\n", " [ 4, 5, 6],\n", " [ 8, 9, 10]])" ] }, "execution_count": 101, "metadata": {}, "output_type": "execute_result" } ], "source": [ "arr = np.arange(12).reshape(3, 4)\n", "print('arr =\\n',arr)\n", "np.delete(arr,2,axis=0) # Deletes row (axis=0) on index 2 of arr\n", "np.delete(arr,3,axis=1) # Deletes column (axis=1) on index 3 of arr" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Sorting\n", "The `np.sort()` function can be used to sort the list in both ascending and descending order." ] }, { "cell_type": "code", "execution_count": 108, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[4, 5, 6, 8],\n", " [1, 2, 4, 8],\n", " [1, 2, 4, 5]])" ] }, "execution_count": 108, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Sort\n", "oned_arr = np.array([3,8,5,1])\n", "np.sort(oned_arr)\n", "arr = np.array([[5, 4, 6, 8],\n", " [1, 2, 4, 8],\n", " [1, 5, 2, 4]])\n", "# sort each column of arr\n", "np.sort(arr, axis=0)\n", "\n", "# sort each row of X\n", "np.sort(arr, axis=1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Concatenate arrays" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Concatenating means putting contents of two or more arrays in a single array. In NumPy, we join arrays by axes. We pass a sequence of arrays that we want to join to the `np.concatenate()` function, along with the axis. If the axis is not explicitly passed, it is taken as 0." ] }, { "cell_type": "code", "execution_count": 104, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([1, 2, 3, 4, 5, 6])" ] }, "execution_count": 104, "metadata": {}, "output_type": "execute_result" } ], "source": [ "arr1 = np.array([1, 2, 3])\n", "arr2 = np.array([4, 5, 6])\n", "# Adds arr2 as rows to the end of arr1\n", "np.concatenate((arr1, arr2), axis=0)" ] }, { "cell_type": "code", "execution_count": 106, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[ 1, 2, 3, 7, 8, 9],\n", " [ 4, 5, 6, 10, 11, 12]])" ] }, "execution_count": 106, "metadata": {}, "output_type": "execute_result" } ], "source": [ "arr1 = np.array([[1, 2, 3],[4, 5, 6]])\n", "arr2 = np.array([[7, 8, 9],[10, 11, 12]])\n", "# Adds arr2 as columns to end of arr1\n", "np.concatenate((arr1,arr2),axis=1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Splitting arrays" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We use `np.array_split()` for splitting arrays. We pass it the array we want to split and the number of splits." ] }, { "cell_type": "code", "execution_count": 111, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[array([1, 2]), array([3, 4]), array([5]), array([6])]\n" ] } ], "source": [ "# Splitting NumPy Arrays\n", "# Splits arr into 4 sub-arrays\n", "arr = np.array([1, 2, 3, 4, 5, 6])\n", "newarr = np.array_split(arr, 4) \n", "print(newarr)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "or `np.hsplit()` to split horizontally." ] }, { "cell_type": "code", "execution_count": 112, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[array([1, 2, 3]), array([4, 5, 6])]\n" ] } ], "source": [ "arr = np.array([1, 2, 3, 4, 5, 6])\n", "# Splits arr horizontally on the 2nd index\n", "newarr = np.hsplit(arr, 2)\n", "print(newarr)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Select element(s)\n", "NumPy offers a wide variety of methods for selecting and slicing elements or groups of elements in arrays." ] }, { "cell_type": "code", "execution_count": 120, "metadata": {}, "outputs": [], "source": [ "user_name = np.array(['Katie','Bob','Scott','Liz','Sam'])\n", "articles = np.array([100, 38, 91, 7, 25])" ] }, { "cell_type": "code", "execution_count": 121, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'Sam'" ] }, "execution_count": 121, "metadata": {}, "output_type": "execute_result" } ], "source": [ "user_name[4] # Returns the element at index 4" ] }, { "cell_type": "code", "execution_count": 122, "metadata": {}, "outputs": [], "source": [ "articles[3] = 17 # Assigns array element on index 1 the value 4" ] }, { "cell_type": "code", "execution_count": 123, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array(['Katie', 'Bob', 'Scott'], dtype='50)]" ] }, { "cell_type": "code", "execution_count": 135, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[ 0 1 2 3 4 5]\n", " [ 6 7 8 9 10 11]\n", " [12 13 14 15 16 17]\n", " [18 19 20 21 22 23]\n", " [24 25 26 27 28 29]\n", " [30 31 32 33 34 35]]\n" ] } ], "source": [ "# Multi-dimensinal arrays\n", "arr = np.arange(36).reshape(6,6)\n", "print(arr)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Exercise: **Try to perform the following selection or assignments of elements**:\n", "* Returns the 2D array element on index [2][5]\n", "* Assigns array element on index [1][3] the value 10\n", "* Returns rows 0,1,2\n", "* Returns the elements on rows 0,1,2 at column 4\n", "* Returns returns rows 0,1\n", "* Returns the elements at index 1 on all rows\n", "* Returns all elements that are divisible by 3.\n", "\n", "Solutions are found below" ] }, { "cell_type": "code", "execution_count": 145, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "17" ] }, "execution_count": 145, "metadata": {}, "output_type": "execute_result" } ], "source": [ "arr[2,5] # Returns the 2D array element on index [2][5]" ] }, { "cell_type": "code", "execution_count": 146, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[ 0 1 2 3 4 5]\n", " [ 6 7 8 10 10 11]\n", " [12 13 14 15 16 17]\n", " [18 19 20 21 22 23]\n", " [24 25 26 27 28 29]\n", " [30 31 32 33 34 35]]\n" ] } ], "source": [ "arr[1,3]=10 # Assigns array element on index [1][3] the value 10\n", "print(arr)" ] }, { "cell_type": "code", "execution_count": 147, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[ 0, 1, 2, 3, 4, 5],\n", " [ 6, 7, 8, 10, 10, 11],\n", " [12, 13, 14, 15, 16, 17]])" ] }, "execution_count": 147, "metadata": {}, "output_type": "execute_result" } ], "source": [ "arr[0:3] # Returns rows 0,1,2" ] }, { "cell_type": "code", "execution_count": 148, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([ 4, 10, 16])" ] }, "execution_count": 148, "metadata": {}, "output_type": "execute_result" } ], "source": [ "arr[0:3,4] # Returns the elements on rows 0,1,2 at column 4" ] }, { "cell_type": "code", "execution_count": 149, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[ 0, 1, 2, 3, 4, 5],\n", " [ 6, 7, 8, 10, 10, 11]])" ] }, "execution_count": 149, "metadata": {}, "output_type": "execute_result" } ], "source": [ "arr[:2] # Returns returns rows 0,1" ] }, { "cell_type": "code", "execution_count": 150, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([ 1, 7, 13, 19, 25, 31])" ] }, "execution_count": 150, "metadata": {}, "output_type": "execute_result" } ], "source": [ "arr[:,1] # Returns the elements at index 1 on all rows" ] }, { "cell_type": "code", "execution_count": 151, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([ 0, 3, 6, 12, 15, 18, 21, 24, 27, 30, 33])" ] }, "execution_count": 151, "metadata": {}, "output_type": "execute_result" } ], "source": [ "arr[np.mod(arr,3)==0] # returns all elements that are divisible by 3." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Statistical Operations" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Find the Maximum and Minimum Values\n", "Often we want to know the maximum and minimum value in an array or subset of an array. This can be accomplished with the `np.max` and `np.min` methods. Using the axis parameter we can also apply the operation along a certain axis." ] }, { "cell_type": "code", "execution_count": 153, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "89" ] }, "execution_count": 153, "metadata": {}, "output_type": "execute_result" } ], "source": [ "articles = np.array([[10, 23, 17],\n", " [41, 54, 65],\n", " [71, 18, 89]])\n", "# Return maximum element\n", "np.max(articles)" ] }, { "cell_type": "code", "execution_count": 154, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([71, 54, 89])" ] }, "execution_count": 154, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.max(articles, axis=0) # Find maximum element in each column" ] }, { "cell_type": "code", "execution_count": 156, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([23, 65, 89])" ] }, "execution_count": 156, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.max(articles, axis=1) # Find maximum element in each row" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Average, Variance, and Standard Deviation\n", "We can easily get descriptive statistics about the whole matrix or do calculations along a single axis." ] }, { "cell_type": "code", "execution_count": 168, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[51.61411251 98.63727273 78.51299733 47.67397746 38.13394163 79.94982351]\n", " [20.79711846 28.45940689 15.43565341 65.56489745 62.2039607 82.87812458]\n", " [63.63362561 33.48293456 93.26403931 32.75818936 82.11566672 60.11872442]\n", " [13.36831461 89.02273101 70.53427644 82.72384089 1.75790964 0.77140016]]\n" ] } ], "source": [ "# 4x6 array of random floats between 0–100\n", "arr=np.random.rand(4,6)*100\n", "print(arr)" ] }, { "cell_type": "code", "execution_count": 172, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "53.89220580775009" ] }, "execution_count": 172, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.mean(arr) # Returns the mean of all elements" ] }, { "cell_type": "code", "execution_count": 173, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1293.412939386002" ] }, "execution_count": 173, "metadata": {}, "output_type": "execute_result" } ], "source": [ "arr.sum() # Returns the sum of all elements" ] }, { "cell_type": "code", "execution_count": 174, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([37.3532928 , 62.4005863 , 64.43674162, 57.18022629, 46.05286967,\n", " 55.92951817])" ] }, "execution_count": 174, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.mean(arr,axis=0) # Returns the mean along a specific axis" ] }, { "cell_type": "code", "execution_count": 175, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[ 51.61411251, 150.25138524, 228.76438257, 276.43836003,\n", " 314.57230166, 394.52212517],\n", " [ 20.79711846, 49.25652534, 64.69217876, 130.25707621,\n", " 192.4610369 , 275.33916148],\n", " [ 63.63362561, 97.11656017, 190.38059948, 223.13878884,\n", " 305.25445556, 365.37317998],\n", " [ 13.36831461, 102.39104562, 172.92532207, 255.64916296,\n", " 257.4070726 , 258.17847276]])" ] }, "execution_count": 175, "metadata": {}, "output_type": "execute_result" } ], "source": [ "arr.cumsum(axis=1) #Cumulative sum of the elements" ] }, { "cell_type": "code", "execution_count": 177, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "863.0809445721999" ] }, "execution_count": 177, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.var(arr) # Returns the variance" ] }, { "cell_type": "code", "execution_count": 178, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([21.35148044, 25.43722262, 22.54357085, 38.33277061])" ] }, "execution_count": 178, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.std(arr,axis=1) # Returns the standard deviation along specific axis" ] }, { "cell_type": "code", "execution_count": 179, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[ 1. , -0.27255049, -0.2131374 , 0.42867177],\n", " [-0.27255049, 1. , -0.23623217, -0.40174745],\n", " [-0.2131374 , -0.23623217, 1. , -0.46511119],\n", " [ 0.42867177, -0.40174745, -0.46511119, 1. ]])" ] }, "execution_count": 179, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.corrcoef(arr) # Returns correlation coefficient of array" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.17" } }, "nbformat": 4, "nbformat_minor": 4 }