This webpage is a static view of an interactive Jupyter Notebook tutorial. To get the full interactive experience where you can run Python code, modify examples and complete exercises, click the "Open in Colab" button. This will access the Google Colab notebook from a GitHub repository github.com/dchappell2/Computational-Physics and open it in Google Colab. You will need a Google account if you want to open it in Colab.
Note: You can download a pdf of the lecture slides for this chapter here: Chapter 0-3 slides
Goals:
The Python library Matplotlib offers two different approaches to plotting:
This chapter will only cover the state machine approach.
An excellent overview of these approaches can be found at the following link and should be required reading for anyone plotting in Python: https://matplotlib.org/2.0.2/faq/usage_faq.html
We will use plotting commands in pyplot, a module within the Matplotlib library. To use this approach one typically imports matplotlib.pyplot as plt. This renames matplotlib.pyplot as simply plt, making it easier to work with.
plt.plot(x,y)``show` to render the figure that we created.import matplotlib.pyplot as plt
import numpy as np
# define a numpy array of 300 x values from 0 to 50
# we use 1e-16 as the lower limit to avo
x = np.linspace(0, 2, 100)
# y = downward-opening parabola
y = x*(2-x)
# plot three functions of x, and define labels for each in the legend
plt.plot(x, y)
plt.xlabel('x') # label the x and y axes
plt.ylabel('y')
plt.title("Simple Plot") # give the plot a title
plt.show() # display the plot
The line style can be modified in a few ways: (1) color, (2) solid/dashed/etc and (3) thickness.
Color and Line Style
The simplest way of specifying the line style and color is to include a specification string like this: 'r-', which means create a solid red line.
Here is a list of color specifications that can be included as a string:
'r' = red'g' = green'b' = blue'c' = cyan'm' = magenta'y' = yellow'k' = black'w' = whiteMatplotlib has an extensive list of named colors like 'darkblue', 'plum' and 'salmon' in addition to being able to numerically specify the RGB (red, green, blue) components of any possible color. You can also adjust the transparency of the lines or markers. For more info see here: https://matplotlib.org/stable/gallery/color/named_colors.html and here: https://matplotlib.org/stable/users/explain/colors/colors.html
Here is a list of line style specifications that can be included as a string:
'-' = solid line'--' = dashed line':' = dotted line'-.' = dash-dot lineHere is a list of marker style specifications that can be included as a string:
'o' = circle's' = square'+' = plus sign'x' = x'D' = diamond'^' = up triangle'r--' specification.plt.grid(True).# run previous code cell to define x and y arrays
plt.plot(x, y, 'r--') # dashed red line
plt.xlabel('x') # label the x and y axes
plt.ylabel('y')
plt.title("Dashed Red Line") # give the plot a title
plt.grid(True) # turn on grid
plt.show() # display the plot
Create a plot of $y(x) = \sin (x^2) $ for $0\leq x \leq 10$. Plot the curve as a solid blue line. How many data points do you need to create a smooth curve?
# your code here
import matplotlib.pyplot as plt
import numpy as np
'linewidth=2.0' or 'lw=2.0' sets the line width to 2 (line width of 1.5 is the default)'markersize=10' or 'ms=10' sets the marker size to 10 (the default value is of 6)Here's an example of a plot with small, green circle markers.
x = np.linspace(0, 2, 100) # create data to plot
y = x*(2-x)
plt.plot(x, y, 'go', markersize=2) # small, green circle markers
plt.xlabel('x') # label the x and y axes
plt.ylabel('y')
plt.title("Circle Markers") # give the plot a title
plt.show() # display the plot
To plot multiple curves, we can just call the plot command multiple times before the show() command is called.
This example plots a linear, quadratic and cubic function on the same graph.
plt.legend() commandlabel specification given in the plot command (see below).import matplotlib.pyplot as plt
import numpy as np
# define a numpy array of 100 x values from 0 to 2
x = np.linspace(0, 2, 100)
# plot three functions of x, and define labels for each in the legend
plt.plot(x, x, label='linear')
plt.plot(x, x**2, label='quadratic')
plt.plot(x, x**3, label='cubic')
plt.xlabel('x') # label the x and y axes
plt.ylabel('y')
plt.title("Three Curves") # give the plot a title
plt.legend() # display the legend
plt.show() # display the plot
Often, when you have experimental data points and a theoretical prediction, the data points are plotted at points (i.e. markers) and the theoretical prediction as a curve.
Here's an example where we generate some synthetic data points using the random number generator and plot the theoretical curve with a solid line.
N = 200
# create array of time values
t = np.linspace(0,2,N)
# Create synthetic data
rng = np.random.default_rng()
z = rng.normal(size=N)
data = 0.1*z + np.sqrt(t)
# define a theoretical curve
y = np.sqrt(t)
# plot three functions of x, and define labels for each in the legend
plt.plot(t, data, 'bo', ms=3, label='data')
plt.plot(t, y, 'r-', label='theory')
plt.xlabel('t') # label the x and y axes
plt.ylabel('y')
plt.title("Theory and Experiment") # give the plot a title
plt.legend() # display the legend
plt.show() # display the plot
If you have estimates of the errors of your measured data points, you can draw error bars with the plt.errorbar() function. Here's an example of the syntax: plt.errorbar(x, y, yerr=y_err, fmt='o', capsize=3, label='data')
x,y = arrays for the x and y valuesyerr=y_err The variable y_err is either a single value if all data points have the same error, or an array the same length as x and y if the data points have different errors. You can plot horizontal error bars by using xerr=.fmt='o' is the format for the markerscapsize=3 is the size of the end caps on the error barslabel='data' is the label displayed in the legendThe following example adds error bars to the previous example.
N = 20
y_err = 0.1 # error bar
# create array of time values
t = np.linspace(0,2,N)
# Create synthetic data
rng = np.random.default_rng()
z = rng.normal(size=N)
data = 0.1*z + np.sqrt(t)
# define a theoretical curve
y = np.sqrt(t)
# We replace the plt.plot() command with plt.errorbar() do display error bars
plt.errorbar(t, data, yerr=y_err, fmt='o', capsize=3, label='data')
plt.plot(t, y, 'r-', label='theory')
plt.xlabel('t')
plt.ylabel('y')
plt.title("Theory and Experiment with error bars")
plt.legend() # display the legend
plt.show() # display the plot
Copy over your function from Chapter 9, that creates a noisy sine wave. Use the noisy sine wave function to create a noisy data data set with:
Code specifications:
# your code here
When two sine waves with almost the same frequencies are added together, the result is the phenomena of beats. We'll assume each wave has the same amplitude and phase. Here's the sum of the two sine waves:
$$y(t) = \sin(\omega_1 t) + \sin(\omega_2 t)$$
The result is a sine wave whose amplitude is modulated with a cosine wave. The modulating cosine wave will have an amplitude of twice the component waves:
$$y_{envelope} = 2 \cos(\omega_3 t)$$
where $\omega_3 = |\omega_2-\omega_1|/2$.
import matplotlib.pyplot as plt
import numpy as np
# your code here
The subplot(n,m,k) command can be used to create a grid of subplots where:
The plt.tight_layout() command reduces the margins around the subplots.
In this example, we plot a sine curve in the upper plot and a cosine curve in the lower plot.
plt.subplot(2,1,1) says create a grid of 2 rows, 1 column and plot in the first position (i.e. top plot).plt.subplot(2,1,2) says create a grid of 2 rows, 1 column and plot in the second position (i.e. lower plot).import matplotlib.pyplot as plt
import numpy as np
# define a numpy array of 200 values from 0 to 2
x = np.linspace(0, 20, 300)
# top plot
y = np.sin(x)
plt.subplot(2,1,1) # 2 rows, 1 column, plot # 1
plt.plot(x,y)
plt.xlabel('x')
plt.ylabel('y')
plt.title('sin(x)')
# lower plot
y = np.cos(x)
plt.subplot(2,1,2) # 2 rows, 1 column, plot # 2
plt.plot(x,y)
plt.xlabel('x')
plt.ylabel('y')
plt.title('cos(x)')
plt.tight_layout() # reduce margins around the subplots
plt.show()
We extend the previous example in several ways:
plt.fill_between() functionimport matplotlib.pyplot as plt
import numpy as np
# create a function to plot
def plot_func(x,y,title):
plt.plot(x,y,'b-',lw=.5)
plt.fill_between(x, y, 0, color='lightblue', alpha=0.5) # shade under curve
plt.xlabel('x')
plt.ylabel('y')
plt.title(title)
# define a numpy array of 300 values from 0 to 1
L = 1
x = np.linspace(0, L, 300)
# Loop to create a grid of 6 subplots
for k in range(1,7):
P = 2*L/k
y = np.sin(2*np.pi*x/P)
plt.subplot(2,3,k) # create a 2x3 grid and place each subplot accordingly
plot_func(x,y,f"n = {k}") # label each subplot with number of modes
plt.tight_layout()
plt.show()
Sometimes you might want to change the size or aspect ratio of the plot to fit the data being plotted.
plt.figure(figsize=(width,height)) creates a figure with a specified width and height (in inches).width=6.4 inches and height=4.8 inches.Lets plot a sine wave with lots of periods. We'll want a long figure to match the data:
import matplotlib.pyplot as plt
import numpy as np
# define a numpy array of 200 values from 0 to 2
x = np.linspace(0, 100, 300)
y = np.sin(x)
plt.figure(figsize=(10,1.5)) # create figure with custom size
plt.plot(x,y)
plt.xlabel('x')
plt.ylabel('y')
plt.title('Custom Aspect Ratio')
plt.show()
Matplotlib usually does a good job of setting the plot limits to reasonable values. However, you can also set the limits manually. For example, notice there's space before and after the sine wave above. If you want to remove that space, you can set the plot limits to 0 and 100.
plt.xlim(xmin,xmax) sets the limits on the horizontal axis to xmin and xmax.plt.ylim(ymin,ymax) sets the limits on the horizontal axis to ymin and ymax.If you want to ensure that the scaling in the x and y directions is the same, you can use plt.axis('equal'). An example of this is shown in the next section.
# define a numpy array of 200 values from 0 to 2
x = np.linspace(0, 100, 300)
y = np.sin(x)
plt.figure(figsize=(10,1.5)) # create figure with custom size
plt.plot(x,y)
plt.xlim(0,100) # adjust x limits
plt.ylim(-1.5,1.5) # adjust y limits
plt.xlabel('x')
plt.ylabel('y')
plt.title('Custom Plot Limits')
plt.show()
Rather than plotting $y(x)$ or $x(t)$, it is sometimes useful to plot $y(t)$ vs. $x(t)$, where both the $x$ and $y$ coordinates depend on a third variable $t$. The following shows an example
Lissajous figures are patterns where two sinusoidal (AC) signals with different frequencies $\omega_1$ and $\omega_2$ are fed into the x and y channels of an oscilloscope. If the frequencies can be expressed as a simple ratio, i.e 2:1, 3:2, etc. then the patterns form simple, closed solutions. Here are the equations:
where the angular frequences $\omega_1 / \omega_2 = n/m$ can be expressed as a ratio of two integers $n$ and $m$. $\phi_0$ is a phase constant between the two signals.
The following example shows a Lissajous figure for $\omega_1 / \omega_2 = 2$ with $\phi_0 = 0$.
Notes:
plt.xticks([]) and plt.xticks([]).# define x and y as a function of the independent variable t
t = np.linspace(0, 2*np.pi, 300)
omega_1 = 1 # angular frequency of signal 1
omega_2 = 2 # angular frequency of signal 2
phi0 = 0 # phase difference
x = np.sin(omega_1 * t + phi0)
y = np.sin(omega_2 * t)
plt.figure(figsize=(5,5))
plt.plot(x,y)
# ensure x and y axes have same scaling
plt.axis('equal')
plt.xticks([]) # hide tick marks
plt.yticks([])
# fancy method to display frequency ratio and phase shift in degrees
phi0_deg = np.rad2deg(phi0)
plt.title(rf'{omega_1}:{omega_2} $\phi_0$ = {phi0_deg:.0f}$^\circ$')
plt.show()
Create a matrix of subplots that display Lissajous patterns for different frequency ratios $\omega_1/\omega_2$ and phase shifts $\phi_0$. Here are the specs:
omega_1, omega_2 and phi0 arrays. Your code should be flexible so that if you change the number of elements in each array, it will automatically create the right number of plots. I.e. since omega_1 has 4 elements and phi_0 has 3 elements, your code should produce a total of 12 plots (4 rows and 3 columns).
phase = [0, np.pi/3, np.pi/2] # phase offset
omega_1 = [1, 1, 2, 2, 3] # angular frequency of signal 1
omega_2 = [1, 2, 1, 3, 4] # angular frequency of signal 2
# your code here
You may want to save your file to include in a paper, report or presentation. The Matplotlib command plt.savefig() lets you save it in a variety of formats. You just need to specify the correct extension:
plt.savefig('my_fig.pdf',dpi=300) saves as a pdf fileplt.savefig('my_fig.png',dpi=300) saves as a png file (lossless, best for most plots)plt.savefig('my_fig.jpg',dpi=300) saves as a jpeg (smaller file size, but lower quality)plt.savefig('my_fig.tiff',dpi=300) saves as a tiff file (higher quality but still lossy)In each example, we specify the file resolution as 300 dpi, which is recommended for publication-quality plots. You can adjust the size of the plot in inches using the plt.figure(figsize=()) command.
plt.savefig() command must come before plt.show()plt.savefig() command:```
from google.colab import files
files.download("sine_wave.pdf")
```
import matplotlib.pyplot as plt
import numpy as np
from google.colab import files
plt.figure(figsize=(6,4)) # create figure with custom size
# Create a simple plot
x = np.linspace(0, 2*np.pi, 300)
plt.plot(x,np.sin(x))
plt.xlabel('x')
plt.ylabel('y')
plt.title('Example: Save to File')
# save figure to your computer
plt.savefig('my_fig.png',dpi=300) # creates the file in your local directory
files.download("my_fig.png") # downloads file from Google to your computer
plt.show() # this must come after plt.savefig()
Include commands in Skill Check 4 to save the plot as a .png file. Make sure you are able to save it onto your computer.
Here are some additional plot commands. You can look up more info on their syntax using the links below.
```
plt.loglog(x,y) # log-log plot
plt.semilogx(x,y) # log-linear plot
plt.bar(x,y) # bar plot
plt.polar(phi,r) # polar plot
plt.hist(y) # histogram plot
```
Now you know the basics of plotting with Matplotlib. Here are a few resources to learn more: