Share @ LinkedIn Facebook  holoviews, basic-plots
Getting Started with Holoviews - Basic Interactive Plotting [Python]

Getting Started with Holoviews - Basic Plotting

Table of Contents

Introduction

Holoviews is an open-source python plotting library designed to make plotting easy and interactive. Python has a very nice set of existing plotting libraries like matplotlib, seaborn, bokeh, plotly, networkx, etc. Libraries like matplotlib and seaborn are static libraries whereas libraries like bokeh, plotly are interactive libraries. All of these libraries require little learning curves to start working with them. Holoviews is designed on top of matplotlib, bokeh, and plotly. It reduces the number of lines of code required for plotting for bokeh and plotly. Holoviews provides a high-level interface on matplotlib, bokeh, and plotly which make plotting interactive plot quite easy task. Holoviews makes plotting tasks easy letting us concentrate on actual analysis and need not worrying about getting plots right much. Holoviews is an intuitive plotting library.

We'll help you get started plotting using holoviews by creating simple plots like bar charts, scatter plots, histograms, etc. We'll be using wine dataset for plotting which comes preloaded with scikit-learn library.

In [1]:
import pandas as pd
import numpy as np

pd.set_option("display.max_columns", 30)

Loading Wine Dataset

We'll start loading the wine dataset available in scikit-learn. It has 13 features and target variable with 3 different classes of wine. We'll keep the total dataset into pandas dataframe so that it becomes easily available for plotting and manipulation.

In [2]:
from sklearn.datasets import load_wine

wine = load_wine()

print("Feature Names : ", wine.feature_names)
print("\nTarget Names : ", wine.target_names)

wine_df = pd.DataFrame(wine.data, columns = wine.feature_names)
wine_df["Target"] = wine.target
wine_df["Target"] = ["Class_1" if typ==0 else "Class_2" if typ==1 else "Class_3"  for typ in wine_df["Target"]]

print("\nDataset Size : ", wine_df.shape)

wine_df.head()
Feature Names :  ['alcohol', 'malic_acid', 'ash', 'alcalinity_of_ash', 'magnesium', 'total_phenols', 'flavanoids', 'nonflavanoid_phenols', 'proanthocyanins', 'color_intensity', 'hue', 'od280/od315_of_diluted_wines', 'proline']

Target Names :  ['class_0' 'class_1' 'class_2']

Dataset Size :  (178, 14)
Out[2]:
alcohol malic_acid ash alcalinity_of_ash magnesium total_phenols flavanoids nonflavanoid_phenols proanthocyanins color_intensity hue od280/od315_of_diluted_wines proline Target
0 14.23 1.71 2.43 15.6 127.0 2.80 3.06 0.28 2.29 5.64 1.04 3.92 1065.0 Class_1
1 13.20 1.78 2.14 11.2 100.0 2.65 2.76 0.26 1.28 4.38 1.05 3.40 1050.0 Class_1
2 13.16 2.36 2.67 18.6 101.0 2.80 3.24 0.30 2.81 5.68 1.03 3.17 1185.0 Class_1
3 14.37 1.95 2.50 16.8 113.0 3.85 3.49 0.24 2.18 7.80 0.86 3.45 1480.0 Class_1
4 13.24 2.59 2.87 21.0 118.0 2.80 2.69 0.39 1.82 4.32 1.04 2.93 735.0 Class_1

1. Basic Plots

We'll start plotting a few basic plots like scatter plot, bar chart, histogram, etc. We'll then further explore holoviews options.

1.1 Loading Holoviews [Bokeh Backend]

We'll start importing holoviews and then set its backend using the hv.extension() method. We'll be using bokeh and matplotlib backends interchangeably to explain how the same holoviews code can be used for both(matplotlib & bokeh).

In [ ]:
import holoviews as hv

hv.extension("bokeh")

Getting Started with holoviews

1.2 Scatter Plot

We are plotting the scatter plot below to show the relationship between alcohol and malic_acid values in wine. We can see that just one line of code is enough to create a simple interactive graph. We need to pass the first argument as a dataframe that maintains data and then kdims and vdims to represent x and y of a graph.

In [ ]:
scat = hv.Scatter(wine_df, kdims="alcohol", vdims="malic_acid", label="alcohol vs malic_acid scatter")
scat

Getting Started with holoviews

1.3 Bar Chart

To plot the bar chart, we are grouping dataframe by Target variable, taking an average of all columns and then filtering dataframe with only one column named malic_acid. We then just pass that to the Bars method to generate a bar chart.

In [ ]:
bar = hv.Bars(wine_df.groupby("Target").mean()[["malic_acid"]], label="Average Malic Acid Per Wine Class")
bar

Getting Started with holoviews

1.4 Histogram

We are using a numpy histogram method to generate histogram entries. We pass it values of column magnesium and it returns and position of bins and value for each bin. We then pass it to the Histogram method of holoviews to generate a histogram.

In [ ]:
hist = hv.Histogram(np.histogram(wine_df['magnesium'], bins=24), kdims="magnesium", label="magnesium histogram")
hist

