This page was generated from `source/notebooks/L2/1_numpy.ipynb`_.
Binder badge

NumPy arrays

The NumPy array, formally called ndarray in NumPy documentation, is the real workhorse of data structures for scientific and engineering applications. The NumPy array is similar to a list but where all the elements of the list are of the same type. The elements of a NumPy array are usually numbers, but can also be booleans, strings, or other objects. When the elements are numbers, they must all be of the same type.

[3]:
import numpy as np

Creating Numpy Arrays

There are a number of ways to initialize new numpy arrays, for example from

  • a Python list or tuples

  • using functions that are dedicated to generating numpy arrays, such as arange, linspace, etc.

  • reading data from files which will be covered in the files section

From lists

For example, to create new vector and matrix arrays from Python lists we can use the numpy.array function.

[4]:
#this is a list
a = [0, 0, 1, 4, 7, 16, 31, 64, 127]
[5]:
#this creates an array out of a list
b=np.array(a)
type(b)
[5]:
numpy.ndarray

Using array-generating functions

For larger arrays it is inpractical to initialize the data manually, using explicit python lists. Instead we can use one of the many functions in numpy that generate arrays of different forms. Some of the more common are:

[6]:
# create a range

x = np.arange(0, 10, 1) # arguments: start, stop, step
x
[6]:
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
[7]:
x = np.arange(-1, 1, 0.1)
x
[7]:
array([-1.00000000e+00, -9.00000000e-01, -8.00000000e-01, -7.00000000e-01,
       -6.00000000e-01, -5.00000000e-01, -4.00000000e-01, -3.00000000e-01,
       -2.00000000e-01, -1.00000000e-01, -2.22044605e-16,  1.00000000e-01,
        2.00000000e-01,  3.00000000e-01,  4.00000000e-01,  5.00000000e-01,
        6.00000000e-01,  7.00000000e-01,  8.00000000e-01,  9.00000000e-01])

linspace and logspace

The linspace function creates an array of N evenly spaced points between a starting point and an ending point. The form of the function is linspace(start, stop, N).If the third argument N is omitted,then N=50.

[8]:
# using linspace, both end points ARE included
np.linspace(0, 10,25)
[8]:
array([ 0.        ,  0.41666667,  0.83333333,  1.25      ,  1.66666667,
        2.08333333,  2.5       ,  2.91666667,  3.33333333,  3.75      ,
        4.16666667,  4.58333333,  5.        ,  5.41666667,  5.83333333,
        6.25      ,  6.66666667,  7.08333333,  7.5       ,  7.91666667,
        8.33333333,  8.75      ,  9.16666667,  9.58333333, 10.        ])

logspace is doing equivelent things with logaritmic spacing. Other types of array creation techniques are listed below. Try around with these commands to get a feeling what they do.

[9]:
np.logspace(0, 10, 10, base=np.e)
[9]:
array([1.00000000e+00, 3.03773178e+00, 9.22781435e+00, 2.80316249e+01,
       8.51525577e+01, 2.58670631e+02, 7.85771994e+02, 2.38696456e+03,
       7.25095809e+03, 2.20264658e+04])

mgrid

mgrid generates a multi-dimensional matrix with increasing value entries, for example in columns and rows:

[14]:
x, y = np.mgrid[0:1:0.1, 0:5] # similar to meshgrid in MATLAB
[15]:
x
[15]:
array([[0. , 0. , 0. , 0. , 0. ],
       [0.1, 0.1, 0.1, 0.1, 0.1],
       [0.2, 0.2, 0.2, 0.2, 0.2],
       [0.3, 0.3, 0.3, 0.3, 0.3],
       [0.4, 0.4, 0.4, 0.4, 0.4],
       [0.5, 0.5, 0.5, 0.5, 0.5],
       [0.6, 0.6, 0.6, 0.6, 0.6],
       [0.7, 0.7, 0.7, 0.7, 0.7],
       [0.8, 0.8, 0.8, 0.8, 0.8],
       [0.9, 0.9, 0.9, 0.9, 0.9]])
[13]:
y
[13]:
array([[0, 1, 2, 3, 4],
       [0, 1, 2, 3, 4],
       [0, 1, 2, 3, 4],
       [0, 1, 2, 3, 4],
       [0, 1, 2, 3, 4]])

diag

diag generates a diagonal matrix with the list supplied to it. The values can be also offset from the main diagonal.

[16]:
# a diagonal matrix
np.diag([1,2,3])
[16]:
array([[1, 0, 0],
       [0, 2, 0],
       [0, 0, 3]])
[18]:
# diagonal with offset from the main diagonal
np.diag([1,2,3], k=-1)
[18]:
array([[0, 0, 0, 0],
       [1, 0, 0, 0],
       [0, 2, 0, 0],
       [0, 0, 3, 0]])

zeros and ones

zeros and ones creates a matrix with the dimensions given in the argument and filled with 0 or 1.

[19]:
np.zeros((3,3))
[19]:
array([[0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.]])
[20]:
np.ones((3,3))
[20]:
array([[1., 1., 1.],
       [1., 1., 1.],
       [1., 1., 1.]])

Manipulating NumPy arrays

Slicing

Slicing is the name for extracting part of an array by the syntax M[lower:upper:step]

[21]:
A = np.array([1,2,3,4,5])
A
[21]:
array([1, 2, 3, 4, 5])
[23]:
A[1:4]
[23]:
array([2, 3, 4])

