HoloViews is a Python library that makes analyzing and visualizing scientific or engineering data much simpler, more intuitive, and more easily reproducible. Instead of specifying every step for each plot, HoloViews lets you store your data in an annotated format that is instantly visualizable, with immediate access to both the numeric data and its visualization. Examples of how HoloViews is used in Python scripts as well as in live Jupyter Notebooks may be accessed directly from the holoviews gallery webpage. Here is a quick example of HoloViews in action:

import numpy as np
import holoviews as hv
hv.notebook_extension('matplotlib')
fractal = hv.Image(np.load('mandelbrot.npy'))

((fractal * hv.HLine(y=0)).hist() + fractal.sample(y=0))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[1], line 6
      3 hv.notebook_extension('matplotlib')
      4 fractal = hv.Image(np.load('mandelbrot.npy'))
----> 6 ((fractal * hv.HLine(y=0)).hist() + fractal.sample(y=0))

File ~/work/holoviews/holoviews/holoviews/core/overlay.py:100, in CompositeOverlay.hist(self, dimension, num_bins, bin_range, adjoin, index, show_legend, **kwargs)
     98     dimension = [dim.name for dim in self.values()[main_layer_int_index].kdims]
     99 # Compute histogram for each dimension and each element in OverLay
--> 100 hists_per_dim = {
    101     dim: dict([  # All histograms for a given dimension
    102         (
    103             elem_key, elem.hist(
    104                 adjoin=False, dimension=dim, bin_range=bin_range,
    105                 num_bins=num_bins, **kwargs
    106             )
    107         )
    108         for i, (elem_key, elem) in enumerate(self.items())
    109         if (index is None) or (getattr(elem, "label", None) == index) or (index == i)
    110     ])
    111     for dim in dimension
    112 }
    113 # Create new Overlays of histograms
    114 hists_overlay_per_dim = {
    115     dim: self.clone(hists).opts(show_legend=show_legend)
    116     for dim, hists in hists_per_dim.items()
    117 }

File ~/work/holoviews/holoviews/holoviews/core/overlay.py:101, in <dictcomp>(.0)
     98     dimension = [dim.name for dim in self.values()[main_layer_int_index].kdims]
     99 # Compute histogram for each dimension and each element in OverLay
    100 hists_per_dim = {
--> 101     dim: dict([  # All histograms for a given dimension
    102         (
    103             elem_key, elem.hist(
    104                 adjoin=False, dimension=dim, bin_range=bin_range,
    105                 num_bins=num_bins, **kwargs
    106             )
    107         )
    108         for i, (elem_key, elem) in enumerate(self.items())
    109         if (index is None) or (getattr(elem, "label", None) == index) or (index == i)
    110     ])
    111     for dim in dimension
    112 }
    113 # Create new Overlays of histograms
    114 hists_overlay_per_dim = {
    115     dim: self.clone(hists).opts(show_legend=show_legend)
    116     for dim, hists in hists_per_dim.items()
    117 }

File ~/work/holoviews/holoviews/holoviews/core/overlay.py:103, in <listcomp>(.0)
     98     dimension = [dim.name for dim in self.values()[main_layer_int_index].kdims]
     99 # Compute histogram for each dimension and each element in OverLay
    100 hists_per_dim = {
    101     dim: dict([  # All histograms for a given dimension
    102         (
--> 103             elem_key, elem.hist(
    104                 adjoin=False, dimension=dim, bin_range=bin_range,
    105                 num_bins=num_bins, **kwargs
    106             )
    107         )
    108         for i, (elem_key, elem) in enumerate(self.items())
    109         if (index is None) or (getattr(elem, "label", None) == index) or (index == i)
    110     ])
    111     for dim in dimension
    112 }
    113 # Create new Overlays of histograms
    114 hists_overlay_per_dim = {
    115     dim: self.clone(hists).opts(show_legend=show_legend)
    116     for dim, hists in hists_per_dim.items()
    117 }

File ~/work/holoviews/holoviews/holoviews/core/element.py:56, in Element.hist(self, dimension, num_bins, bin_range, adjoin, **kwargs)
     54 hists = []
     55 for d in dimension[::-1]:
---> 56     hist = histogram(self, num_bins=num_bins, bin_range=bin_range,
     57                      dimension=d, **kwargs)
     58     hists.append(hist)
     59 if adjoin:

File /usr/share/miniconda3/envs/test-environment/lib/python3.10/site-packages/param/parameterized.py:4428, in ParameterizedFunction.__new__(class_, *args, **params)
   4426 inst = class_.instance()
   4427 inst.param._set_name(class_.__name__)
-> 4428 return inst.__call__(*args,**params)

File ~/work/holoviews/holoviews/holoviews/core/operation.py:220, in Operation.__call__(self, element, **kwargs)
    218 kwargs['link_dataset'] = self._propagate_dataset
    219 kwargs['link_inputs'] = self.p.link_inputs
--> 220 return element.apply(self, **kwargs)

File ~/work/holoviews/holoviews/holoviews/core/accessors.py:36, in AccessorPipelineMeta.pipelined.<locals>.pipelined_call(*args, **kwargs)
     32 inst = args[0]
     34 if not hasattr(inst._obj, '_pipeline'):
     35     # Wrapped object doesn't support the pipeline property
---> 36     return __call__(*args, **kwargs)
     38 inst_pipeline = copy.copy(inst._obj. _pipeline)
     39 in_method = inst._obj._in_method

