Plotting

Introduction

The goal of xray’s plotting is to make exploratory plotting quick and easy by using metadata from xray.DataArray objects to add informative labels.

Xray plotting functionality is a thin wrapper around the popular matplotlib library. Matplotlib syntax and function names were copied as much as possible, which makes for an easy transition between the two. Matplotlib must be installed and working before plotting with xray.

For more extensive plotting applications consider the following projects:

  • Seaborn: “provides a high-level interface for drawing attractive statistical graphics.” Integrates well with pandas.
  • Holoviews: “Composable, declarative data structures for building even complex visualizations easily.” Works for 2d datasets.
  • Cartopy: Provides cartographic tools.

Imports

# Use defaults so we don't get gridlines in generated docs
In [1]: import matplotlib as mpl

In [2]: mpl.rcdefaults()

The following imports are necessary for all of the examples.

In [3]: import numpy as np

In [4]: import matplotlib.pyplot as plt

In [5]: import xray

One Dimension

Simple Example

Xray uses the coordinate name to label the x axis:

In [6]: t = np.linspace(0, np.pi, num=20)

In [7]: sinpts = xray.DataArray(np.sin(t), {'t': t}, name='sin(t)')

In [8]: sinpts
 Out[8]: 
<xray.DataArray 'sin(t)' (t: 20)>
array([  0.000e+00,   1.646e-01,   3.247e-01, ...,   3.247e-01,   1.646e-01,   1.225e-16])
Coordinates:
  * t        (t) float64 0.0 0.1653 0.3307 0.496 0.6614 0.8267 0.9921 1.157 ...

In [9]: sinpts.plot()
 Out[9]: [<matplotlib.lines.Line2D at 0x7fce229e4e90>]
_images/plotting_example_sin.png

Additional Arguments

Additional arguments are passed directly to the matplotlib function which does the work. For example, xray.DataArray.plot_line() calls matplotlib.pyplot.plot passing in the index and the array values as x and y, respectively. So to make a line plot with blue triangles a matplotlib format string can be used:

In [10]: sinpts.plot_line('b-^')
Out[10]: [<matplotlib.lines.Line2D at 0x7fce21f5d8d0>]
_images/plotting_example_sin2.png

Warning

Not all xray plotting methods support passing positional arguments to the wrapped matplotlib functions, but they do all support keyword arguments.

Keyword arguments work the same way, and are more explicit.

In [11]: sinpts.plot_line(color='purple', marker='o')
Out[11]: [<matplotlib.lines.Line2D at 0x7fce225917d0>]
_images/plotting_example_sin3.png

Adding to Existing Axis

To add the plot to an existing axis pass in the axis as a keyword argument ax. This works for all xray plotting methods. In this example axes is a tuple consisting of the left and right axes created by plt.subplots.

In [12]: fig, axes = plt.subplots(ncols=2)

In [13]: axes
Out[13]: 
array([<matplotlib.axes.AxesSubplot object at 0x7fce220e7690>,
       <matplotlib.axes.AxesSubplot object at 0x7fce229fe910>], dtype=object)

In [14]: sinpts.plot(ax=axes[0])
Out[14]: [<matplotlib.lines.Line2D at 0x7fce22053910>]

In [15]: sinpts.plot_hist(ax=axes[1])
Out[15]: 
(array([ 2.,  2.,  0.,  2.,  2.,  0.,  2.,  2.,  2.,  6.]),
 array([ 0.   ,  0.1  ,  0.199, ...,  0.797,  0.897,  0.997]),
 <a list of 10 Patch objects>)

In [16]: plt.show()
_images/plotting_example_existing_axes.png

On the right is a histogram created by xray.DataArray.plot_hist().

Time Series

The index may be a date.

In [17]: import pandas as pd

In [18]: npts = 20

In [19]: time = pd.date_range('2015-01-01', periods=npts)

In [20]: noise = xray.DataArray(np.random.randn(npts), {'time': time})

In [21]: noise.plot_line()
Out[21]: [<matplotlib.lines.Line2D at 0x7fce22c946d0>]
_images/plotting_example_time.png

TODO- rotate dates printed on x axis.

Two Dimensions

Simple Example

