Graphics and Equation Typesetting

Some of this tour is modeled on the Matplotlib pyplot tutorial, the SciPy lecture notes on plotting, Jake Vanderplas' Matplotlib Intro and the Software Carpentry Bootcamp lesson on Matplotlib.


Instructions: Create a new directory called Graphics with a new notebook called GraphicsTour. Give it a heading 1 cell title Graphics and Equation Typesetting. Read this page, typing in the code in the code cells and executing them as you go.

Do not copy/paste.

Type the commands yourself to get the practice doing it. This will also slow you down so you can think about the commands and what they are doing as you type them.</font>

Save your notebook when you are done, then try the accompanying exercises.


Matplotlib

Matplotlib is probably the single most used Python package for 2D-graphics. It provides both a very quick way to visualize data from Python and publication-quality figures in many formats.

This notebook will provide a brief summary of the most important aspects of Matplotlib for our class, but you are encouraged to explore the extensive documentation for the library and in particular, the gallery of plots.

No one can keep all of the functions and fine layout control commands in their brain. Often when I need to make a plot, I go to the gallery page and browse the images until I find one that is similar to what I want to create and then I copy the code and modify it to suit my needs. You are encouraged to do the same.

Importing the library

To import the parts of Matplotlib that we will need, we use


In [ ]:
# In iPython or the iPython notebook, it's easiest to use the pylab magic, which
# imports matplotlib, numpy, and scipy.

# The inline flag means that images will be shown here in the notebooks, rather
# than in pop-up windows.

%pylab inline

# If you are using 'regular' Python, however, you'll want the following. You'll
# need to also separately import numpy and any other packages that you might need.

#import matplotlib.pyplot as plt
#import numpy as np

Creating figures

There are two major challenges with creating figures. First is understanding the syntax to actually make the basic plot appear. Second is formatting the basic plot to look exactly how you would like it to look. In general, the formatting will probably take you longer...

Within Matplotlib's pyplot module (currently imported as 'plt'), there are two basic ways to go about making plots - using the Matlab-like clone, and using the object-oriented approach. The latter provides better control over plot features, while only requiring slightly more typing. We will use a little bit of both here. The Matlab-clone syntax is good for quick and dirty, fast and simple plotting, while the object-oriented syntax is better for refining your plots to make them "publication-ready".

Note: When you look at the source code from the Matplotlib gallery, the examples will mostly be using the object-oriented syntax.

Simple Plots

Here is an example of creating a simple plot of two functions. To plot a function, we need to create a NumPy array for the independent variable ($x$) and then set the dependent variable ($y = f(x)$) by using NumPy's built-in functions.


In [ ]:
#create the data to be plotted
x = np.linspace(0, 2*np.pi, 300)
y = np.sin(x)
y2 = np.sin(x**2)

In this case, x is now a NumPy array with 300 values ranging from 0 to 2$\pi$ (included). y is the sine (array of 300 values) and y2 is the square of the sine (array of 300 values) at each of those 300 x values.


In [ ]:
#Now plot it
plt.plot(x, y) 
plt.plot(x, y2)
plt.show()

What this plot lacks is important information about what is in it - there are no axis labels, no legend telling us what is different about blue vs. green, and the size of the font on the axis labels is a bit small. We should probably try to improve the plot's readability so other people viewing it will understand what we are trying to convey.

Font size for labels is especially important when you are creating plots for inclusion in a slide presentation. If your labels are too small for the audience to read, you will quickly lose their attention.

Our demos during the final exam are a perfect place to practice creating readable plots. Grading of the demos will include an evaluation of how well information is presented, including readability of plots.

We'll explore some of the capabilities in Matplotlib for refining how our data is presented graphically below, but first, we'll look at a couple more simple tricks.

You can control the style, color and other properties of the markers, for example:


In [ ]:
plt.plot(x, y, linewidth=2);
plt.plot(x, y2, linewidth=2);

In [ ]:
#decrease the number of points to illustrate the use of markers
x = np.linspace(0, 2*np.pi, 50)
y = np.sin(x)
y2 = np.cos(x)
plt.plot(x, y, 'o', markersize=5, color='r');
plt.plot(x, y2, '^', markersize=5, color='b');

See the Matplotlib line style demo to view the different types of line styles you can choose from. The source code is a little "slick", though, so it may not be obvious to you how the markers are set in that example.

Saving a figure

To save a figure in a standard format such as png, jpg or pdf, just call the savefig method with a filename that includes the extension for the type of file you wish to create:


In [ ]:
#back to our original data
x = np.linspace(0, 2*np.pi, 300)
y = np.sin(x)
y2 = np.sin(x**2)

