{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Part 2: Visual Overview of Plotting Functions\n", "\n", "We've talked a lot about laying things out, etc, but we haven't talked about actually plotting data yet. Matplotlib has a number of different plotting functions -- many more than we'll cover here, in fact. There's a more complete list in the pyplot documentation, and Matplotlib gallery is a great place to get examples of all of them. \n", "\n", "However, a full list and/or the gallery can be a bit overwhelming at first. Instead we'll condense it down and give you a look at some of the ones you're most likely to use, and then go over a subset of those in more detail.\n", "\n", "Here's a simplified visual overview of matplotlib's most commonly used plot types. Let's browse through these, and then we'll go over a few in more detail. Clicking on any of these images will take you to the code that generated them. We'll skip that for now, but feel browse through it later." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## The Basics: 1D series/points\n", "### What we've mentioned so far\n", "\n", "\n", "### Other common plot types\n", "\n", "\n", "\n", "## 2D Arrays and Images\n", "\n", "\n", "\n", "\n", "## Vector Fields\n", "\n", "\n", "## Data Distributions\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Detailed Examples (of a few of these)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Let's get our standard imports out of the way\n", "from __future__ import print_function\n", "import numpy as np\n", "import matplotlib\n", "matplotlib.use('nbagg')\n", "import matplotlib.pyplot as plt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Input Data: 1D Series\n", "\n", "We've briefly mentioned `ax.plot(x, y)` and `ax.scatter(x, y)` to draw lines and points, respectively. We'll cover some of their options (markers, colors, linestyles, etc) in the next section. Let's move on to a couple of other common plot types.\n", "\n", "### Bar Plots: `ax.bar(...)` and `ax.barh(...)`\n", "\n", "\n", "Bar plots are one of the most common plot types. Matplotlib's `ax.bar(...)` method can also plot general rectangles, but the default is optimized for a simple sequence of x, y values, where the rectangles have a constant width. There's also `ax.barh(...)` (for horizontal), which makes a constant-height assumption instead of a constant-width assumption." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.random.seed(1)\n", "x = np.arange(5)\n", "y = np.random.randn(5)\n", "\n", "fig, axes = plt.subplots(ncols=2, figsize=plt.figaspect(1./2))\n", "\n", "vert_bars = axes[0].bar(x, y, color='lightblue', align='center')\n", "horiz_bars = axes[1].barh(x, y, color='lightblue', align='center')\n", "\n", "# I'll also introduce axhline & axvline to draw a line all the way across the axes\n", "# This can be a quick-n-easy way to draw an axis \"spine\".\n", "axes[0].axhline(0, color='gray', linewidth=2)\n", "axes[1].axvline(0, color='gray', linewidth=2)\n", "\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that we held on to what `ax.bar(...)` returned. Matplotlib plotting methods return an `Artist` or a sequence of artists. Anything you can see in a Matplotlib figure/axes/etc is an `Artist` of some sort. Most of the time, you will not need to retain these returned objects. You will want to capture them for special customizing that may not be possible through the normal plotting mechanism.\n", "\n", "Let's re-visit that last example and modify what's plotted. In the case of `bar`, a container artist is returned, so we'll modify its contents instead of the container itself (thus, `for bar in vert_bars`)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots()\n", "vert_bars = ax.bar(x, y, color='lightblue', align='center')\n", "\n", "# We could have also done this with two separate calls to `ax.bar` and numpy boolean indexing.\n", "for bar, height in zip(vert_bars, y):\n", " if height < 0:\n", " bar.set(edgecolor='darkred', color='salmon', linewidth=3)\n", "\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Keep in mind that any plotting method in matplotlib returns the artists that are plotted. We'll use it again, particularly when we get to adding colorbars to images." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Filled Regions: `ax.fill(x, y)`, `fill_between(...)`, etc\n", "\n", "\n", "Of these functions, `ax.fill_between(...)` is probably the one you'll use the most often. In its most basic form, it fills between the given y-values and 0:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.random.seed(1)\n", "y = np.random.randn(100).cumsum()\n", "x = np.linspace(0, 10, 100)\n", "\n", "fig, ax = plt.subplots()\n", "ax.fill_between(x, y, color='lightblue')\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "However, it can also be used to fill between two curves. This is particularly useful when you want to show an envelope of some sort (e.g. error, confidence, amplitude, etc)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x = np.linspace(0, 10, 200)\n", "y1 = 2 * x + 1\n", "y2 = 3 * x + 1.2\n", "y_mean = 0.5 * x * np.cos(2*x) + 2.5 * x + 1.1\n", "\n", "fig, ax = plt.subplots()\n", "\n", "# Plot the envelope with `fill_between`\n", "ax.fill_between(x, y1, y2, color='yellow')\n", "\n", "# Plot the \"centerline\" with `plot`\n", "ax.plot(x, y_mean, color='black')\n", "\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## `data` keyword argument\n", "\n", "When using nested data structures such as h5py objects, Pandas DataFrames, or XArrays, the data can be accessed via `[]` like dictionary elements. This can get very repetitive and tedious as one types out a plotting command accessing those elements. So, the `data` keyword argument was added to almost all of the plotting functions in v1.5. With this feature, one can pass in a single dictionary-like object as `data`, and use the string key names in the place of the usual input data arguments.\n", "\n", "Let's revisit the above example:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x = np.linspace(0, 10, 200)\n", "data_obj = {'x': x,\n", " 'y1': 2 * x + 1,\n", " 'y2': 3 * x + 1.2,\n", " 'mean': 0.5 * x * np.cos(2*x) + 2.5 * x + 1.1}\n", "\n", "fig, ax = plt.subplots()\n", "\n", "# Plot the envelope with `fill_between`\n", "ax.fill_between('x', 'y1', 'y2', color='yellow', data=data_obj)\n", "\n", "# Plot the \"centerline\" with `plot`\n", "ax.plot('x', 'mean', color='black', data=data_obj)\n", "\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Exercise 2.1:\n", "\n", "Now let's try combining `bar` and `fill_between` to make a nice prediction of what will happen as this class progresses:\n", "\n", "Can you reproduce the figure below?\n", "" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%load exercises/2.1-bar_and_fill_between.py" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "np.random.seed(1)\n", "\n", "# Generate data...\n", "y_raw = np.random.randn(1000).cumsum() + 15\n", "x_raw = np.linspace(0, 24, y_raw.size)\n", "\n", "# Get averages of every 100 samples...\n", "x_pos = x_raw.reshape(-1, 100).min(axis=1)\n", "y_avg = y_raw.reshape(-1, 100).mean(axis=1)\n", "y_err = y_raw.reshape(-1, 100).ptp(axis=1)\n", "\n", "bar_width = x_pos[1] - x_pos[0]\n", "\n", "# Make a made up future prediction with a fake confidence\n", "x_pred = np.linspace(0, 30)\n", "y_max_pred = y_avg[0] + y_err[0] + 2.3 * x_pred\n", "y_min_pred = y_avg[0] - y_err[0] + 1.2 * x_pred\n", "\n", "# Just so you don't have to guess at the colors...\n", "barcolor, linecolor, fillcolor = 'wheat', 'salmon', 'lightblue'\n", "\n", "# Now you're on your own!\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Input Data: 2D Arrays or Images\n", "\n", "There are several options for plotting 2D datasets. `imshow`, `pcolor`, and `pcolormesh` have a lot of overlap, at first glance. The image below is meant to clarify that somewhat.\n", "\n", "\n", "\n", "\n", "In short, `imshow` can interpolate and display large arrays very quickly, while `pcolormesh` and `pcolor` are much slower, but can handle flexible (i.e. more than just rectangular) arrangements of cells.\n", "\n", "We won't dwell too much on the differences and overlaps here. They have overlapping capabilities, but different default behavior because their primary use-cases are a bit different (there's also `matshow`, which is `imshow` with different defaults). \n", "\n", "Instead we'll focus on what they have in common.\n", "\n", "`imshow`, `pcolor`, `pcolormesh`, `scatter`, and any other Matplotlib plotting methods that map a range of data values onto a colormap will return artists that are instances of `ScalarMappable.` In practice, what that means is a) you can display a colorbar for them, and b) they share several keyword arguments." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Colorbars\n", "\n", "Let's add a colorbar to the figure to display what colors correspond to values of `data` we've plotted. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from matplotlib.cbook import get_sample_data\n", "data = np.load(get_sample_data('axes_grid/bivariate_normal.npy'))\n", "\n", "fig, ax = plt.subplots()\n", "im = ax.imshow(data, cmap='gist_earth')\n", "fig.colorbar(im)\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You may notice that `colorbar` is a `Figure` method and not an `Axes` method. That's because `colorbar` doesn't operate on the axes. Instead, it shrinks the current axes by a bit, adds a _new_ axes to the figure, and places the colorbar on that axes.\n", "\n", "The new axes that `fig.colorbar` creates is fairly limited in where it can be positioned. For example, it's always outside the axes it \"steals\" room from. Sometimes you may want to avoid \"stealing\" room from an axes or maybe even have the colorbar _inside_ another axes. In that case, you can manually create the axes for the colorbar and position it where you'd like:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots()\n", "cax = fig.add_axes([0.27, 0.8, 0.5, 0.05])\n", "\n", "im = ax.imshow(data, cmap='gist_earth')\n", "fig.colorbar(im, cax=cax, orientation='horizontal')\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "One note: In the last module in this tutorial, we'll briefly cover `axes_grid`, which is very useful for aligning colorbars and/or other axes with images displayed with `imshow`. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " ### Shared parameters for `imshow`, `pcolormesh`, `contour`, `scatter`, etc\n", " \n", " As we mentioned earlier, any plotting method that creates a `ScalarMappable` will have some common kwargs. The ones you'll use the most frequently are:\n", " \n", " * `cmap` : The colormap (or name of the colormap) used to display the input. (We'll go over the different colormaps in the next section.)\n", " * `vmin` : The minimum data value that will correspond to the \"bottom\" of the colormap (defaults to the minimum of your input data).\n", " * `vmax` : The maximum data value that will correspond to the \"top\" of the colormap (defaults to the maximum of your input data).\n", " * `norm` : A `Normalize` instance to control how the data values are mapped to the colormap. By default, this will be a linear scaling between `vmin` and `vmax`, but other norms are available (e.g. `LogNorm`, `PowerNorm`, etc).\n", " \n", "`vmin` and `vmax` are particularly useful. Quite often, you'll want the colors to be mapped to a set range of data values, which aren't the min/max of your input data. For example, you might want a symmetric ranges of values around 0.\n", "\n", "See the documentation for longer discussions of [colormaps](https://matplotlib.org/tutorials/colors/colormaps.html) and [norms](https://matplotlib.org/tutorials/colors/colormapnorms.html).\n", "\n", "As an example of that, let's use a divergent colormap with the data we showed earlier. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from matplotlib.cbook import get_sample_data\n", "data = np.load(get_sample_data('axes_grid/bivariate_normal.npy'))\n", "\n", "fig, ax = plt.subplots()\n", "im = ax.imshow(data, cmap='seismic')\n", "fig.colorbar(im)\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this case, we'd really like the white in the colormap to correspond to 0. A quick way to do this is to make the `vmin` equal to the negative of the `vmax`. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots()\n", "im = ax.imshow(data, \n", " cmap='seismic',\n", " vmin=-2, vmax=2)\n", "fig.colorbar(im)\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`vmin` and `vmax` are also very useful when we want multiple plots to share one colorbar, as our next exercise will do.\n", "\n", "## Exercise 2.2:\n", "\n", "Can you reproduce the figure below?\n", "" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%load exercises/2.2-vmin_vmax_imshow_and_colorbars.py" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "np.random.seed(1)\n", "\n", "plt.style.use('classic')\n", "\n", "# Generate random data with different ranges...\n", "data1 = np.random.random((10, 10))\n", "data2 = 2 * np.random.random((10, 10))\n", "data3 = 3 * np.random.random((10, 10))\n", "\n", "# Set up our figure and axes...\n", "fig, axes = plt.subplots(ncols=3, figsize=plt.figaspect(0.5))\n", "fig.tight_layout() # Make the subplots fill up the figure a bit more...\n", "cax = fig.add_axes([0.25, 0.1, 0.55, 0.03]) # Add an axes for the colorbar\n", "\n", "# Now you're on your own!\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "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.6.5" } }, "nbformat": 4, "nbformat_minor": 1 }