User Tools

Site Tools


other:python:matplotlib_by_jyp

Working with matplotlib (JYP version)

Summary: there are lots of python libraries that you can use for plotting, but Matplotlib has become a de facto standard

You can get more information about python on the JYP's recommended steps for learning python page.

You can get more information on plotting maps in the Cartopy + Iris section and the basemap section, with examples on the JYP's map room page. Note: there will be no python3 support in basemap, and basemap is being slowly replaced by cartopy

matplotlib official website: Matplotlib web site

matplotlib help on stack overflow: matplotlib help

Starting (and more) with matplotlib

The matplotlib documentation is good, but not always easy to use : the package is powerful, there are lots of ways to do the same thing and it's easy to get lost.

There are lots of online tutorials that often don't show much more than a simple scatter plot with basic page setup.

This page will hopefully give you some keys to become (progressively) a matplotlib power user.

A good way to start with matplotlib is to quickly read this section, practice, and read this section again (and again)

  1. Have a quick look at the matplotlib gallery to get an idea of all you can do with matplotlib. Later, when you need to plot something, go back to the gallery to find some examples that are close to what you need and click on them to view their source code
  2. Use the free hints provided by JY!
    1. You will usually initialize matplotlib with: import matplotlib.pyplot as plt
      • in some cases you may also need: import matplotlib as mpl
      • later, you may need other matplotlib related modules, for advanced usage
    2. You need to know some matplotlib specific vocabulary:
      • a Matplotlib Figure (or canvas) is a graphical window or page in which you create your plots…
        • example: my_page = plt.figure()
        • if you need several display windows at the same time, create several figures!
          win_1 = plt.figure()
          win_2 = plt.figure()
        • the parts of a figure are usually positioned in normalized coordinates: (0, 0) is the bottom left of the figure, and (1, 1) is the top right
        • You don't really specify the page orientation (portrait or landscape) of a plot. If you want a portrait plot, it's up to you to create a plot that will look higher than it is large. The idea is not to worry about this and just check the final resulting plot: create a plot, save it, display the resulting png/pdf and then adjust the creation script
          • If you do have an idea of the layout of what you want to plot, it may be easier to explicitly specify the figure size/ratio at creation time, and then try to fill the normalized coordinates space of the figure
          • my_page = plt.figure(): the ratio of the default figure is landscape, because it is 33% larger than it is high. Creating a default figure will be OK most of the time!
          • my_page = plt.figure(figsize=(width, height)): create a figure with a custom ratio
            • The specified width and height are supposed to be in inches (1 inch = 2.54 cm)
            • my_page = plt.figure(figsize=(8.3, 11.7)): create a figure that will theoretically fill an A4 size page in portrait mode (check Dimensions Of A Series Paper Sizes if you need more details about standard paper sizes)
      • a Matplotlib Axes (not to be confused with an axis) is a (sub-)plot inside a Figure… (much) More details
        • reserve space for one plot that will use most of the available area of the figure/page:
          • my_plot = my_page.add_subplot(1, 1, 1) or my_plot = my_page.subplots()
          • the syntax for more than one plot is: add_subplot(nrows, ncols, index)
        • create 3 plots on 1 column (each plot uses the full width of the figure):
          • top_plot = my_page.add_subplot(3, 1, 1)
            mid_plot = my_page.add_subplot(3, 1, 2)
            bot_plot = my_page.add_subplot(3, 1, 3)
          • creating an array of plots with subplots is more efficient than add_subplot, when there are lots of plots on a page
            plot_array = my_page.subplots(3, 1)
            top_plot = plot_array[0]
            mid_plot = plot_array[1]
            bot_plot = plot_array[2]
          • it is even more efficient to create a figure and axes with a single line:
            • one plot on one page: my_page, my_plot = plt.subplots()
            • three plots on one A4 portrait page:
              my_page, plot_array = plt.subplots(nrows=3, ncols=1,
                                                 figsize=(8.3, 11.) # A4 portrait)
              top_plot = plot_array[0]
              mid_plot = plot_array[1]
              bot_plot = plot_array[2]
            • if you need to specify the same additional keyword parameters (that will be passed behind the scene to the add_subplot function), you can use the subplot_kw parameter. For example, if all the plots will be created with cartopy, using the Robinson projection, the example above becomes:
              my_page, plot_array = plt.subplots(nrows=3, ncols=1,
                                                 figsize=(8.3, 11.), # A4 portrait
                                                 subplot_kw=dict(projection=ccrs.Robinson()))
        • use my_page.add_axes(...) to add an axis in an arbirary location of the page
          my_page.add_axes([left, bottom, width, height])
      • a Matplotlib Artist or Patch is something (e.g a line, a group of markers, text, the legend…) plotted on the Figure/Axis
      • clearing the page (or part of it): you probably won't need that…
        • my_page.clear() or my_page.clf() or plt.clf(): clear the (current) figure
        • my_plot.clear() or my_plot.cla(): clear the (current) axis
    3. some resources for having multiple plots on the same figure
        • plt.subplots(...) with an s at the end (demo)
        • subplots_adjust can be used to change the overall boundaries of the subplots on the figure, and the spacing between the subplots
          plt.subplots_adjust(left=None, bottom=None, right=None, top=None, wspace=None, hspace=None)
          or my_page.subplots_adjust(left=None, bottom=None, right=None, top=None, wspace=None, hspace=None)
          • hspace/wspace is the amount of height/width between the subplots
            • hspace=0.1 is enough for just displaying the ticks and the labels, without the axis name
            • use hspace=0 to stick the plots together vertically
              • do not forget to disable the ticks where there is no space to plot them: my_plot.set_xticks([])
          • my_page.subplots_adjust(right=0.75) will leave 25% on the right of the page for adding a legend outside of a plot
        • You can also resize an existing (sub)plot the following way:
          1. Get the current size information: pl_x_bottomleft, pl_y_bottomleft, pl_width, pl_height = my_plot.get_position().bounds
          2. Set the new size: e.g reduce the height with my_plot.set_position( (pl_x_bottomleft, pl_y_bottomleft, pl_width, pl_height * 0.5) )
    4. use my_page.savefig(...) to save a figure
      • savefig(…) must be called before plt.show()!
      • my_page.savefig('my_plot.pdf'): save the figure to a pdf file
      • my_page.savefig('my_plot.png', dpi=200, transparent=True, bbox_inches='tight'): save the figure to a png file at a higher resolution than the default (default is 100 dots per inch), with a transparent background and no extra space around the figure
    5. display the figure and its plots, and start interacting (zooming, panning…) with them:
      plt.show()
    6. it may be hard to (remember how to) work with colors and colorbars. Some examples from the matplotlib Gallery can help you!
      Note: A reversed version of each colormap is available by appending _r to the name, e.g., viridis_r
    7. if you don't see a part of what you have plotted, maybe it's hidden behind other elements! Use the zorder parameter to explicitly specify the plotting order/layers/depth
      • things should automatically work as expected if zorder is not explicitly specified
      • Use the zorder=NN parameter when creating objects. NN is an integer where 0 is the lowest value (the farthest from the eye), and objects are plotted above objects with a lower zorder value
      • Use matplotlib_object.set_order(NN) to change the order after an object has been created
    8. you can use transparency to partially show what is behind some markers or other objects. Many artists accept the alpha parameter where 0.0 means that the object is completely transparent, and 1.0 means completely opaque
      e.g. my_plot.scatter(…, alpha=0.7)
    9. sometimes the results of the python/matplolib commands are displayed immediately, sometimes not. It depends if you are in interactive or non-interactive mode
    10. if your matplotlib is executed in a batch script, it will generate an error when trying to create (show()) a plot, because matplotlib expects to be able to display the figure on a screen (with an X server running) by default.
      Find out how to deal with this in Creating a plot offline.
    11. the documentation may mention backends. What?? Basically, you use python commands to create a plot, and the backend is the thing that will render your plot on the screen or in a file (png, pdf, etc…)
  3. Download the pdf version of the manual. Do not print the 2300+ pages of the manual! Read the beginner's guide (Chapter FIVE of Part II) and have a super quick look at the table of contents of the whole document.

Useful matplotlib reference pages

  • Some plot types:
    • plot(...): Plot y versus x as lines and/or markers
    • scatter(...): A scatter plot of y vs x with varying marker size and/or color
    • The plot function will be faster for scatterplots where markers don't vary in size or color
    • contour(...) and contourf(...): draw contour lines and filled contours
  • X and Y axes parameters (see also Anatomy of a figure):
    • Axis range: my_plot.set_xlim(x_leftmost_value, x_rightmost_value)
      • Use the leftmost and rightmost values to specify the orientation of the axis (i.e the rightmost value can be smaller than the leftmost)
    • Axis label: my_plot.set_xlabel(x_label_string, fontsize=axis_label_fontsize)
      • Use the extra labelpad parameter to move the label closer (negative value) to the axis or farther (positive value): e.g. my_plot.set_xlabel('A closer label', labelpad=-20)
      • cartopy dirty trick: you have to use my_plot.set_xticks([]), otherwise the X axis label will not be printed
        • Trick needs to be used with cartopy 0.17.0
          Remember to update/remove this information in the future
    • Major (and minor) tick marks location: my_plot.set_xticks(x_ticks_values, minor=False) (set_xticks)
      • Use an empty list if you don't want tick marks: my_plot.set_xticks([])
    • Tick labels: my_plot.set_xticklabels(x_tick_labels, minor=False, fontsize=ticklabels_fontsize) (set_xticklabels)
      • If you do not specify labels, the default labels will just be the values specifying the ticks' location
      • x_tick_labels is a list of strings that has the same length as x_ticks_values.
        Use an empty string in the positions where you don't want a label
      • The default numerical labels may be too long, due to numerical approximations. You can try to explicitly round the values, or generate correct label strings from the values
        >>> x_tick_values = np.arange(0, 1, 0.2)
        >>> x_tick_values.tolist()
        [0.0, 0.2, 0.4, 0.6000000000000001, 0.8]
        >>> x_tick_values.round(decimals=1).tolist()
        [0.0, 0.2, 0.4, 0.6, 0.8]
        >>> x_tick_labels = [ '%.1f' % (t_val,) for t_val in x_tick_values ]
        >>> x_tick_labels
        ['0.0', '0.2', '0.4', '0.6', '0.8']
        >>> x_tick_labels[0] = 'START'
        >>> x_tick_labels[-1] = 'END'
        >>> x_tick_labels
        ['START', '0.2', '0.4', '0.6', 'END']
      • You can also use fancy tick formatters
      • Many more options for ticks, labels, orientation, …
    • Grid lines:
      • Their position is determined by the values used for set_xticks and set_yticks
      • Activate all (horizontal and vertical) grid lines with: my_plot.grid(True, linestyle=“--”, linewidth=0.5, color='.25',zorder=some_value)
        You can adjust the zorder value to determine if the grid lines should be above or below other parts of the plot!
      • Plot only the horizontal or vertical lines with:
        ax.yaxis.grid(True)
        or ax.xaxis.grid(True)
      • Note: special case of cartopy plots: the location of the gridlines, and the properties of the associated labels are determined by myplot.gridlines! See Cartopy map gridlines and tick labels
  • line parameters
    • Default marker size and edge width:
      • mpl.rcParams['lines.markersize'] ** 2 ⇒ 36
      • mpl.rcParams['lines.linewidth'] ⇒ 1.5
    • Other marker attributes. For plot, all the markers have the same attributes, and for scatter the attributes can be the same, or specified for each marker
      • plot(...): fmt (see documentation) or marker and markerfacecolor/mfc (and markerfacecoloralt/mfcalt for dual color markers), markersize, markeredgewidth/mew, markeredgecolor (use markeredgecolor='none' if you don't want to plot the edge of the markers), fillstyle (full, None, other)
      • scatter(...): marker (marker type), c (color), s (size), linewidths (linewidth of the marker edges), edgecolors
  • colors and colormaps
    • HTML color picker and different ways of choosing colors
    • Reverting the colors: add _r at the end of the colormap name
    • Number of colors in the my_cmap colormap (usually 256): my_cmap.N
      • Accessing the RGB color definition by index, from 0 to my_cmap.N - 1. Note that the index will saturate below 0 and above my_cmap.N - 1
        >>> my_cmap.N
        256
        >>> my_cmap(-1) # Same as ano_cmap(0)
        (0.3686274509803922, 0.30980392156862746, 0.6352941176470588, 1.0)
        >>> my_cmap(0)
        (0.3686274509803922, 0.30980392156862746, 0.6352941176470588, 1.0)
        >>> my_cmap(1)
        (0.36186082276047676, 0.3185697808535179, 0.6394463667820068, 1.0)
        >>> my_cmap(255)
        (0.6196078431372549, 0.00392156862745098, 0.25882352941176473, 1.0)
        >>> my_cmap(256) # Same as ano_cmap(255)
        (0.6196078431372549, 0.00392156862745098, 0.25882352941176473, 1.0)
        >>> my_cmap(257) # Same as ano_cmap(255)
        (0.6196078431372549, 0.00392156862745098, 0.25882352941176473, 1.0)
    • Special colormap colors
      • my_cmap.set_bad(color='k'): color to be used for masked values
      • my_cmap.set_over(color='k'): color to be used for high out-of-range values if extend is specified and is 'both' or 'max'. Default color is my_cmap(my_cmap.N - 1)
      • my_cmap.set_under(color='k'): color to be used for low out-of-range values if extend is specified and is 'both' or 'min'. Default color is my_cmap(0)
  • colorbar (see also the colorbar api)
    • The legend will show the lines (or other objects) that were associated with a label with the label= keyword when creating/updating a plot
      • If there are some elements of a plot that you do not want to associate with a legend (e.g. there are several lines with the same color and markers, but you want to plot the legend only once), do not specify a label= keyword for these elements, or add a _ at the front of the label strings
    • The legend is positioned somewhere (that can be specified) inside the plot. In order to place a legend outside the plot, use the bbox_to_anchor parameter
      • the parameters of bbox_to_anchor are in normalized coordinates of the current (sub)plot:
        • (0, 0) is the lower left corner of the plot, and (1, 1) the upper right corner
        • legend(… bbox_to_anchor=(1.05, 1.), loc='upper left', …) will put the upper left corner of the legend slightly right ((1.05, 1.)) of the upper right corner ((1, 1)) of the plot
      • if the legend is outside of the plot, you have to explicitly provide enough space for the legend on the page
        • e.g. with subplots_adjust, plt.subplots_adjust(right=0.75) will make all the plots use 75% on the left of the page, and leave 25% on the right for the legend
  • The figure(...) and the associated methods
  • The axes and the associated methods
  • matplotlib default config/settings can be queried and updated
    • example: the default figure size (inches) is mpl.rcParams['figure.figsize'] ([6.4, 4.8])
    • current settings' file: mpl.matplotlib_fname()

Misc Matplotlib tricks

Creating a plot offline

You may need to create a plot offline when your network connection is not good enough, you don't have an X server running to display the plot (possibly because the script is running on a cluster), etc… This is easily done with the following code:

# offline_plot = False
offline_plot = True

import matplotlib as mpl
if offline_plot:
    # Define the graphic back-end BEFORE importing pyplot
    mpl.use('Agg')
    
# Import the rest of the matplotlib based modules
import matplotlib.pyplot as plt

[ ...your actual code... ] 

# Done at last! Save the result
my_page.savefig(out_name, dpi=300, transparent=True, bbox_inches='tight')

if not offline_plot:
    # Enter the interactive mode to display the plot
    plt.show()

Note: see also CanvasAgg demo for a pure offline plot, and How to use Matplotlib in a web application server. But the code above is much easier!

Specifying the background color of a plot

e.g. You need to plot a masked variable, but you don't want the masked areas to be white

# make the background dark gray (call this before the contourf)
plt.gca().patch.set_color('.25')
plt.contourf(d)
plt.show()

trick source

Unsorted matplotlib stuff

Some useful notes and links that cannot be placed (yet) in a section of the main page

Plotting arcs (segments of ellipses)

Using hatches with contourf

other/python/matplotlib_by_jyp.txt · Last modified: 2023/10/26 08:39 by jypeter