plt.plot(x, y)
plt.plot(x, y2)

#add a grid
plt.grid()

plt.savefig("Example.pdf")
plt.savefig("Example.png")

In [ ]:
#check that they exist
!ls -l Example.*

Refining our plots

Now that we've seen how to create some data and plot it, we'll iteratively improve on the look of the plots as we explore features of Matplotlib. Start with something simple: draw the cosine/sine functions.


In [ ]:
#create the data
x = np.linspace(-np.pi, np.pi, 56, endpoint=True)
c, s = np.cos(x), np.sin(x)

In [ ]:
plt.plot(x, c)
plt.plot(x, s)
plt.show()

Matplotlib comes with a set of default settings that allow customizing all kinds of properties. You can control the defaults of almost every property with Matplotlib: figure size and dpi, line width, color and style, axes, axis and grid properties, text and font properties and so on.

We can explicitly set all of the parameters that define the plot when we create it. Here are the default settings, explicitly shown.

Play around with them to see how they change the look of the plot.


In [ ]:
# Create a figure of size 8x6 points, 80 dots per inch
plt.figure(figsize=(8, 6), dpi=80)

# Create a new subplot from a grid of 1x1
plt.subplot(1, 1, 1)

# Plot cosine with a blue continuous line of width 1 (pixels)
plt.plot(x, c, color="blue", linewidth=1.0, linestyle="-")

# Plot sine with a green continuous line of width 1 (pixels)
plt.plot(x, s, color="green", linewidth=1.0, linestyle="-")

# Set x limits
plt.xlim(-4.0, 4.0)

# Set x ticks
plt.xticks(np.linspace(-4, 4, 9, endpoint=True))

# Set y limits
plt.ylim(-1.0, 1.0)

# Set y ticks
plt.yticks(np.linspace(-1, 1, 5, endpoint=True))

# Save figure using 72 dots per inch
plt.savefig("plot_example.png", dpi=72)

# Show result on screen
plt.show()

Changing colors and line widths

First step, we want to have the cosine in blue and the sine in red and a slighty thicker line for both of them. We’ll also slightly alter the figure size to make it more horizontal.


In [ ]:
plt.figure(figsize=(10, 6), dpi=80)
plt.plot(x, c, color="blue", linewidth=2.5, linestyle="-")
plt.plot(x, s, color="red",  linewidth=2.5, linestyle="-")
plt.show()

As you follow along with each successive iteration below, feel free to cut and paste the old code from previous cells you've already typed in, and add the new code labeled between the identifiers:

 #-----Start new code

 #-----End new code

Setting limits

Current limits of the figure are a bit too tight and we want to make some space in order to clearly see all data points.


In [ ]:
plt.figure(figsize=(10, 6), dpi=80)
plt.plot(x, c, color="blue", linewidth=2.5, linestyle="-")
plt.plot(x, s, color="red",  linewidth=2.5, linestyle="-")

#------Start new code
plt.xlim(x.min() * 1.1, x.max() * 1.1)
plt.ylim(c.min() * 1.1, c.max() * 1.1)
#------End new code

plt.show()

Setting ticks

Current ticks are not ideal because they do not show the interesting values ($\pm\pi$,$\pm\pi/2$) for sine and cosine. We’ll change them such that they show only these values.


In [ ]:
plt.figure(figsize=(10, 6), dpi=80)
plt.plot(x, c, color="blue", linewidth=2.5, linestyle="-")
plt.plot(x, s, color="red",  linewidth=2.5, linestyle="-")
plt.xlim(x.min() * 1.1, x.max() * 1.1)
plt.ylim(c.min() * 1.1, c.max() * 1.1)

#-----Start new code
plt.xticks([-np.pi, -np.pi/2, 0, np.pi/2, np.pi])
plt.yticks([-1, 0, +1])
#-----End new code

plt.show()

Setting tick labels

Ticks are now properly placed but their label is not very explicit. We could guess that 3.142 is $\pi$ but it would be better to make it explicit. When we set tick values, we can also provide a corresponding label in the second argument list. Note that we’ll use $\LaTeX$ (more on that at the end of this tour) to allow for nice rendering of the label.


In [ ]:
plt.figure(figsize=(10, 6), dpi=80)
plt.plot(x, c, color="blue", linewidth=2.5, linestyle="-")
plt.plot(x, s, color="red",  linewidth=2.5, linestyle="-")
plt.xlim(x.min() * 1.1, x.max() * 1.1)
plt.ylim(c.min() * 1.1, c.max() * 1.1)

#-----Start new code
plt.xticks([-np.pi, -np.pi/2, 0, np.pi/2, np.pi],
          [r'$-\pi$', r'$-\pi/2$', r'$0$', r'$+\pi/2$', r'$+\pi$'])

