HoloViews encapsulates your continuous or discrete data into storable, composable, sliceable objects, leveraging matplotlib for visualization and IPython Notebook for maximum productivity.
Main features:
The IPython notebook environment and matplotlib allow you to do interactive exploration and analysis of your data and measurements, using the rich ecosystem of tools available in Python. However, your notebooks can very quickly fill up with verbose, specialized plotting code whenever you want to visualize your data, which is often. To make all this practical, you can use HoloViews to greatly improve your productivity, requiring orders of magnitude fewer lines of code and letting you focus on your data itself, not on writing code to visualize and display it.
Here we will show some of the main features of HoloViews, and why it is useful for scientists and engineers.
In [ ]:
import holoviews as hv
from holoviews.operation import contours, threshold, gradient
import numpy as np
%reload_ext holoviews.ipython
For simplicity in this showcase, we use an unqualified "import *
", but in general we recommend using qualified imports and namespaces (as in "from holoviews import Image
" or "import holoviews as hv
" followed by "hv.Image
").
First, let us define a mathematical function to explore, using the Numpy array library:
In [ ]:
def sine(x, phase=0, freq=100):
return np.sin((freq * x + phase))
We will examine the effect of varying phase and frequency:
In [ ]:
phases = np.linspace(0,2*np.pi,11) # Explored phases
freqs = np.linspace(50,150,5) # Explored frequencies
Over a specific spatial area, sampled on a grid:
In [ ]:
dist = np.linspace(-0.5,0.5,202) # Linear spatial sampling
x,y = np.meshgrid(dist, dist)
grid = (x**2+y**2) # 2D spatial sampling
With HoloViews, we can immediately view our simple function by creating Image
and Curve
objects to visualize the 2D arrays and 1D cross-sections generated by the sine
function, respectively:
In [ ]:
freq1 = hv.Image(sine(grid, freq=50)) + hv.Curve(zip(dist, sine(dist**2, freq=50)))
freq2 = hv.Image(sine(grid, freq=200)) + hv.Curve(zip(dist, sine(dist**2, freq=200)))
(freq1 + freq2).cols(2)
As you can see, Image
takes a 2D numpy array as input and Curve
accepts a list of (x,y)
points. With the +
operator we can lay these elements out together and with .cols(2)
we can arrange them into two columns.
HoloViews objects like Image
and Curve
are a great way to work with your data, because they display so easily and flexibly, yet preserve the raw data (the Numpy array in this case) in the .data
attribute. Calling matplotlib directly to generate such a figure would take much, much more code, e.g., to label each of the axes, to create a figure with subfigures, etc. Moreover, such code would be focused on the plotting, whereas with HoloViews you can focus directly on what matters: your data, letting it plot itself. Because the HoloViews code is so succinct, you don't need to hide it away in some difficult-to-maintain external script; you can simply type what you want to see right in the notebook, changing it at will and being able to come back to your analysis just as you left it.
Only two element types are shown above, but HoloViews supports many other types of element that behave in the same way: scatter points, histograms, tables, vectorfields, RGB images, 3D plots, annotations, and many more as shown in the Elements overview. All of these can be combined easily to create even quite complex plots with a minimum of code to write or maintain.
We can interactively explore this simple function if we declare the dimensions of the parameter space to be explored (dimensions
) as well as the specific samples to take in this parameter space (keys
):
In [ ]:
dimensions = ['Phase', 'Frequency']
keys = [(p,f) for p in phases for f in freqs]
Now we create a high-dimensional HoloMap
to explore: The tuple keys are the points in the parameter space, and the values are the corresponding Image
objects:
In [ ]:
items = [(k, hv.Image(sine(grid, *k), vdims=['Amplitude'])) for k in keys]
circular_wave = hv.HoloMap(items, kdims=dimensions)
circular_wave
You can still compose as many visualization elements as you wish together. Here is a demonstration of how to generate the horizontal cross-section of the circular wave using Curve
elements. This is then positioned next to our circular wave:
In [ ]:
items = [(k, hv.Curve(zip(dist, sine(dist**2, *k)))) for k in keys]
sections = hv.HoloMap(items, kdims=dimensions)
circular_wave + sections
You can then easily export your HoloMap
objects to an interactive notebook, video formats, or GIF animations to use on a web page.
HoloViews is focused on making your data readily available, both for visualization and for numerical analysis. Because HoloViews objects preserve your raw data, you can use any Python analysis tools you wish, such as those in the SciPy library. We also support some very general-purpose data analysis and manipulation operations directly in HoloViews, exploiting the generality of the HoloViews data structures to simplify common tasks.
Here, we pick a point on the circular wave at (0,0.25)
and plot the amplitude value as a function of phase. The circular wave is shown annotated with a point at the chosen position. Note how we can still interactively explore the remaining dimensions, namely Frequency
.
In [ ]:
sample_pos = (0,0.25)
annotated = circular_wave * hv.Points([sample_pos])
sample = circular_wave.sample(samples=[sample_pos]).to.curve('Phase', 'Amplitude', ['Frequency'])
annotated + sample
Note that the spatial frequency of our curve plot is not affected by the frequency of our wave. That is because the phase always spans exactly one cycle at any of the chosen frequencies.
Here is the circular wave annotated with contours at the 0.5 level, followed by a thresholded version of the same wave, and then the gradient of this pattern, along with a histogram of the gradient values:
In [ ]:
%output holomap='gif'
In [ ]:
%%opts Image (cmap='gray') Contours (color='r')
m = hv.HoloMap([(p, hv.Image(sine(grid, phase=p))) for p in phases], kdims=['Phase'])
contours(m, levels=[0.5]) + threshold(m, level=0.5) + gradient(m).hist(bin_range=(0,0.7))
(The %%
syntax for specifying options will be briefly explained in the next section below.)
You can see that this data is shown as an animation, because the data covers multiple phases; each frame of the animation shows the result from one phase. To get a static plot, just select one phase out of this space instead of a whole list of phases as done here. Throughout HoloViews, if there are more dimensions of data than will fit into the plot as it has been laid out, they will simply be displayed as an animation or using slider bars. Supplied operations include gradient, fft_power, convolve, histogram and other common general-purpose analysis tools.
Remember the initial test of the sine
function? Those two composite objects are still in the namespace and even though the objects have already been created, we can still style the display in any way we like. This styling is done separately from specifying your data, so that your data's specification is always clearly visible, independently of how it is being viewed:
In [ ]:
%%opts Image (cmap='RdYlGn') Curve (color='g')
(freq1 + freq2).cols(2)
For convenience, the IPython-magic syntax for setting options like %%opts
is used throughout these tutorials. However, you can use pure Python code to control the options in a similar way, e.g. from within an external non-IPython program where you want to render a HoloViews plot straight to a file, though it requires a few more curly brackets and quote marks:
In [ ]:
red_wave = circular_wave(options={'Image':{'style':{'cmap':'RdGy'}}})
red_wave
HoloViews is designed to make it easy to understand your data. For instance, consider two circular waves with very different amplitudes:
In [ ]:
comparison = hv.Image(sine(grid)) + hv.Image(sine(grid, phase=np.pi)*0.02)
HoloViews ensures that these differences are visible by default, by normalizing across any elements of the same type that are displayed together, and even across the frames of an animation:
In [ ]:
%%opts Image (cmap='gray')
comparison = hv.Image(sine(grid)) + hv.Image(sine(grid, phase=np.pi)*0.02)
comparison
This default visualization makes it clear that the two patterns differ greatly in amplitude. However, it is difficult to see the structure of the low-amplitude wave in B. If you wish to focus on the spatial structure rather than the amplitude, you can instruct HoloViews to normalize data in different axes separately:
In [ ]:
%%opts Image {+axiswise} (cmap='gray')
comparison
Similarly, you could supply +framewise
to tell it to normalize data per frame of an animation, not across all frames as it does by default. As with any other customization, you can always specify which specific element you want the customization to apply to, even in a complex multiple-subfigure layout.
HoloViews is designed as a highly modular library with only minimal dependencies, so you only need to focus on the components that implement the functionality that you actually need to use. With that in mind, there are several different ways that HoloViews can fit into a scientific and engineering workflow. For each approach, we point you to the relevant tutorials that will help you get started.
If you just have some simple data in Python, such as a few dozen 1D and 2D Numpy arrays from any source, HoloViews makes it very simple to view those as images, curves, 3D surfaces, etc., and combine them into composite figures any way you like.
To use HoloViews the easy way, just work through the Introduction tutorial, then pick suitable Elements for your data types, then make figures using +
to lay out figures side by side, and *
to overlay curves, etc. on top of each other. You can read about changing options if you want, or just follow the examples in the other tutorials. You should be able to ignore the more powerful features below, while still being able to build figures much, much more simply and conveniently than you could using matplotlib directly.
When you are ready, you can automatically export your figures and completed notebooks to files on disk, ready for use in publications and reports.
The easy way is fine, but you still end up with the same data you started with -- you can't easily slice, decompose, recompose, and repackage your data, because each bit of it has been wrapped into separate HoloViews Element objects.
The better way is to move all or large ranges of your data into HoloViews Container objects, organizing it in a way that is meaningful to you. Once it is all organized, you can then slice, select, sample, and animate whatever combination of data you want to analyze at any given time, using convenient and succinct HoloViews operations, always yielding something that can be visualized directly and with no further coding. The +
and *
operations are an easy way to generate some of these containers, but there are other containers that work in very different ways that are important for other visualizations and analyses, such as parameter space exploration.
To set things up in the better way will take some time, because you will have to learn about which HoloViews containers are appropriate for the types of data you have and how you want to manipulate it. You should start with the Introduction tutorial above for the basics, then work through the Exploring Data Tutorial to understand what sort of operations are possible on the data. You can then see examples of each of the different Container objects, along with a reference for the complete, most fully general container structure possible in HoloViews.
Once you see how to do what you want, the better way isn't hard to use, as you can see in some of the examples above, but it will take some time at first to understand how it all works!
The best way to use HoloViews is if someone provides you the data already wrapped up as HoloViews objects, organized into meaningful categories and dimensions according to how you work in your own field. This way, you can sit down to your data ready to do your analyses and visualizations without even thinking about code, just specifying what data you want to see, in what combination, and letting HoloViews do the rest.
Exporting data in HoloViews format is very simple, and adding HoloViews export support to a program requires very little code. In fact, it's mostly the same code as in the easy way, just done for you by a program. For instance, all of the data generated from the ImaGen image-generation library can be viewed directly as a HoloViews Image
or RGB
object. With just a few lines of code added to the base classes in the project, ImaGen objects can be viewed immediately within IPython notebook without needing any matplotlib programming. ImaGen also exploits some of the general-purpose data structures provided by HoloViews, for its own implementation, but that is not required just to be able to export data back into HoloViews.
A much richer interface to HoloViews is provided by the FeatureMapper library, which is mainly used as the data-analysis package for the Topographica neural simulator. You can see the power of HoloViews in the Topographica Tutorials notebooks; with very little code users can do even quite complex analyses on continuous data without having to deal with the underlying discrete array data structures.
Of course, using the best way requires someone to add HoloViews support to your simulator, data acquisition, or data analysis tools, which leads to the next way.
Nearly all of the HoloViews tutorials focus on interactive use within the IPython Notebook interface, because the rich display system of the notebook allows you to view HoloViews objects as soon as you have defined them, with no further coding or files required. However, nothing in HoloViews requires IPython, and so you can also use it within your pure Python programs.
First, you can easily import holoviews.core
into your own program, which only has Numpy and Param as dependencies, both of which have no required dependencies. This will allow you to create and export HoloViews objects, either to save to disk for later analysis, or when called in a Python session.
You can also use the full features of HoloViews in a purely non-interactive mode, without IPython notebook or any windowing systems. I.e., you can create HoloViews objects in Python, customize them with whatever styling options you like, and then render them directly to a .png
, .gif
, or .svg
file on disk, perhaps to serve them directly to the web as part of an automated analysis workflow.
Finally, HoloViews itself is designed to be extensible. If you want, you can directly manipulate the matplotlib objects constructed by HoloViews, e.g. to add functionality not currently offered by HoloViews. You can also subclass or copy any existing element type to change its behavior or add features you need. Note that HoloViews is explicitly designed as a general-purpose library, focusing on visualizations and analyses common to very many different areas of research, but researchers in different fields may want to build toolboxes of additional specialized plot types suitable for their domain. Once defined, these new visualizations will all seamlessly combine (adjoin, overlay, etc.) with existing HoloViews objects.
Whichever way you choose, HoloViews is designed to support your workflow, from initial exploration to final publication. HoloViews is agnostic to whatever task or data you happen to be analyzing, allowing you to discover whatever is important for your engineering applications or scientific problems of any sort. It lets you focus on your data, not on writing plotting code!
To learn more, check out the many other tutorials and notebooks, including:
Introduction: Step-by-step explanation of the basic concepts in HoloViews
Exploring Data: How to use HoloViews to explore heterogenous collections of data, by combining and selecting your data of interest
Options: How to find out all of the options available for a given component, and set them from within Python or IPython
Elements: Overview of all the basic Elements
Containers: Overview of all the containers for Elements