Chemical Engineering Logo


Arrays in Python

Contributors:


Note: if you are a matlab user, you may want to look at

Overview

In python, there are a few ways to manage collections of information:

  • A list is built-in to the language, and can contain any type
  • A tuple is also built-in to the language, and is similar to a list, but is immutable.
  • A numpy array is provided by the numpy package in python and is useful for holding collections of numbers.

Lists

A list in python is enclosed by square brackets, and can contain any types (including other lists):

In [1]:
# A list of integers:
x = [1,2,3,4]

# A list of strings:
fruits = ['apple','banana','orange']

# A list of many things, including other lists!
my_list = [ 2, 'a string', 5, 3.14, x, fruits ]

print(my_list)

print(my_list.index(x))  # find where x appears (4)
print(my_list[2])        # 5
[2, 'a string', 5, 3.14, [1, 2, 3, 4], ['apple', 'banana', 'orange']]
4
5

You can think of a list in python as an array in Matlab, C/C++, Fortran, etc. The primary difference is that, in Python, a list can have different types (strings, integeres, etc.) in the same list/array.

Caution: copying arrays in python is unique:

In [2]:
a = [1,2,3]
b = a

makes b alias to a. This means that

In [3]:
b[1]=5

also changes a[1] to 5:

In [4]:
print(a,b)
[1, 5, 3] [1, 5, 3]

To make a full copy of an array, use the copy method:

In [5]:
b = a         # b and a are the same array
c = a.copy()  # c and a are different arrays with the same contents
c[2] = 19
print(a,b,c)
[1, 5, 3] [1, 5, 3] [1, 5, 19]

Operations on Lists

Frequently we want to perform operations on lists

Method Description Example
len Returns the number of entries in the list len(my_list)
append(entry) Append an entry to the list my_list.append( 'another entry' )
insert(index,entry) Insert an entry in the list my_list.insert(2,10)
extend(list2) append a list my_list.append( ('a','b',x) )
+ append a list (same as extend) my_list + my_list
remove(entry) Append an entry to the list my_list.remove( x )
sort() sort the list - only works when the list is homogeneous x.sort()
reverse() reverses the entries in the list my_list.reverse()
pop(index) return and remove the element in the list at index my_list.pop(3)
count(entry) return the number of occurences of entry in the list my_list.count(2)
index(entry) return the index at which the first occurence of entry occurs my_list.index(x)

You can also use the keyword in with a list:

In [6]:
fruits = ['apple','pear','banana','payapa']
if 'banana' in fruits:
    print('Yes!')
else:
    print('No!')
Yes!

Indexing and Slicing Lists

Indexing a list is done with the [] operator, and is 0-based (0 indicates the first entry in the list). For example, given:

In [7]:
fruits = ['apple','banana','grapefruit','orange']

the following table shows various indexing operations:

Operation Result Description
fruits[1] banana Accesses the second element in the list
fruits[-1] orange Accesses the last element in the list
fruits[-2] grapefruit Accesses the second to last element in the list
fruits[:2] [apple,banana] The first two elements in the list
fruits[1:2] [banana,grapefruit] The second and third elements in the list

The : operator allows us to perform slicing, accessing a subset of the entries in a list.

You can also use slicing to replace elements of a list. For example:

In [8]:
x = [1,2,3,4]
y = x[1:3]
y.reverse()
x[0:2] = y
print(x)
[3, 2, 3, 4]

Loops and Lists

There are a few ways to loop over lists. This is perhaps best illustrated by a few examples.

Iterating a list

In [9]:
fruits = ['apple','banana','orange']
for i in fruits:
    print(i)
apple
banana
orange

Here, i is an iterator that represents each entry in the list

Index loops

Here we use the range() function, which builds a range space for the loop. This allows us to use i as an index:

In [10]:
fruits = ['apple','banana','orange']
for i in range(0,len(fruits)):
    print('Fruit {} = {}'.format(i,fruits[i]))
Fruit 0 = apple
Fruit 1 = banana
Fruit 2 = orange

