#!/usr/bin/env python
# coding: utf-8
# In[ ]:
get_ipython().run_line_magic('config', "InlineBackend.figure_formats = ['svg']")
import matplotlib.pyplot as plt
plt.rcParams['figure.figsize'] = [5, 3]
# # Lecture 4: More NumPy and Matplotlib
# # Lecture 4: More NumPy and Matplotlib
#
# In this lecture we will talk more about NumPy and introduce Matplotlib.
# - The material covers more parts from the NumPy User Manual [4]
# - as well parts of the Matplotlib User Guide [5].
# ## Working with NumPy
# - Lecture 3 introduced NumPy and the `ndarray` array type
# - Now it's time to start working with these!
#
# To access the `numpy` package it is standard to use the import:
# In[ ]:
import numpy as np
# # Element-wise (binary) operations
# The first thing to note is that when working with `ndarrays`: Operations are **element-wise**
#
# ## Example: Addition
# In[ ]:
a = np.arange(4.)
b = a + 1
print("type(a) = {}\na = {}\nb = {}".format(type(a), a, b))
# In[ ]:
c = a + b
print('c =', c)
# ## Example: Multiplication
# In[ ]:
a = np.arange(4)
b = np.linspace(1., 2.5, num=4)
c = a * b
print(f" a = {a} \n b = {b} \n c = {c}")
# **Note:** The elements in an `ndarray` has a common `dtype`
# the results of array operations is promoted to the proper type.
# In[ ]:
print(a.dtype, b.dtype, c.dtype, sep=', ')
# ## Shape compatibility
#
# If shapes of operands are incompatible, NumPy will give you an error:
# In[ ]:
a = np.arange(4)
b = np.linspace(1, 2, num=3)
print(a.shape, b.shape)
# In[ ]:
c = a * b
# # Broadcasting
# # Broadcasting
#
# If the shapes of arrays does not match, NumPy tries to apply **broadcasting** rules
#
# - If one array has a dimension with only a single index, `.shape[d] == 1`
# - the elements in the other dimensions are repeated across
# In[ ]:
a = np.zeros(shape=(3, 4))
b = np.arange(4.).reshape((1, 4))
print('a =\n', a)
print('b =', b)
print(a.shape, b.shape)
# In[ ]:
c = a + b # What happens here!?!
# In[ ]:
print(c)
# ## Adding one-dimensional axies `np.newaxis`
# - To make broadcasting easy it is possible to add extra axies to arrays.
# - This is done using slicing and the `np.newaxis` (or `None`)
# - Consider the previous example again
# In[ ]:
a = np.zeros(shape=(3, 4))
b = np.arange(4.)
c = a + b[np.newaxis, :]
print(f'a =\n{a}\nb = {b}\nc =\n{c}')
# If you like to think in index notation the above operation is equivalent to: $ c_{ij} = a_{ij} + b_{i} $
# What is `np.newaxis` actually doing?
# In[ ]:
x = b[np.newaxis, :]
print(b.shape)
print(x.shape)
# ## More broadcasting
#
# Take the difference $d_{ij}$ of all pairs of entries in two vectors $a_i$ and $b_j$
# $$ d_{ij} \equiv a_i - b_j $$
#
# Solve by applying broadcasting to both arrays
# In[ ]:
a_i = np.arange(4.)
b_j = np.arange(4., 0., -1.)
print(f'a_i = {a_i}\nb_j = {b_j}')
# In[ ]:
d_ij = ? # ToDo
# # Indexing and slicing
# In many algorithms you need to work on parts of an array, i.e. only selected entries will be used.
#
# * When we talk about *indexing* we are choosing data for specific indices out of an array
# * For NumPy indexing, you can use Python lists or NumPy arrays as *index arrays*
# ## Example: Index arrays
# In[ ]:
a = np.linspace(1., 10., 7)
index = np.array([0, 2, 3])
print( f"a = {a}" )
print( f"index = {index}" )
print()
print( a[0] ) # Single element
print( a[[0, 2, 3]] )# Indexing with index array - Python list
print( a[index] ) # Indexing with index array - NumPy array
# ## Example: Boolean index arrays
# * You can also use arrays of Booleans as "mask" arrays to choose specific values
# In[ ]:
mask = a > 4
print( f"mask = {mask}" )
print( f"a[mask] = {a[mask]}" )
# ## Example: Slicing
# * When we talk about *slicing* we are choosing specific data *continuously* or in a *regular pattern*
# In[ ]:
print( a[0:2] ) # Slicing: Elements from (and including) no. 0 to no. 2 (not including)
print( a[::2] ) # Slicing: Every second element from start to end
print( a[:0:-1] ) # Slicing: Every element _except_ the first one, in reverse order
# ## Example: Multidimensional slicing
# * NumPy arrays can be of _any_ dimension, indexing follows the same pattern as for one dimension. Different dimensions are separated with a comma (**,**)
# In[ ]:
a = np.arange(24).reshape(3, 2, 4)
print( f"a = \n{a}" )
print()
print( "a[0,:,:] = \n{}".format(a[0,:,:]) ) # "block 0"
print()
print( "a[:,1,:] = \n{}".format(a[:,1,:]) ) # "row 1"
print()
print( "a[:,:,2] = \n{}".format(a[:,:,2]) ) # "column 2"
# - For more ways to work with arrays, see the NumPy methods **where()**, **argsort()**, **concatenate()**, **hstack()** and **vstack()**
# # Numerics with NumPy
# There are **many** numerical routines available in NumPy
# * Standard mathematical functions: `np.sin`, `np.cosh`, `np.exp`, `np.log`, `np.log10`, `np.sign`, etc.
# * Reductions, `np.max`, `np.min`, `np.sum`, `np.prod`, `np.mean`, `np.median`, etc.
# * Cumulative functions: `np.cumsum`, `np.cumprod`
# * Integration and differentiation: `np.trapz`, `np.diff`, `np.gradient`
# * Contractions, i.e. matrix multiplication and its generalizations: `np.dot`, `np.tensordot`, `np.einsum`, etc.
# * Linear algebra `np.linalg`
# * Random number generators `np.random`
# * Fourier transforms `np.fft`
# * Polynomials `np.polynomial`
# * And more
#
# see the [NumPy reference manual](https://docs.scipy.org/doc/numpy/reference/) for a complete list
# ## Sums and Products (`np.sum`, `np.prod`)
# In[ ]:
a = 0.1 * np.arange(1., 25.).reshape(2, 3, 4)
print(f"a =\n{a}")
# Sum all elements: $\sum_{ijk} a_{ijk}$
# In[ ]:
np.sum(a)
# Product of all elements: $\prod_{ijk} a_{ijk}$
# In[ ]:
np.prod(a)
# To work over a subset of axes, use the `axis` key-word argument
#
# Sum all elements over 2nd axis: $\sum_{j} a_{ijk}$
# In[ ]:
np.sum(a, axis=1)
# Product of all elements over the 2nd and 3rd axis: $\prod_{jk} a_{ijk}$
# In[ ]:
np.prod(a, axis=(1, 2))
# ## Differentiation `np.diff`
# The difference between neighbouring elements
# $$y_i = x_{i+1} - x_i$$
#
# can be computed by: `y = np.diff(x, n=1, axis=-1)`
# - `n :` number of times to take the difference
# - `axis :` axis to work on, default `-1`
#
# In[ ]:
x = np.arange(5.)
print(x)
# In[ ]:
y = np.diff(x)
print(y)
# In[ ]:
z = np.diff(x, 2)
print(z)
# ## Gradients `np.gradient`
# Approximate the gradient calculation
# $$\frac{df(x_n)}{dx} \approx \frac{f(x_{n+1})- 2f(x_n) + f(x_{n-1})}{2\Delta x}$$
# use `np.gradient(f , [dx, dy, ....])`
#
# Example:
# $$f(x) = \cos(x) \quad \Rightarrow \quad f'(x) \equiv \frac{d}{dx}f(x) = \frac{d}{dx} \cos(x) = -\sin(x)$$
# In[ ]:
dx = 0.1
x = np.arange(0, 3*np.pi, dx)
f = np.cos(x)
f_prime = np.gradient(f, dx)
# In[ ]:
import matplotlib.pyplot as plt
plt.plot(x, f, label='$f(x)$')
plt.plot(x, f_prime, label='$f\'(x)$')
plt.xlabel('$x$')
plt.legend();
# ## Cumulative sums (integration) `np.cumsum`
# For the Riemann sum approximation of an integral
# $$F(x) = \int_{x_0}^x f(y) \, dy
# \quad \Rightarrow \quad F(x_n) \approx \Delta x \cdot \sum_{i = 0}^n f(x_i)$$
#
# * use `np.cumsum(f, axis=None, out=None)`
# * `axis` is the axis to work along (default is to *flatten* array and use all elements)
#
# * Example: $f(x) = \cos(x) \Rightarrow F(x) = \sin(x)$
# In[ ]:
x = np.linspace(0, 4*np.pi, num=400)
dx = x[1] - x[0] # All distances equal
f = np.cos(x)
F = np.cumsum(f) * dx
plt.plot(x, f, label='$f(x)$')
plt.plot(x, F, label='$F(x)$')
plt.xlabel('$x$')
plt.legend();
# For integration over an entire interval
# $$ \int_{x_0}^{x_{-1}} f(x) \, dx $$
# * use `numpy.trapz(f, x=None, dx=1.0, axis=-1)` (trapetzoidal rule)
# * give `x` **or** `dx`!
# In[ ]:
F_trapz = np.trapz(f, x=x)
print("Trapezoidal integration:", F_trapz)
print("Rieman integration: ", F[-1])
# ## Array dot-product `np.dot`
# - The standard binary operations, `+`, `-`, `*`, `/` etc., work *element-wise* on NumPy `ndarray`s
# - For Vector/matrix multiplications use `np.dot(a, b)`
# - Note: `ndarrays` have `.dot` *method*, see below
#
# The dot product `np.dot` is defined as
# * For vectors: $a \cdot b = \sum_j a_j b_j$ (the result is a scalar)
# * For matrices: $A \cdot B = \sum_k A_{ik} B_{kj}$
# In[ ]:
a = np.array([1., 2., 3.])
b = np.array([2., 1., 0.])
# In[ ]:
np.dot(a, b)
# In[ ]:
a.dot(b)
# In[ ]:
a * b
# ## Matrix vector product
#
# `np.dot` does matrix-vector multiplication when given one 2D and one 1D array
# In[ ]:
A = np.arange(6.).reshape( (3,2) )
b = np.arange(2.)
print( "A =\n{}".format(A) )
print( "b = {}".format(b) )
# In[ ]:
A.dot(b)
# ## Matrix multiplication
# `np.dot` does matrix multiplication when given two 2D arrays
# In[ ]:
A = np.arange(9).reshape((3,3))
B = np.random.randint(0,10,(3,3))
print("A =", A, 'B =', B, sep='\n')
# In[ ]:
A.dot(B)
# ## Array manipulation
# * Transpose given by the `T` view, e.g. `B.T`
# * Array conjugate using the `.conjugate` or `.conj` methods
# * General transpose-like operations, `np.swapaxes` etc.
# see [NumPy Array Manipulation Routines](https://docs.scipy.org/doc/numpy/reference/routines.array-manipulation.html)
#
# In[ ]:
B = np.arange(6).reshape(2, 3)
B
# In[ ]:
B.T
# In[ ]:
np.swapaxes(B, 0, 1)
# Complex valued arrays and complex conjugate
# In[ ]:
C = (1 + 1j) * B
C
# In[ ]:
C.conj()
# ## NumPy Linear Algebra module `np.linalg`
# Provides additional operations
# * `norm(A)`: matrix or vector norm (default: 2-norm)
# * `det(A)`: matrix determinant
# * `x = solve(A, b)`: linear equation system solver: $A \cdot \mathbf{x} = \mathbf{b}$
# * `inv(A)`, `pinv(A)`: matrix inverse and pseudo inverse
# * `Q, R = qr(A)` QR-factorization
# * `eig(A)`: matrix eigen value and eigen vector decomposition
# * `svd(A)`: matrix single-value decomposition
#
# Example: inverse
# In[ ]:
A = np.array([
[1, 0, 1],
[3, 1, 0],
[1, 2, 1]])
A_inv = np.linalg.inv(A)
print(A_inv)
# In[ ]:
A.dot(A_inv)
# For more information use the built-in help!
# ## The matrix type `np.matrix`
# - It is **deprecated**, please do not use it!
# - Instead use the new matrix multiplication syntax: `A @ B`
# Here are the previous examples with `np.dot` replaced by `@`
# In[ ]:
A = np.arange(6.).reshape((3, 2))
b = np.arange(2.)
A @ b
# In[ ]:
A = np.arange(9).reshape((3 ,3))
B = np.random.randint(0, 10, size=(3, 3))
A @ B
# In[ ]:
A = np.array([
[1, 0, 1],
[3, 1, 0],
[1, 2, 1]])
A_inv = np.linalg.inv(A)
A @ A_inv
# ## More NumPy!
#
#
# There are many additional sub-packages and functions in NumPy not covered here, for example the modules
# - `np.fft`,
# - `np.random`, and
# - `np.polynomial`,
#
# for more information use the built-in `help()` and the online documentation.
# # Visualisation
# # Visualisation
#
# Visualization is a cornerstone of scientific computing
#
# There are many tools for data visualisation, e.g.
# - Matlab, Mathematica, GNUplot, R, etc., and
# - many more specialised software suites
# For Python the dominating package is:
# - **Matplotlib**, for 2D plots with some 3D support
# - there are interesting competitors, **plotly**, **Bokeh**, etc.
#
# Here we will only look into Matplotlib.
# # Matplotlib
# - is a Python 2D plotting library which produces publication quality figures
#
# Matplotlib can be used in
# - Python scripts,
# - the Python and IPython shell,
# - the jupyter notebook,
# - web application servers, and
# - four graphical user interface toolkits.
# ## Matplotlib motto:
#
# *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 simple plotting the pyplot module provides a MATLAB-like interface,
# - 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
#
# For a sampling, see the screenshots, [thumbnail gallery](https://matplotlib.org/gallery/index.html), and examples directory.
# ## `matplotlib.pyplot`
# - `matplotlib.pyplot` is the "main" module for plotting
# - this module contain all the things you need to create figures, draw axes, plot results etc.
# - it is standard to import Matplotlib according to:
# In[ ]:
import matplotlib.pyplot as plt
# ## Figures, axes and plots
# To understand how to work with Matplotlib we need to know how Matplotlib constructs its figures.
# - The "basic" object for all figures is the `figure` object.
# - The figure object contains all the axes, plots and axis lables, etc. that you create
#
# - The figure object can be explicitly created using `plt.figure()`,
# - However, it is created automatically when using most plotting-commands.
#
# In[ ]:
import matplotlib.pyplot as plt
plt.figure()
plt.plot([0, 2, 3], [5, 3, 1]);
plt.show()
# The call to `plt.plot(...)` creates a line plot (it also automatically creates an axes object and makes it the current axis).
# ## What makes a figure?
#
# A figure comprise a number of components
# - **axes**, with
# - title
# - legend
# - spines
# - axis labels
# - grid
# - minor and major ticks, and
# - tick labes
# - different types of **plots**
# - line plots
# - scatter plots
# - etc.
# ![L4_matplotlib_fig_map.png](attachment:L4_matplotlib_fig_map.png)
# (Source: [the Matplotlib homepage](http://matplotlib.org/faq/usage_faq.html#parts-of-a-figure))
#
# ## Controlling the axes
# Methods for the figure object, or for individual axes objects include:
# In[ ]:
plt.figure()
plt.title('Bell progress')
plt.plot([0,2,3], [5,3,1], label='Tim')
plt.plot([0,1,3], [2,3,5], label='Alice')
plt.legend(title='User', loc='best')
plt.xlabel('time')
plt.ylabel('bells')
plt.xlim(right=3.5)
plt.ylim([0., 5.5])
plt.grid(True)
plt.text( 2.5, 3., r'$\Omega = \sum_i \frac{K_i}{2 \cdot B_i}$', size=15 )
plt.yticks(np.arange(6), ['', 'One', '**', 3, r'$\Delta$', '>>>>>'])
plt.tick_params(axis='x', direction='in', color='r', labelcolor='g',
width=2, length=8, top=False)
plt.savefig('my_plot.svg')
plt.show()
# - There are many more possibilities available to modify your plots
# - See the online material and the ["gallery" section on the the Matplotlib homepage](https://matplotlib.org/gallery/index.html) for more information.
# - **Note:** For text labels, you can use raw strings (`r"..."`) with LaTeX-syntax to display symbols etc. (See the simple example above.)
# ## Multiple axes
#
# You can combine plots with completely different scales in one direction into one plot (or subplot)
# by using `plt.twinx()` or `plt.twiny()`.
# In[ ]:
plt.figure()
plt.plot([1, 2, 3, 4], [1, 2.5, 3.1, 4], label='Data 1')
plt.ylabel('Data 1 [m]')
plt.legend()
plt.twinx()
plt.scatter( [1, 2, 3, 4], [60, 48, 42, 30], label='Data 2' )
plt.ylabel('Data 2 [kg]')
plt.legend()
plt.xlabel('X')
plt.show()
# # Plot styling
# ## Plot styling: Colors
# Colors, markers and linestyles can be specified using a Matlab-like notation for simple cases
# - `"r*-"` to give a red line with `*`-markers at the points.
# - To separately control color use the `color=` keyword-argument
#
# There are four color specifications:
# - By name (or the single letter version like above). All the 140 color names from the [HTML/CSS specification](https://www.w3schools.com/colors/colors_names.asp) are supported.
# - Using a [RGB hex string (as in HTML/CSS)](https://www.w3schools.com/colors/colors_hex.asp), e.g. `#0000FF` for blue etc.
# - By an RGB(A) tuple of floats `(r, g, b, alpha)` with values in $a,b,c,\alpha \in [0, 1]$. The last parameter $\alpha$ is optional and controls the transparency.
e.g. red is (1.0, 0.0, 0.0), while (0.0, 0.0, 1.0, 0.5) gives transparent blue.
# - Setting gray level with a string containing a number between 0 and 1. For black use `"0.0"` and for white `"1.0"`
#
# (Note: Transparency can also be set by the `alpha=` key-word argument)
# In[ ]:
plt.plot([0, 1, 2], [1.2, 1.5, 1.0], color='OliveDrab')
plt.plot([0, 1, 2], [0.8, 0.4, 0.2], color=(0.2, 0.8, 0.8, 0.5))
plt.plot([0, 1, 2], [0.5, 0.8, 1.3], color="0.6", alpha=0.5, linewidth=5)
plt.show()
# ## Plot styling: Line styles
# - `-` : solid lines
# - `--` : dashed lines
# - `-.` : dot-dashed lines
# - `dashes=[dash_length, space_length, ...]` : arbitrary dashes
# In[ ]:
x = np.linspace(0, 1, num=10)
a, b, c, d = np.cumsum(np.random.random((4, len(x))), axis=-1)
plt.plot(x, a, '-', label='a')
plt.plot(x, b, '--', label='b')
plt.plot(x, c, '-.', label='c')
plt.plot(x, d, dashes=[6, 1, 1, 1, 1, 1], label='d')
plt.legend(loc='best'); plt.show()
# ## Plot styling: Markers
# Markers are set in the `fmt` argument or the `marker=` keyword argument.
#
# marker | description ||marker | description ||marker | description ||marker | description
# :----------|:--------------||:---------|:----------------||:-------|:--------------||:---------|:--------------
# "." | point ||"+" | plus ||"," | pixel ||"x" | cross
# "o" | circle ||"D" | diamond ||"d" | thin_diamond || |
# "8" | octagon ||"s" | square ||"p" | pentagon ||"\*" | star
# "|" | vertical line||"\_" | horizontal line ||"h" | hexagon1 ||"H" | hexagon2
# 0 | tickleft ||4 | caretleft ||"<" | triangle_left ||"3" | tri_left
# 1 | tickright ||5 | caretright ||">" | triangle_right||"4" | tri_right
# 2 | tickup ||6 | caretup ||"^" | triangle_up ||"2" | tri_up
# 3 | tickdown ||7 | caretdown ||"v" | triangle_down ||"1" | tri_down
# "None" | nothing ||`None` | nothing ||" " | nothing ||"" | nothing
#
# It is also possible to construct new markers by giving a list of xy-coordinate tuples (relative to the center of the marker at `(0,0)`).
# In[ ]:
plt.plot([0, 1, 2], [1.2, 1.5, 1.0], 'kD-', markersize=10 )
plt.plot([0, 1, 2], [0.5, 0.8, 1.3], color="green", marker="8", markersize=15 )
plt.plot([0, 1, 2], [0.8, 0.4, 0.2], color='SlateBlue', alpha=0.75,
marker=[(-3, -5), (0, 3), (3, -5), (0, -3)], markersize=25)
plt.show()
# ## Plot functions
# There is a wide range of plot functions available in Matplotlib.
# - We have already used `plt.plot` and `plt.scatter`
# - Lets look at a fev more
# ## `plt.plot`
# The `plt.plot()` is very flexible
# - `plt.plot(x, y)` will plot the values in `x` and `y`. `x`, `y` can be single values or iterable items (list, tuples, NumPy arrays etc.) of coordinates
# * `plot(x1, y1, x2, y2, ...)` will plot the sets of `x` and `y`:s at once in the same figure
# * `plot(y)` will plot *y* with *x* as integers starting from 0
#
# Common keyword arguments to `plt.plot` are
# - `color=`, `alpha=`,
# - `linestyle=`, `linewidth=`, `fillstyle=`,
# - `marker=`, `markersize=`,
# - `label=` (will be shown by `plt.legend`)
#
# Note: It is still possible to use Matlab-like colour/marker/linestyle specifications like `"rx-."`
# Another example:
# In[ ]:
coord = np.array( [[0.05, 0.05], [0.4, 0.3], [0.6, 0.5], [0.7, 0.55], [0.95, 0.61]] )
print(coord)
plt.plot(coord[:, 0], coord[:, 1], color='g', label='data', fillstyle='bottom', marker='D', markersize=10 )
plt.plot(coord[:, 1], coord[:, 0], color='b', label='flipped data', linestyle='--', linewidth=4 )
plt.legend(loc='lower right')
plt.show()
# Multi-dimensional `y` array example
# In[ ]:
x = np.linspace(0, 1, num=10)
y = np.cumsum(np.random.random((len(x), 4)), axis=0)
print(x.shape, y.shape)
plt.plot(x, y)
plt.show()
# ## Logarithmic plots: `plt.semilogx`, `plt.semilogy`, `plt.loglog`
# Any axis can be scaled logarithmically by using
# - `plt.semilogx` : scale the x-axis logaritmically
# - `plt.semilogy` : the y-axis
# - `plt.loglog` : scale both axes
# In[ ]:
x = np.linspace(0, 100, num=1000)
plt.loglog(x, x**2, label=r'$x^2$')
plt.loglog(x, x**3, label=r'$x^3$')
plt.legend(loc='upper left'); plt.show()
# In[ ]:
plt.semilogy(x, x**2, label=r'$x^2$')
plt.semilogy(x, x**3, label=r'$x^3$')
plt.legend(loc='lower right'); plt.show()
# ## Error bars: `plt.errorbar`
# For errorbars (or any other interval you want to show like an errorbar), use
# - `plt.errorbar(x, y, xerr=..., yerr=...)`
# In[ ]:
x = np.linspace(0, 1, num=10)
y = x*(x-1)*(x+1)
xerror = np.random.normal(size=len(x), scale=0.1)
yerr = np.random.normal(size=len(x), scale=0.05)
# In[ ]:
plt.errorbar(x, -y, xerr=xerror, yerr=yerr, ecolor='r');
# In[ ]:
plt.errorbar(x, y, xerr=yerr, ecolor='g');
# ## Filled plots `plt.fill_between`
# - You can create plots with filled areas between lines
# - or between a line and the x-axis) using `plt.fill_between(x, y1, y2=)`
# In[ ]:
x = np.linspace(1e-10, 4*np.pi, num=1000)
y1 = np.sin(x + np.pi*0.5)
y2 = np.sin(x) / x
plt.fill_between(x, y1, y2=y2, edgecolor='Purple', alpha=0.5, linewidth=4);
# In[ ]:
plt.fill_between(x, y2, edgecolor='r', facecolor='SeaGreen', alpha=0.75);
# ## Histograms `plt.hist`
# - Create histograms using `plt.hist(y, bins=10, normed=False, histtype= ...)`
# - See the built-in help or online documentation for more options
# In[ ]:
x = 100 + 15 * np.random.randn(100000)
plt.hist(x, bins=40, density=True, facecolor='g', alpha=0.75);
# In[ ]:
plt.hist(x, bins=20, histtype='stepfilled', facecolor='PowderBlue', alpha=0.5);
# ## Contour plots `plt.contour`, `plt.contourf`
# You can create filled and non-filled contour plots using `plt.contourf` and `plt.contour` respectively.
# - To specify colour-map, use the `cmap=` keyword argument
# - For predefined color maps see [the Matplotlib documentation](https://matplotlib.org/3.1.0/tutorials/colors/colormaps.html)
# In[ ]:
x = np.linspace(-4*np.pi, 4*np.pi)
X, Y = np.meshgrid(x, x)
R = np.sqrt(X**2 + Y**2)
Z = np.sin(R) / R
plt.contour(X, Y, Z, 15, cmap=plt.cm.RdBu)
plt.axis('image');
# In[ ]:
plt.contourf(X, Y, Z, 15, cmap=plt.cm.summer); plt.axis('image');
# # Axes and subplots `plt.axis`, `plt.subplot`
# - In the previous examples we have not explicitly worked with the `axis` object in Matplotlib
# - For a single plot the axis is generated automatically when plotting data, using e.g. `plt.plot`
# - Working with more than one set of axes in a single figure sometimes requires explicit axes
# In[ ]:
x = np.linspace(-1, 1, num=10)
fig = plt.figure()
ax = fig.add_subplot()
ax.plot(x, x**2)
ax.set_xlabel('x');
# - All functionality is reachable using methods of the `figure` and `axis` objects
# - This is the *object oriented* way of using Matplotlib
# ## Multiple subplots `plt.subplot`
# * `ax = plt.subplot(nrows, ncols, plot_number)` : call for all values of `plot_number`
# * `fig1, (ax1, ax2, ...) = plt.subplots(nrows, ncols)` : call once
#
# Both these gives a `nrows x ncols` grid of subplots.
# In[ ]:
plt.subplot(1, 2, 1)
plt.plot([0,2,3], [5,3,1])
plt.subplot(1, 2, 2)
plt.plot([0,1,3], [2,4,3])
plt.show()
# In[ ]:
fig, (ax1, ax2) = plt.subplots(1, 2)
ax1.plot([0,2,3], [5,3,1])
ax2.plot([0,1,3], [2,4,3])
plt.show()
# ## Subplot layouts
#
# How do we make a figure with
# - **one** subplot on the left and
# - **two** subplots on top of each other to the right?
# Solution:
# 1. "Pretend" to have a $1 \times 2$ layout for the first subplot, and chose position 1 (the leftmost for it).
# 2. For the two right plots, "pretend" to have a $2 \times 2$ layout and chose positions 2 and 4 (the numbering goes from left to right from the top down).
# In[ ]:
x, y_L, y_R = [0,2,3], [5,3,1], [2,4,3]
plt.subplot(1, 2, 1)
plt.plot(x, y_L)
plt.subplot(2, 2, 2)
plt.plot(x, y_R)
plt.subplot(2, 2, 4)
plt.plot(x, y_R)
plt.tight_layout()
# Tip: For more advanced subplot layouts use [the `GridSpec` class](http://matplotlib.org/users/gridspec.html)
# # 3D plotting
# - To contruct 3D-plots with Matplotlib, you additional need to import an extra axes object
# ```python
# from mpl_toolkits.mplot3d import Axes3D
# ```
# - setting the `projection='3d'` keyword argument when creating axes gives a 3D plot
# In[ ]:
get_ipython().run_line_magic('config', "InlineBackend.figure_formats = ['retina']")
# In[ ]:
theta = np.linspace(-4, 4, num=100)
z = 0.5*theta; r = z**2 + 1
x = r * np.sin(np.pi*theta)
y = r * np.cos(np.pi*theta)
from mpl_toolkits.mplot3d import Axes3D
plt.figure(figsize=(12, 12))
plt.subplot(1,1,1, projection='3d')
plt.plot(x, y, z, label='parametric curve')
plt.legend()
plt.show()
# In[ ]:
x = np.linspace(-4*np.pi, 4*np.pi, num=40)
X, Y = np.meshgrid(x, x)
R = np.sqrt(X**2 + Y**2)
Z = np.sin(R) / R
plt.figure(figsize=(12, 12))
ax = plt.subplot(1,1,1, projection='3d')
ax.plot_wireframe(X, Y, Z, alpha=0.5)
plt.show()
# In[ ]:
x = np.linspace(-4*np.pi, 4*np.pi, num=100)
X, Y = np.meshgrid(x, x)
R = np.sqrt(X**2 + Y**2)
Z = np.sin(R) / R
plt.figure(figsize=(12, 12))
ax = plt.subplot(1,1,1, projection='3d')
ax.plot_surface(X, Y, Z, cmap=plt.cm.rainbow, rstride=1, cstride=1, alpha=.5, linewidth=0)
ax.contour(X, Y, Z, zdir='y', offset=15)
ax.contour(X, Y, Z, zdir='x', offset=-15)
plt.show()
# ## Matplotlib collections
# If you want to plot a large number of elements it is somewhat inefficient to plot them one by one using the methods shown above.
#
# To remedy this Matplotlib have a **collections** submodule that helps in collecting many objects of the same type that can be used efficiently when drawing images.
#
# There are collection-types for polygons, lines, triangular meshes, paths and more.
#
# For example the **matplotlib.collections** **LineCollection(segments, ...)** gathers segments of lines for convinient plotting of many line-segments.
# * **segments** is a sequence of (line0, line1, line2), where:
# * **lineX** is a sequence of coordinates, i.e. lineX = (x0, y0), (x1, y1), ... (xm, ym)
# In[ ]:
Ny = 10
x = np.arange(8)
# Ny lines where y is randoms between 0 and 2 * Ny + 3
from random import random
lines = []
for i in np.arange(Ny):
line = []
for xj in x:
y = random() * 3 + 2 * i
line.append( (xj, y) )
#print(line)
lines.append(line)
from matplotlib.collections import LineCollection
line_segments = LineCollection(lines)
fig = plt.figure(figsize=(15, 8))
ax = fig.gca()
ax.add_collection(line_segments)
ax.set_ylim((0, 2*Ny+3))
ax.set_xlim((0, np.amax(x)))
plt.show()
# ## More Matplotlib?
#
# Have a look at the [thumbnail gallery](https://matplotlib.org/gallery/index.html) and see if you **see** what you want to do.
# # Lecture 4: The End
# In[ ]:
import this