Many datasets in science and engineering consist of n-dimensional data. Gridded datasets usually represent observations of some continuous variable across multiple dimensions---a monochrome image representing luminance values across a 2D surface, volumetric 3D data, an RGB image sequence over time, or any other multi-dimensional parameter space. This type of data is particularly common in research areas that make use of spatial imaging or modeling, such as climatology, biology, and astronomy, but can also be used to represent any arbitrary data that varies over multiple dimensions.
For gridded data, we'll use xarray, a convenient way of working with and representing labelled n-dimensional arrays, like pandas for labelled n-D arrays, along with our other usual libraries:
import numpy as np
import holoviews as hv
import xarray as xr
hv.extension('bokeh')
mri_xr = xr.open_dataset('../data/mri.nc')
mri_xr
xarray is particularly useful for geographic data, but it supports any type of n-dimensional data. The data here represents volumetric data from an MRI scan, with three coordinate dimensions 'x', 'y' and 'z'. In this simple example these coordinates are integers, but they are not required to be. Instead of volumetric data, we could imagine the data could be 2D spatial data that evolves over time, as is common in climatology and many other fields.
Unlike with Pandas, in a gridded dataset the dimensions are typically already declared unambiguously, with coordinates (i.e. key dimensions) and data variables (i.e. value dimensions) that HoloViews can determine automatically:
mri = hv.Dataset(mri_xr)
mri
Just as we saw in the previous tutorial, we can group the data by one or more dimensions. Since we are dealing with volumetric data but have only a 2D display device, we can take slices along each axis. Here we will slice along the sagittal plane corresponding to the z-dimension:
mri.to(hv.Image, groupby='z', dynamic=True)
Here we supplied dynamic=True
to get a DynamicMap
, which works like a HoloMap
but computes each frame on demand, so that it only creates each plot when it is being used.
# Exercise: Display transverse (x,z) or frontal (z,y) sections of the data by declaring the kdims in the .to method
We can use .to
to slice the cube along all three axes separately:
%%opts Image [xaxis=None yaxis=None width=225 height=225]
(mri.to(hv.Image, ['z', 'y'], dynamic=True) +
mri.to(hv.Image, ['z', 'x'], dynamic=True) +
mri.to(hv.Image, ['x', 'y'], dynamic=True)).redim.range(MR=(0, 255))
We can also easily compute aggregates across one or more dimensions. Previously we used the aggregate
method for this purpose, but when working with gridded datasets it often makes more sense to think of aggregation as a reduce
operation. We can for example reduce the z
dimension using np.mean
and display the resulting averaged 2D array as an Image
:
hv.Image(mri.reduce(z=np.mean))
# Exercise: Recreate the plot above using the aggregate method
# Hint: The aggregate and reduce methods are inverses of each other
# Try typing "hv.Image(mri.aggregate(" and press shift-Tab to see the signature of `aggregate`.
If you keep a handle on your image, you can use HoloViews to generate the aggregate array by accessing .data
:
im = hv.Image(mri.reduce(z=np.mean))
im.data
As you can see, it is straightforward to work with the additional dimensions of data available in gridded datasets, as explained in more detail in the user guide.
The previous sections focused on displaying plots that provide certain standard types of interactivity, whether widget-based (to select values along a dimension) or within each plot (for panning, zooming, etc.). A wide range of additional types of interactivity can also be defined by the user for working with specific types of data, as outlined in the following sections, beginning with Network Graphs.