plt.yticks([-1, 0, +1],
          [r'$-1$', r'$0$', r'$+1$'])
#-----End new code

plt.show()

Note: To get the $\pi$ symbol, we used syntax from the $\LaTeX$ typesetting library, which is widely used in physics and math for creating high quality publications, presentations and equation formatting. We will learn more about it later on in this notebook.

Moving spines

Spines are the lines connecting the axis tick marks and noting the boundaries of the data area. They can be placed at arbitrary positions and until now, they were on the border of the axis. We’ll change that since we want to have them in the middle. Since there are four of them (top/bottom/left/right), we’ll discard the top and right by setting their color to none and we’ll move the bottom and left ones to coordinate 0 in data space coordinates.


In [ ]:
plt.figure(figsize=(10, 6), dpi=80)
plt.plot(x, c, color="blue", linewidth=2.5, linestyle="-")
plt.plot(x, s, color="red",  linewidth=2.5, linestyle="-")
plt.xlim(x.min() * 1.1, x.max() * 1.1)
plt.ylim(c.min() * 1.1, c.max() * 1.1)

#-----Start new code
ax = plt.gca()  # gca stands for 'get current axis'
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.spines['bottom'].set_position(('data',0))
ax.yaxis.set_ticks_position('left')
ax.spines['left'].set_position(('data',0))
#-----End new code

#The tick labels must come after the spine adjustment
plt.xticks([-np.pi, -np.pi/2, 0, np.pi/2, np.pi],
          [r'$-\pi$', r'$-\pi/2$', r'$0$', r'$+\pi/2$', r'$+\pi$'])

plt.yticks([-1, 0, +1],
          [r'$-1$', r'$0$', r'$+1$'])

plt.show()

Adding a legend

Let’s add a legend in the upper left corner. This only requires adding the keyword argument label (that will be used in the legend box) to the plot commands.


In [ ]:
plt.figure(figsize=(10, 6), dpi=80)
plt.plot(x, c, color="blue", linewidth=2.5, linestyle="-")
plt.plot(x, s, color="red",  linewidth=2.5, linestyle="-")
plt.xlim(x.min() * 1.1, x.max() * 1.1)
plt.ylim(c.min() * 1.1, c.max() * 1.1)

ax = plt.gca()  # gca stands for 'get current axis'
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.spines['bottom'].set_position(('data',0))
ax.yaxis.set_ticks_position('left')
ax.spines['left'].set_position(('data',0))

plt.xticks([-np.pi, -np.pi/2, 0, np.pi/2, np.pi],
          [r'$-\pi$', r'$-\pi/2$', r'$0$', r'$+\pi/2$', r'$+\pi$'])

plt.yticks([-1, 0, +1],
          [r'$-1$', r'$0$', r'$+1$'])

#-----Start new code
plt.plot(x, c, color="blue", linewidth=2.5, linestyle="-", label="cosine")
plt.plot(x, s, color="red",  linewidth=2.5, linestyle="-", label="sine")
plt.legend(loc='upper left')
#-----End new code

plt.show()

Note: It was not actually necessary to repeat the plotting commands in lines 22 and 23, we could have just added labels to the plot commands the first time they were called in lines 2 and 3. But for clarity of presentation in showing the evolution of the plot as we iteratively add features, I repeated the commands.

Annotate some points

Let’s annotate some interesting points using the annotate command. We choose the 2$\pi$/3 value and we want to annotate both the sine and the cosine. We’ll first draw a marker on the curve as well as a straight dotted line. Then, we’ll use the annotate command to display some text with an arrow. The text is also using $\LaTeX$ formatting.


In [ ]:
plt.figure(figsize=(10, 6), dpi=80)
plt.plot(x, c, color="blue", linewidth=2.5, linestyle="-")
plt.plot(x, s, color="red",  linewidth=2.5, linestyle="-")
plt.xlim(x.min() * 1.1, x.max() * 1.1)
plt.ylim(c.min() * 1.1, c.max() * 1.1)

ax = plt.gca()  # gca stands for 'get current axis'
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.spines['bottom'].set_position(('data',0))
ax.yaxis.set_ticks_position('left')
ax.spines['left'].set_position(('data',0))

plt.xticks([-np.pi, -np.pi/2, 0, np.pi/2, np.pi],
          [r'$-\pi$', r'$-\pi/2$', r'$0$', r'$+\pi/2$', r'$+\pi$'])

plt.yticks([-1, 0, +1],
          [r'$-1$', r'$0$', r'$+1$'])

