Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Image: ubuntu2004
(More on) Numpy
Camilo A. Garcia Trillos - 2020
In this notebook
We learn more about the Numpy package: we look at arrays and matrices, and some of the associated modules.
We start by importing the modules we will use in this notebook: only numpy.
Let us look deeper at the numpy library. The numpy library contains useful tools to work with vectors and matrices, solve linear operations and generate random entries.
We have a look at data types and their implications, basic operations, some canonical matrices, matrix inversion and solution of linear systems.
Two important methods for arrays are size and shape. The former give sus the total number of elements in an array. The latter tells us how these elements are organised, whcih is particularly useful to define matrices or higher order tensors.
This means that at this time, all elements are organised in a unique row. We can render this vector into a 'column vector' or into a 2x2 matrix with the same entries simply assigning the shape we want or by using the reshape method.
When assigning the shape property, the array itself changes (this is called 'inplace'):
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
<ipython-input-8-685c588a6827> in <module>
1 print(a[0,1]) # This is OK
2 print(a[0]) # this is OK
----> 3 a[1] #This is an error
IndexError: index 1 is out of bounds for axis 0 with size 1
The above error occurs because there is only one entry on the first dimension of the array.
Arrays, unlike lists, cannot in general combine different types of entries. The vector we created above was an integer vector (note the lack of dots at the end of all prints!). Hence, if we try to assign a float, we will get a roundup version of it
As mentioned above, 9.2 becomes 9. Let su try now by creating a vector of floats from the start:
Note the difference! The array b was initialized to have floats, thanks to writing 1.0 instead of 1. Thus, in contrast to a, it contains 9.2 (instead of 9, as the array a does)
As lists, arrays are assigned by reference. What follows should not surprise you if you followed the first notebook.
If an independent copy of a given array is needed, we can use the method copy
In the previous notebook, we looked at the method arange. Here are ways to use it:
Example 1
Example 2
Example 3
Example 4
However, recall that the following gives an error:
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-39-276b4363f031> in <module>
1 g = np.array([1,2,3])
2 print(d.shape,g.shape)
----> 3 print(g*d) # This gives an error
ValueError: operands could not be broadcast together with shapes (3,) (5,2)
If you have not figure it out, the explanation is as follows: suppose we are operating the arrays a and b. Suppose that the shape of a is , while the shapes of b are :
If and both are different from one or empty for some , then the operation cannot be done.
If and one of the two is equal to one or empty for some , then the operation is done by completing the missing dimensions via copying the remaining components.
Look again to the example to make sure you understand this process. In particular, see how the empty spaces can be accomodated to the above template.
Note: arrays can be tensors, i.e. have more than 2 dimensions. Broadcasting is generalised as you would expect.
Example 5
Observe the following example
The above works, because 1 is taken as an array of empty size, so it can be broadcasted by repeating itself to operate on each entry.
Note though that the following is an error:
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-43-d2424fe8248b> in <module>
----> 1 d ** (-1) # This raises an error
ValueError: Integers to negative integer powers are not allowed.
What's wrong? We initialized d with integers, and there is no well defined change of type for this operation.
Let us introduce some canonical matrices and vectors.
By default these are float. If an integer is desired, this has to be stated explicitly.
Some other useful functions are zeros_like and ones_like: they create zeros and ones of the lentgh and type of the parameter
We have also an identity matrix and the function diag. Let us look at what they do:
Diag can also be used to build a diagonal matrix, as shown below.
Recall how to generate a random Gaussian matrix: here we generate a matrix of size 3x3, where each entry is i.i.d. with distribution
We can check that the matrix is invertible, by looking at the rank of the matrix
If the rank is 3 then we can invert the matrix g. We already showed that g ** (-1) does not give the matrix inverse. Instead it computes the reciprocals of each component:
Instead the package numpy has a specific command to obtain the matrix inverse:
Let's check:
Apart from rounding errors this is basically the identity matrix.
We need to be careful though:
Inverting matrices is expensive
Inversion is done numerically, and this algorithm is not stable when applied to certain matrices.
Let us illustrate this point with an example. We are going to use another canonical matrix, the Hilbert matrix. This matrix is famous for being ill conditioned (that is, there is a huge gap between its eigenvalues), which makes it a bad example for inversion algorithms.
Note that the above tests show that we are not really getting a good approximation of the inverse.
There are several ways to account for this problem. One can add a small amount to the diagonal
Note that this solution is much closer to the one vector, and so the inverse is close to what we would like.
But this solution has a bias, that is, we have a systematic error. A more expensive alternative is to add an error that is neglected in probability: we add a small random amount to the matrix, and then average out a couple of solutions
This works specially well when the matrix is a calculated covariance matrix: in this case one can randomly perturb the data before calculating the covariance.
Solving linear systems
Sometimes we are not interested in finding the inverse matrix but rather in solving a liner system like
Doing this directly is usually more efficient.
There is a whole theory on how to solve linear equations(this is at the core of numerical analysis), but we will simply use the predifined functions.
We test it:
Very close to (a vector of ones). Compare with the result we obain using the inverse:
And compare with what we obtain using the modified inverse (with the small perturbation):
Better... but solving the linear system is superior.
Remark: Finding an inverse might be advantageous if several linear systems with the smae matrices have to be solved one after the other.
We consider a one-period market model in a finite probability space.
Create a function 'complete_market_1p(S0,S1)' that receives a vector S0 with the initial prices of the assets in a market, and a matrix S1 that gives the prices of each asset in the market under each scenario, and returns a boolean that states if the market is complete or not.