import matplotlib.pyplot as plt
import numpy as np
Recall: we can generate data easily with NumPy and plot with Matplotlib.
t = np.linspace(0, 2*np.pi, 1000)
x = np.cos(t)
y = np.sin(t)
plt.plot(x,y)
Note: the parametric curve $x(t) = \cos(t)$, $y(t) = \sin(t)$ traces a cirlce, but the figure does not look circular. We need to adjust the aspect ratio of the figure to correct this.
First, a little more information on the structure of figures in matplotlib.
figure
objectaxes
object (or several)axes
object, we can draw things (lines, points, etc.)When we call plt.plot
(in the absence of other commands), matplotlib automatically generates a figure and axes object. We can also take control of that process ourselves:
plt.figure()
will generate a figure objectfig = plt.figure()
t = np.linspace(0, 2*np.pi, 1000)
x = np.cos(t)
y = np.sin(t)
plt.plot(x,y)
new_fig = plt.figure()
plt.plot(t,x)
plt.plot(t,y)
One advantage of defining our own figure is that we can set a figure size, by supplying an optional argument figsize=(<horizontal size>, <vertical size>)
.
fig = plt.figure(figsize=(3,3))
t = np.linspace(0, 2*np.pi, 1000)
x = np.cos(t)
y = np.sin(t)
plt.plot(x,y)
We can get a circle by setting a figure size with equal height and width.
fig = plt.figure(figsize=(3,3))
t = np.linspace(0, 2*np.pi, 1000)
x = np.cos(t)
y = np.sin(t)
plt.plot(x,y)
plt.plot(t,x)
Unfortunately, using the figure size is not a robust way to set aspect ratios. Instead, we can take control of the aspect ratio through the axes
object. How can we add an axes
object ourselves?
One way is through the plt.subplot
function.
fig = plt.figure(figsize=(3,3))
ax = plt.subplot()
t = np.linspace(0, 2*np.pi, 1000)
x = np.cos(t)
y = np.sin(t)
plt.plot(x,y)
plt.plot(t,x)
The plt.subplot
function can take in an optional argument, aspect
. We can use aspect='equal'
to force an equal aspect ratio.
fig = plt.figure(figsize=(3,3))
ax = plt.subplot(aspect='equal')
t = np.linspace(0, 2*np.pi, 1000)
x = np.cos(t)
y = np.sin(t)
plt.plot(x,y)
plt.plot(t,x)
plt.ylim(-4,4)
We can also use the plt.subplot
to create several axes
objects within the same figure. We can call plt.subplot(r, c, n)
to create an r
by c
grid of subplots and insert an axes
object in the n
th position. The subplot position is counted starting in the top-left corner, then proceeding left-to-right, then top-to-bottom.
fig = plt.figure()
r = 3
c = 4
for n in range(1,13):
plt.subplot(r,c,n)
plt.plot(t, np.cos(n*t))
#plt.plot(t, np.sin(n*t))
plt.title('Plot #{}'.format(n))
In the above figure, the titles of one subplot are clashing with the tick labels of others. We can use the plt.tight_layout
function to have matplotlib automatically adjust the spacing.
fig = plt.figure()
r = 3
c = 4
for n in range(1,13):
plt.subplot(r,c,n)
plt.plot(t, np.cos(n*t))
#plt.plot(t, np.sin(n*t))
plt.title('Plot #{}'.format(n))
plt.tight_layout()
We can also add a title to the entire figure using plt.suptitle
:
fig = plt.figure()
r = 3
c = 4
for n in range(1,13):
plt.subplot(r,c,n)
plt.plot(t, np.cos(n*t), 'm--')
#plt.plot(t, np.sin(n*t))
plt.title('Plot #{}'.format(n))
plt.suptitle('Creating subplots with matplotlib')
plt.tight_layout()
fig = plt.figure()
r = 3
c = 4
plt.subplot(r,c,1)
plt.plot(t, np.cos(t), 'm--')
plt.subplot(r,c,6)
plt.plot(t, np.cos(6*t), 'g-')
plt.subplot(r,c,12)
plt.plot(t, np.cos(12*t), 'r-')
plt.suptitle('Creating subplots with matplotlib')
plt.tight_layout()
Note: we can mix-and-match the subplot sub-plot grid sizes (within reason). Consider the following example.
fig = plt.figure()
plt.subplot(2,2,1)
plt.plot(t, np.cos(t), 'm--')
plt.subplot(2,2,2)
plt.plot(t, np.cos(2*t), 'g-')
# plt.subplot(2,2,3)
# plt.plot(t, np.cos(3*t), 'r-')
# plt.subplot(2,2,4)
# plt.plot(t, np.cos(4*t), 'b-')
plt.subplot(2, 1, 2) # Add an axes object in the 2nd position of a 2 by 1 grid
plt.plot(t, np.cos(5*t), 'c')
plt.suptitle('Creating subplots with matplotlib')
plt.tight_layout()
fig = plt.figure()
plt.subplot(3,2,1)
plt.plot(t, np.cos(t), 'm--')
plt.subplot(3,2,2)
plt.plot(t, np.cos(2*t), 'g-')
plt.subplot(3, 1, 2) # Add an axes object in the 2nd position of a 2 by 1 grid
plt.plot(t, np.cos(5*t), 'c')
plt.subplot(3,2,5)
plt.plot(t, np.cos(3*t), 'r-')
plt.subplot(3,2,6)
plt.plot(t, np.cos(4*t), 'b-')
plt.suptitle('Creating subplots with matplotlib')
plt.tight_layout()
fig = plt.figure()
# plt.subplot(3,2,1)
# plt.plot(t, np.cos(t), 'm--')
plt.subplot(3,2,2)
plt.plot(t, np.cos(2*t), 'g-')
plt.subplot(1, 2, 1) # Add an axes object in the 1st position of a 1 by 2 grid
plt.plot(t, np.cos(5*t), 'c')
# plt.subplot(3,2,5)
# plt.plot(t, np.cos(3*t), 'r-')
plt.subplot(3,2,6)
plt.plot(t, np.cos(4*t), 'b-')
plt.plot(x,y,'k--')
plt.suptitle('Creating subplots with matplotlib')
plt.tight_layout()
By default, all plot commands will draw to the most recently generated axes object. If necessary, we can use the plt.axes
function to set a previous axes object as the active drawing space.
fig = plt.figure()
top_right_ax = plt.subplot(3,2,2)
plt.plot(t, np.cos(2*t), 'g-')
left_side_ax = plt.subplot(1, 2, 1) # Add an axes object in the 1st position of a 1 by 2 grid
plt.plot(t, np.cos(5*t), 'c')
bottom_right_ax = plt.subplot(3,2,6)
plt.plot(t, np.cos(4*t), 'b-')
plt.axes(top_right_ax) # Set the top_right_ax as the active drawing space
plt.plot(x,y,'k--')
plt.title('This axes is active')
plt.suptitle('Creating subplots with matplotlib')
plt.tight_layout()
The plt.axes
function can also be used to insert an axes
object with arbitrary position and size. To use plt.axes
in this way, we supply a list input [x_left, y_bottom, width, height]
where
x_left
is the left edge of the axes object (measured between 0
and 1
where 0
is the at the far left and 1
is at the far right)y_bottom
is the bottom edge of the axes object (measured between 0
and 1
where 0
is at the bottom and 1
is at the top)width
is the width of the axes object (measured between 0
and 1
as a proportion of the full figure width)height
is the height of the axes object (measured between 0
and 1
as a proportion of the full figure height)fig = plt.figure()
top_right_ax = plt.subplot(3,2,2,aspect='equal')
plt.plot(t, np.cos(2*t), 'g-')
left_side_ax = plt.subplot(1, 2, 1) # Add an axes object in the 1st position of a 1 by 2 grid
plt.plot(t, np.cos(5*t), 'c')
bottom_right_ax = plt.subplot(3,2,6)
plt.plot(t, np.cos(4*t), 'b-')
plt.axes(top_right_ax) # Set the top_right_ax as the active drawing space
plt.plot(x,y,'k--')
plt.title('This axes is active')
plt.suptitle('Creating subplots with matplotlib')
plt.tight_layout()
plt.axes([.25, .1, .6, .4])
plt.plot(x,y,'r-')
Project 2 thoughts:
ptriples = [[3, 4, 5],
[4, 3, 5],
[5, 12, 13],
[6, 8, 10],
[8, 6, 10],
[8, 15, 17],
[9, 12, 15],
[12, 5, 13],
[12, 9, 15],
[12, 16, 20],
[15, 8, 17],
[15, 20, 25],
[16, 12, 20],
[20, 15, 25]]
a_list = [a for (a,b,c) in ptriples]
b_list = [b for (a,b,c) in ptriples]
plt.plot(a_list,b_list, 'o', markersize=5)
a_list
prim_ptriples = [ptriple for ptriple in ptriples if is_prim_ptriple(ptriple)]
Sometimes, we want to include plots that are interactive and can be changed. We can this by using the "magic", %matplotlib notebook
%matplotlib notebook
fig = plt.figure(figsize=(3,3))
t = np.linspace(0, 2*np.pi, 1000)
x = np.cos(t)
y = np.sin(t)
plt.plot(x,y)
With an interactive plot, we can make changes to it and update the figure.
fig = plt.figure(figsize=(3,3))
r = 1
t = np.linspace(0, 2*np.pi, 1000)
x = r*np.cos(t)
y = r*np.sin(t)
circle_plot, = plt.plot(x,y)
plt.xlim(-3,3)
plt.ylim(-3,3)
Suppose we want to change the radius of the plotted circle. Before doing so, let's give a name to the line object object by setting circle_plot, = plt.plot(x,y)
. We can then circle_plot.set_xdata
and circle_plot.set_ydata
to modify the data that was plotted.
r = 2
x = r*np.cos(t)
y = r*np.sin(t)
circle_plot.set_xdata(x)
circle_plot.set_ydata(y)
We can use this idea to create animations. We will use fig.canvas.draw()
to force Python to update the plot and sleep
to briefly pause after each update.
from time import sleep
for r in np.linspace(0,3,200):
x = r*np.cos(t)
y = r*np.sin(t)
circle_plot.set_xdata(x)
circle_plot.set_ydata(y)
fig.canvas.draw()
sleep(.01)
from matplotlib.widgets import Slider # import the Slider widget
import numpy as np
import matplotlib.pyplot as plt
from math import pi
a_min = 0 # the minimial value of the paramater a
a_max = 10 # the maximal value of the paramater a
a_init = 1 # the value of the parameter a to be used initially, when the graph is created
x = np.linspace(0, 2*pi, 500)
fig = plt.figure(figsize=(8,3))
# first we create the general layount of the figure
# with two axes objects: one for the plot of the function
# and the other for the slider
sin_ax = plt.axes([0.1, 0.2, 0.8, 0.65])
slider_ax = plt.axes([0.1, 0.05, 0.8, 0.05])
# in plot_ax we plot the function with the initial value of the parameter a
plt.axes(sin_ax) # select sin_ax
plt.title('y = sin(ax)')
sin_plot, = plt.plot(x, np.sin(a_init*x), 'r')
plt.xlim(0, 2*pi)
plt.ylim(-1.1, 1.1)
# here we create the slider
a_slider = Slider(slider_ax, # the axes object containing the slider
'a', # the name of the slider parameter
a_min, # minimal value of the parameter
a_max, # maximal value of the parameter
valinit=a_init # initial value of the parameter
)
# Next we define a function that will be executed each time the value
# indicated by the slider changes. The variable of this function will
# be assigned the value of the slider.
def update(a):
sin_plot.set_ydata(np.sin(a*x)) # set new y-coordinates of the plotted points
fig.canvas.draw_idle() # redraw the plot
# the final step is to specify that the slider needs to
# execute the above function when its value changes
a_slider.on_changed(update)
plt.show()
import matplotlib.pyplot as plt
import numpy as np
Last time, we looked at creating subplots. Consider the family Lissajous curves:
$$ x(t) = \sin(at) \qquad y(t) = \cos(bt)$$for some choice of parameters $a$, $b$.
Let's plot Lissajous curves for several choices of $a$ and $b$.
Exercise: Createa 5
by 5
grid of subplots, where each row plots $x(t)$ vs $y(t)$ with a fixed choice of $a$ and each column has a fixed choice of $b$.
fig = plt.figure(figsize=(8,8))
a_arr = np.arange(1,6)
b_arr = np.arange(1,6)
t = np.linspace(0,2*np.pi,1000)
pos = 1 # subplot position counter
for a in a_arr:
for b in b_arr:
x = np.sin(a*t)
y = np.cos(b*t)
plt.subplot(5,5,pos)
pos += 1
plt.plot(x,y)
plt.title('a = {}, b = {}'.format(a,b))
plt.tight_layout()
Note: we can use plt.axis('off')
to remove horizontal and vertical ticks.
fig = plt.figure(figsize=(8,8))
a_arr = np.arange(1,6)
b_arr = np.arange(1,6)
t = np.linspace(0,2*np.pi,1000)
pos = 1 # subplot position counter
for a in a_arr:
for b in b_arr:
x = np.sin(a*t)
y = np.cos(b*t)
plt.subplot(5,5,pos,aspect=1)
pos += 1
plt.plot(x,y)
plt.title('a = {}, b = {}'.format(a,b))
plt.axis('off')
plt.tight_layout()
Recall: we can use the magic %matplotlib notebook
to enable interactive plotting:
%matplotlib notebook
Exercise: Modify the slider sample code to plot a Lissajous curve with a fixed $b$ value (say $b=2$) and with a variable $a$ value controlled by a slider.
from matplotlib.widgets import Slider # import the Slider widget
import numpy as np
import matplotlib.pyplot as plt
from math import pi
a_min = 0 # the minimial value of the paramater a
a_max = 10 # the maximal value of the paramater a
a_init = 1 # the value of the parameter a to be used initially, when the graph is created
b_init = 2
t = np.linspace(0, 2*pi, 1000)
x = np.sin(a_init*t)
y = np.cos(b_init*t)
fig = plt.figure(figsize=(4,4))
# first we create the general layount of the figure
# with two axes objects: one for the plot of the function
# and the other for the slider
lissajous_ax = plt.axes([0.1, 0.2, 0.8, 0.65])
a_slider_ax = plt.axes([0.1, 0.05, 0.8, 0.05])
# in plot_ax we plot the function with the initial value of the parameter a
plt.axes(lissajous_ax) # select sin_ax
plt.title('x = sin(at), y = cos(bt)')
lissajous_plot, = plt.plot(x, y, 'r')
plt.xlim(-1.1, 1.1)
plt.ylim(-1.1, 1.1)
# here we create the slider
a_slider = Slider(a_slider_ax, # the axes object containing the slider
'a', # the name of the slider parameter
a_min, # minimal value of the parameter
a_max, # maximal value of the parameter
valinit=a_init # initial value of the parameter
)
# Next we define a function that will be executed each time the value
# indicated by the slider changes. The variable of this function will
# be assigned the value of the slider.
def a_update(a):
x = np.sin(a*t)
lissajous_plot.set_xdata(x) # set new y-coordinates of the plotted points
fig.canvas.draw_idle() # redraw the plot
# the final step is to specify that the slider needs to
# execute the above function when its value changes
a_slider.on_changed(a_update)
plt.show()
Exercise: Modify the code above to add a second slider, b_slider
that controls the parameter b
.
from matplotlib.widgets import Slider # import the Slider widget
import numpy as np
import matplotlib.pyplot as plt
from math import pi
a_min = 0 # the minimial value of the paramater a
a_max = 10 # the maximal value of the paramater a
a_init = 5 # the value of the parameter a to be used initially, when the graph is created
b_min = 0
b_max = 10
b_init = 5
t = np.linspace(0, 2*pi, 1000)
x = np.sin(a_init*t)
y = np.cos(b_init*t)
fig = plt.figure(figsize=(4,4))
# first we create the general layount of the figure
# with two axes objects: one for the plot of the function
# and the other for the slider
lissajous_ax = plt.axes([0.1, 0.2, 0.8, 0.65])
a_slider_ax = plt.axes([0.1, 0.05, 0.8, 0.05])
b_slider_ax = plt.axes([0.1, 0, 0.8, 0.05])
# in plot_ax we plot the function with the initial value of the parameter a
plt.axes(lissajous_ax) # select sin_ax
plt.title('x = sin(at), y = cos(bt)')
lissajous_plot, = plt.plot(x, y, 'r')
plt.xlim(-1.1, 1.1)
plt.ylim(-1.1, 1.1)
# here we create the slider
a_slider = Slider(a_slider_ax, # the axes object containing the slider
'a', # the name of the slider parameter
a_min, # minimal value of the parameter
a_max, # maximal value of the parameter
valinit=a_init # initial value of the parameter
)
b_slider = Slider(b_slider_ax, # the axes object containing the slider
'b', # the name of the slider parameter
b_min, # minimal value of the parameter
b_max, # maximal value of the parameter
valinit=b_init # initial value of the parameter
)
# Next we define a function that will be executed each time the value
# indicated by the slider changes. The variable of this function will
# be assigned the value of the slider.
def a_update(a):
x = np.sin(a*t)
lissajous_plot.set_xdata(x) # set new y-coordinates of the plotted points
fig.canvas.draw_idle() # redraw the plot
def b_update(b):
y = np.cos(b*t)
lissajous_plot.set_ydata(y) # set new y-coordinates of the plotted points
fig.canvas.draw_idle() # redraw the plot
# the final step is to specify that the slider needs to
# execute the above function when its value changes
a_slider.on_changed(a_update)
b_slider.on_changed(b_update)
plt.show()
Some thoughts about slider update functions:
from matplotlib.widgets import Slider # import the Slider widget
import numpy as np
import matplotlib.pyplot as plt
from math import pi
a_min = 0 # the minimial value of the paramater a
a_max = 10 # the maximal value of the paramater a
a_init = 5 # the value of the parameter a to be used initially, when the graph is created
b_min = 0
b_max = 10
b_init = 5
t = np.linspace(0, 2*pi, 1000)
x = np.sin(a_init*t)
y = np.cos(b_init*t)
fig = plt.figure(figsize=(4,4))
# first we create the general layount of the figure
# with two axes objects: one for the plot of the function
# and the other for the slider
lissajous_ax = plt.axes([0.1, 0.2, 0.8, 0.65])
a_slider_ax = plt.axes([0.1, 0.05, 0.8, 0.05])
b_slider_ax = plt.axes([0.1, 0, 0.8, 0.05])
# in plot_ax we plot the function with the initial value of the parameter a
plt.axes(lissajous_ax) # select sin_ax
plt.title('x = sin(at), y = cos(bt)')
lissajous_plot, = plt.plot(x, y, 'r')
plt.xlim(-1.1, 1.1)
plt.ylim(-1.1, 1.1)
# here we create the slider
a_slider = Slider(a_slider_ax, # the axes object containing the slider
'a', # the name of the slider parameter
a_min, # minimal value of the parameter
a_max, # maximal value of the parameter
valinit=a_init # initial value of the parameter
)
b_slider = Slider(b_slider_ax, # the axes object containing the slider
'b', # the name of the slider parameter
b_min, # minimal value of the parameter
b_max, # maximal value of the parameter
valinit=b_init # initial value of the parameter
)
# Next we define a function that will be executed each time the value
# indicated by the slider changes. The variable of this function will
# be assigned the value of the slider.
def update(dummy):
# We can access the current value of any slider using <slider>.val
# E.g. a_slider.val gives the current value of the a_slider
# b_slider.val gives the current value of the b_slider
x = np.sin(a_slider.val*t)
y = np.cos(b_slider.val*t)
lissajous_plot.set_xdata(x) # set new y-coordinates of the plotted points
lissajous_plot.set_ydata(y)
fig.canvas.draw_idle() # redraw the plot
# the final step is to specify that the slider needs to
# execute the above function when its value changes
a_slider.on_changed(update)
b_slider.on_changed(update)
plt.show()
So far, we've been working with 1-dimensional arrays (essentially lists of numbers).
a = np.arange(10)
print(a)
We can also create multi-dimensional NumPy arrays. For example, we can reshape a 1D array into a compatible 2D shape using the .reshape
method:
b = a.reshape(2,5)
print(b)
a = np.arange(25)
b = a.reshape(5,5)
print(b)
Just like with 1D arrays, we can perform arithmetic operations elementwise on 2D arrays:
b + 100
2**b
We can also add arrays of compatible together:
b + 2**b
Each array has an attribute .shape
that stores the shape of the array:
a = np.arange(30)
print(a.shape)
b = a.reshape(6,5)
print(b.shape)
c = a.reshape(2,3,5)
print(c.shape)
We can also use np.ones
or np.zeros
to generate multi-dimensional arrays:
a = np.ones(5)
print(a)
a = np.ones((5,6))
print(a)
With multi-dimensional arrays, we can slice across any axis of the array.
a = np.arange(25)
b = a.reshape(5,5)
c = 2**b
print(c)
print(c[2:-1, :3])
Recall: NumPy slices are mutable, so changes to slices will propogate back to the original array.
c[2:-1, :3] = 0
print(c)
We can also use Boolean NumPy arrays to select slices:
A Boolean array is just a NumPy array that contains True
and False
values.
a = np.arange(25)
b = b.reshape(5,5)
print(b)
We can write Boolean expressions involving NumPy arrays:
print(b > 10)
print(b % 2 == 0)
We can use logical operators on Boolean arrays:
~
will negate a Boolean array&
works like and
for two Boolean arrays|
works like or
for two Boolean arraysprint((b % 2 == 0) | ~(b > 10))
We can use Boolean arrays as slicing tools for regular arrays:
print(b)
mask = (b % 2 == 0) | ~(b > 10) # Identify entries that are either even or not greater than 10
print(mask)
print(b[mask])
We can then make changes to the b
array based on the mask
slice:
b[mask] = 100
print(b)
Project 2 thoughts:
ptriples = get_ptriples(2000)
#small_ptriples = get_ptriples(20)
#small_ptriples = [(a,b,c) for (a,b,c) in ptriples if a <= 20 and b <= 20]
def plot_ptriples(ptriples):
a_list = [a for (a,b,c) in ptriples]
b_list = [b for (a,b,c) in ptriples]
plt.plot(a,b)
plot_ptriples(ptriples)
plt.xlim(0,20)
plt.ylim(0,20)