HoloViews elements like the Scatter
points illustrated in the Introduction contain two types of information:
What elements do not contain is:
HoloViews is designed to let you work naturally with the meaningful features of your data, while making it simple to adjust the display details separately using the Options system. Among many other benefits, this separation of content from presentation simplifies your data analysis workflow, and makes it independent of any particular plotting backend.
To illustrate how the options system works, we will use a dataset containing "spike" (neural firing) events extracted from the recorded electrical activity of a neuron. We will be visualizing the first trial of this publicly accessible neural recording. First, we import pandas and holoviews and load our data:
In [ ]:
import pandas as pd
import holoviews as hv
from holoviews import opts
spike_train = pd.read_csv('../assets/spike_train.csv.gz')
spike_train.head(n=3)
This dataset contains the spike times (in milliseconds) for each detected spike event in this five-second recording, along with a spiking frequency in Hertz (spikes per second), averaged over a rolling 200 millisecond window. We will now declare Curve
and Spike
elements using this data and combine them into a Layout
:
In [ ]:
curve = hv.Curve( spike_train, 'milliseconds', 'Hertz', label='Firing Rate')
spikes = hv.Spikes(spike_train, 'milliseconds', [], label='Spike Train')
layout = curve + spikes
layout
Notice that the representation for this object is purely textual; so far we have not yet loaded any plotting system for HoloViews, and so all you can see is a description of the data stored in the elements.
To be able to see a visual representation and adjust its appearance, we'll need to load a plotting system, and here let's load two so they can be compared:
In [ ]:
hv.extension('bokeh', 'matplotlib')
Even though we can happily create, analyze, and manipulate HoloViews objects without using any plotting backend, this line is normally executed just after importing HoloViews so that objects can have a rich graphical representation rather than the very-limited textual representation shown above. Putting 'bokeh' first in this list makes visualizations default to using Bokeh, but including matplotlib as well means that backend can be selected for any particular plot as shown below.
In [ ]:
layout
As you can see, we can immediately appreciate more about this dataset than we could from the textual representation. The curve plot, in particular, conveys clearly that the firing rate varies quite a bit over this 5-second interval. However, the spikes plot is much more difficult to interpret, because the plot is nearly solid black.
One thing we can do is click on one of the Bokeh plot's zoom tools to enable it, then zoom in until individual spikes are clearly visible. Even then, though, it's difficult to relate the spiking and firing-rate representations to each other. Maybe we can do better by adjusting the display options away from their default settings?
In [ ]:
layout.opts(
opts.Curve( height=200, width=900, xaxis=None, line_width=1.50, color='red', tools=['hover']),
opts.Spikes(height=150, width=900, yaxis=None, line_width=0.25, color='grey')).cols(1)
Much better! It's the same underlying data, but now we can clearly see both the individual spike events and how they affect the moving average. You can also see how the moving average trails the actual spiking, due to how the window function was defined.
A detailed breakdown of this exact customization is given in the User Guide, but we can use this example to understand a number of important concepts:
.opts()
method.opts.Curve
and opts.Spikes
here, so that we can set options separately for each component of a composite object (as for height here)opts.Curve
or opts.Spikes
keyword list to see what's available!).cols
method to specify the number of columns in the layout.The corresponding User Guide entry explains the keywords used in detail, but a quick summary is that when you tab-complete using the opts.*
builders, you are completing across two fundamental types of options: plot options (processed by HoloViews) and style options (processed by the underlying backend, either Bokeh or Matplotlib here). If you only use a single backend, you don't need to worry much about this distinction because HoloViews will ensure that the option setting is given to the appropriate backend when needed. Here, for instance, the color
and line_width
keywords are not used by HoloViews; they will just be passed on to the corresponding Bokeh glyphs. In this way you can control both HoloViews and the current backend, to customize almost any aspect of your plot.
In the above cell, the result of calling opts.Curve()
is passed into the .opts
method returning an Options
object. opts.Curve()
and the other option builders aren't always needed, but the are very helpful for validating options and offer tab completion to help you discover possible values:
In [ ]:
dotted_options = opts.Curve(color='purple', width=600, height=250, line_dash='dotted')
dotted_options
Try tab-completing the options for Curve
above or specifying an invalid keyword. Now the dotted_options
object can be passed to the .opts
method call to customize a Curve
:
In [ ]:
dotted = hv.Curve(spike_train, 'milliseconds', 'Hertz')
dotted.opts(dotted_options)
When working directly with a single element, you can omit the options builder entirely because it's clear what type the options apply to:
In [ ]:
dashed = hv.Curve( spike_train, 'milliseconds', 'Hertz')
dashed.opts(color='orange', width=600, height=250, line_dash='dashed')
The code is then a bit shorter and more readable with the same result, but it no longer tab completes, and so omitting the builder is probably only useful for a final, published set of code, not during exploration. When using the .opts
method on compositions of elements (i.e., layouts or overlays) you still need to use the options builders to indicate which type of object the options should be applied to.
If you want to find out which options have been changed on a given object, you can use .opts.info()
:
In [ ]:
dashed.opts.info()
For more information on how to work with options, see the the User Guide.
Now let's customize our layout
with options appropriate for the Matplotlib renderer, by supplying the backend='matplotlib'
argument to the .opts
method:
In [ ]:
layout = layout.opts(
opts.Curve( aspect=6, xaxis=None, color='blue', linewidth=2, show_grid=False, linestyle='dashed'),
opts.Spikes(aspect=6, yaxis='bare', color='red', linewidth=0.25),
opts.Layout(sublabel_format='', vspace=0.1, fig_size=200), backend='matplotlib')
layout
The plot is still rendered with bokeh as we haven't switched to the matplotlib backend just yet (although matplotlib support was was loaded by hv.extension
at the start of this notebook). The above code sets the options appropriate to matplotlib without immediately making use of them and naturally, a few changes needed to be made:
aspect
instead of setting width
and height
. In some cases, but not all, HoloViews can smooth over such differences in the plotting options to make it simpler to switch backends.line_width
option is called linewidth
in matplotlib. These "style" options are directly inherited from the API of the plotting library backend, not defined by HoloViews.Layout
s also have some options to control the arrangement of its components. Here we adjust the gap betwen the plots using vspace
.Now we can use the hv.output
utility to to show the same elements in layout
as rendered with these different customizations, in a different output format (SVG), with a completely different plotting library:
In [ ]:
hv.output(layout, backend='matplotlib', fig='svg')
This approach allows you to associate options for multiple different backends with the same object. See the User Guide for more details, including information of how to use hv.output
to affect global output settings.
In [ ]:
hv.output(backend='bokeh')
spikes.select(milliseconds=(2000,4000))
Note how HoloViews remembered the Bokeh-specific styles we previously applied to the spikes
object! This feature allows us to style objects once and then keep that styling as we work, without having to repeat the styles every time we work with that object. Note that even though this styling is associated with the element, it is not actually stored on it, which is mostly an implementation detail but does define a strict separation between what HoloViews considers parts of your data (the Element) and what is part of the "look" or the "view" of that data (the options associated with the object, but stored separately).
If we want to reset back to the original styling, we can call .opts.clear()
:
In [ ]:
spikes.select(milliseconds=(2000,4000)).opts.clear()
You can learn more about the output utility and how the options system handles persistent options in the User Guide.
If you look closely, the example above might worry you. First we defined our Spikes
element with kdims=['milliseconds']
, which we then used as a keyword argument in select
above. This is also the string used as the axis label. Does this mean we are limited to Python identifiers for axis labels, if we want to use the corresponding dimension with select
?
Luckily, there is no limitation involved. Dimensions specified as strings are often convenient, but behind the scenes, HoloViews always uses a much richer Dimensions
object that you can pass to the kdims
and vdims
explicitly (see the User Guide for more information). One of the things each Dimension
object supports is a long, descriptive label
, which complements the short programmer-friendly name.
We can set the dimension labels on our existing spikes
object as follows:
In [ ]:
spikes = spikes.redim.label(milliseconds='Time in milliseconds (10⁻³ seconds)')
curve = curve.redim.label(Hertz='Frequency (Hz)')
(curve + spikes).select(milliseconds=(2000,4000)).cols(1)
As you can see, we can set long descriptive labels on our dimensions (including unicode) while still making use of the short dimension name in methods like select
.
Now that you know how to set up and customize basic visualizations, the next Getting-Started sections show how to work with various common types of data in HoloViews.