{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "![Matplotlib logo](https://matplotlib.org/stable/_images/sphx_glr_logos2_003.png)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Matplotlib Basics" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "## Overview\n", "We will cover the basics of using the Matplotlib library to create plots in Python, including a few different plots available within the library. This page is laid out as follows:\n", "\n", "1. Why Matplotlib?\n", "1. Figure and axes\n", "1. Basic line plots\n", "1. Labels and grid lines\n", "1. Customizing colors\n", "1. Subplots\n", "1. Scatterplots\n", "1. Displaying Images\n", "1. Contour and filled contour plots." ] }, { "cell_type": "markdown", "metadata": { "tags": [] }, "source": [ "## Prerequisites\n", "| Concepts | Importance | Notes |\n", "| --- | --- | --- |\n", "| [NumPy Basics](../numpy/numpy-basics) | Necessary | |\n", "| MATLAB plotting experience | Helpful | |\n", "\n", "* **Time to Learn**: 30 minutes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Imports" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's import the Matplotlib library's `pyplot` interface; this interface is the simplest way to create new Matplotlib figures. To shorten this long name, we import it as `plt`; this helps keep things short, but clear." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "import numpy as np" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "

Info

\n", " Matplotlib is a Python 2-D plotting library. It is used to produce publication quality figures in a variety of hard-copy formats and interactive environments across platforms.\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Generate test data using `NumPy`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here, we generate some test data to use for experimenting with plotting:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "times = np.array(\n", " [\n", " 93.0,\n", " 96.0,\n", " 99.0,\n", " 102.0,\n", " 105.0,\n", " 108.0,\n", " 111.0,\n", " 114.0,\n", " 117.0,\n", " 120.0,\n", " 123.0,\n", " 126.0,\n", " 129.0,\n", " 132.0,\n", " 135.0,\n", " 138.0,\n", " 141.0,\n", " 144.0,\n", " 147.0,\n", " 150.0,\n", " 153.0,\n", " 156.0,\n", " 159.0,\n", " 162.0,\n", " ]\n", ")\n", "temps = np.array(\n", " [\n", " 310.7,\n", " 308.0,\n", " 296.4,\n", " 289.5,\n", " 288.5,\n", " 287.1,\n", " 301.1,\n", " 308.3,\n", " 311.5,\n", " 305.1,\n", " 295.6,\n", " 292.4,\n", " 290.4,\n", " 289.1,\n", " 299.4,\n", " 307.9,\n", " 316.6,\n", " 293.9,\n", " 291.2,\n", " 289.8,\n", " 287.1,\n", " 285.8,\n", " 303.3,\n", " 310.0,\n", " ]\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Figure and Axes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, let's make our first plot with Matplotlib. Matplotlib has two core objects: the `Figure` and the `Axes`. The `Axes` object is an individual plot, containing an x-axis, a y-axis, labels, etc.; it also contains all of the various methods we might use for plotting. A `Figure` contains one or more `Axes` objects; it also contains methods for saving plots to files (e.g., PNG, SVG), among other similar high-level functionality. You may find the following diagram helpful:\n", "\n", "![anatomy of a figure](https://matplotlib.org/stable/_images/sphx_glr_anatomy_001.png \"anatomy of a figure\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Basic Line Plots\n", "\n", "Let's create a `Figure` whose dimensions, if printed out on hardcopy, would be 10 inches wide and 6 inches long (assuming a landscape orientation). We then create an `Axes` object, consisting of a single subplot, on the `Figure`. After that, we call the `Axes` object's `plot` method, using the `times` array for the data along the x-axis (i.e., the independent values), and the `temps` array for the data along the y-axis (i.e., the dependent values).\n", "\n", "
\n", "

Info

\n", " By default, ax.plot will create a line plot, as seen in the following example: \n", "
\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Create a figure\n", "fig = plt.figure(figsize=(10, 6))\n", "\n", "# Ask, out of a 1x1 grid of plots, the first axes.\n", "ax = fig.add_subplot(1, 1, 1)\n", "\n", "# Plot times as x-variable and temperatures as y-variable\n", "ax.plot(times, temps);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Labels and Grid Lines" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Adding labels to an `Axes` object" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, we add x-axis and y-axis labels to our `Axes` object, like this:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Add some labels to the plot\n", "ax.set_xlabel('Time')\n", "ax.set_ylabel('Temperature')\n", "\n", "# Prompt the notebook to re-display the figure after we modify it\n", "fig" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can also add a title to the plot and increase the font size:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ax.set_title('GFS Temperature Forecast', size=16)\n", "\n", "fig" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There are many other functions and methods associated with `Axes` objects and labels, but they are too numerous to list here.\n", "\n", "Here, we set up another test array of temperature data, to be used later:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "temps_1000 = np.array(\n", " [\n", " 316.0,\n", " 316.3,\n", " 308.9,\n", " 304.0,\n", " 302.0,\n", " 300.8,\n", " 306.2,\n", " 309.8,\n", " 313.5,\n", " 313.3,\n", " 308.3,\n", " 304.9,\n", " 301.0,\n", " 299.2,\n", " 302.6,\n", " 309.0,\n", " 311.8,\n", " 304.7,\n", " 304.6,\n", " 301.8,\n", " 300.6,\n", " 299.9,\n", " 306.3,\n", " 311.3,\n", " ]\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Adding labels and a grid" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here, we call `plot` more than once, in order to plot multiple series of temperature data on the same plot. We also specify the `label` keyword argument to the `plot` method to allow Matplotlib to automatically create legend labels. These legend labels are added via a call to the `legend` method. By utilizing the `grid()` method, we can also add gridlines to our plot." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig = plt.figure(figsize=(10, 6))\n", "ax = fig.add_subplot(1, 1, 1)\n", "\n", "# Plot two series of data\n", "# The label argument is used when generating a legend.\n", "ax.plot(times, temps, label='Temperature (surface)')\n", "ax.plot(times, temps_1000, label='Temperature (1000 mb)')\n", "\n", "# Add labels and title\n", "ax.set_xlabel('Time')\n", "ax.set_ylabel('Temperature')\n", "ax.set_title('Temperature Forecast')\n", "\n", "# Add gridlines\n", "ax.grid(True)\n", "\n", "# Add a legend to the upper left corner of the plot\n", "ax.legend(loc='upper left');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Customizing colors" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We're not restricted to the default look for plot elements. Most plot elements have style attributes, such as `linestyle` and `color`, that can be modified to customize the look of a plot. For example, the `color` attribute can accept a wide array of color options, including keywords (named colors) like `red` or `blue`, or HTML color codes. Here, we use some different shades of red taken from the Tableau colorset in Matplotlib, by using the `tab:red` option for the color attribute." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig = plt.figure(figsize=(10, 6))\n", "ax = fig.add_subplot(1, 1, 1)\n", "\n", "# Specify how our lines should look\n", "ax.plot(times, temps, color='tab:red', label='Temperature (surface)')\n", "ax.plot(\n", " times,\n", " temps_1000,\n", " color='tab:red',\n", " linestyle='--',\n", " label='Temperature (isobaric level)',\n", ")\n", "\n", "# Set the labels and title\n", "ax.set_xlabel('Time')\n", "ax.set_ylabel('Temperature')\n", "ax.set_title('Temperature Forecast')\n", "\n", "# Add the grid\n", "ax.grid(True)\n", "\n", "# Add a legend to the upper left corner of the plot\n", "ax.legend(loc='upper left');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Subplots\n", "\n", "The term \"subplots\" refers to working with multiple plots, or panels, in a figure." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here, we create yet another set of test data, in this case dew-point data, to be used in later examples:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dewpoint = 0.9 * temps\n", "dewpoint_1000 = 0.9 * temps_1000" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, we can use subplots to plot this new data alongside the temperature data." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Using add_subplot to create two different subplots within the figure\n", "We can use the `.add_subplot()` method to add subplots to our figure! This method takes the arguments `(rows, columns, subplot_number)`.\n", "\n", "For example, if we want a single row and two columns, we can use the following code block:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig = plt.figure(figsize=(10, 6))\n", "\n", "# Create a plot for temperature\n", "ax = fig.add_subplot(1, 2, 1)\n", "ax.plot(times, temps, color='tab:red')\n", "\n", "# Create a plot for dewpoint\n", "ax2 = fig.add_subplot(1, 2, 2)\n", "ax2.plot(times, dewpoint, color='tab:green');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can also call `plot.subplots()` with the keyword arguments `nrows` (number of rows) and `ncols` (number of columns). This initializes a new `Axes` object, called `ax`, with the specified number of rows and columns. This object also contains a 1-D list of subplots, with a size equal to `nrows` x `ncols`.\n", "\n", "You can index this list, using `ax[0].plot()`, for example, to decide which subplot you're plotting to. Here is some example code for this technique:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(10, 6))\n", "\n", "ax[0].plot(times, temps, color='tab:red')\n", "ax[1].plot(times, dewpoint, color='tab:green');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Adding titles to each subplot\n", "We can add titles to these plots too; notice that these subplots are titled separately, by calling `ax.set_title` after plotting each subplot:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig = plt.figure(figsize=(10, 6))\n", "\n", "# Create a plot for temperature\n", "ax = fig.add_subplot(1, 2, 1)\n", "ax.plot(times, temps, color='tab:red')\n", "ax.set_title('Temperature')\n", "\n", "# Create a plot for dewpoint\n", "ax2 = fig.add_subplot(1, 2, 2)\n", "ax2.plot(times, dewpoint, color='tab:green')\n", "ax2.set_title('Dewpoint');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Using `ax.set_xlim` and `ax.set_ylim` to control the plot boundaries\n", "\n", "It is common when plotting data to set the extent (boundaries) of plots, which can be performed by calling `.set_xlim` and `.set_ylim` on the `Axes` object containing the plot or subplot(s):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig = plt.figure(figsize=(10, 6))\n", "\n", "# Create a plot for temperature\n", "ax = fig.add_subplot(1, 2, 1)\n", "ax.plot(times, temps, color='tab:red')\n", "ax.set_title('Temperature')\n", "ax.set_xlim(110, 130)\n", "ax.set_ylim(290, 315)\n", "\n", "# Create a plot for dewpoint\n", "ax2 = fig.add_subplot(1, 2, 2)\n", "ax2.plot(times, dewpoint, color='tab:green')\n", "ax2.set_title('Dewpoint')\n", "ax2.set_xlim(110, 130);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Using `sharex` and `sharey` to share plot limits\n", "\n", "You may want to have both subplots share the same x/y axis limits. When setting up a new `Axes` object through a method like `add_subplot`, specify the keyword arguments `sharex=ax` and `sharey=ax`, where `ax` is the `Axes` object with which to share axis limits.\n", "\n", "Let's take a look at an example:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig = plt.figure(figsize=(10, 6))\n", "\n", "# Create a plot for temperature\n", "ax = fig.add_subplot(1, 2, 1)\n", "ax.plot(times, temps, color='tab:red')\n", "ax.set_title('Temperature')\n", "ax.set_ylim(260, 320)\n", "\n", "# Create a plot for dewpoint\n", "ax2 = fig.add_subplot(1, 2, 2, sharex=ax, sharey=ax)\n", "ax2.plot(times, dewpoint, color='tab:green')\n", "ax2.set_title('Dewpoint');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Putting this all together" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "

Info

\n", " If desired, you can move the location of your legend; to do this, specify the loc keyword argument when calling ax.legend().\n", "
" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig = plt.figure(figsize=(10, 6))\n", "ax = fig.add_subplot(1, 2, 1)\n", "\n", "# Specify how our lines should look\n", "ax.plot(times, temps, color='tab:red', label='Temperature (surface)')\n", "ax.plot(\n", " times,\n", " temps_1000,\n", " color='tab:red',\n", " linestyle=':',\n", " label='Temperature (isobaric level)',\n", ")\n", "\n", "# Add labels, grid, and legend\n", "ax.set_xlabel('Time')\n", "ax.set_ylabel('Temperature')\n", "ax.set_title('Temperature Forecast')\n", "ax.grid(True)\n", "ax.legend(loc='upper left')\n", "ax.set_ylim(257, 312)\n", "ax.set_xlim(95, 162)\n", "\n", "\n", "# Add our second plot - for dewpoint, changing the colors and labels\n", "ax2 = fig.add_subplot(1, 2, 2, sharex=ax, sharey=ax)\n", "ax2.plot(times, dewpoint, color='tab:green', label='Dewpoint (surface)')\n", "ax2.plot(\n", " times,\n", " dewpoint_1000,\n", " color='tab:green',\n", " linestyle=':',\n", " marker='o',\n", " label='Dewpoint (isobaric level)',\n", ")\n", "\n", "ax2.set_xlabel('Time')\n", "ax2.set_ylabel('Dewpoint')\n", "ax2.set_title('Dewpoint Forecast')\n", "ax2.grid(True)\n", "ax2.legend(loc='upper left');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Scatterplot\n", "Some data cannot be plotted accurately as a line plot. Another type of plot that is popular in science is the marker plot, more commonly known as a scatter plot. A simple scatter plot can be created by setting the `linestyle` to `None`, and specifying a marker type, size, color, etc., like this:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig = plt.figure(figsize=(10, 6))\n", "ax = fig.add_subplot(1, 1, 1)\n", "\n", "# Specify no line with circle markers\n", "ax.plot(temps, temps_1000, linestyle='None', marker='o', markersize=5)\n", "\n", "ax.set_xlabel('Temperature (surface)')\n", "ax.set_ylabel('Temperature (1000 hPa)')\n", "ax.set_title('Temperature Cross Plot')\n", "ax.grid(True);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "

Info

\n", " You can also use the scatter method, which is slower, but will give you more control, such as being able to color the points individually based upon a third variable.\n", "
\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig = plt.figure(figsize=(10, 6))\n", "ax = fig.add_subplot(1, 1, 1)\n", "\n", "# Specify no line with circle markers\n", "ax.scatter(temps, temps_1000)\n", "\n", "ax.set_xlabel('Temperature (surface)')\n", "ax.set_ylabel('Temperature (1000 hPa)')\n", "ax.set_title('Temperature Cross Plot')\n", "ax.grid(True);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's put together the following:\n", " * Beginning with our code above, add the `c` keyword argument to the `scatter` call; in this case, to color the points by the difference between the temperature at the surface and the temperature at 1000 hPa.\n", " * Add a 1:1 line to the plot (slope of 1, intercept of zero). Use a black dashed line.\n", " * Change the colormap to one more suited for a temperature-difference plot.\n", " * Add a colorbar to the plot (have a look at the Matplotlib documentation for help)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig = plt.figure(figsize=(10, 6))\n", "ax = fig.add_subplot(1, 1, 1)\n", "\n", "ax.plot([285, 320], [285, 320], color='black', linestyle='--')\n", "s = ax.scatter(temps, temps_1000, c=(temps - temps_1000), cmap='bwr', vmin=-5, vmax=5)\n", "fig.colorbar(s)\n", "\n", "ax.set_xlabel('Temperature (surface)')\n", "ax.set_ylabel('Temperature (1000 hPa)')\n", "ax.set_title('Temperature Cross Plot')\n", "ax.grid(True);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Displaying Images\n", "\n", "`imshow` displays the values in an array as colored pixels, similar to a heat map.\n", "\n", "Here, we declare some fake data in a bivariate normal distribution, to illustrate the `imshow` method:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x = y = np.arange(-3.0, 3.0, 0.025)\n", "X, Y = np.meshgrid(x, y)\n", "Z1 = np.exp(-(X**2) - Y**2)\n", "Z2 = np.exp(-((X - 1) ** 2) - (Y - 1) ** 2)\n", "Z = (Z1 - Z2) * 2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can now pass this fake data to `imshow` to create a heat map of the distribution:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots()\n", "im = ax.imshow(\n", " Z, interpolation='bilinear', cmap='RdYlGn', origin='lower', extent=[-3, 3, -3, 3]\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Contour and Filled Contour Plots\n", "\n", "- `contour` creates contours around data.\n", "- `contourf` creates filled contours around data." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's start with the `contour` method, which, as just mentioned, creates contours around data:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots()\n", "ax.contour(X, Y, Z);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "After creating contours, we can label the lines using the `clabel` method, like this:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots()\n", "c = ax.contour(X, Y, Z, levels=np.arange(-2, 2, 0.25))\n", "ax.clabel(c);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As described above, the `contourf` (contour fill) method creates filled contours around data, like this:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots()\n", "c = ax.contourf(X, Y, Z);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As a final example, let's create a heatmap figure with contours using the `contour` and `imshow` methods. First, we use `imshow` to create the heatmap, specifying a colormap using the `cmap` keyword argument. We then call `contour`, specifying black contours and an interval of 0.5. Here is the example code, and resulting figure:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots()\n", "im = ax.imshow(\n", " Z, interpolation='bilinear', cmap='PiYG', origin='lower', extent=[-3, 3, -3, 3]\n", ")\n", "c = ax.contour(X, Y, Z, levels=np.arange(-2, 2, 0.5), colors='black')\n", "ax.clabel(c);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Summary\n", "* `Matplotlib` can be used to visualize datasets you are working with.\n", "* You can customize various features such as labels and styles.\n", "* There are a wide variety of plotting options available, including (but not limited to):\n", " * Line plots (`plot`)\n", " * Scatter plots (`scatter`)\n", " * Heatmaps (`imshow`)\n", " * Contour line and contour fill plots (`contour`, `contourf`)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## What's Next?\n", "In the next section, [more plotting functionality](histograms-piecharts-animation) is covered, such as histograms, pie charts, and animation." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Resources and References\n", "\n", "The goal of this tutorial is to provide an overview of the use of the Matplotlib library. It covers creating simple line plots, but it is by no means comprehensive. For more information, try looking at the following documentation:\n", "- [Matplotlib documentation](http://matplotlib.org)\n", "- [Matplotlib examples gallery](https://matplotlib.org/stable/gallery/index.html)\n", "- [GeoCAT examples gallery](https://geocat-examples.readthedocs.io/en/latest/gallery/index.html)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.9" } }, "nbformat": 4, "nbformat_minor": 4 }