range(lo,hi) creates a range of integers from lo to hi-1

List comprehensions

List comprehensions can be used to quickly build lists conforming to specific patterns. For example, if we wanted to build a list $$x_i=i^2, \quad i=1\ldots4$$ we can do this by:

In [11]:
x = [i**2 for i in range(1,4)]
print(x)
[1, 4, 9]

Similarly, to achieve $y_i=2^i, \; i=1\ldots 8$, we can do:

In [12]:
y = [2**i for i in range(1,9)]
print(y)
[2, 4, 8, 16, 32, 64, 128, 256]

List comprehensions provide a relatively simple syntax to build these types of lists.

Tuples

In python, a tuple is like a list, but has a few differences:

  • It is declared using () rather than []
  • It is immutable - it cannot be changed once it is built

Elements in a tuple are accessed in the same way as lists, using the [] operator.

Generally, you will use lists rather than tuples.


Numpy Arrays

The list functionality in python is not as useful as it could be when it comes to numerical operations. For example, you cannot perform mathematical operations on lists. This is where numpy comes in.

Unlike a Python list, a Numpy arrays is homogeneous - they contain entries of the same type (e.g., integer, real, complex).

Here and below, we will assume that you have:

In [13]:
import numpy as np

so that we can use np. to shorten reference to numpy functions.

Constructing Numpy Arrays

A numpy array is characterized by its shape and the type of elements it contains.

In [14]:
x = np.array( [1,2,3] )             # a 1-dimensional row vector
y = np.array( [ [1,2,3],[4,5,6] ] ) # a 2-dimensional matrix
print(x.shape,x)
print(y.shape,y)
(3,) [1 2 3]
(2, 3) [[1 2 3]
 [4 5 6]]

Data Types

You can explicitly specify the type of the data in the array. Here are some of the common types you will use:

Keyword Description
int Integer
bool boolean (True/False)
float Floating point (real)
complex Complex numbers
string string
(other) User-defined data types

Example

x = np.array([1,2,3],dtype=complex) print(x) ## [ 1.+0.j 2.+0.j 3.+0.j ]

Arrays over specified ranges

Function Description
linspace(lo,hi,npts) Builds a 1D array with a npts entries between lo and hi
arange(lo,hi,spacing) Builds a 1D array spaced with spacing starting at lo and ending near hi
logspace(lo,hi,npts) Builds a 1D array with npts points between $10^\mathrm{lo}$ and $10^\mathrm{hi}$
meshgrid(x,y,...) Given vectors specifying the range of each axis, builds a grid.

Other Constructors

Command Description
empty(shape) Build an empty array.
empty_like(a) Build an empty array shaped like a
eye(N) Build a 2D identity matrix with N rows and columns
ones(shape) build an array of ones with the specified shape
ones_like(a) Build an array of ones shaped like a
zeros(shape) build an array of zeros with the specified shape
zeros_like(a) Build an array of zeros shaped like a
full(shape,val) Build an array of the specified shape filled with val
full_like(a,val) Build an array shaped like a filled with val
random.random(shape) Build an array of random numbers with the specified shape

Examples:

In [15]:
x = np.ones( [1,3] )      # 3-element row vector
y = np.empty_like( x )    # empty 3-element row vector
z = np.zeros( [3,3] )     # 3x3 matrix
p = np.full_like(z,np.pi) # 3x3 matrix full of 𝜋
r = np.random.random([2,3])

All of these can have an additional dypte argument to specify the type of array to build (see above).

For more information on many other ways of building arrays, see the numpy docs

Numpy Matrices

Most of the functions mentioned above such as zeros, ones, full, eye, etc. will create matrices as well - just give the appropriate shape.

Additionally, the diag function is very useful:

  • diag(v,k) builds a matrix with v on its kth diagonal. For example, the following builds a tridiagonal matrix:
In [16]:
n   = 5
d   = np.full(n,-3)
ud  = np.full(n-1,1)
mat = np.diag(d,0) + np.diag(ud,1) + np.diag(ud,-1)
  • diag(m,k) extracts the kth diagonal of the matrix m