The default method xray.DataArray.plot() sees that the data is 2 dimensional. If the coordinates are uniformly spaced then it calls xray.DataArray.plot_imshow().

In [22]: a = xray.DataArray(np.zeros((4, 3)), dims=('y', 'x'))

In [23]: a[0, 0] = 1

In [24]: a
Out[24]: 
<xray.DataArray (y: 4, x: 3)>
array([[ 1.,  0.,  0.],
       [ 0.,  0.,  0.],
       [ 0.,  0.,  0.],
       [ 0.,  0.,  0.]])
Coordinates:
  * x        (x) int64 0 1 2
  * y        (y) int64 0 1 2 3

The plot will produce an image corresponding to the values of the array. Hence the top left pixel will be a different color than the others. Before reading on, you may want to look at the coordinates and think carefully about what the limits, labels, and orientation for each of the axes should be.

In [25]: a.plot()
Out[25]: <matplotlib.image.AxesImage at 0x7fce22092410>
_images/plotting_example_2d_simple.png

It may seem strange that the values on the y axis are decreasing with -0.5 on the top. This is because the pixels are centered over their coordinates, and the axis labels and ranges correspond to the values of the coordinates.

All 2d plots in xray allow the use of the keyword arguments yincrease=True to produce a more conventional plot where the coordinates increase in the y axis. xincrease works similarly.

In [26]: a.plot(yincrease=True)
Out[26]: <matplotlib.image.AxesImage at 0x7fce21f7ba50>
_images/2d_simple_yincrease.png

Missing Values

Xray plots data with Missing values.

# This data has holes in it!
In [27]: a[1, 1] = np.nan

In [28]: a.plot()
Out[28]: <matplotlib.image.AxesImage at 0x7fce21d4ff50>
_images/plotting_missing_values.png

Simulated Data

For further examples we generate two dimensional data by computing the Euclidean distance from a 2d grid point to the origin.

In [29]: x = np.arange(start=0, stop=10, step=2)

In [30]: y = np.arange(start=9, stop=-7, step=-3)

In [31]: xy = np.dstack(np.meshgrid(x, y))

In [32]: distance = np.linalg.norm(xy, axis=2)

In [33]: distance = xray.DataArray(distance, zip(('y', 'x'), (y, x)))

In [34]: distance
Out[34]: 
<xray.DataArray (y: 6, x: 5)>
array([[  9.   ,   9.22 ,   9.849,  10.817,  12.042],
       [  6.   ,   6.325,   7.211,   8.485,  10.   ],
       [  3.   ,   3.606,   5.   ,   6.708,   8.544],
       [  0.   ,   2.   ,   4.   ,   6.   ,   8.   ],
       [  3.   ,   3.606,   5.   ,   6.708,   8.544],
       [  6.   ,   6.325,   7.211,   8.485,  10.   ]])
Coordinates:
  * y        (y) int64 9 6 3 0 -3 -6
  * x        (x) int64 0 2 4 6 8

Note the coordinate y here is decreasing. This makes the y axes appear in the conventional way.

In [35]: distance.plot()
Out[35]: <matplotlib.image.AxesImage at 0x7fce21e45a50>
_images/plotting_2d_simulated.png

Changing Axes

To swap the variables plotted on vertical and horizontal axes one can transpose the array.

In [36]: distance.T.plot()
Out[36]: <matplotlib.image.AxesImage at 0x7fce21c2cbd0>
_images/plotting_changing_axes.png

To make x and y increase:

In [37]: distance.T.plot(xincrease=True, yincrease=True)
Out[37]: <matplotlib.image.AxesImage at 0x7fce21af7e10>
_images/plotting_changing_axes2.png

Nonuniform Coordinates

It’s not necessary for the coordinates to be evenly spaced. If not, then xray.DataArray.plot() produces a filled contour plot by calling xray.DataArray.plot_contourf(). This example demonstrates that by using one coordinate with logarithmic spacing.

In [38]: x = np.linspace(0, 500)

In [39]: y = np.logspace(0, 3)

In [40]: xy = np.dstack(np.meshgrid(x, y))

In [41]: d_ylog = np.linalg.norm(xy, axis=2)

In [42]: d_ylog = xray.DataArray(d_ylog, zip(('y', 'x'), (y, x)))