Any of the three parameters in M[lower:upper:step] can be ommited.

[24]:
A[::] # lower, upper, step all take the default values
[24]:
array([1, 2, 3, 4, 5])
[25]:
A[::2] # step is 2, lower and upper defaults to the beginning and end of the array
[25]:
array([1, 3, 5])

Negative indices counts from the end of the array (positive index from the begining):

[26]:
A = np.array([1,2,3,4,5])
[27]:
A[-1] # the last element in the array
[27]:
5
[29]:
A[::-1] # the last three elements
[29]:
array([5, 4, 3, 2, 1])

Index slicing works exactly the same way for multidimensional arrays:

[30]:
A = np.array([[n+m*10 for n in range(5)] for m in range(5)])
A
[30]:
array([[ 0,  1,  2,  3,  4],
       [10, 11, 12, 13, 14],
       [20, 21, 22, 23, 24],
       [30, 31, 32, 33, 34],
       [40, 41, 42, 43, 44]])
[31]:
# a block from the original array
A[1:4, 1:4]
[31]:
array([[11, 12, 13],
       [21, 22, 23],
       [31, 32, 33]])

Note: Differences

Slicing can be effectively used to calculate differences for example for the calculation of derivatives. Here the position \(y_i\) of an object has been measured at times \(t_i\) and stored in an array each. We wish to calculate the average velocity at the times \(t_{i}\) from the arrays by

\begin{equation} v_{i}=\frac{y_i-y_{i-1}}{t_{i}-t_{i-1}} \end{equation}

[32]:
y = np.array([ 0. , 1.3, 5. , 10.9, 18.9, 28.7, 40. ])
t = np.array([ 0. , 0.49, 1. , 1.5 , 2.08, 2.55, 3.2 ])
[33]:
v = (y[1:]-y[:-1])/(t[1:]-t[:-1])
v
[33]:
array([ 2.65306122,  7.25490196, 11.8       , 13.79310345, 20.85106383,
       17.38461538])

Reshaping

Arrays can be reshaped into any form, which contains the same number of elements.

[34]:
a=np.zeros(9)
[35]:
a.reshape(3,3)
[35]:
array([[0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.]])

Adding a new dimension: newaxis

With newaxis, we can insert new dimensions in an array, for example converting a vector to a column or row matrix.

[36]:
v = np.array([1,2,3])
[37]:
np.shape(v)
[37]:
(3,)
[38]:
# make a column matrix of the vector v
v[:, np.newaxis]
[38]:
array([[1],
       [2],
       [3]])
[39]:
# column matrix
v[:,np.newaxis].shape
[39]:
(3, 1)
[40]:
# row matrix
v[np.newaxis,:].shape
[40]:
(1, 3)

Stacking and repeating arrays

Using function repeat, tile, vstack, hstack, and concatenate we can create larger vectors and matrices from smaller ones. Please try the individual functions yourself in your notebook. We wont discuss them in detail.

Tile and repeat

[ ]:
a = np.array([[1, 2], [3, 4]])
[ ]:
# repeat each element 3 times
np.repeat(a, 3)
[ ]:
# tile the matrix 3 times
np.tile(a, 3)

Concatenate

[ ]:
b = np.array([[5, 6]])
[ ]:
np.concatenate((a, b), axis=0)
[ ]:
np.concatenate((a, b.T), axis=1)

Hstack and vstack

[ ]:
np.vstack((a,b))
[ ]:
np.hstack((a,b.T))

Applying mathematical functions

All kinds of mathematica operations can be carried out on arrays. Typically these operation act element wise as seen from the examples below.

Operation involving one array

[41]:
a=np.arange(0, 10, 1.5)
a
[41]:
array([0. , 1.5, 3. , 4.5, 6. , 7.5, 9. ])
[42]:
a/2
[42]:
array([0.  , 0.75, 1.5 , 2.25, 3.  , 3.75, 4.5 ])
[43]:
a**2
[43]:
array([ 0.  ,  2.25,  9.  , 20.25, 36.  , 56.25, 81.  ])
[45]:
 np.sin(a)
[45]:
array([ 0.        ,  0.99749499,  0.14112001, -0.97753012, -0.2794155 ,
        0.93799998,  0.41211849])
[46]:
np.exp(-a)
[46]:
array([1.00000000e+00, 2.23130160e-01, 4.97870684e-02, 1.11089965e-02,
       2.47875218e-03, 5.53084370e-04, 1.23409804e-04])
[47]:
(a+2)/3
[47]:
array([0.66666667, 1.16666667, 1.66666667, 2.16666667, 2.66666667,
       3.16666667, 3.66666667])

Operations involving multiple arrays

Operation between multiple vectors allow in particular very quick operations. The operations address then elements of the same index. These operations are called vector operations since the concern the whole array at the same time. The product between two vectors results therefore not in a dot product, which gives one number but in an array of multiplied elements.

[48]:
a = np.array([34., -12, 5.])
b = np.array([68., 5.0, 20.]),
[49]:
a+b
[49]:
array([102.,  -7.,  25.])
[50]:
a*b
[50]:
array([2312.,  -60.,  100.])
[51]:
a*np.exp(-b)
[51]:
array([ 9.98743918e-29, -8.08553640e-02,  1.03057681e-08])