In [17]:
m  = np.random.random([5,5])
# extract the main diagonal of m
md = np.diag(m,0) 

Manipulating Numpy Arrays

There are many array manipulation tools. Some of the more frequently used ones include:

Function Description
reshape(a,shape) Reshape the data in a to a shape
ndarray.flat Obtain an iterator over the array
transpose(a) Transposes the array
tile(a,nrep) Tile a nrep times. nrep can be an array.
flip(a,axis) Reverse the elements along the axis dimension of a.
fliplr(a) Flip the array in the left/right direction
flipud(a) Flip the array in the up/down direction.

Indexing and Slicing

Numpy arrays are indexed just like python lists:

In [18]:
a = np.linspace(0,4,10)
a[3] = -2
for i in a:
    print(i)
0.0
0.4444444444444444
0.8888888888888888
-2.0
1.7777777777777777
2.2222222222222223
2.6666666666666665
3.1111111111111107
3.5555555555555554
4.0

You can also slice arrays:

In [19]:
a = np.random.random([4,4])
print(a)
a[1:2,1:3]

x = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
print(x[1:7:2])
[[0.03533361 0.13114037 0.42474318 0.62253432]
 [0.75652141 0.64262473 0.42286252 0.67319462]
 [0.30819661 0.56497425 0.8179415  0.47857807]
 [0.21509378 0.27477645 0.37631399 0.66887225]]
[1 3 5]

And slice from the "back" of arrays:

In [20]:
a = np.arange(0,10,1)
print(a[-3:4:-1])   # start at third-to last, end at fifth
[7 6 5]

And slice all after:

In [21]:
a = np.arange(0,10,1)
print(a[5:])
[5 6 7 8 9]

Mathematical Operations on Numpy Arrays

Numpy arrays support typical mathematical operations like +, -, * (element-wise multipy), / (element-wise divide) and ** (element-wise exponentiation) provided the arrays are the same shape.

Numpy provides numerous mathematical functions that operate on arrays. For example:

In [22]:
x = np.linspace(-np.pi,np.pi)
y = np.sin(x)
z = x**2

Or

In [23]:
n = 10
np.prod( np.arange(1,n,1) )  # the factorial of n
Out[23]:
362880

Array Multiplication

Given the arrays $A = \left[\begin{array}{cc} 1 & 2\\ 3 & 4 \end{array}\right]$, $b=\left(\begin{array}{c} 5\\6\end{array}\right)$, and $c=\left(\begin{array}{c} 7\\8\end{array}\right)$,

In [24]:
A = np.array([[1,2],[3,4]])
b = np.array([[5],[6]])
c = np.array([[7],[8]])

Elemental Multiplication

For those coming from Matlab, the most familiar application of elemental multiplication is on arrays of the same shape. However, elemental multiplication does not require arrays to have the same shape. For example:

Operation Description Result
A * A Square elements in A $\begin{array}{cc} 1&4\\9&16\end{array}$
b * c Element-wise multiplication of b and c $\begin{array}{c} 35\\48\end{array}$
A * b Multiply a 2x2 by a 2x1. Results in a 2x2 $\begin{array}{cc} 5&10\\18&24\end{array}$
b.transpose() * c Element-Multiply a row vector to a column vector $\begin{array}{cc} 35&42\\40&48\end{array}$

Matrix Multiplication

While * implies elemental multiplication, @ implies matrix multiplication.

Here are some example operations on the above arrays:

Operation Result
A @ b $\begin{array}{c} 17 \\ 39 \end{array}$
b.transpose() @ c 39
[email protected] error
b.transpose() @ A @ c 433

More useful stuff

Among the other things that you should be aware of:

  • Fourier Transform through the numpy.fft module.
  • Linear algebra through the numpy.linalg module. This includes things like dot products, matrix products, norms, matrix decomositions, etc.
  • Statistics.
  • I/O to help with reading/writing arrays from/to disk.
  • Advanced indexing tools.