{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Customizing visual appearance" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "HoloViews elements like the `Scatter` points illustrated in the [Introduction](1-Introduction.ipynb) contain two types of information:\n", "\n", "- **Your data**, in as close to its original form as possible, so that it can be analyzed and accessed as you see fit.\n", "- **Metadata specifying what your data *is***, which allows HoloViews to construct a visual representation for it.\n", "\n", "What elements do *not* contain is:\n", "\n", "- The endless details that one might want to tweak about the visual representation, such as line widths, colors, fonts, and spacing.\n", "\n", "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." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Visualizing neural spike trains\n", "\n", "To illustrate how the options system works, we will use a dataset containing [\"spike\"](https://en.wikipedia.org/wiki/Action_potential) (neural firing) events extracted from the recorded electrical activity of a [neuron](https://en.wikipedia.org/wiki/Neuron). We will be visualizing the first trial of this [publicly accessible neural recording](http://www.neuralsignal.org/data/04/nsa2004.4/433l019). First, we import pandas and holoviews and load our data:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "import holoviews as hv\n", "spike_train = pd.read_csv('../assets/spike_train.csv.gz')\n", "spike_train.head(n=3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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, averaging over a rolling 200 millisecond window). We will now declare ``Curve`` and ``Spike`` elements using this data and combine them into a ``Layout``:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "curve = hv.Curve(spike_train, 'milliseconds', 'Hertz', group='Firing Rate')\n", "spikes = hv.Spikes(spike_train.sample(300), kdims='milliseconds', vdims=[], group='Spike Train')\n", "curve + spikes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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. \n", "\n", "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:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "hv.extension('bokeh', 'matplotlib')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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](http://bokeh.pydata.org), but including [matplotlib](http://matplotlib.org) as well means that backend can be selected for any particular plot as shown below." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Default appearance\n", "\n", "With the extension loaded, let's look at the default appearance as rendered with Bokeh:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "curve + spikes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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 even though we already downsampled from 700 spikes to 300 spikes when we declared the element. \n", "\n", "One thing we can do is enable one of Bokeh's zoom tools and 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?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Customization\n", "\n", "Let's see what we can achieve when we do decide to customize the appearance:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%output size=150\n", "%%opts Curve [height=100 width=600 xaxis=None tools=['hover']]\n", "%%opts Curve (color='red' line_width=1.5)\n", "%%opts Spikes [height=100 width=600 yaxis=None] (color='grey' line_width=0.25)\n", "curve = hv.Curve( spike_train, 'milliseconds', 'Hertz')\n", "spikes = hv.Spikes(spike_train, 'milliseconds', [])\n", "(curve + spikes).cols(1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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.\n", "\n", "A detailed breakdown of this exact customization is given in the [User Guide](../user_guide/03-Customizing_Plots.ipynb), but we can use this example to understand a number of important concepts:\n", "\n", "* The option system is based around keyword settings.\n", "* You can customize the output format using the ``%%output`` and the element appearance with the ``%%opts`` *cell magics*.\n", "* These *cell magics* affect the display output of the Jupyter cell where they are located. For use outside of the Jupyter notebook, consult the [User Guide](../user_guide/03-Customizing_Plots.ipynb) for equivalent Python-compatible syntax.\n", "* The layout container has a ``cols`` method to specify the number of columns in the layout.\n", "\n", "While the ``%%output`` cell magic accepts a simple list of keywords, we see some special syntax used in the ``%%opts`` magic:\n", "\n", "* The element type is specified following by special groups of keywords.\n", "* The keywords in square brackets ``[...]`` are ***plot options*** that instruct HoloViews how to build that type of plot.\n", "* The keywords in parentheses ``(...)`` are **style options** with keywords that are passed directly to the plotting library when rendering that type of plot.\n", "\n", "The corresponding [User Guide](../user_guide/03-Customizing_Plots.ipynb) entry explains the keywords used in detail, but a quick summary is that we have elongated the ``Curve`` and ``Scatter`` elements and toggled various axes with the ***plot options***. We have also specified the color and line widths of the [Bokeh glyphs](http://bokeh.pydata.org/en/latest/docs/user_guide/plotting.html) with the ***style options***.\n", "\n", "As you can see, these tools allow significant customization of how our elements appear. HoloViews offers many other tools for setting options either locally or globally, including the ``%output`` and ``%opts`` *line magics*, the ``.opts`` and ``.options`` methods on all HoloViews objects and the ``hv.output`` and ``hv.opts`` utilities. We will briefly consider the ``.options`` based approach, which makes it possible to work outside the notebook. All these tools, how they work and details of the opts syntax can be found in the [User Guide](../user_guide/03-Customizing_Plots.ipynb)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Outside the notebook\n", "\n", "When working inside the notebook environment using the ``%%opts`` magic is a very convenient and powerful way of working because it allows tab-completion of the options however outside the notebook and when you're are customizing a specific object it is often useful to set the options directly on an object. The simplest way of doing so is using the ``.options`` method, which will automatically deduce whether an option is a ``style``, ``plot`` or ``norm`` option.\n", "\n", "To demonstrate this we can reproduce the plot above by combining both the plot and style options above into a flat set of options:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "curve_opts = dict(height=100, width=600, xaxis=None, tools=['hover'], color='red', line_width=1.5)\n", "spike_opts = dict(height=100, width=600, yaxis=None, color='grey', line_width=0.25)\n", "\n", "curve = hv.Curve(spike_train, 'milliseconds', 'Hertz')\n", "spikes = hv.Spikes(spike_train, 'milliseconds', [])\n", "\n", "(curve.options(**curve_opts) + spikes.options(**spike_opts)).cols(1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When using ``.options`` to apply options directly to an individual object we do not have to explicitly declare which object the options apply to, however often it is useful to set options on a composite object. In these cases the options can be declared as a dictionary of the type name and the options. The code below is therefore equivalent to the syntax we used above:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "layout = (curve + spikes).options({'Curve': curve_opts, 'Spikes': spike_opts}).cols(1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Switching to matplotlib\n", "\n", "Now let's switch our backend to [matplotlib](http://matplotlib.org/) to show the same elements as rendered with different customizations, in a different output format (SVG), with a completely different plotting library:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%output size=200 backend='matplotlib' fig='svg'\n", "%%opts Layout [sublabel_format='' vspace=0.1]\n", "%%opts Spikes [aspect=6 yaxis='bare'] (color='red' linewidth=0.25 )\n", "%%opts Curve [aspect=6 xaxis=None show_grid=False] (color='blue' linewidth=2 linestyle='dashed')\n", "(hv.Curve(spike_train, 'milliseconds', 'Hertz')\n", " + hv.Spikes(spike_train, 'milliseconds', vdims=[])).cols(1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here we use the same tools with a different plotting extension. Naturally, a few changes needed to be made:\n", "\n", "* A few of the plotting options are different because of differences in how the plotting backends work. For instance, matplotlib uses ``aspect`` instead of setting ``width`` and ``height``. In some cases, but not all, HoloViews can smooth over such differences to make it simpler to switch backends.\n", "* The Bokeh hover tool is not supported by the matplotlib backend, as you might expect, nor are there any other interactive controls.\n", "* Some style options have different names; for instance, the Bokeh ``line_width`` option is called ``linewidth`` in matplotlib.\n", "* Containers like Layouts have plot options, but no style options, because they are processed by HoloViews itself. Here we adjust the gap betwen the plots using ``vspace``.\n", "\n", "Note that you can even write options that work across multiple backends, as HoloViews will ignore keywords that are not applicable to the current backend (as long as they are valid for *some* loaded backend). See the [User Guide](../user_guide/03-Customizing_Plots.ipynb) for more details." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Persistent styles\n", "\n", "Let's switch back to the default (Bokeh) plotting extension for this notebook and apply the ``.select`` operation illustrated in the Introduction, to the ``spikes`` object we made earlier:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%output size=150\n", "spikes.select(milliseconds=(2000,4000))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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. You can learn more about the output line magic and the exact semantics of the opts magic in the [User Guide](../user_guide/03-Customizing_Plots.ipynb)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Setting axis labels\n", "\n", "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 literals for axis labels, if we want to use the corresponding dimension with ``select``?\n", "\n", "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 which you can pass to the ``kdims`` and ``vdims`` explicitly (see the [User Guide](../user_guide/01-Annotating_Data.ipynb) for more information). One of the things each ``Dimension`` object supports is a long, descriptive ``label``, which complements the short programmer-friendly name.\n", "\n", "We can set the dimension labels on our existing ``spikes`` object as follows:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "spikes= spikes.redim.label(milliseconds='Time in milliseconds (10⁻³ seconds)')\n", "curve = curve.redim.label(Hertz='Frequency (Hz)')\n", "(curve + spikes).select(milliseconds=(2000,4000)).cols(1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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 such as ``select``. \n", "\n", "Now that you know how to set up and customize basic visualizations, the next [Getting-Started sections](./3-Tabular_Datasets.ipynb) show how to work with various common types of data in HoloViews." ] } ], "metadata": { "language_info": { "name": "python", "pygments_lexer": "ipython3" } }, "nbformat": 4, "nbformat_minor": 2 }