In the first two sections of this tutorial we discovered how to declare static elements and compose them one by one into composite objects, allowing us to quickly visualize data as we explore it. However, many datasets contain numerous additional dimensions of data, such as the same measurement repeated across a large number of different settings or parameter values. To address these common situations, HoloViews provides ontainers that allow you to explore extra dimensions of your data using widgets, as animations, or by "faceting" it (splitting it into "small multiples") in various ways.
To begin with we will discover how we can quickly explore the parameters of a function by having it return an element and then evaluating the function over the parameter space.
In [ ]:
import numpy as np
import holoviews as hv
hv.extension('bokeh')
%opts Curve Area [width=600]
If we write a function that accepts one or more parameters and constructs an element, we can build plots that do things like:
As a basic example, let's declare a function that generates a frequency-modulated signal and returns a Curve
element:
In [ ]:
def fm_modulation(f_carrier=110, f_mod=110, mod_index=1, length=0.1, sampleRate=3000):
x = np.arange(0, length, 1.0/sampleRate)
y = np.sin(2*np.pi*f_carrier*x + mod_index*np.sin(2*np.pi*f_mod*x))
return hv.Curve((x, y), kdims=['Time'], vdims=['Amplitude'])
The function defines a number of parameters that will change the signal, but using the default parameters the function outputs a Curve
like this:
In [ ]:
fm_modulation()
The HoloMap
is the first container type we will start working with, because it is often the starting point of a parameter exploration. HoloMaps allow exploring a parameter space sampled at specific, discrete values, and can easily be created using a dictionary comprehension. When declaring a HoloMap
, just ensure the length and ordering of the key tuple matches the key dimensions:
In [ ]:
carrier_frequencies = [10, 20, 110, 220, 330]
modulation_frequencies = [110, 220, 330]
hmap = hv.HoloMap({(fc, fm): fm_modulation(fc, fm) for fc in carrier_frequencies
for fm in modulation_frequencies}, kdims=['fc', 'fm'])
hmap
Note how the keys in our HoloMap
map on to two automatically generated sliders. HoloViews supports two types of widgets by default: numeric sliders, or a dropdown selection menu for all non-numeric types. These sliders appear because a HoloMap can display only a single Element at one time, and the user must thus select which of the available elements to show at any one time.
In [ ]:
# Exercise: Try changing the function below to return an ``Area`` or ``Scatter`` element,
# in the same way `fm_modulation` returned a ``Curve`` element.
def fm_modulation2(f_carrier=220, f_mod=110, mod_index=1, length=0.1, sampleRate=3000):
x = np.arange(0,length, 1.0/sampleRate)
y = np.sin(2*np.pi*f_carrier*x + mod_index*np.sin(2*np.pi*f_mod*x))
In [ ]:
# Then declare a HoloMap like above and assign it to a ``exercise_hmap`` variable and display that
Apart from their simplicity and generality, one of the key features of HoloMaps is that they can be exported to a static HTML file, GIF, or video, because every combination of the sliders (parameter values) has been pre-computed already. This very convenient feature of pre-computation becomes a liability for very large or densely sampled parameter spaces, however, leading to the DynamicMap type discussed next.
A DynamicMap
is very similar to a HoloMap
except that it evaluates the function lazily. This property makes DynamicMap require a live, running Python server, not just an HTML-serving web site or email, and it may be slow if each frame is slower to compute than it is to display. However, because of these properties, DynamicMap allows exploring arbitrarily large parameter spaces, dynamically generating each element as needed to satisfy a request from the user. The key dimensions kdims
must match the arguments of the function:
In [ ]:
%%opts Curve (color='red')
dmap = hv.DynamicMap(fm_modulation, kdims=['f_carrier', 'f_mod', 'mod_index'])
dmap = dmap.redim.range(f_carrier=((10, 110)), f_mod=(10, 110), mod_index=(0.1, 2))
dmap
In [ ]:
# Exercise: Declare a DynamicMap using the function from the previous exercise and name it ``exercise_dmap``
In [ ]:
# Exercise (Optional): Use the ``.redim.step`` method and a floating point range to modify the slider step
HoloMaps and DynamicMaps let you explore a multidimensional parameter space by looking at one point in that space at a time, which is often but not always sufficient. If you want to see more data at once, you can facet the HoloMap to put some data points side by side or overlaid to facilitate comparison. One easy way to do that is to cast your HoloMap into a GridSpace
, NdLayout
, or NdOverlay
container:
In [ ]:
%%opts Curve [width=150]
hv.GridSpace(hmap).opts()
In [ ]:
# Exercise: Try casting your ``exercise_hmap`` HoloMap from the first exercise to an ``NdLayout`` or
# ``NdOverlay``, guessing from the name what the resulting organization will be before testing it.
Using the .overlay
, .grid
and .layout
methods we can facet multi-dimensional data by a specific dimension:
In [ ]:
hmap.overlay('fm')
Using these methods with a DynamicMap requires special attention, because a dynamic map can return an infinite number of different values along its dimensions, unlike a HoloMap. Obviously, HoloViews could not comply with such a request, but these methods are perfectly legal with DynamicMap
if you also define which specific dimension values
you need, using the .redim.values
method:
In [ ]:
%%opts Curve [width=150]
dmap.redim.values(f_mod=[10, 20, 30], f_carrier=[10, 20, 30]).overlay('f_mod').grid('f_carrier').opts()
In [ ]:
# Exercise: Facet the ``exercise_dmap`` DynamicMap using ``.overlay`` and ``.grid``
# Hint: Use the .redim.values method to set discrete values for ``f_mod`` and ``f_carrier`` dimensions
HoloMaps and other containers also allow you to easily index or select by key, allowing you to:
obj[10, 110]
obj[10, 200:]
obj[[10, 110], 110]
In [ ]:
%%opts Curve [width=300]
hmap[10, 110] + hmap[10, 200:].overlay() + hmap[[10, 110], 110].overlay()
You can do the same using the select method:
In [ ]:
(hmap.select(fc=10, fm=110) +
hmap.select(fc=10, fm=(200, None)).overlay() +
hmap.select(fc=[10, 110], fm=110).overlay())
In [ ]:
# Exercise: Try selecting two carrier frequencies and two modulation frequencies on the ``exercise_hmap``
The following section will talk about building containers from data stored in tabular (spreadsheet-like) formats, which is a very common situation given special support.