plt.plot(x, c, color="blue", linewidth=2.5, linestyle="-", label="cosine")
plt.plot(x, s, color="red",  linewidth=2.5, linestyle="-", label="sine")
plt.legend(loc='upper left')

#-----Start new code
t = 2 * np.pi / 3
plt.plot([t, t], [0, np.cos(t)], color='blue', linewidth=2.5, linestyle="--")
plt.scatter([t, ], [np.cos(t), ], 50, color='blue')

plt.annotate(r'$sin(\frac{2\pi}{3})=\frac{\sqrt{3}}{2}$',
            xy=(t, np.sin(t)), xycoords='data',
            xytext=(+10, +30), textcoords='offset points', fontsize=16,
            arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=.2"))

plt.plot([t, t],[0, np.sin(t)], color='red', linewidth=2.5, linestyle="--")
plt.scatter([t, ],[np.sin(t), ], 50, color='red')

plt.annotate(r'$cos(\frac{2\pi}{3})=-\frac{1}{2}$',
            xy=(t, np.cos(t)), xycoords='data',
            xytext=(-90, -50), textcoords='offset points', fontsize=16,
            arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=.2"))
#-----End new code

plt.show()

Cleaning up the details

The tick labels are now hardly visible because of the blue and red lines. We can make them bigger and we can also adjust their properties such that they’ll be rendered on a semi-transparent white background. This will allow us to see both the data and the labels. It might also be good to think about how our plot would look in black and white. Maybe we should change one of the linestyles.


In [ ]:
plt.figure(figsize=(10, 6), dpi=80)
#-----Changed line style to "--"
plt.plot(x, c, color="blue", linewidth=2.5, linestyle="--")
#-----
plt.plot(x, s, color="red",  linewidth=2.5, linestyle="-")
plt.xlim(x.min() * 1.1, x.max() * 1.1)
plt.ylim(c.min() * 1.1, c.max() * 1.1)

ax = plt.gca()  # gca stands for 'get current axis'
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.spines['bottom'].set_position(('data',0))
ax.yaxis.set_ticks_position('left')
ax.spines['left'].set_position(('data',0))

plt.xticks([-np.pi, -np.pi/2, 0, np.pi/2, np.pi],
          [r'$-\pi$', r'$-\pi/2$', r'$0$', r'$+\pi/2$', r'$+\pi$'])

plt.yticks([-1, 0, +1],
          [r'$-1$', r'$0$', r'$+1$'])

#-----Changed line style to "--"
plt.plot(x, c, color="blue", linewidth=2.5, linestyle="--", label="cosine")
#-----
plt.plot(x, s, color="red",  linewidth=2.5, linestyle="-", label="sine")
plt.legend(loc='upper left', fontsize=20)

t = 2 * np.pi / 3
plt.plot([t, t], [0, np.cos(t)], color='blue', linewidth=2.5, linestyle="--")
plt.scatter([t, ], [np.cos(t), ], 50, color='blue')

plt.annotate(r'$sin(\frac{2\pi}{3})=\frac{\sqrt{3}}{2}$',
            xy=(t, np.sin(t)), xycoords='data',
            xytext=(+10, +30), textcoords='offset points', fontsize=20,
            arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=.2"))

plt.plot([t, t],[0, np.sin(t)], color='red', linewidth=2.5, linestyle="--")
plt.scatter([t, ],[np.sin(t), ], 50, color='red')

plt.annotate(r'$cos(\frac{2\pi}{3})=-\frac{1}{2}$',
            xy=(t, np.cos(t)), xycoords='data',
            xytext=(-90, -50), textcoords='offset points', fontsize=20,
            arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=.2"))

#-----Start new code
for label in ax.get_xticklabels() + ax.get_yticklabels():
    label.set_fontsize(20)
    label.set_bbox(dict(facecolor='white', edgecolor='None', alpha=0.65))
#-----End new code

plt.show()

That's a pretty nicely formatted figure, ready for publication or presentation. Lines are thick enough to see and distinguishable even in black and white, and the legend and labels are also large enough to read easily. Axis labels would also generally be wise to include, but in this case they are implicit from the legend and the values on the axes.

Having fun

And now for something really fun, "xkcd-ify" it:


In [ ]:
plt.figure(figsize=(10, 6), dpi=80)

#-----Start new code
plt.xkcd()
#-----End new code

plt.plot(x, c, color="blue", linewidth=2.5, linestyle="--")
plt.plot(x, s, color="red",  linewidth=2.5, linestyle="-")
plt.xlim(x.min() * 1.1, x.max() * 1.1)
plt.ylim(c.min() * 1.1, c.max() * 1.1)

ax = plt.gca()  # gca stands for 'get current axis'
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.spines['bottom'].set_position(('data',0))
ax.yaxis.set_ticks_position('left')
ax.spines['left'].set_position(('data',0))

plt.xticks([-np.pi, -np.pi/2, 0, np.pi/2, np.pi],
          [r'$-\pi$', r'$-\pi/2$', r'$0$', r'$+\pi/2$', r'$+\pi$'])

plt.yticks([-1, 0, +1],
          [r'$-1$', r'$0$', r'$+1$'])

plt.plot(x, c, color="blue", linewidth=2.5, linestyle="--", label="cosine")
plt.plot(x, s, color="red",  linewidth=2.5, linestyle="-", label="sine")
plt.legend(loc='upper left', fontsize=20)

t = 2 * np.pi / 3
plt.plot([t, t], [0, np.cos(t)], color='blue', linewidth=2.5, linestyle="--")
plt.scatter([t, ], [np.cos(t), ], 50, color='blue')

plt.annotate(r'$sin(\frac{2\pi}{3})=\frac{\sqrt{3}}{2}$',
            xy=(t, np.sin(t)), xycoords='data',
            xytext=(+10, +30), textcoords='offset points', fontsize=20,
            arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=.2"))

plt.plot([t, t],[0, np.sin(t)], color='red', linewidth=2.5, linestyle="--")
plt.scatter([t, ],[np.sin(t), ], 50, color='red')

plt.annotate(r'$cos(\frac{2\pi}{3})=-\frac{1}{2}$',
            xy=(t, np.cos(t)), xycoords='data',
            xytext=(-90, -50), textcoords='offset points', fontsize=20,
            arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=.2"))

for label in ax.get_xticklabels() + ax.get_yticklabels():
    label.set_fontsize(20)
    label.set_bbox(dict(facecolor='white', edgecolor='None', alpha=0.65))

plt.show()

In [ ]:
#But turn it off before you move on or all your plots in this session will look like that.
plt.rcdefaults()
%pylab inline

Other types of plots

In the example above, we used the plot method to make line plots. There are also methods to make scatter plots, barplots, histograms, loglog plots, semilog plots, etc.

Errorbars

For data you might collect in the laboratory, you want to show the uncertainties on your data points.


In [ ]:
#Simple constant error bars on each point
x = np.array([0.0, 2.0, 4.0, 6.0, 8.0])
y = np.array([1.1, 1.9, 3.2, 4.0, 5.9])
plt.figure()
plt.errorbar(x, y, xerr=0.2, yerr=0.6, marker='o')
plt.title("Simplest errorbars, 0.2 in x, 0.6 in y");

Perhaps your error bars vary from point to point:


In [ ]:
# example data
x = np.arange(0.1, 4, 0.5)
y = np.exp(-x)

# example variable error bar values
yerr = 0.1 + 0.2*np.sqrt(x)
xerr = 0.1 + yerr

plt.figure()
plt.errorbar(x, y, xerr, yerr, marker='^')
plt.show()

Subplots and Logarithmic axes

You may wish to have the axes plot on a logarithmic scale. There are two ways to do this: "log-log" or "semilog", where only one axis is in log scale. To simplify the presentation of the different options, we will also divide our figure up into four subplots. Have a look at each possibility:


In [ ]:
x = np.linspace(0., 5.)
y = np.exp(-x)

#Make a figure with 4 subplots and axes side-by-side
fig, ax = plt.subplots(1,4, figsize=(10,6))

#Plot on each axis
ax[0].plot(x,y)
ax[1].loglog(x,y)
ax[2].semilogx(x,y)
ax[3].semilogy(x,y);

Recall that an exponential function plotted in semilogy is a straight line!

Scatter plots

For sparse data, sometimes you want to see the values plotted as a scatter plot of y vs. x:


In [ ]:
# Make some data to plot
x = np.arange(0, 100)
y = np.random.rand(100)  # 100 random numbers

plt.scatter(x,y);

Histograms

Histograms are a class of plot that allow you to present information on the frequency of a particular value occuring in a distribution of events. They are used universally in many fields of science, physics included.

Here is the wikipedia definition:

A histogram is a representation of tabulated frequencies, shown as adjacent rectangles, erected over discrete intervals (bins), with an area proportional to the frequency of the observations in the interval. The height of a rectangle is also equal to the frequency density of the interval, i.e., the frequency divided by the width of the interval. The total area of the histogram is equal to the number of data. A histogram may also be normalized displaying relative frequencies. It then shows the proportion of cases that fall into each of several categories, with the total area equaling 1. The categories are usually specified as consecutive, non-overlapping intervals of a variable. The categories (intervals) must be adjacent, and often are chosen to be of the same size. The rectangles of a histogram are drawn so that they touch each other to indicate that the original variable is continuous.

Here is an example histogram annotated with text inside the plot, using the text function:


In [ ]:
mu, sigma = 100, 15
x = mu + sigma * np.random.randn(10000)

# the histogram of the data
n, bins, patches = plt.hist(x, 50, normed=1, facecolor='g', alpha=0.75)

plt.xlabel('Smarts',fontsize=20)
plt.ylabel('Probability',fontsize=20)
plt.title('Histogram of IQ',fontsize=20)

# This will put a text fragment at the position given:
plt.text(45, .027, r'$\mu=100,\ \sigma=15$', fontsize=20)
plt.axis([40, 160, 0, 0.03])
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.grid();

The number of bins was set at 50 in the second argument of plt.hist. The area is normalized to 1, so the values on the y axis are fractions that will add up to 1 if all of the green bars are added together. The alpha parameter sets the transparency of the fill color.

Play with the settings a little bit to see how they change the look of the plot.

Plotting images or 2D NumPy arrays

Recall that images can be read in as NumPy arrays. We can plot them with imshow:


In [ ]:
# Use an image file for first subplot, generate random 2D array for second subplot
from scipy import misc
img1 = misc.lena()
img2 = np.random.rand(128, 128) #128x128 random numbers as a 2D array

# Make figure
fig, ax = plt.subplots(1, 2)
ax[0].imshow(img1)
ax[1].imshow(img2)

ax[0].set_axis_off()  # Hide "spines" on first axis, since it is a "picture"

When you use the Matplotlib gallery to template a figure, you can very easily load the source code into your notebook and then modify it as needed to fit your specific needs. Try it now. After the code is loaded, just execute the cell to see the output.


In [ ]:
# Try it here...
%loadpy http://matplotlib.org/mpl_examples/pylab_examples/contour_demo.py

In [ ]:
#!/usr/bin/env python
"""
Illustrate simple contour plotting, contours on an image with
a colorbar for the contours, and labelled contours.

See also contour_image.py.
"""
import matplotlib
import numpy as np
import matplotlib.cm as cm
import matplotlib.mlab as mlab
import matplotlib.pyplot as plt

matplotlib.rcParams['xtick.direction'] = 'out'
matplotlib.rcParams['ytick.direction'] = 'out'

delta = 0.025
x = np.arange(-3.0, 3.0, delta)
y = np.arange(-2.0, 2.0, delta)
X, Y = np.meshgrid(x, y)
Z1 = mlab.bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0)
Z2 = mlab.bivariate_normal(X, Y, 1.5, 0.5, 1, 1)
# difference of Gaussians
Z = 10.0 * (Z2 - Z1)



# Create a simple contour plot with labels using default colors.  The
# inline argument to clabel will control whether the labels are draw
# over the line segments of the contour, removing the lines beneath
# the label
plt.figure()
CS = plt.contour(X, Y, Z)
plt.clabel(CS, inline=1, fontsize=10)
plt.title('Simplest default with labels')


# contour labels can be placed manually by providing list of positions
# (in data coordinate). See ginput_manual_clabel.py for interactive
# placement.
plt.figure()
CS = plt.contour(X, Y, Z)
manual_locations = [(-1, -1.4), (-0.62, -0.7), (-2, 0.5), (1.7, 1.2), (2.0, 1.4), (2.4, 1.7)]
plt.clabel(CS, inline=1, fontsize=10, manual=manual_locations)
plt.title('labels at selected locations')


# You can force all the contours to be the same color.
plt.figure()
CS = plt.contour(X, Y, Z, 6,
                 colors='k', # negative contours will be dashed by default
                 )
plt.clabel(CS, fontsize=9, inline=1)
plt.title('Single color - negative contours dashed')

# You can set negative contours to be solid instead of dashed:
matplotlib.rcParams['contour.negative_linestyle'] = 'solid'
plt.figure()
CS = plt.contour(X, Y, Z, 6,
                 colors='k', # negative contours will be dashed by default
                 )
plt.clabel(CS, fontsize=9, inline=1)
plt.title('Single color - negative contours solid')


# And you can manually specify the colors of the contour
plt.figure()
CS = plt.contour(X, Y, Z, 6,
                 linewidths=np.arange(.5, 4, .5),
                 colors=('r', 'green', 'blue', (1,1,0), '#afeeee', '0.5')
                 )
plt.clabel(CS, fontsize=9, inline=1)
plt.title('Crazy lines')


# Or you can use a colormap to specify the colors; the default
# colormap will be used for the contour lines
plt.figure()
im = plt.imshow(Z, interpolation='bilinear', origin='lower',
                cmap=cm.gray, extent=(-3,3,-2,2))
levels = np.arange(-1.2, 1.6, 0.2)
CS = plt.contour(Z, levels,
                 origin='lower',
                 linewidths=2,
                 extent=(-3,3,-2,2))

#Thicken the zero contour.
zc = CS.collections[6]
plt.setp(zc, linewidth=4)

plt.clabel(CS, levels[1::2],  # label every second level
           inline=1,
           fmt='%1.1f',
           fontsize=14)

# make a colorbar for the contour lines
CB = plt.colorbar(CS, shrink=0.8, extend='both')

plt.title('Lines with colorbar')
#plt.hot()  # Now change the colormap for the contour lines and colorbar
plt.flag()

# We can still add a colorbar for the image, too.
CBI = plt.colorbar(im, orientation='horizontal', shrink=0.8)

# This makes the original colorbar look a bit out of place,
# so let's improve its position.

l,b,w,h = plt.gca().get_position().bounds
ll,bb,ww,hh = CB.ax.get_position().bounds
CB.ax.set_position([ll, b+0.1*h, ww, h*0.8])


plt.show()

Common formatting tricks

There are hundreds of formatting options available in Matplotlib, many of which you will end up using occasionally. There are a few options, however, that you will use very frequently. Here's a short list...

  • Changing axis limits
  • Changing line colors
  • Changing lines to dashed (for black and white figures)
  • Adding markers to lines
  • Make tick labels point outward instead of inward
  • Get rid of the box surrounding the plot
  • Adding subplot letters, like (a) and (b)

...and some examples for how to accomplish all of these things.


In [ ]:
# Make some data to plot
x = np.linspace(0, 2*np.pi)
y1 = np.sin(x)
y2 = np.cos(x)

# First, create an empty figure with 1 subplot
fig, ax1 = plt.subplots(1, 1)

# Add title and labels
ax1.set_title('My Plot',fontsize=20)
ax1.set_xlabel('x',fontsize=20)
ax1.set_ylabel('y',fontsize=20)

# Change axis limits
ax1.set_xlim([0,2])
ax1.set_ylim([-1, 2])

# Add the lines, changing their color, style, and marker
ax1.plot(x, y1, 'k--o', label='sin') # Black line, dashed, with 'o' markers
ax1.plot(x, y2, 'r-^', label='cos') # Red line, solid, with triangle-up markers

# Adjust tick marks and get rid of 'box'
ax1.tick_params(direction='out', top=False, right=False) # Turn ticks out
ax1.spines['top'].set_visible(False) # Get rid of top axis line
ax1.spines['right'].set_visible(False) #  Get rid of bottom axis line

# Add subplot letter
ax1.annotate('(a)', (0.01, 0.96), size=12, xycoords='figure fraction')

# Add legend
ax1.legend()

# Finally, save the figure as a png file
fig.savefig('myfig-formatted.png')

Simple 3D plotting with Matplotlib

For 3D plots, you must execute at least once in your session:


In [ ]:
from mpl_toolkits.mplot3d import Axes3D

Once this has been done, you can create 3D axes with the projection='3d' keyword to add_subplot:

fig = plt.figure()
fig.add_subplot(...,
                projection='3d')

Here is a simple 3D surface plot:


In [ ]:
from mpl_toolkits.mplot3d.axes3d import Axes3D
from matplotlib import cm

fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, projection='3d')
X = np.arange(-5, 5, 0.25)
Y = np.arange(-5, 5, 0.25)
#Re-factor the x,y arrays to be a 2-D grid
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=cm.jet,
        linewidth=0, antialiased=False)
ax.set_zlim3d(-1.01, 1.01);

This graph uses a meshgrid to create a 2D array corresponding to $(x,y)$ points covering the whole $x-y$ plane. It is critical for being able to plot 3D functions.

An example from E&M:

A situation in physics where we might use 3D plots is for visualizing the electric potential of a group of charges. For example, the electric dipole potential is just the sum of the potentials from two point charges separated by some distance. Consider a pair of point charges $\pm q$ located at $x$ = 0, $y$ = $\pm d$.

The potential is just the sum of the two terms - one for each point charge:

$$ V(x,y) = V_1 + V_2 = \frac{k(q)}{\sqrt{x^2+(y-d)^2}} + \frac{k(-q)}{\sqrt{x^2+(y+d)^2}} $$

Recall that $k = 1/4\pi\epsilon_0$. To see what is happening we will just let $k$=1, $q$=1, and $d$=1. The answer won't depend in an important way on these choices.

First, we have to make the 2D function $V(x,y)$. 2D functions are more complicated than 1D functions. We need a 2D array corresponding to $(x,y)$ points covering the whole $x-y$ plane. The meshgrid function creates the right $x$ and $y$ arrays.


In [ ]:
from mpl_toolkits.mplot3d import Axes3D

#make the x-y grid to cover the plane -2<x<2 and -2<y<2 in steps of 0.06
dx = 0.06
dy = 0.06
x = np.arange(-2, 2, dx)
y = np.arange(-2, 2, dy)
x, y = np.meshgrid(x, y)

#make the function, V(x,y)
k = 1; q = 1; d = 1
V=(k*q/(x**2+(y-d)**2)**(1/2.)) - (k*q/(x**2+(y+d)**2)**(1/2.))

#To use 3D graphics in matplotlib, we first need to create an axes instance of the class Axes3D. 
#3D axes can be added to a matplotlib figure canvas in exactly the same way as 2D axes, but a 
#convenient way to create a 3D axis instance is to use the projection='3d' keyword argument to 
#the add_axes or add_subplot functions.

fig = plt.figure()

#Call the "get current axis" method on the figure object and set the projection to 3D
ax = fig.gca(projection='3d')
p = ax.plot_surface(x, y, V, rstride=1, cstride=1, linewidth=0,cmap=cm.coolwarm)

#To understand what the arguments to the plotting function do, try calling the help: 
#help(Axes3D.plot surface). In particular, investigate the colormap option. You can 
#also add a color bar to represent the value of the potential at each space point (x, y)
cb = fig.colorbar(p, shrink=0.5)

plt.xlabel("x")
plt.ylabel("y")

#plt.show()
plt.savefig("Dipole.png")

Contour plots

Fully 3D axes can be nice visually, but you can also represent 3D data in 2 dimensions with a contour plot. Here is that same data plotted as contours. The contour levels are defined with the levels array. Each one represents an equipotential surface.


In [ ]:
#Represent 3D data by contours
levels = array([8, 4, 2, 1, 0.5, 0.25, 0, -0.25, -0.5, -1, -2, -4, -8])
plt.contour(x,y,V,levels,linewidths=4,cmap=cm.coolwarm);

Equation Typesetting

As mentioned before, when we need to use symbols or create nicely formatted equations, the most widely used tool in physics and math is $\LaTeX$. It is also used as the primary method of displaying formulas on Wikipedia.

The American Math Society’s short math guide for $\LaTeX$ formatting of math equations and symbols is a great reference for how to create beautiful equation output in the notebook and in $\LaTeX$ documents. Type the following into separate markdown cells in your notebook and run them to see what I mean.

(a)

\begin{equation}
\psi_k(x) = A \left(\frac{ik - a \tanh(ax)}{ik + a}\right) e^{+ikx}
\end{equation}

(b)

$$
v_1 = m_2\sqrt{\frac{2G}{M}\left(\frac{1}{r} - \frac{1}{r_0}\right)}
$$

(c)

\begin{equation}
f(\theta) = \frac{\hbar}{p} \sum_{\ell = 0}^{\infty} (2\ell + 1) e^{i\delta_{\ell}}\sin{\delta_{\ell}}P_{\ell}(\cos{\theta})
\end{equation}

(d)

$$
\frac{d\sigma}{d\Omega} = \frac{k\pi^2(\pi - \theta)}{mu_0^2\theta^2(2\pi - \theta)^2 \sin{\theta}}
$$

(e)

\begin{equation}
\langle f \rangle = \frac{1}{\tau} \int^{\tau}_{0} f(t) dt.
\end{equation}

(f)

$$
\Omega_{S} = \omega\frac{\sin{\alpha}}{\sin{\theta}} = \frac{L}{\lambda_1} = \omega\frac{\sqrt{\lambda_3^2 + (\lambda_1^2 - \lambda_3^2)\sin{\alpha}}}{\lambda_1}
$$

There is no significant difference between $$...$$ and \begin{equation}...\end{equation} in the iPython notebook, however, when rendering the notebooks as webpages, $$...$$ works, whereas the other method does not, so I showed you both.

For inline equations embedded in your text, add $ around the $\LaTeX$ syntax, similar to how it is done in figure labels. Here is an example in plain text:

I calculated the expectation value $\langle f \rangle$ using the integral of the function over the interval as 
$\int f(t) dt$ using $\LaTeX$.

Type that into a markdown cell to see it for yourself.

You can get Greek symbols, math symbols, and a whole variety of text widgets from $\LaTeX$. There are lots of quick-references and cheat sheets out on the web. The AMS guide I mentioned above is a great definitive authority, but here's another example for comparison. Often when I don't know the syntax for a specific symbol I google "latex symbol reference" and I can find what I need pretty quickly.


All content is under a modified MIT License, and can be freely used and adapted. See the full license text here.