# 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:

# 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: