Scientific python: NumPy, SciPy and Matplotlib¶
Learning objectives¶
Learn what NumPy arrays are
Learn basic array manipulations
Learn what vectorial code is
Quick overview of SciPy
Learn how to do a simple 2D plot and decorate it
Learn how to combine plots into a single figure
Further reading¶
http://scipy-lectures.org
https://www.nature.com/articles/s41586-020-2649-2
https://numpy.org/doc/
SciPy ecosystem¶
From their website:
SciPy (pronounced “Sigh Pie”) is a Python-based ecosystem of open-source software for mathematics, science, and engineering. In particular, these are some of the core packages:
NumPy
SciPy library
Matplotlib
IPython
Sympy
Pandas
…and much more.
NumPy¶
NumPy is a key building block used by SciPy.
NumPy supports:
Multidimensional arrays (
ndarray
)Matrices and linear algebra operations
Random number generation
Fourier transforms
Polynomials
Tools for integrating with Fortran/C libraries
NumPy provides fast precompiled functions for numerical routines.
Official website: https://www.numpy.org/
“Array programming with NumPy”¶
https://www.nature.com/articles/s41586-020-2649-2
Abstract: Array programming provides a powerful, compact and expressive syntax for accessing, manipulating and operating on data in vectors, matrices and higher-dimensional arrays. NumPy is the primary array programming library for the Python language. It has an essential role in research analysis pipelines in fields as diverse as physics, chemistry, astronomy, geoscience, biology, psychology, materials science, engineering, finance and economics. For example, in astronomy, NumPy was an important part of the software stack used in the discovery of gravitational waves and in the first imaging of a black hole. Here we review how a few fundamental array concepts lead to a simple and powerful programming paradigm for organizing, exploring and analysing scientific data. NumPy is the foundation upon which the scientific Python ecosystem is constructed. It is so pervasive that several projects, targeting audiences with specialized needs, have developed their own NumPy-like interfaces and array objects. Owing to its central position in the ecosystem, NumPy increasingly acts as an interoperability layer between such array computation libraries and, together with its application programming interface (API), provides a flexible framework to support the next decade of scientific and industrial analysis.
NumPy Arrays overview¶
Core (or Standard) Python Library provides lists and 1D arrays (array.array)
Lists are general containers for objects
Arrays are 1D containers for objects of the same type
Limited functionality
Some memory and performance overhead associated with these structures
NumPy provides multidimensional arrays (numpy.ndarray)
Can store many elements of the same data type in multiple dimensions
cf. Fortran/C/C++ arrays
More functionality than Core Python e.g. many conveninent methods for array manipulation
Efficient storage and execution
Creating 1D arrays¶
# Convention: Import numpy and alias/rename it as np
import numpy as np
There are many ways to create 1d array, e.g.:
# Create array from Python list.
a = np.array([-1, 0, 1])
print(a)
# Create array using the array class copy constructor.
b = np.array(a)
print(b)
While these might look the same when printed above, all Numpy arrays are of type ‘ndarray
’:
print(type(b))
li = [1, 2, 3]
print(type(li))
Arrays can also be created using numpy
functions, e.g.
# https://numpy.org/doc/stable/reference/generated/numpy.arange.html
# arange for arrays (like using range for lists)
a = np.arange( -2, 6, 2 )
print(a)
# https://numpy.org/doc/stable/reference/generated/numpy.linspace.html
# linspace to create sample step points in an interval
a = np.linspace(-10, 10, 4)
print(a)
# Contrast this with arange.
print(np.arange(-10, 10, 4))
Exercise 2.1¶
Add comments explaining what the following NumPy functions do.
b = np.zeros(3)
print(b)
c = np.ones(3)
print(c)
d = np.eye(3)
print(d)
Array attributes¶
As part of the array structure, NumPy keeps track of metadata for the array as “attributes”
# Taking "a" from the previous example
a = np.linspace(-10, 10, 5)
# Examine key array attributes
print(a)
print("Dimensions ", a.ndim) # Number of dimensions
print("Shape ", a.shape) # number of elements in each dimension
print("Size ", a.size) # total number of elements
print("Data type ", a.dtype) # data type of element, 64 bit float (IEEE 754) by default
Basic data type can be specified at creation:
a = np.array( [1.1,2.2,3.3], np.float32)
print(a)
print("Data type", a.dtype)
a = np.array([1,2,3,4], np.float64)
print(a)
print("Data type", a.dtype)
Multi-dimensional arrays¶
There are many different ways to create N-dimensional arrays. A two-dimensional array or matrix can be created from, e.g., list of lists
mat = np.array( [[1,2,3], [4,5,6]] )
print(mat)
print("Dimensions: ", mat.ndim)
print("Size: ", mat.size)
print("Shape: ", mat.shape)
pprint
can be used for fancier display of multi-dimensional arrays:
from pprint import pprint
pprint(mat)
You can create 2d arrays with complex elements by specifying the data type
alist = [[1, 2, 3], [4, 5, 6]]
mat = np.array(alist, complex)
pprint(mat)
Exercise 2.2¶
Work out the shape of the resulting arrays before executing the following cells
(HINT: length of the innermost list gives the size of the rightmost shape index)
i = np.array( [[1,1,1], [2,2,2], [3,3,3], [4,4,4]] )
pprint(i)
print("i", i.shape)
print(i.ndim)
print(i.size)
j = np.array( [[[1,1],[2,2],[3,3],[4,4]] , [[1,1],[2,2],[3,3],[4,4]], [[1,1],[2,2],[3,3],[4,4]]] )
print("j", j.shape)
print(j.ndim)
print(j.size)
Accessing arrays¶
Basic indexing and slicing can be used to access array elements, as we know from lists
# a[start:stop:stride] (not inclusive of stop)
a = np.arange(8) # another function for creating arrays
print("a", a)
print(a[2:6])
print("a[0:7:3]", a[0:7:3])
print("a[:5:2]", a[:5:2])
Negative (or modulo) indices are valid:
# To, e.g., access the last element
print(a[-1])
Exercise 2.3¶
Can you guess the output of the following cell?
print(a[2:-3:2])
For multi-dimensional arrays, tuples or index notations can be used.
# Basic indexing of a 3d array
c = np.array([[[1,2],[3,4]],[[5,6],[7,8]]])
pprint(c)
print(c.shape)
print("c[1][0][1]:", c[1][0][1]) # using index notation
print("c[1,0,1]:", c[1,0,1]) # using a tuple (more performant)
If the number of indices given is less than the number of axes, missing axes are taken as complete slices
c = np.array([[1,2,3],[5,6,8]])
pprint(c)
print(c.shape)
print("c[1][2]:", c[1][2]) # using index notation
print("c[1,2]:", c[1,2]) # using a tuple (more performant)
c = np.array([[[1,2],[3,4]],[[5,6],[7,8]]])
pprint(c)
print(c[1])
print(c[1,0])
print(c[1,0,...]) # can use elipsis (3 dots) for missing indices
Slicing tuple objects can also be created to extract/manipulate indices. A slicing object takes the form slice(start, stop, step)
. Therefore, to take the slice a[2:-3:2]
we could also write
my_slice = (slice(2, -3, 2))
print(a[my_slice])
Note that a negative step can also be used to invert an array. Take for example
d = np.arange(9).reshape(3,3)
print(d)
This can be inverted along both axes via
print(d[::-1, ::-1])
or equivalently
print(d[(slice(None, None, -1), slice(None, None, -1))])
Array copies¶
Simple assignment creates references or ‘shallow’ copies of arrays
a = np.array( [-2,6,2] )
print("a", a)
b = a
a[0] = 20
# b points to a, hence printing a and b will produce the same result
print(a)
print(b)
Use copy()
to create a true or ‘deep’ copy
c = a.copy()
# check c really is an independent copy of a
c[2] = 42
print(a)
print(c)
Views from slices¶
A “view” is an array that refers to another array’s data (like references). You can create a view on an array by selecting a slice of an array. No data is copied when a view is created.
a = np.array([[1,2,3],[4,5,6],[7,8,9]])
pprint(a)
# Can assign a slice to a variable and change the array referred to
# by the slice
s = a[2:3, 1:3]
print(s)
s[:,:] = -2
print("s", s)
print("a", a)
Exercise 2.4¶
Change all the elements with values 7,8,10,11 in matrix m
below (i.e. bottom right corner elements) to 1000 using a slice
m = np.array([[0,1,2],[3,4,5],[6,7,8],[9,10,11]])
pprint(m)
# solution...
Reshaping arrays¶
The shape of an array can be modified, and/or its size changed:
a = np.arange(6)
print("a = ")
pprint(a)
print("a.shape = ", a.shape, "\n")
# modifying the shape attribute (not a copy) requires
# that the size remains the same
a.shape = (3,2)
pprint(a)
# Or can alter the size and shape of the array with resize().
# https://numpy.org/doc/stable/reference/generated/numpy.resize.html
mat = np.arange(6)
print("mat = ", mat)
mat1 = np.resize(mat, (3, 2))
print("mat1 = ", mat1)
mat2 = np.resize(mat, (3, 9))
print("mat2 = ", mat2)
base
can be used to check if arrays share the same data (i.e. they are not copies):
mat1.base is mat
Exercise 2.5¶
What does the reshape()
method do?
Vectorization and operations on arrays¶
Vectorization is why numpy arrays are great. It allows element-wise operations (avoids writing loops!). What output do the following cells give?
a = np.arange(10).reshape([2,5])
b = np.arange(10).reshape([2,5])
print(a)
print(b)
# Try these
-0.1*a
print(-0.1*a)
print(a+b)
# This is NOT matrix multiplication!
print(b)
a*b
# Use dot product for vector/matrix multiplication
# Note: .T gives you transpose of matrix i.e. reshapes it
a.dot(b.T)
Be careful: the type of data elements matters here!
Vectorization also works with functions!
def f(x):
return x**3
x = np.array([1,2,3,4,5,6,7,8,9])
y = f(x)
print(y)
Exercise 2.6¶
Let \(A\) be the two-dimensional array $\( \mathbf{A} = \left\lbrack\begin{array}{ccc} 2 & 1 & 0\cr -1 & 2 & 1\cr 0 & -1 & 2 \end{array}\right\rbrack \)$
A = np.array([[2.,1.,0.], [-1.,2.,1.], [0.,-1.,2.]])
pprint(A)
Implement and apply the function $\( f(x) = x^3 + xe^x + 1 \)\( to \)A$. Check that you get what you expect.
# Solution...
Manipulating arrays¶
There are many methods for manipulating arrays (reshaping, joining, splitting, inserting, …). Check the documentation.
E.g.,
concatenate((a1,a2),axis=0)
split(a, indices_or_sections, axis=0)
flatten
ravel(a)
stack(arrays[, axis])
tile(a, reps)
repeat(a, repeats[, axis])
unique(ar[, return_index, return_inverse, ...])
trim_zeros(filt[, trim])
fill(scalar)
xv, yv = meshgrid(x,y)
Exercise 2.7¶
See what arrays you can create from some of the functions listed above.
Fancy indexing¶
Advanced or fancy indexing lets you do more than simple indexing.
p = np.array([[0, 1, 2],[3, 4, 5],[6, 7,8],[9, 10, 11]])
pprint(p)
rows = [0,0,3,3] # indices for rows
cols = [0,2,0,2] # indices for columns
q=p[rows,cols]
print(q)
Fancy indexing returns a copy (not a view like slicing)
# ... check if a is a view or a copy
q[0]=1000
print(q)
print(p)
Exercise 2.8¶
Use base
to check whether q
is a copy. Do the same for a simple indexed slice of p
(e.g. p[1:2,3:4]
)
Logical expressions and boolean ‘masks’ can be used to find indices of elements of interest e.g.
# Find indices of elements with value less than zero
m = np.array( [[0,-1,4,20,99],[-3,-5,6,7,-10]] )
print(m)
print(m[ m < 0 ])
Exercise 2.9¶
Challenge yourself to anticipate the output of the following code before running it.
a = np.arange(10)
print(a)
mask = np.ones(len(a), dtype=bool)
mask[[0,2,4]] = False # set certain mask values to False
result = a[mask]
print(result)
Random number generation¶
Numpy provides utilities for random number generation
# Create an array of 10 random real numbers
a = np.random.ranf(10)
print(a)
# Create a 2d array (5x5) reshaped matrix from a 1d array of (25)
# random ints between 0 and 5 (not inclusize)
a = np.random.randint(0,high=5,size=25).reshape(5,5)
pprint(a)
# Generate sample from normal distribution
# (mean=0, standard deviation=1)
s = np.random.standard_normal((5,5))
pprint(s)
Exercise 2.10¶
Explore other ways of generating random numbers. What other distributions can you sample?
File operations¶
Numpy provides an easy way to save data to text file and to read structured data.
# Generate an array of 5 random real numbers
pts = 5
x = np.arange(pts)
y = np.random.random(pts)
print(x)
print(y)
# data format specifiers: d = int, f = float, e = exponential
np.savetxt('savedata.txt', np.stack((x,y),axis=1), header='DATA', \
footer='END', fmt='%d %1.4f')
!cat savedata.txt
# Reload data to an array
p = np.loadtxt('savedata.txt')
print(p)
More flexibility is offered with genfromtext()
(query ?np.genfromtext
)
p = np.genfromtxt('savedata.txt', skip_header=2, skip_footer=1)
print(p)
However, be aware that using plain text files for data is considered bad practice due to the lack of any form of standardization. For example, next week you will be introduced to Pandas which is designed to handle structured data.
Exercise 2.11¶
What do numpy.save()
and numpy.load()
do ?
Linear algebra with numpy.linalg¶
Numpy provides some linear algebra capabilities, from matrix-vector product to matrix inversion and system solution
Simple matrix vector product within numpy core:
A = np.array([[1,2,3],[4,5,6],[7,8,8]])
b = np.array([1,2,1])
print(np.dot(A,b))
The numpy.linalg
module is necessary for more complex operations.
import numpy.linalg as la
Usual quantities can be computed from arrays:
n = la.norm(b)
print(n)
n = la.norm(A)
print(n)
d = la.det(A)
print(d)
And it is possible to solve linear systems, using low level C/Fortran code:
la.solve(A,b)
or to invert matrices (which is generally not a good thing to do due to computational cost!)
A_inv = la.inv(A)
print(A_inv)
The eigen decomposition (of a square matrix) can also be computed:
eival, eivec = la.eig(A)
print(eival)
pprint(eivec)
Performance¶
Python has a convenient timing function called timeit
.
Can use this to measure the execution time of small code snippets.
From python:
import timeit
and supply code snippet as a stringFrom ipython: can use magic command
%timeit
By default, %timeit
loops (repeats) over your code 3 times and outputs the best time. It also tells you how many iterations it ran the code per loop.
You can specify the number of loops and the number of iterations per loop.
%timeit -n <iterations> -r <repeats> <code_snippet>
See
%timeit?
for more informationhttps://docs.python.org/2/library/timeit.html
Exercise 2.12¶
Here are some timeit
experiments for you to try. Which methods are faster?
# Accessing a 2d array
nd = np.arange(100).reshape((10,10))
# accessing element of 2d array
%timeit -n 10000000 -r 3 nd[5][5]
%timeit -n 10000000 -r 3 nd[5,5]
# Multiplying two vectors
x = np.arange(10E7)
%timeit -n 1 -r 10 x*x
%timeit -n 1 -r 10 x**2
# Comparing range functions and iterating in loops
# Note: some distributions may see overflow in range() example
size = int(1E6)
%timeit for x in range(size): x**2
%timeit for x in np.arange(size): x**2
%timeit np.arange(size)**2
# Extra : from the linear algebra package
%timeit -n 1 -r 10 np.dot(x,x)
Exercise 2.13¶
The Taylor series expansion for the trigonometric function \(\arctan(y)\) is : $\(\arctan ( y) \, = \,y - \frac{y^3}{3} + \frac{y^5}{5} - \frac{y^7}{7} + \dots \)\( Now, \)\arctan(1) = \pi/4 \(, so \)\( \pi = 4 \, \Big( 1- \frac{1}{3} + \frac{1}{5} - \frac{1}{7} + ... \Big) \)\( We can represent the series expansion using a numpy `Polynomial`, with coefficients: \)\(0, +1, 0, -1/3, 0, +1/5, 0, -1/7,\ldots\,\)\( and use it to approximate \)\pi$.
Write some code that calculates an approximation of \(\pi\) using polynomials.
Bonus: Find the size of the expansion that results in an approximation of order \(10^{-4}\). (You can check the difference with numpy.pi
)
# Solution...
Exercise 2.14 : Darts (calculating \(\pi\) again)¶
A Monte Carlo method (aka “throwing darts”)¶
Geometry gives us an expression for \(\pi\):
if \(N_{in}\) is the number of darts falling on the board, and \(N_{tot}\) is the total number of trials
<img src=”darts.png”; style=”float: right; width: 25%; margin-right: 15%; margin-top: 0%; margin-bottom: 1%”>
Try using numpy arrays to compute the following:
Choose a sample size
ntot
Generate an array of random \(x\) coordinates \(0 \leq x < 1\).
Generate an array of random \(y\) coordinates \(0 \leq y < 1\).
Count the number for which \(x^2 + y^2 < 1\)
Compute appromination to \(\pi\)
Repeat for several values of
ntot
and print the error.
# Solution...
SciPy library:¶
It provides a wide range of user-friendly routines (often built on NumPy) needed in scientific work. It is organised in several sub-modules:
scipy.constants
scipy.special
scipy.io
scipy.linalg
scipy.sparse
scipy.integrate
scipy.interpolate
scipy.stats
scipy.cluster
scipy.odr
scipy.optimize
scipy.signal
scipy.fftpack
scipy.ndimage
scipy.spatial
Linear algebra¶
scipy.linalg contains additional routines compared to numpy.linalg
import scipy.linalg as sla
Construct an orthonormal basis for the range of A using its SVD:
A = np.array([[2,1,1],[1,2,1],[1,1,2]])
b = np.array([1,2,3])
orth_basis = sla.orth(A)
pprint(orth_basis)
Compute the Schur decomposition of a matrix:
A = np.array([[0, 2, 2], [0, 1, 2], [1, 0, 1]])
T, Z = sla.schur(A)
pprint(T)
pprint(Z)
print("A == Z.T.Z^t: ", np.allclose(A, Z.dot(T).dot(Z.transpose())))
Integration¶
scipy.integrate
contains routines to intgrate expressions as well as discrete data:
import scipy.integrate as sint
x2 = lambda x: x**2
sint.quad(x2, 0, 4)
x = np.arange(0, 10)
y = np.arange(0, 10)
y = np.power(x, 1)
sint.simps(y, x)
Interpolation¶
scipy.interpolate
provides functions to interpolate data (regression, curve fitting, etc)
import scipy.interpolate as sitp
Find the Lagrange polynomial (polynomial that passes through all data points) :
%matplotlib notebook
import matplotlib.pyplot as plt
# Invent some raw data
x=np.array([0.5,2.0,4.0,5.0,7.0,9.0])
y=np.array([0.5,0.4,0.3,0.1,0.9,0.8])
lp=sitp.lagrange(x, y)
xx= np.linspace(0.4, 9.1, 100)
fig = plt.figure()
plt.plot(x,y, "o", xx, lp(xx))
plt.show()
Fit a polynomial of arbitrary degree to a data set. Notice that the object returned is not a function this time and needs to be transformed into a function.
poly_coeffs=np.polyfit(x, y, 3)
p3 = np.poly1d(poly_coeffs)
fig = plt.figure()
plt.plot(x, y, "o", xx, p3(xx), 'g', label='Cubic')
plt.show()
Optimisation¶
scipy.optimise
provides a very wide range of optimisation methods: Newton-like minimization algorithms, least square methods, root finding…
NB. Examples below are inspired from the official documentation. Interesting examples require setting up complex problems ands exceed the scope of this presentation.
import scipy.optimize as sopt
From a simple 1D problem:
def f(x):
return -np.exp(-(x - 0.2)**4)
result = sopt.minimize_scalar(f)
x_min = result.x
print(x_min)
To a more complex one:
def f(x): # rosenbrock function
return .5*(1 - x[0])**2 + (x[1] - x[0]**2)**2
def jacobian(x):
return np.array((-2*.5*(1 - x[0]) - 4*x[0]*(x[1] - x[0]**2), 2*(x[1] - x[0]**2)))
sopt.minimize(f, [2,-1], method="Newton-CG", jac=jacobian)
Special functions¶
scipy.specials
provides a certain number of useful mathematical functions: Airy functions, Bessel functions, elliptic integrals, gamma function, erf, binomial law, etc.
Plotting with Matplotlib¶
From the official documentation:
Matplotlib is a Python 2D plotting library which produces publication quality figures in a variety of hardcopy formats and interactive environments across platforms. […]
Matplotlib tries to make easy things easy and hard things possible. You can generate plots, histograms, power spectra, bar charts, errorcharts, scatterplots, etc., with just a few lines of code. For examples, see the sample plots and thumbnail gallery.
For simple plotting the pyplot module provides a MATLAB-like interface, particularly when combined with IPython. For the power user, you have full control of line styles, font properties, axes properties, etc, via an object oriented interface or via a set of functions familiar to MATLAB users.
In this notebook, we are mostly focused on the matplotlib.pyplot
submodule
Importing Matplotlib¶
The most standard:
import matplotlib.pyplot as plt
Be careful to anonymous imports (from matplotlib.pyplot import *
that will pollute your namespace.
iPython magic can be used to display plots in the browser:
%matplotlib inline
or for fancier dynamic plots
%matplotlib notebook
You can see what magic commands do by typing ?%matplotlib inline
. Note that, in that case, you need to import pyplot
after the magic command.
Basic plots (re: lecture 3)¶
Let’s prepare some data:
import numpy as np
%matplotlib notebook
import matplotlib.pyplot as plt
x = np.linspace(0, 2*np.pi, 100)
y = np.sin(x)
z = np.sin(x+np.pi/2)
Plotting this data is as simple as:
fig = plt.figure()
plt.plot(x, y)
plt.plot(x, z)
plt.show()
or
fig = plt.figure()
plt.plot(x,y, x, z)
plt.show()
We can specify axes limits and titles, and a global plot title:
fig = plt.figure()
plt.xlim((x.max()*0.25, x.max()*0.75))
plt.xlabel("x")
plt.title("Trigonometry")
plt.plot(x,y, x, z)
plt.show()
as well as legends:
fig = plt.figure()
plt.xlabel("x")
plt.plot(x,y, x, z)
plt.legend(("sin(x)", "sin(x+pi/2)"))
plt.show()
Line style¶
Each curve of the plot can be decorated individually:
x1 = np.linspace(0, 2*np.pi, 10)
y1 = np.sin(x1)
z1 = np.sin(x1+np.pi/2)
fig = plt.figure()
plt.plot(x1, y1, '+-r', label="sin", linewidth=5.0)
plt.plot(x1, z1, 'o--g', label="$\sin(x+\pi/2)$", markersize=7.0)
plt.legend()
plt.show()
Plots can even be annotated !
fig = plt.figure()
plt.plot(x, z)
atext = 'This is a beautiful curve'
arrowtiploc = (1.5, 0.5)
textloc=(3, 0.75)
plt.annotate(atext, xy=arrowtiploc, xytext=textloc,
arrowprops=dict(width=5,headwidth=15,
facecolor='red'))
plt.show()
Histograms¶
Other kinds of plots can be made, like histograms. A more complete list of plots can be found here
fig = plt.figure()
#10,000 Uniform random numbers
x2 = np.random.random(10000)
#10,000 Normally distributed random numbers
y2 = np.random.randn(10000)
#Plot both on a histogram with 50 bins
plt.hist(y2, 50)
plt.hist(x2, 50)
plt.show() #Or plt.save g("out.png")
Saving images¶
Sometimes, you might want to save images for a report or a presentation. Instead of taking a screenshot of the plot, you can save it to a file. The file format is determined by the filename extension you supply. The following common formats are supported: .png
, .jpg
, .pdf
and .ps
. There are other options to control the size and resolution of the output.
fig = plt.figure()
plt.plot(x, y, 'bx')
plt.savefig("sin.png", dpi=200)
plt.show()
Subplots¶
Sometimes, you will want to group several plots in one figure. Matplotlib provides an flexible way of managing subplots.
(fig, axes) = plt.subplots(nrows, ncols)
subplots()
returns a tuple with a reference to the figure (fig
) and an array of references to each subplot axes (axes
)
fig, axes = plt.subplots(2,1)
axes[0].plot(x, y, lw=3., c='r')
axes[1].plot(x, z, '--b')
axes[1].set_xlabel("x", fontsize=16)
axes[0].set_ylabel("sin(x)", fontsize=16)
axes[1].set_ylabel("sin(x+pi/2)", fontsize=16)
plt.show()
(fig, axes) = plt.subplots(2, 2, figsize=(10,7))
axes[0,0].plot(x, y, 'r')
axes[0,1].plot(x, y, 'b')
axes[1,0].plot(x, y, 'g')
axes[1,1].plot(x, y, 'k')
plt.show()
Alternatively, you can select a subplot and make it the “current subplot”
(fig, axes) = plt.subplots(2, 2)
plt.subplots_adjust(wspace = 1., hspace=1.5)
plt.sca(axes[0,0])
plt.plot(x, y, 'r')
plt.sca(axes[0,1])
plt.plot(x, y, 'b')
plt.sca(axes[1,0])
plt.plot(x, y, 'g')
plt.sca(axes[1,1])
plt.plot(x, y, 'k')
plt.show()
For more complex subplot layouts plt.subplot2grid
can be used. It maps the figure to a grid, and you can define subplots spanning across several cells of the grid. It takes 4 arguments:
subplot2grid(shape,
location,
rowspan = 1,
colspan = 1)
shape
is the shape of the grid (number of cells), it needs to be the same for each call of the function within one figure. location
is the location of the top-left corner of the subplot. rowspan
and colspan
define respectively the length and heigth of the plot.
fig = plt.figure()
fig.set_figheight(5)
fig.set_figwidth(5)
ax1 = plt.subplot2grid((3, 3), (0, 0))
ax2 = plt.subplot2grid((3, 3), (0, 1), colspan=1)
ax3 = plt.subplot2grid((3, 3), (1, 0), colspan=2, rowspan=2)
ax4 = plt.subplot2grid((3, 3), (1, 2), rowspan=2)
# what happens if you uncomment the following line?
#ax5 = plt.subplot2grid((3, 3), (1, 1), colspan=1, rowspan=1)
ax1.plot(x, y, 'r')
ax2.plot(x, y, 'b')
ax3.plot(x, y, 'g')
ax4.plot(x, y, 'k')
plt.show()
2D¶
There are several ways to represent multidimensional data. One can choose to represent the data contained in a 2D domain (a matrix) with colors (in a 2D plot), or with surfaces (in a 3D plot).
fig = plt.figure()
N = 100
x = np.linspace(0,2*np.pi,N)
y = np.sin(x); z = np.cos(x)
#Create 2D field from outer product of previous 1D functions
noise = np.random.random(N**2)
u = np.outer(y,z) + noise.reshape(N,N)
plt.contourf(u, 40, cmap=plt.cm.RdYlBu_r)
plt.colorbar()
plt.show()
Exercise 2.15¶
Consider the following functions defined on a 2D domain \(\Omega = [0,1]\): $\( a(x) = \tanh(50\sin(\frac{25}{2}\pi x))\)\( and \)\( f(x) = a(a(|x-x_0|)) + a(a(|x-x_1|)) \)\( for any \)x \in \Omega\(, with \)x_0=(0,0)\( and \)x_0=(1,1)$
Plot \(f\) on \(\Omega\). You may want to use numpy.meshgrid
.
# Solution...
3D¶
A wide variety of options are available for 3D plots, that are usually contained in specific submodules.
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.axes3d import get_test_data
fig = plt.figure()
fig.set_figheight(5)
fig.set_figwidth(15)
ax = fig.add_subplot(1,3,1, projection='3d')
# plot a curve
x = np.linspace(0.,2*np.pi,100)
ax.plot(x, np.cos(x), np.sin(x))
ax = fig.add_subplot(1, 3, 2, projection='3d')
# plot a 3D surface
X = np.arange(-5, 5, 0.25)
Y = np.arange(-5, 5, 0.25)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X**2 + Y**2)
Z = np.sin(R)
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=plt.cm.coolwarm,
linewidth=0, antialiased=False)
ax.set_zlim(-1.01, 1.01)
fig.colorbar(surf, shrink=0.5, aspect=10)
ax = fig.add_subplot(1, 3, 3, projection='3d')
# plot a 3D wireframe like in the example mplot3d/wire3d_demo
X, Y, Z = get_test_data(0.05)
ax.plot_wireframe(X, Y, Z, rstride=10, cstride=10)
plt.show()
Animations¶
Animations (i.e. series of images) can be created with matplotlib. However, exercise some restraint as they can require significant resources.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation
fig = plt.figure()
def get_field(a, N = 100):
x = a*np.linspace(0,2*np.pi,N)
y = np.sin(x); z = np.cos(x)
out = np.outer(y,z)
plt.contourf(out, 40, cmap=plt.cm.RdYlBu_r)
ani= matplotlib.animation.FuncAnimation(plt.gcf(), get_field, frames=range(0,10),
interval=500, repeat=False)
plt.show()
import random
ysample = random.sample(range(-50, 50), 100)
xdata = []
ydata = []
fig = plt.figure()
axes = plt.gca()
axes.set_xlim(0, 100)
axes.set_ylim(-50, +50)
line, = axes.plot(xdata, ydata, 'r-')
def update(i):
xdata.append(i)
ydata.append(ysample[i])
line.set_xdata(xdata)
line.set_ydata(ydata)
ani= matplotlib.animation.FuncAnimation(plt.gcf(), update, frames=100,
interval=100, repeat=False)
plt.show()
Movies can be generated by saving images then calling an external program like ffmpeg
, or using the writers
function from the animation
submodule, which also uses ffmpef
under the hood.
Widgets¶
Matplotlib provides widgets, that allow you to create dynamic plots with sliders and things like that.
import matplotlib.widgets as mw
fig = plt.figure()
#Setup initial plot of sine function
x = np.linspace(0, 2*np.pi, 200)
l, = plt.plot(x, np.sin(x))
#Adjust figure to make room for slider
plt.subplots_adjust(bottom=0.15)
axslide = plt.axes([0.15, 0.05, 0.75, 0.03])
s = mw.Slider(axslide, 'A value', 0., 5.)
#s = widgets.FloatSlider(axslide, 'A value', 0., 5.)
#Define function
def update(A):
l.set_ydata(np.sin(A*x))
plt.draw()
#Bind update function to change in slider
s.on_changed(update)
plt.show()
Summary¶
NumPy
: definesnd.array
which is an efficient structure for large arrays, matrices and tensors, and functions to manipulate themSciPy
: defines a lot of user-friendly routines useful for scientific codesBoth rely on efficient low level code, and will generally be much faster than if you try to re-implement them yourself
Vectorization !
Matplotlib
: allows you to draw and save plots, from the simplest 2D line plot to complicated 3D plots.Refer to online documentation for a complete list of features: https://docs.scipy.org/doc/