File ~/work/holoviews/holoviews/holoviews/core/accessors.py:203, in Apply.__call__(self, apply_function, streams, link_inputs, link_dataset, dynamic, per_element, **kwargs)
    201 if hasattr(apply_function, 'dynamic'):
    202     inner_kwargs['dynamic'] = False
--> 203 new_obj = apply_function(self._obj, **inner_kwargs)
    204 if (link_dataset and isinstance(self._obj, Dataset) and
    205     isinstance(new_obj, Dataset) and new_obj._dataset is None):
    206     new_obj._dataset = self._obj.dataset

File ~/work/holoviews/holoviews/holoviews/core/operation.py:214, in Operation.__call__(self, element, **kwargs)
    210         return element.clone([(k, self._apply(el, key=k))
    211                               for k, el in element.items()])
    212     elif ((self._per_element and isinstance(element, Element)) or
    213           (not self._per_element and isinstance(element, ViewableElement))):
--> 214         return self._apply(element)
    215 elif 'streams' not in kwargs:
    216     kwargs['streams'] = self.p.streams

File ~/work/holoviews/holoviews/holoviews/core/operation.py:141, in Operation._apply(self, element, key)
    139     if not in_method:
    140         element._in_method = True
--> 141 ret = self._process(element, key)
    142 if hasattr(element, '_in_method') and not in_method:
    143     element._in_method = in_method

File ~/work/holoviews/holoviews/holoviews/operation/element.py:886, in histogram._process(self, element, key)
    884         hist /= hist.max()
    885 else:
--> 886     hist, edges = np.histogram(data, normed=normed, weights=weights, bins=edges)
    887     if self.p.weight_dimension and self.p.mean_weighted:
    888         hist_mean, _ = np.histogram(data, density=False, bins=self.p.num_bins)

TypeError: histogram() got an unexpected keyword argument 'normed'

Fundamentally, a HoloViews object is just a thin wrapper around your data, with the data always being accessible in its native numerical format, but with the data displaying itself automatically whether alone or alongside or overlaid with other HoloViews objects as shown above. The actual rendering is done using a separate library like matplotlib or bokeh, but all of the HoloViews objects can be used without any plotting library available, so that you can easily create, save, load, and manipulate HoloViews objects from within your own programs for later analysis. HoloViews objects support arbitrarily high dimensions, using continuous, discrete, or categorical indexes and values, with flat or hierarchical organizations, and sparse or dense data formats. The objects can then be flexibly combined, selected, sliced, sorted, sampled, or animated, all by specifying what data you want to see rather than by writing plotting code. The goal is to put the plotting code into the background, as an implementation detail to be written once and reused often, letting you focus clearly on your data itself in daily work.

More detailed example#

Even extremely complex relationships between data elements can be expressed succinctly in HoloViews, allowing you to explore them with ease:

%%opts Points [scaling_factor=50] Contours (color='w')
dots = np.linspace(-0.45, 0.45, 19)

layouts = {y: (fractal * hv.Points(fractal.sample([(i,y) for i in dots])) +
               fractal.sample(y=y) +
               hv.operation.threshold(fractal, level=np.percentile(fractal.sample(y=y)['z'], 90)) +
               hv.operation.contours(fractal, levels=[np.percentile(fractal.sample(y=y)['z'], 60)]))
            for y in np.linspace(-0.3, 0.3, 21)}

hv.HoloMap(layouts, kdims=['Y']).collate().cols(2)

Here we have built a dictionary indexed by a numerical value y, containing a set of Layout objects that are each composed of four HoloViews objects. We then collated the Layout objects into a HoloViews data structure that can display arbitrarily high dimensional data. The result is that in A we can see the same fractal data as above, but with a horizontal cross section indicated using a set of dots with sizes proportional to the underlying data values, illustrating how even a simple annotation can be used to reflect other data of interest. B shows a cross-section of the fractal, C shows a thresholded version of it, and D shows the same data with a contour outline overlaid. The threshold and contour levels used are not fixed, but are calculated as the 90th or 60th percentile of the data values along the selected cross section, using standard Python/NumPy functions. All of this data is packaged into a single HoloViews data structure for a range of cross sections, allowing the data for a particular cross section to be revealed by moving the Y-value slider at right. Even with these complicated interrelationships between data elements, the code still only needs to focus on the data that you want to see, not on the details of the plotting or interactive controls, which are handled by HoloViews and the underlying plotting libraries.

Note that just as the 2D array became a 1D curve automatically by sampling to get the cross section, this entire figure would become a single static frame with no slider bar if you chose a specific Y value by re-running with .select(Y=0.3) before .cols(2). There is nothing in the code above that adds the slider bar explicitly – it appears automatically, just because there is an additional dimension of data that has not been laid out spatially. Additional sliders would appear if there were other dimensions being varied as well, e.g. for parameter-space explorations.

This functionality is designed to complement the IPython/Jupyter Notebook interface, though it can be used just as well separately. This web page and all the HoloViews Tutorials are runnable notebooks, which allow you to interleave text, Python code, and graphical results easily. With HoloViews, you can put a minimum of code in the notebook (typically one or two lines per subfigure), specifying what you would like to see rather than the details of how it should be plotted. HoloViews makes the IPython Notebook a practical solution for both exploratory research (since viewing nearly any chunk of data just takes a line or two of code) and for long-term reproducibility of the work (because both the code and the visualizations are preserved in the notebook file forever, and the data and publishable figures can both easily be exported to an archive on disk). See the Tutorials for detailed examples, and then start enjoying working with your data!