Monday, February 24th

In [1]:
import matplotlib.pyplot as plt
import numpy as np

Recall: we can generate data easily with NumPy and plot with Matplotlib.

In [3]:
t = np.linspace(0, 2*np.pi, 1000)

x = np.cos(t)
y = np.sin(t)

plt.plot(x,y)
Out[3]:
[<matplotlib.lines.Line2D at 0x2c29dd72790>]

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.

  • Each figure starts with a figure object
  • Within that figure object, we can place an axes object (or several)
  • With an 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 object
In [6]:
fig = 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)
Out[6]:
[<matplotlib.lines.Line2D at 0x2c29e69b370>]

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>).

In [10]:
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)
Out[10]:
[<matplotlib.lines.Line2D at 0x2c29e6e3c10>]

We can get a circle by setting a figure size with equal height and width.

In [11]:
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)
Out[11]:
[<matplotlib.lines.Line2D at 0x2c29de54040>]

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.

In [15]:
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)
Out[15]:
[<matplotlib.lines.Line2D at 0x2c29fb3d4f0>]

The plt.subplot function can take in an optional argument, aspect. We can use aspect='equal' to force an equal aspect ratio.

In [18]:
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)
Out[18]:
(-4.0, 4.0)

Subplots in Matplotlib

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 nth position. The subplot position is counted starting in the top-left corner, then proceeding left-to-right, then top-to-bottom.

In [21]:
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.

In [22]:
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:

In [25]:
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()
In [26]:
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.

In [28]:
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()
In [29]:
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()
In [32]:
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.

In [33]:
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)
In [41]:
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-')
Out[41]:
[<matplotlib.lines.Line2D at 0x2c2a5062310>]

Project 2 thoughts:

In [42]:
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]]
In [73]:
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)
Out[73]:
[<matplotlib.lines.Line2D at 0x2c2a5e4ff70>]
In [44]:
a_list
Out[44]:
[3, 4, 5, 6, 8, 8, 9, 12, 12, 12, 15, 15, 16, 20]
In [ ]:
prim_ptriples = [ptriple for ptriple in ptriples if is_prim_ptriple(ptriple)]

Interactive plotting

Sometimes, we want to include plots that are interactive and can be changed. We can this by using the "magic", %matplotlib notebook

In [56]:
%matplotlib notebook
In [57]:
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)
Out[57]:
[<matplotlib.lines.Line2D at 0x2c2a525cb20>]

With an interactive plot, we can make changes to it and update the figure.

In [61]:
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)
Out[61]:
(-3.0, 3.0)

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.

In [63]:
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.

In [67]:
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)

Slider widget

In [68]:
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()

Wednesday, February 26th

In [1]:
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$.

In [6]:
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.

In [8]:
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()

Slider widget

Recall: we can use the magic %matplotlib notebook to enable interactive plotting:

In [9]:
%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.

In [15]:
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.

In [16]:
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:

In [17]:
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()

Multi-dimensional NumPy arrays

So far, we've been working with 1-dimensional arrays (essentially lists of numbers).

In [18]:
a = np.arange(10)
print(a)
[0 1 2 3 4 5 6 7 8 9]

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:

In [20]:
b = a.reshape(2,5)
print(b)
[[0 1 2 3 4]
 [5 6 7 8 9]]
In [21]:
a = np.arange(25)
b = a.reshape(5,5)

print(b)
[[ 0  1  2  3  4]
 [ 5  6  7  8  9]
 [10 11 12 13 14]
 [15 16 17 18 19]
 [20 21 22 23 24]]

Just like with 1D arrays, we can perform arithmetic operations elementwise on 2D arrays:

In [23]:
b + 100
Out[23]:
array([[100, 101, 102, 103, 104],
       [105, 106, 107, 108, 109],
       [110, 111, 112, 113, 114],
       [115, 116, 117, 118, 119],
       [120, 121, 122, 123, 124]])
In [24]:
2**b
Out[24]:
array([[       1,        2,        4,        8,       16],
       [      32,       64,      128,      256,      512],
       [    1024,     2048,     4096,     8192,    16384],
       [   32768,    65536,   131072,   262144,   524288],
       [ 1048576,  2097152,  4194304,  8388608, 16777216]], dtype=int32)