Getting Started with holoviews

1.5 BoxWhisker

We can generate box whisker plot easily by passing kdims=Target and vdims=total_pheonols. It'll generate distribution of total_phenols values per each wine class.

In [ ]:
box_whisker = hv.BoxWhisker(wine_df, kdims="Target", vdims="total_phenols" , label="total_phenols per wine class distribution")
box_whisker

Getting Started with holoviews

1.6 Elements

We can also generate basic line charts and points chart using holoviews as described below.

In [ ]:
curve = hv.Curve(wine_df.sort_values(by="nonflavanoid_phenols"), kdims="nonflavanoid_phenols")
curve

Getting Started with holoviews

In [ ]:
points = hv.Points(wine_df, kdims=["color_intensity", "hue"], label="color_intensity vs hue scatter")
points

Getting Started with holoviews

We can notice by looking at all the above graphs is generated using bokeh. It shows the bokeh symbol as well in the toolbar to confirm it.

2. Analyzing Plotting Objects

We'll now print holoviews objects which we created above to see their structure further which can give us meaningful insights.

In [ ]:
print(scat)

Getting Started with holoviews

In [ ]:
print(bar)

Getting Started with holoviews

In [ ]:
print(hist)

Getting Started with holoviews

In [ ]:
print(box_whisker)

Getting Started with holoviews

In [ ]:
print(curve)

Getting Started with holoviews

In [ ]:
print(points)

Getting Started with holoviews

We can see that all of the plot objects printed kdims in square brackets and all vdims as a parenthesis. Holoviews sees kdims are primary dimensions which are must to generate plot and vdims as additional secondary dimensions which can add further to primary dimensions. Holoviews plots need just primary dimensions for plotting purposes and if secondary dimensions do not provide meaningful plotting information then it ignores it. We can see that in Curve and Points, it took all other columns of dataframe as a secondary dimension and not using it for any purpose.

3. Changing Backend

As we already discussed above that holoviews just maintains plotting metadata and uses underlying back-end python library for plotting, we'll now explain below on how can we change different back-ends according to our needs. It won't require any code change in plotting graphs to change back-end.

3.1 Matplotlib Backend

We can change backend by simply calling the extension() method and passing it a new backend. It'll then start using that backend for plotting. Below we have initialized holoviews with matplotlib backend.

In [ ]:
hv.extension("matplotlib")

Getting Started with holoviews

Scatter Plot

We can see that we have clearly used the same code for generating a matplotlib graph without changing code even a little bit. We might need to sometime change some argument of code as parameters might be different for the different backend but in the majority of cases, it'll be almost the same.

In [ ]:
scat = hv.Scatter(wine_df, kdims="alcohol", vdims="malic_acid", label="alcohol vs malic_acid scatter")
scat

Getting Started with holoviews

Bar Chart

In [ ]:
bar = hv.Bars(wine_df.groupby("Target").mean()[["malic_acid"]], label="Average Malic Acid Per Wine Class")
bar

Getting Started with holoviews

Histogram

In [ ]:
hist = hv.Histogram(np.histogram(wine_df['magnesium'], bins=24), kdims="magnesium", label="magnesium histogram")
hist

Getting Started with holoviews

3.2 Plotly Backend

We'll now initialize holoviews using plotly backend and plot various graphs.

In [ ]:
hv.extension("plotly")

Getting Started with holoviews

Bar Chart

In [ ]:
bar = hv.Bars(wine_df.groupby("Target").mean()[["malic_acid"]], label="Average Malic Acid Per Wine Class")
bar

Getting Started with holoviews Plotly Graphs

Scatter Plot

In [ ]:
scat = hv.Scatter(wine_df, kdims="alcohol", vdims="malic_acid", label="alcohol vs malic_acid scatter")
scat

Getting Started with holoviews Plotly Graphs

4. Changing Graphs by Providing more Options

We can notice from the majority of the above graphs that the majority of plotting options like height, width, axes labels, ticks limits, colors, etc. are default selected by holoviews.

But what if a person needs control over it and needs to change various options according to it needs?

Holoviews provided two very easy ways to provide configuration options that will be passed to underlying libraries. It'll also ignore options which do not available in the underlying library by giving warnings. We'll start setting our extension as bokeh again.

In [ ]:
hv.extension("bokeh")

Getting Started with holoviews

The first way of providing configuration options to graph is by using jupyter notebook magic command %%opts. We need to give a plot object to which we need to apply the configuration options followed by a magic command. Holoviews has divided configuration options in two categories primary and secondary here as well. All primary options are about to look of a graph like xlabel, ylabel, height, width, etc. All secondary options are about actual data plotting options like the size of points, alpha, color, a width of the bar, etc.

In [ ]:
%%opts Scatter [tools=["hover"] xlabel="Alcohol" ylabel="Malic Acid" height=400 width=600](alpha=0.7 size=15 color="green" line_color="black")

