*This notebook contains an excerpt from the Python Programming and Numerical Methods - A Guide for Engineers and Scientists, the content is also available at Berkeley Python Numerical Methods.*

*The copyright of the book belongs to Elsevier. We also have this interactive book online for a better learning experience. The code is released under the MIT license. If you find this content useful, please consider supporting the work on Elsevier or Amazon!*

< 2.6 Data Structure - Dictionaries | Contents | 2.8 Summary and Problems >

# Introducing Numpy Arrays¶

In the 2nd part of this book, we will study the numerical methods by using Python. We will use array/matrix a lot later in the book. Therefore, here we are going to introduce the most common way to handle arrays in Python using the Numpy module. Numpy is probably the most fundamental numerical computing module in Python.

NumPy is important in scientific computing, it is coded both in Python and C (for speed). On its website, a few important features for Numpy is listed:

a powerful N-dimensional array object

sophisticated (broadcasting) functions

tools for integrating C/C++ and Fortran code

useful linear algebra, Fourier transform, and random number capabilities

Here we will only introduce you the Numpy array which is related to the data structure, but we will gradually touch on other aspects of Numpy in the following chapters.

In order to use Numpy module, we need to import it first. A conventional way to import it is to use “np” as a shortened name.

```
import numpy as np
```

**WARNING!** Of course, you could call it any name, but conventionally, “np” is accepted by the whole community and it is a good practice to use it for obvious purposes.

To define an array in Python, you could use the *np.array* function to convert a list.

**TRY IT!** Create the following arrays:

\(x = \begin{pmatrix} 1 & 4 & 3 \\ \end{pmatrix}\)

\(y = \begin{pmatrix} 1 & 4 & 3 \\ 9 & 2 & 7 \\ \end{pmatrix}\)

```
x = np.array([1, 4, 3])
x
```

```
array([1, 4, 3])
```

```
y = np.array([[1, 4, 3], [9, 2, 7]])
y
```

```
array([[1, 4, 3],
[9, 2, 7]])
```

**NOTE!** A 2-D array could use a nested lists to represent, with the inner list represent each row.

Many times we would like to know the size or length of an array. The array *shape* attribute is called on an array M and returns a 2 × 3 array where the first element is the number of rows in the matrix M and the second element is the number of columns in M. Note that the output of the *shape* attribute is a tuple. The *size* attribute is called on an array M and returns the total number of elements in matrix M.

**TRY IT!** Find the rows, columns and the total size for array y.

```
y.shape
```

```
(2, 3)
```

```
y.size
```

```
6
```

**NOTE!** You may notice the difference that we only use *y.shape* instead of *y.shape()*, this is because *shape* is an attribute rather than a method in this array object. We will introduce more of the object-oriented programming in a later chapter. For now, you need to remember that when we call a method in an object, we need to use the parentheses, while the attribute don’t.

Very often we would like to generate arrays that have a structure or pattern. For instance, we may wish to create the array z = [1 2 3 … 2000]. It would be very cumbersome to type the entire description of z into Python. For generating arrays that are in order and evenly spaced, it is useful to use the *arange* function in Numpy.

**TRY IT!** Create an array *z* from 1 to 2000 with an increment 1.

```
z = np.arange(1, 2000, 1)
z
```

```
array([ 1, 2, 3, ..., 1997, 1998, 1999])
```

Using the *np.arange*, we could create * z* easily. The first two numbers are the start and end of the sequence, and the last one is the increment. Since it is very common to have an increment of 1, if an increment is not specified, Python will use a default value of 1. Therefore

*will have the same result as*

*np.arange(1, 2000)**np.arange(1, 2000, 1)*. Negative or noninteger increments can also be used. If the increment “misses” the last value, it will only extend until the value just before the ending value. For example,

*x = np.arange(1,8,2)*would be [1, 3, 5, 7].

**TRY IT!** Generate an array with [0.5, 1, 1.5, 2, 2.5].

```
np.arange(0.5, 3, 0.5)
```

```
array([ 0.5, 1. , 1.5, 2. , 2.5])
```

Sometimes we want to guarantee a start and end point for an array but still have evenly spaced elements. For instance, we may want an array that starts at 1, ends at 8, and has exactly 10 elements. For this purpose you can use the function *np.linspace*. *linspace* takes three input values separated by commas. So *A = linspace(a,b,n)* generates an array of n equally spaced elements starting from a and ending at b.

**TRY IT!** Use *linspace* to generate an array starting at 3, ending at 9, and containing 10 elements.

```
np.linspace(3, 9, 10)
```

```
array([ 3. , 3.66666667, 4.33333333, 5. , 5.66666667,
6.33333333, 7. , 7.66666667, 8.33333333, 9. ])
```

Getting access to the 1D numpy array is similar to what we described for lists or tuples, it has an index to indicate the location. For example:

```
# get the 2nd element of x
x[1]
```

```
4
```

```
# get all the element after the 2nd element of x
x[1:]
```

```
array([4, 3])
```

```
# get the last element of x
x[-1]
```

```
3
```

For 2D arrays, it is slightly different, since we have rows and columns. To get access to the data in a 2D array M, we need to use M[r, c], that the row r and column c are separated by comma. This is referred to as array indexing. The r and c could be single number, a list and so on. If you only think about the row index or the column index, than it is similar to the 1D array. Let’s use the \(y = \begin{pmatrix} 1 & 4 & 3 \\ 9 & 2 & 7 \\ \end{pmatrix}\) as an example.

**TRY IT!** Get the element at first row and 2nd column of array *y*.

```
y[0,1]
```

```
4
```

**TRY IT!** Get the first row of array *y*.

```
y[0, :]
```

```
array([1, 4, 3])
```

**TRY IT!** Get the last column of array *y*.

```
y[:, -1]
```

```
array([3, 7])
```

**TRY IT!** Get the first and third column of array *y*.

```
y[:, [0, 2]]
```

```
array([[1, 3],
[9, 7]])
```

There are some predefined arrays that are really useful. For example, the *np.zeros*, *np.ones*, and *np.empty* are 3 useful functions. Let’s see the examples.

**TRY IT!** Generate a 3 by 5 array with all the as 0.

```
np.zeros((3, 5))
```

```
array([[ 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0.]])
```

**TRY IT!** Generate a 5 by 3 array with all the element as 1.

```
np.ones((5, 3))
```

```
array([[ 1., 1., 1.],
[ 1., 1., 1.],
[ 1., 1., 1.],
[ 1., 1., 1.],
[ 1., 1., 1.]])
```

**NOTE!** The shape of the array is defined in a tuple with row as the first item, and column as the second. If you only need a 1D array, then it could be only one number as the input: *np.ones(5)*.

**TRY IT!** Generate a 1D empty array with 3 elements.

```
np.empty(3)
```

```
array([ 0., 0., 0.])
```

**NOTE!** The empty array is not really empty, it is filled with random very small numbers.

You can reassign a value of an array by using array indexing and the assignment operator. You can reassign multiple elements to a single number using array indexing on the left side. You can also reassign multiple elements of an array as long as both the number of elements being assigned and the number of elements assigned is the same. You can create an array using array indexing.

**TRY IT!** Let a = [1, 2, 3, 4, 5, 6]. Reassign the fourth element of A to 7. Reassign the first, second, and thrid elements to 1. Reassign the second, third, and fourth elements to 9, 8, and 7.

```
a = np.arange(1, 7)
a
```

```
array([1, 2, 3, 4, 5, 6])
```

```
a[3] = 7
a
```

```
array([1, 2, 3, 7, 5, 6])
```

```
a[:3] = 1
a
```

```
array([1, 1, 1, 7, 5, 6])
```

```
a[1:4] = [9, 8, 7]
a
```

```
array([1, 9, 8, 7, 5, 6])
```

**TRY IT!** Create a zero array b with shape 2 by 2, and set \(b = \begin{pmatrix}
1 & 2 \\
3 & 4 \\
\end{pmatrix}\) using array indexing.

```
b = np.zeros((2, 2))
b[0, 0] = 1
b[0, 1] = 2
b[1, 0] = 3
b[1, 1] = 4
b
```

```
array([[ 1., 2.],
[ 3., 4.]])
```

**WARNING!** Although you can create an array from scratch using indexing, we do not advise it. It can confuse you and errors will be harder to find in your code later. For example, b[1, 1] = 1 will give the result \(b = \begin{pmatrix}
0 & 0 \\
0 & 1 \\
\end{pmatrix}\), which is strange because b[0, 0], b[0, 1], and b[1, 0] were never specified.

Basic arithmetic is defined for arrays. However, there are operations between a scalar (a single number) and an array and operations between two arrays. We will start with operations between a scalar and an array. To illustrate, let c be a scalar, and b be a matrix.