In [43]: d_ylog.plot()
Out[43]: <matplotlib.contour.QuadContourSet instance at 0x7fce2260a7e8>
_images/plotting_nonuniform_coords.png

Calling Matplotlib

Since this is a thin wrapper around matplotlib, all the functionality of matplotlib is available.

In [44]: d_ylog.plot(cmap=plt.cm.Blues)
Out[44]: <matplotlib.contour.QuadContourSet instance at 0x7fce368d4e18>

In [45]: plt.title('Euclidean distance from point to origin')
Out[45]: <matplotlib.text.Text at 0x7fce2199fc10>

In [46]: plt.xlabel('temperature (C)')
Out[46]: <matplotlib.text.Text at 0x7fce21b19310>

In [47]: plt.show()
_images/plotting_2d_call_matplotlib.png

Warning

Xray methods update label information and generally play around with the axes. So any kind of updates to the plot should be done after the call to the xray’s plot. In the example below, plt.xlabel effectively does nothing, since d_ylog.plot() updates the xlabel.

In [48]: plt.xlabel('temperature (C)')
Out[48]: <matplotlib.text.Text at 0x7fce218c56d0>

In [49]: d_ylog.plot()
Out[49]: <matplotlib.contour.QuadContourSet instance at 0x7fce34047a70>

In [50]: plt.show()
_images/plotting_2d_call_matplotlib2.png

Contour plots can have missing values also.

In [51]: d_ylog[30:48, 10:30] = np.nan

In [52]: d_ylog.plot()
Out[52]: <matplotlib.contour.QuadContourSet instance at 0x7fce216d2950>

In [53]: plt.text(100, 600, 'So common...')
Out[53]: <matplotlib.text.Text at 0x7fce216fd990>

In [54]: plt.show()
_images/plotting_nonuniform_coords_missing.png

Colormaps

Suppose we want two plots to share the same color scale. This can be achieved by passing in the appropriate arguments and adding the color bar later.

In [55]: fig, axes = plt.subplots(ncols=2)

In [56]: kwargs = {'cmap': plt.cm.Blues, 'vmin': distance.min(), 'vmax': distance.max(), 'add_colorbar': False}

In [57]: im = distance.plot(ax=axes[0], **kwargs)

In [58]: halfd = distance / 2

In [59]: halfd.plot(ax=axes[1], **kwargs)
Out[59]: <matplotlib.image.AxesImage at 0x7fce21508e90>

In [60]: plt.colorbar(im, ax=axes.tolist())
Out[60]: <matplotlib.colorbar.Colorbar instance at 0x7fce215222d8>

In [61]: plt.show()
_images/plotting_same_color_scale.png

Here we’ve used the object returned by xray.DataArray.plot() to pass in as an argument to plt.colorbar. Take a closer look:

In [62]: im
Out[62]: <matplotlib.image.AxesImage at 0x7fce21508290>

In general xray’s plotting functions modify the axes and return the same objects that the wrapped matplotlib functions return.

Maps

To follow this section you’ll need to have Cartopy installed and working.

This script will plot an image over the Atlantic ocean.

import xray
import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs

nlat = 15
nlon = 5
atlantic = xray.DataArray(np.random.randn(nlat, nlon),
        coords = (np.linspace(50, 20, nlat), np.linspace(-60, -20, nlon)),
        dims = ('latitude', 'longitude'))

ax = plt.axes(projection=ccrs.PlateCarree())

atlantic.plot(ax=ax)

ax.set_ylim(0, 90)
ax.set_xlim(-180, 30)
ax.coastlines()

plt.savefig('atlantic_noise.png')

Here is the resulting image:

_images/atlantic_noise.png

Details

There are two ways to use the xray plotting functionality:

  1. Use the plot convenience methods of xray.DataArray

  2. Directly from the xray plotting submodule:

    import xray.plotting as xplt
    

The convenience method xray.DataArray.plot() dispatches to an appropriate plotting function based on the dimensions of the DataArray and whether the coordinates are sorted and uniformly spaced. This table describes what gets plotted:

Dimensions Coordinates Plotting function
1   xray.DataArray.plot_line()
2 Uniform xray.DataArray.plot_imshow()
2 Irregular xray.DataArray.plot_contourf()
Anything else   xray.DataArray.plot_hist()