Exercise 5.5: Heat maps with Bokeh
A heat map is a type of plot where magnitude is expressed in terms of color. You can see an example of a heatmap generated using Bokeh here.
a) Write a function with call signature heatmap(x, y, z)
(you can add any kwargs you like) that make a heat map from x, y, z data. For simplicity for this function, assume x
and y
are categorical variables.
b) 96 well plates are often used in analyzing biochemical reactions. Some absorbance data from a 96 well plate experiment are in the file ~git/bootcamp/data/96_well.csv
. Use your heatmap()
function to make a display of the data.
Solution
[1]:
import numpy as np
import pandas as pd
import bokeh.io
import bokeh.models
import bokeh.palettes
import bokeh.plotting
bokeh.io.output_notebook()
a) In our heatmap()
function, we will allow for tooltips when hovering, inclusion of a colorbar, and specification of palette. Importantly, we make sure the x- and y- values are categorical. We then use rect
glyphs to color the heat map.
[2]:
def heatmap(
x,
y,
z,
x_range=None,
y_range=None,
palette=bokeh.palettes.Viridis256,
z_range=(None, None),
x_label="x",
y_label="y",
z_label="z",
flip_x_axis=False,
flip_y_axis=False,
colorbar=True,
**kwargs
):
"""Create a heatmap for x, y, z data where the x and y data are categorial.
Parameters
----------
x : array_like
x-values for heat map. Assumed to be categorical. Any entries are
converted to strings.
y : array_like
y-values for heat map. Assumed to be categorical. Any entries are
converted to strings.
z : array_like
z-values for heat map. These data are quantitative and displayed
with color.
x_range : array_like
Array of unique values that determines the values of the x-axis.
y_range : array_like
Array of unique values that determines the values of the y-axis.
palette : List of hex colors, default bokeh.palettes.Viridis256
Color palette to use to make linear color mapper for heat map.
z_range : 2-tuple, default (None, None)
Range of allowed z-values. If an entry is None, the min or max
is used.
x_label : str, defualt "z"
Label to be used in tool tips for the x-values.
y_label : str, defualt "z"
Label to be used in tool tips for the y-values.
z_label : str, defualt "z"
Label to be used in tool tips for the z-values.
flip_x_axis : bool, default False
If True, x-axis is reversed.
flip_y_axis : bool, default False
If True, y-axis is reversed.
colorbar : bool, default True
If True, display color bar.
kwargs : dict
All other kwargs are passed to bokeh.plotting.figure() when
setting up the plot.
Returns
-------
output : Bokeh plotting object
Heatmap plot.
"""
# Convert x and y values to strings; assuming evenly spread
x_str = [str(x_val) for x_val in x]
y_str = [str(y_val) for y_val in y]
# Ranges of z-values
z_min = z.min() if z_range[0] is None else z_range[0]
z_max = z.max() if z_range[1] is None else z_range[1]
# Categorical axis values
if x_range is None:
x_range = [str(x_val) for x_val in sorted(np.unique(x))]
if y_range is None:
y_range = [str(y_val) for y_val in sorted(np.unique(y))]
if flip_x_axis:
x_range = x_range[::-1]
if flip_y_axis:
y_range = y_range[::-1]
# Set up defaults
x_axis_label = kwargs.pop("x_axis_label", x_label)
y_axis_label = kwargs.pop("y_axis_label", y_label)
tools = kwargs.pop("tools", "pan,box_zoom,wheel_zoom,reset,hover,save")
tooltips = kwargs.pop(
"tooltips", [(x_label, "@x"), (y_label, "@y"), (z_label, "@z")]
)
toolbar_location = kwargs.pop("toolbar_location", "above")
frame_height = kwargs.pop("frame_height", None)
frame_width = kwargs.pop("frame_width", None)
# Adjust frame heights and widths to have square rectangles
if frame_width is not None:
if frame_height is None:
frame_height = frame_width * len(y_range) // len(x_range)
else:
if frame_height is None:
frame_height = 250
frame_width = frame_height * len(x_range) // len(y_range)
# Data source
source = bokeh.models.ColumnDataSource(
dict(x_str=x_str, y_str=y_str, x=x, y=y, z=z)
)
# Color mapper
mapper = bokeh.models.LinearColorMapper(palette=palette, low=z_min, high=z_max)
# Figure
p = bokeh.plotting.figure(
x_range=x_range,
y_range=y_range,
frame_width=frame_width,
frame_height=frame_height,
x_axis_label=x_axis_label,
y_axis_label=y_axis_label,
tools=tools,
tooltips=tooltips,
toolbar_location=toolbar_location,
**kwargs
)
p.rect(
x="x_str",
y="y_str",
width=frame_width / frame_height * len(y_range) / len(x_range),
height=frame_height / frame_width * len(x_range) / len(y_range),
source=source,
fill_color={"field": "z", "transform": mapper},
line_color=None,
)
# Add color bar
color_bar = bokeh.models.ColorBar(
color_mapper=mapper, major_label_text_font_size="8px", border_line_color=None,
)
p.add_layout(color_bar, "right")
return p
b) Let’s put it to use!
[3]:
df = pd.read_csv('data/96_well.csv')
p = heatmap(
df["column"],
df["row"],
df["absorbance"],
x_range=[str(x) for x in range(1, 13)],
y_range=[a for a in reversed("ABCDEFGH")],
x_label='column',
y_label='row',
z_label='absorbance',
x_axis_label=None,
y_axis_label=None,
)
bokeh.io.show(p)
Very nice!
Computing environment
[4]:
%load_ext watermark
%watermark -v -p numpy,pandas,bokeh,jupyterlab
Python implementation: CPython
Python version : 3.9.12
IPython version : 8.3.0
numpy : 1.21.5
pandas : 1.4.2
bokeh : 2.4.2
jupyterlab: 3.3.2