We can also add arrays of compatible together:

In [27]:
b + 2**b
Out[27]:
array([[       1,        3,        6,       11,       20],
       [      37,       70,      135,      264,      521],
       [    1034,     2059,     4108,     8205,    16398],
       [   32783,    65552,   131089,   262162,   524307],
       [ 1048596,  2097173,  4194326,  8388631, 16777240]])

Each array has an attribute .shape that stores the shape of the array:

In [28]:
a = np.arange(30)
print(a.shape)
(30,)
In [30]:
b = a.reshape(6,5)
print(b.shape)
(6, 5)
In [31]:
c = a.reshape(2,3,5)
print(c.shape)
(2, 3, 5)

We can also use np.ones or np.zeros to generate multi-dimensional arrays:

In [32]:
a = np.ones(5)
print(a)
[1. 1. 1. 1. 1.]
In [33]:
a = np.ones((5,6))
print(a)
[[1. 1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1. 1.]]

With multi-dimensional arrays, we can slice across any axis of the array.

In [34]:
a = np.arange(25)
b = a.reshape(5,5)
c = 2**b

print(c)
[[       1        2        4        8       16]
 [      32       64      128      256      512]
 [    1024     2048     4096     8192    16384]
 [   32768    65536   131072   262144   524288]
 [ 1048576  2097152  4194304  8388608 16777216]]
In [37]:
print(c[2:-1, :3])
[[  1024   2048   4096]
 [ 32768  65536 131072]]

Recall: NumPy slices are mutable, so changes to slices will propogate back to the original array.

In [38]:
c[2:-1, :3] = 0

print(c)
[[       1        2        4        8       16]
 [      32       64      128      256      512]
 [       0        0        0     8192    16384]
 [       0        0        0   262144   524288]
 [ 1048576  2097152  4194304  8388608 16777216]]

We can also use Boolean NumPy arrays to select slices:

Boolean arrays

A Boolean array is just a NumPy array that contains True and False values.

In [42]:
a = np.arange(25)
b = b.reshape(5,5)
print(b)
[[ 0  1  2  3  4]
 [ 5  6  7  8  9]
 [10 11 12 13 14]
 [15 16 17 18 19]
 [20 21 22 23 24]]

We can write Boolean expressions involving NumPy arrays:

In [44]:
print(b > 10)
[[False False False False False]
 [False False False False False]
 [False  True  True  True  True]
 [ True  True  True  True  True]
 [ True  True  True  True  True]]
In [45]:
print(b % 2 == 0)
[[ True False  True False  True]
 [False  True False  True False]
 [ True False  True False  True]
 [False  True False  True False]
 [ True False  True False  True]]

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 arrays
In [48]:
print((b % 2 == 0) | ~(b > 10))
[[ True  True  True  True  True]
 [ True  True  True  True  True]
 [ True False  True False  True]
 [False  True False  True False]
 [ True False  True False  True]]

We can use Boolean arrays as slicing tools for regular arrays:

In [52]:
print(b)
[[ 0  1  2  3  4]
 [ 5  6  7  8  9]
 [10 11 12 13 14]
 [15 16 17 18 19]
 [20 21 22 23 24]]
In [53]:
mask = (b % 2 == 0) | ~(b > 10)   # Identify entries that are either even or not greater than 10
print(mask)
[[ True  True  True  True  True]
 [ True  True  True  True  True]
 [ True False  True False  True]
 [False  True False  True False]
 [ True False  True False  True]]
In [51]:
print(b[mask])
[ 0  1  2  3  4  5  6  7  8  9 10 12 14 16 18 20 22 24]

We can then make changes to the b array based on the mask slice:

In [54]:
b[mask] = 100
print(b)
[[100 100 100 100 100]
 [100 100 100 100 100]
 [100  11 100  13 100]
 [ 15 100  17 100  19]
 [100  21 100  23 100]]

Project 2 thoughts:

In [ ]:
ptriples = get_ptriples(2000)
In [ ]:
#small_ptriples = get_ptriples(20)
#small_ptriples = [(a,b,c) for (a,b,c) in ptriples if a <= 20 and b <= 20]
In [55]:
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)
In [ ]:
plot_ptriples(ptriples)
plt.xlim(0,20)
plt.ylim(0,20)