%run set_env.py
%matplotlib inline
Check versions: numpy version :'1.20.3' matplotlib version:'3.4.3'
ndarray is the NumPy base/super class.
The ndarray class has several fields/methods.
Among them:
# arange(20) -> Linux x86_64: 'int64'
a=np.arange(20).reshape(4,5)
print(f"a := np.arange(20).reshape((4,5)) :\n{a}\n")
print(f" type(a) :'{type(a)}'")
print(f" a.dtype:'{a.dtype}'")
print(f" a.ndim:'{a.ndim}'")
print(f" a.shape:'{a.shape}'")
print(f" a.size:'{a.size}'")
print(f" a.itemsize:'{a.itemsize}'")
print(f" a.nbytes:'{a.nbytes}'")
print(f" a.strides:'{a.strides}'\n")
print(f" a.real :\n{a.real}\n")
print(f" a.imag :\n{a.imag}\n")
print(f" a.flags:\n{a.flags}")
print(f" a.T:\n{a.T}")
a := np.arange(20).reshape((4,5)) : [[ 0 1 2 3 4] [ 5 6 7 8 9] [10 11 12 13 14] [15 16 17 18 19]] type(a) :'<class 'numpy.ndarray'>' a.dtype:'int64' a.ndim:'2' a.shape:'(4, 5)' a.size:'20' a.itemsize:'8' a.nbytes:'160' a.strides:'(40, 8)' a.real : [[ 0 1 2 3 4] [ 5 6 7 8 9] [10 11 12 13 14] [15 16 17 18 19]] a.imag : [[0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0]] a.flags: C_CONTIGUOUS : True F_CONTIGUOUS : False OWNDATA : False WRITEABLE : True ALIGNED : True WRITEBACKIFCOPY : False UPDATEIFCOPY : False a.T: [[ 0 5 10 15] [ 1 6 11 16] [ 2 7 12 17] [ 3 8 13 18] [ 4 9 14 19]]
Notice the fundamental difference!
a = np.arange(20).reshape(4,5)
b = np.arange(20.).reshape(4,5)
print(f"a := np.arange(20).reshape((4,5)) :\n{a}\n")
print(f"b :=b np.arange(20.).reshape((4,5)) :\n{b}\n")
print(f" type(a) :'{type(a)}'")
print(f" type(b) :'{type(b)}'")
a := np.arange(20).reshape((4,5)) : [[ 0 1 2 3 4] [ 5 6 7 8 9] [10 11 12 13 14] [15 16 17 18 19]] b :=b np.arange(20.).reshape((4,5)) : [[ 0. 1. 2. 3. 4.] [ 5. 6. 7. 8. 9.] [10. 11. 12. 13. 14.] [15. 16. 17. 18. 19.]] type(a) :'<class 'numpy.ndarray'>' type(b) :'<class 'numpy.ndarray'>'
print(f" a.ndim:'{a.ndim}'")
print(f" b.ndim:'{b.ndim}'")
print(f" a.shape:'{a.shape}'")
print(f" b.shape:'{b.shape}'")
print(f" a.size:'{a.size}'")
print(f" b.size:'{b.size}'")
print(f" a.dtype:'{a.dtype}'")
print(f" b.dtype:'{b.dtype}'")
print(f" a.itemsize:'{a.itemsize}'")
print(f" b.itemsize:'{b.itemsize}'")
print(f" a.nbytes:'{a.nbytes}'")
print(f" b.nbytes:'{b.nbytes}'")
a.ndim:'2' b.ndim:'2' a.shape:'(4, 5)' b.shape:'(4, 5)' a.size:'20' b.size:'20' a.dtype:'int64' b.dtype:'float64' a.itemsize:'8' b.itemsize:'8' a.nbytes:'160' b.nbytes:'160'
Numpy support several native data types e.g.:
# Create arrays of a certain data type
i1=np.arange(10,dtype='int32')
print(f"i1 := np.arange(10,dtype='int32'):\n {i1}")
print(f" i1.dtype:'{i1.dtype}'")
print(f" i1.size :'{i1.size}'")
print(f" i1.itemsize:'{i1.itemsize}'")
print(f" i1.nbytes:'{i1.nbytes}'")
z1=np.arange(10,dtype='complex128')
print(f"\nz1 := np.arange(10,dtype='complex128'):\n {z1}\n")
print(f" z1.dtype:'{z1.dtype}'")
print(f" z1.size :'{z1.size}'")
print(f" z1.itemsize:'{z1.itemsize}'")
print(f" z1.nbytes:'{z1.nbytes}'")
i1 := np.arange(10,dtype='int32'): [0 1 2 3 4 5 6 7 8 9] i1.dtype:'int32' i1.size :'10' i1.itemsize:'4' i1.nbytes:'40' z1 := np.arange(10,dtype='complex128'): [0.+0.j 1.+0.j 2.+0.j 3.+0.j 4.+0.j 5.+0.j 6.+0.j 7.+0.j 8.+0.j 9.+0.j] z1.dtype:'complex128' z1.size :'10' z1.itemsize:'16' z1.nbytes:'160'
# Native scalar data types in numpy:
for key in np.sctypes:
print(f" key:{key:<8s} value:{np.sctypes[key]}")
key:int value:[<class 'numpy.int8'>, <class 'numpy.int16'>, <class 'numpy.int32'>, <class 'numpy.int64'>] key:uint value:[<class 'numpy.uint8'>, <class 'numpy.uint16'>, <class 'numpy.uint32'>, <class 'numpy.uint64'>] key:float value:[<class 'numpy.float16'>, <class 'numpy.float32'>, <class 'numpy.float64'>, <class 'numpy.float128'>] key:complex value:[<class 'numpy.complex64'>, <class 'numpy.complex128'>, <class 'numpy.complex256'>] key:others value:[<class 'bool'>, <class 'object'>, <class 'bytes'>, <class 'str'>, <class 'numpy.void'>]
The class np.dtype allows one to create compound data types
(structured arrays cfr. C structs).
For more info:
https://docs.scipy.org/doc/numpy/user/basics.rec.html
To perform a data type conversion (cast) between different scalar data types:
# Change dtype of an array
f1 = np.array([1.0,2.5,3.0,7.2])
print(f"\nf1 := np.array([1.0,2.5,3.0,7.2]):\n {f1}")
i3 = f1.astype('complex128')
print(f" i3 := f1.astype(dtype='complex128'):\n {i3}\n")
print(f" i3.dtype:'{i3.dtype}'")
print(f" i3.size :'{i3.size}'")
print(f" i3.itemsize:'{i3.itemsize}'")
print(f" i3.nbytes:'{i3.nbytes}'")
f1 := np.array([1.0,2.5,3.0,7.2]): [1. 2.5 3. 7.2] i3 := f1.astype(dtype='complex128'): [1. +0.j 2.5+0.j 3. +0.j 7.2+0.j] i3.dtype:'complex128' i3.size :'4' i3.itemsize:'16' i3.nbytes:'64'
Numpy has specific initialization functions
A few of the most important ones:
# Diag function
a1 = np.array([i for i in range(20)]).reshape(4,5)
print(f" a1:\n{a1}")
# Extract the diagonal
print(f" \n np.diag(a1):\n{np.diag(a1)}")
# Extract a SHIFTED diagonal
print(f" \n np.diag(a1,k=1):\n{np.diag(a1,k=1)}")
# Create a diagonal matrix
print(f" \n np.diag(range(4)):\n{np.diag(range(4))}")
# Create a SHIFTED diagonal matrix
print(f" \n np.diag(range(4),k=1):\n{np.diag(range(4),k=1)}")
a1: [[ 0 1 2 3 4] [ 5 6 7 8 9] [10 11 12 13 14] [15 16 17 18 19]] np.diag(a1): [ 0 6 12 18] np.diag(a1,k=1): [ 1 7 13 19] np.diag(range(4)): [[0 0 0 0] [0 1 0 0] [0 0 2 0] [0 0 0 3]] np.diag(range(4),k=1): [[0 0 0 0 0] [0 0 1 0 0] [0 0 0 2 0] [0 0 0 0 3] [0 0 0 0 0]]
# Memory allocation
a2=np.empty((2,3))
print(f"a2=np.empty((2,3)) :\n{a2}\n")
# Create ones on the diagonal
a3=np.eye(5,4,k=1)
print(f"a3=np.eye(5,4,k=1,dtype='float64') :\n{a3}\n")
# Use a function (index based) to generate entries
def myfunc(x,y):
return x+2*y
a4=np.fromfunction(lambda x,y: x+2*y,(2,5),dtype='float64')
print(f"a4=np.fromfunction(lambda x,y: x+2*y,(2,5),dtype='float64') :\n{a4}\n")
a10=np.fromfunction(myfunc,(2,5),dtype='float64')
print(f"a10:{a10}")
# Return a SQUARE identity matrix
a5=np.identity(5,dtype='int64')
print(f"a5=np.identity(5,dtype='int64') :\n{a5}\n")
a6=np.ones((2,7),dtype='int64')
print(f"a6=np.ones((2,7),dtype='int64') :\n{a6}\n")
a7=np.zeros((3,4),dtype='complex128')
print(f"a7=np.zeros((3,4),dtype='complex128') :\n{a7}\n")
a2=np.empty((2,3)) : [[4.67277158e-310 0.00000000e+000 0.00000000e+000] [0.00000000e+000 0.00000000e+000 0.00000000e+000]] a3=np.eye(5,4,k=1,dtype='float64') : [[0. 1. 0. 0.] [0. 0. 1. 0.] [0. 0. 0. 1.] [0. 0. 0. 0.] [0. 0. 0. 0.]] a4=np.fromfunction(lambda x,y: x+2*y,(2,5),dtype='float64') : [[0. 2. 4. 6. 8.] [1. 3. 5. 7. 9.]] a10:[[0. 2. 4. 6. 8.] [1. 3. 5. 7. 9.]] a5=np.identity(5,dtype='int64') : [[1 0 0 0 0] [0 1 0 0 0] [0 0 1 0 0] [0 0 0 1 0] [0 0 0 0 1]] a6=np.ones((2,7),dtype='int64') : [[1 1 1 1 1 1 1] [1 1 1 1 1 1 1]] a7=np.zeros((3,4),dtype='complex128') : [[0.+0.j 0.+0.j 0.+0.j 0.+0.j] [0.+0.j 0.+0.j 0.+0.j 0.+0.j] [0.+0.j 0.+0.j 0.+0.j 0.+0.j]]
You can also fill up your ndarray with random numbers
The Numpy random generator is based on the Mersenne Twister.
https://en.wikipedia.org/wiki/Mersenne_Twister
import numpy.random as rnd
a = rnd.random((4,5))
print(f" a:\n{a}\n")
b = rnd.randint(5,15,(4,6))
print(f" b:\n{b}\n")
a: [[3.94606318e-01 8.45238172e-01 3.25814326e-01 3.11498246e-01 2.42952454e-01] [6.38646025e-01 7.35127033e-01 8.73541195e-01 6.58374658e-01 5.52337678e-01] [5.85534654e-04 9.65623936e-01 2.86566301e-01 1.88678765e-01 1.12736092e-01] [3.83845591e-02 4.69818458e-01 2.21441105e-01 8.08761863e-01 3.01690511e-01]] b: [[12 6 12 8 7 5] [12 9 13 6 5 12] [ 8 11 9 12 14 12] [ 7 13 12 12 14 11]]
Create a 5x5 matrix with the following (staggered) entries:
$
\begin{equation*}
a =
\begin{pmatrix}
0 & 1 & 2 & 3 & 4 \\
1 & 2 & 3 & 4 & 5 \\
2 & 3 & 4 & 5 & 6 \\
3 & 4 & 5 & 6 & 7 \\
4 & 5 & 6 & 7 & 8
\end{pmatrix}
\end{equation*}
$
(Hint: use the np.fromfunction)
Create a 6x6 'tridiagonal' matrix (see https://en.wikipedia.org/wiki/Tridiagonal_matrix)
where:
Boolean matrices are key components for fancy indexing (see later)
(Hint: 0:False & non-zero:True)
Let P be a 8x8 transition matrix representing a discrete (periodic) Markov Chain:
$
\begin{equation*}
P =
\begin{pmatrix}
0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 \\
q & 0 & p & 0 & 0 & 0 & 0 & 0 \\
0 & q & 0 & p & 0 & 0 & 0 & 0 \\
0 & 0 & q & 0 & p & 0 & 0 & 0 \\
0 & 0 & 0 & q & 0 & p & 0 & 0 \\
0 & 0 & 0 & 0 & q & 0 & p & 0 \\
0 & 0 & 0 & 0 & 0 & q & 0 & p \\
0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 \\
\end{pmatrix}
\end{equation*}
$
where $q:=1-p$.
def myfunc(x,y):
return x +y
A = np.fromfunction(lambda x,y: x+y,(5,5))
A
myfunc(3,4)
B=np.eye(6) + np.eye(6,k=1) + np.eye(6,k=-1)
print(B.astype('bool'))
p = 0.7
q = 0.3
lst=[1] + 6*[p]
print(lst)
a =[1]
a.extend([2,3,4])
print(a)
b=[1]
b
C=np.diag([1]+6*[p],k=1)+np.diag(6*[q]+[1],k=-1)
import numpy.linalg as la
la.matrix_power(C,10001)
[[ True True False False False False] [ True True True False False False] [False True True True False False] [False False True True True False] [False False False True True True] [False False False False True True]] [1, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7] [1, 2, 3, 4]
array([[0. , 0.01183409, 0. , 0.06443004, 0. , 0.35078577, 0. , 0.5729501 ], [0.00355023, 0. , 0.02761287, 0. , 0.15033676, 0. , 0.81850014, 0. ], [0. , 0.01183409, 0. , 0.06443004, 0. , 0.35078577, 0. , 0.5729501 ], [0.00355023, 0. , 0.02761287, 0. , 0.15033676, 0. , 0.81850014, 0. ], [0. , 0.01183409, 0. , 0.06443004, 0. , 0.35078577, 0. , 0.5729501 ], [0.00355023, 0. , 0.02761287, 0. , 0.15033676, 0. , 0.81850014, 0. ], [0. , 0.01183409, 0. , 0.06443004, 0. , 0.35078577, 0. , 0.5729501 ], [0.00355023, 0. , 0.02761287, 0. , 0.15033676, 0. , 0.81850014, 0. ]])
# %load ../solutions/ex2.py
Numpy allows one to create empty, zeros, ones ndarrays
with the SAME shape as an existing ndarrays.
(Syntax: y = np.zeros_like(x), etc.)
np.set_printoptions(precision=4)
a = np.random.random((4,6))
print(f" a:\n{a}")
b = np.zeros_like(a,dtype='int32')
print(f" b:\n{b}")
# To create a proper vector a Python array-type object is required
a = np.array(5)
print(f" a:{a}")
print(f" ndim:{a.ndim}")
print(f" shape:{a.shape}")
print(f" type:{type(a)}")
# should either be
b = np.array([5])
print(f"\n b:{b}")
print(f" ndim:{b.ndim}")
print(f" shape:{b.shape}")
print(f" type:{type(b)}")
# OR
c = np.atleast_1d(5)
print(f"\n c:{c}")
print(f" ndim:{c.ndim}")
print(f" shape:{c.shape}")
print(f" type:{type(c)}")