*b + c*, *b − c*, *b * c* and *b / c* adds a to every element of b, subtracts c from every element of b, multiplies every element of b by c, and divides every element of b by c, respectively.

**TRY IT!** Let \(b = \begin{pmatrix}
1 & 2 \\
3 & 4 \\
\end{pmatrix}\). Add and substract 2 from b. Multiply and divide b by 2. Square every element of b. Let c be a scalar. On your own, verify the reflexivity of scalar addition and multiplication: b + c = c + b and cb = bc.

```
b + 2
```

```
array([[ 3., 4.],
[ 5., 6.]])
```

```
b - 2
```

```
array([[-1., 0.],
[ 1., 2.]])
```

```
2 * b
```

```
array([[ 2., 4.],
[ 6., 8.]])
```

```
b / 2
```

```
array([[ 0.5, 1. ],
[ 1.5, 2. ]])
```

```
b**2
```

```
array([[ 1., 4.],
[ 9., 16.]])
```

Describing operations between two matrices is more complicated. Let b and d be two matrices of the same size. b − d takes every element of b and subtracts the corresponding element of d. Similarly, b + d adds every element of d to the corresponding element of b.

**TRY IT!** Let \(b = \begin{pmatrix}
1 & 2 \\
3 & 4 \\
\end{pmatrix}\) and \(d = \begin{pmatrix}
3 & 4 \\
5 & 6 \\
\end{pmatrix}\). Compute b + d and b - d.

```
b = np.array([[1, 2], [3, 4]])
d = np.array([[3, 4], [5, 6]])
```

```
b + d
```

```
array([[ 4, 6],
[ 8, 10]])
```

```
b - d
```

```
array([[-2, -2],
[-2, -2]])
```

There are two different kinds of matrix multiplication (and division). There is element-by-element matrix multiplication and standard matrix multiplication. For this section, we will only show how element-by-element matrix multiplication and division work. Standard matrix multiplication will be described in later chapter on Linear Algebra. Python takes the * symbol to mean element-by-element multiplication. For matrices b and d of the same size, b * d takes every element of b and multiplies it by the corresponding element of d. The same is true for / and **.

**TRY IT!** Compute b * d, b / d, and b**d.

```
b * d
```

```
array([[ 3, 8],
[15, 24]])
```

```
b / d
```

```
array([[ 0.33333333, 0.5 ],
[ 0.6 , 0.66666667]])
```

```
b**d
```

```
array([[ 1, 16],
[ 243, 4096]])
```

The transpose of an array, b, is an array, d, where b[i, j] = d[j, i]. In other words, the transpose switches the rows and the columns of b. You can transpose an array in Python using the array method *T*.

**TRY IT!** Compute the transpose of array b.

```
b.T
```

```
array([[1, 3],
[2, 4]])
```

Numpy has many arithmetic functions, such as sin, cos, etc., can take arrays as input arguments. The output is the function evaluated for every element of the input array. A function that takes an array as input and performs the function on it is said to be **vectorized**.

**TRY IT!** Compute *np.sqrt* for x = [1, 4, 9, 16].

```
x = [1, 4, 9, 16]
np.sqrt(x)
```

```
array([ 1., 2., 3., 4.])
```

Logical operations are only defined between a scalar and an array and between two arrays of the same size. Between a scalar and an array, the logical operation is conducted between the scalar and each element of the array. Between two arrays, the logical operation is conducted element-by-element.

**TRY IT!** Check which elements of the array x = [1, 2, 4, 5, 9, 3] are larger than 3. Check which elements in x are larger than the corresponding element in y = [0, 2, 3, 1, 2, 3].

```
x = np.array([1, 2, 4, 5, 9, 3])
y = np.array([0, 2, 3, 1, 2, 3])
```

```
x > 3
```

```
array([False, False, True, True, True, False], dtype=bool)
```

```
x > y
```

```
array([ True, False, True, True, True, False], dtype=bool)
```

Python can index elements of an array that satisfy a logical expression.

**TRY IT!** Let x be the same array as in the previous example. Create a variable y that contains all the elements of x that are strictly bigger than 3. Assign all the values of x that are bigger than 3, the value 0.

```
y = x[x > 3]
y
```

```
array([4, 5, 9])
```

```
x[x > 3] = 0
x
```

```
array([1, 2, 0, 0, 0, 3])
```

< 2.6 Data Structure - Dictionaries | Contents | 2.8 Summary and Problems >