scat = hv.Scatter(wine_df, kdims="alcohol", vdims="malic_acid", label="Alcohol vs Malic Acid Relation")
scat

Getting Started with holoviews

The below graph makes use of a hover color option which changes bar color when the mouse pointer is hovered over it and highlights it.

In [ ]:
%%opts Bars [height=400 width=600 tools=["hover"] bgcolor="grey" xlabel="Wine Class" ylabel="Malic Acid" ylim=(0.0, 3.5)]
%%opts Bars (color="black" hover_color="red" bar_width=0.5)

bar = hv.Bars(wine_df.groupby("Target").mean()[["malic_acid"]], label="Average Malic Acid Per Wine Class")
bar

Getting Started with holoviews

Below, we have explained another way of passing configuration options. We can also call opts() or options() method on graph object and pass it all parameters as per need.

In [ ]:
scat = hv.Scatter(wine_df, kdims="alcohol", vdims="malic_acid", label="Alcohol vs Malic Acid Relation")

scat.opts(xlabel="Alcohol",
          ylabel="Malic Acid",
          height=400, width=600,
          tools=["hover"],
          alpha=0.7, size=15,
          color="purple", line_color="black")

Getting Started with holoviews

5. Merging Graphs

We have explained how to create basic graphs. We'll now explain how can we merge more than one graph into holoviews.

Holoviews lets us merge graph objects using 2 operations

  • + - It merges graphs by putting them next to each other
  • * - It overlays graphs on one another to create one single graph combining all individuals.

We'll start merging three bar charts with + operation.

In [ ]:
%%opts Bars (color="limegreen")

bar1 = hv.Bars(wine_df.groupby("Target").mean()[["malic_acid"]], label="Average Malic Acid Per Class")
bar2 = hv.Bars(wine_df.groupby("Target").mean()[["ash"]], label="Average Ash Per Class")
bar3 = hv.Bars(wine_df.groupby("Target").mean()[["alcohol"]], label="Average Alcohol Per Class")

bars = bar1 + bar2 + bar3
bars

Getting Started with holoviews

In [ ]:
print(bars)

Getting Started with holoviews

By printing bars object, we can see that it’s of type Layout. Layout objects are responsible for maintaining the layout of the charts. The above layout is set out a grid of 1 row and 3 columns(1x3). Below we'll give another example where we'll make changes to these default layout.

We can even access an individual elements as well by simply following dot notation.

In [ ]:
bars.Bars.Average_Alcohol_Per_Class

Getting Started with holoviews

We'll now explain another merge operation using *. We'll be using * to overlay 3 scatter plots over one another. We are creating 3 scatter plot of alcohol vs malic_acid for 3 different wine categories. We'll then overlay them on each other to create a single scatter plot.

In [ ]:
%%opts Scatter [tools=["hover"] xlabel="Alcohol" ylabel="Malic Acid" height=400 width=600 xlim=(10.8, 15.0) ylim=(0.0,6.0) title="Alcohol vs malic acid color-encoded by wine category"]
%%opts Scatter (alpha=0.7 size=15 line_color="black")

scat1 = hv.Scatter(wine_df[wine_df["Target"] == "Class_1"], kdims="alcohol", vdims="malic_acid")
scat2 = hv.Scatter(wine_df[wine_df["Target"] == "Class_2"], kdims="alcohol", vdims="malic_acid")
scat3 = hv.Scatter(wine_df[wine_df["Target"] == "Class_3"], kdims="alcohol", vdims="malic_acid")

scatters = (scat1 * scat2 * scat3)
scatters

Getting Started with holoviews

In [ ]:
print(scatters)

Getting Started with holoviews

By printing the scatters object, we can see that its type is Overlay. We can access an individual elements of Overlay by simply following dot notation.

In [ ]:
scatters.Scatter.II

Getting Started with holoviews

The below example explains, how can we organize a Layout object created when we merge more than one graph using + operation. We'll be using it's cols() method passing it a number of columns to organize graphs. We have below created 4 graphs and passed 2 as cols value which will organize graphs into 2 columns instead of just putting them next to each other.

In [ ]:
%%opts BoxWhisker [tools=["hover"]] (box_fill_color="tomato" whisker_color="limegreen")

box_whisker1 = hv.BoxWhisker(wine_df, kdims="Target", vdims="total_phenols" , label="total_phenols per wine class distribution")
box_whisker2 = hv.BoxWhisker(wine_df, kdims="Target", vdims="ash" , label="ash per wine class distribution")
box_whisker3 = hv.BoxWhisker(wine_df, kdims="Target", vdims="alcohol" , label="alcohol per wine class distribution")
box_whisker4 = hv.BoxWhisker(wine_df, kdims="Target", vdims="malic_acid" , label="malic_acid per wine class distribution")

(box_whisker1 + box_whisker2 + box_whisker3 + box_whisker4).cols(2)

Getting Started with holoviews

The simple process of merging more than 1 graph explained above a reduced a lot of coding from the developer side. We can even mix + and * operations into one expression to create a complicated figure.

References


Sunny Solanki  Sunny Solanki