Plot a gaussian connected to the HeatMap by tap option

Hi, all

I am completely new to HV (since last night, actually) so be gentle.

I am playing around with HV so I have constructed a heat map where x and y are some values of the mean and standard deviation (sigma). Then for each combo of mean and sigma, I calculate the coefficient of variation (just the ratio of sigma and mean). That I have successfully done.

  1. Now what I want to do is to, for each cell in the HeatMap, plot a gaussian that corresponds to a std and mean combo. I have no clue how to do this. Anyone has an idea? (as you can see I have tried but with no success.)
  2. Secondly, I have no clue how to change my hover tool to display some custom info like to say mean, std and cv instead of x, y and z
  3. Thirdly, how to use this HV code as a HTML (output HTML file?) so I can import it to the website?

Thanks in advance!

import pandas as pd
import numpy as np
import holoviews as hv
from holoviews import opts
hv.extension('bokeh')

# here the mean and std vales are given (hardcoded)
mu = [-1, 5, 10]
std = [10, 50, 90]

# make an empty list to append coefficient of variation
cov = []

# calculate the coefficient of variation c_v = std/mu for all combos of std and mu

for i in range(0, len(mu)):
for j in range(0, len(std)):
    cov.append(std[j] / mu[i])

# to place the values of std and mean on a 2D plot, values have to be
# repeated in a certain way, therefore

std_rep = np.tile(std, 3)
mu_rep = np.repeat(mu, 3)

sigma = std_rep.astype(str)
mean = mu_rep.astype(str)

###heatmap entries per cell have to be strings, like this
sigma2=['10', '50', '90', '10', '50', '90', '10', '50', '90']
mean2 = ['-1', '-1', '-1', '5', '5', '5', '10', '10', '10']
################################################

# make heatmap
heatmap = hv.HeatMap((sigma, mean, cov))



# declare tap stream with heatmap as source and initial values
posxy = hv.streams.Tap(source=heatmap, x='sigma', y='mean')

# Define function to compute histogram based on tap location
def tap_histogram(x, y):

x_vals = np.arange(-100, 100, 0.1)
y_vals = norm(mean, sigma)

plt.plot(x_vals, y_vals.pdf(x_vals))

return hv.Curve((x_vals, y_vals.pdf(x_vals)), mean, sigma) 



(heatmap).opts(opts.HeatMap(cmap='RdBu', tools=['hover', 'tap'], colorbar=True, 
                        width=500, height=500, toolbar='above', clim=(-100,15),
                        title ='coefficient of variation',
                        fontsize={'xticks': '10pt', 'yticks': '10pt', 
                                  'xlabel': '10pt', 'ylabel': '10pt',
                                 'title': '15pt'},
                        xlabel='STD', ylabel='MEAN'
                       
                       
                       ))

#cmap examples cmpap = 'RdBu', 'Viridis', etc
# more at http://holoviews.org/user_guide/Colormaps.html

Hi,

1 - you were pretty close, it can be confusing but you need to instantiate your Tap stream with a default value for x and y, It’s not like the other syntaxe where you put the columns names.

posxy = hv.streams.Tap(source=heatmap, x=10, y=10)

Then to create a gaussian you can use hv.Distribution

def tap_histogram(x, y):
    points = np.random.normal(int(y), int(x),1000)
    h= hv.Distribution(points).opts(width=500, height=500)
    return h

2 - In order to customize the hover tool your can import HoverTool from bokeh.models and create a custom tool. Check this page

hoverCustom = HoverTool(tooltips={'mean':'@x','std':'@y','cv':'@z'})

3 - Sorry, I’ve never tried to save holoviews as HTML

Here is the complete code :

import pandas as pd
import numpy as np
import holoviews as hv
from holoviews import opts
hv.extension('bokeh')
from bokeh.models import HoverTool
from holoviews import streams
# here the mean and std vales are given (hardcoded)
mu = [-1, 5, 10]
std = [10, 50, 90]

# make an empty list to append coefficient of variation
cov = []

# calculate the coefficient of variation c_v = std/mu for all combos of std and mu

for i in range(0, len(mu)):
    for j in range(0, len(std)):
        cov.append(std[j] / mu[i])

# to place the values of std and mean on a 2D plot, values have to be
# repeated in a certain way, therefore

std_rep = np.tile(std, 3)
mu_rep = np.repeat(mu, 3)

sigma = std_rep.astype(str)
mean = mu_rep.astype(str)

###heatmap entries per cell have to be strings, like this
sigma2=['10', '50', '90', '10', '50', '90', '10', '50', '90']
mean2 = ['-1', '-1', '-1', '5', '5', '5', '10', '10', '10']
################################################

# make heatmap
heatmap = hv.HeatMap((sigma, mean, cov))



# declare tap stream with heatmap as source and initial values
posxy = hv.streams.Tap(source=heatmap, x=10, y=10)

# Define function to compute histogram based on tap location
def tap_histogram(x, y):
    points = np.random.normal(int(y), int(x),1000)
    h= hv.Distribution(points).opts(width=500, height=500)
    return h

hoverCustom = HoverTool(tooltips={'mean':'@x','std':'@y','cv':'@z'})

heat=(heatmap).opts(opts.HeatMap(cmap='RdBu', tools=[hoverCustom, 'tap'], colorbar=True, 
                        width=500, height=500, toolbar='above', clim=(-100,15),
                        title ='coefficient of variation',
                        fontsize={'xticks': '10pt', 'yticks': '10pt', 
                                  'xlabel': '10pt', 'ylabel': '10pt',
                                 'title': '15pt'},
                        xlabel='STD', ylabel='MEAN'
                       
                       
                       ))

tap_dmap = hv.DynamicMap(tap_histogram, streams=[posxy])

(heat+tap_dmap)

This code works but the range of the dynamic_map does not change so you may need to zoom_out.

I’ve another version using panel, in which I update programmatically the ranges. If you’re insterested you can take a look at the panel doc.
Here is the other version :

import pandas as pd
import numpy as np
import holoviews as hv
from holoviews import opts
hv.extension('bokeh')
from bokeh.models import HoverTool
from holoviews import streams
import panel as pn
import param

class HeatView(param.Parameterized):
    # here the mean and std vales are given (hardcoded)
    mu = [-1, 5, 10]
    std = [10, 50, 90]

    # make an empty list to append coefficient of variation
    cov = []

    # calculate the coefficient of variation c_v = std/mu for all combos of std and mu

    for i in range(0, len(mu)):
        for j in range(0, len(std)):
            cov.append(std[j] / mu[i])

    # to place the values of std and mean on a 2D plot, values have to be
    # repeated in a certain way, therefore

    std_rep = np.tile(std, 3)
    mu_rep = np.repeat(mu, 3)

    sigma = std_rep.astype(str)
    mean = mu_rep.astype(str)

    ###heatmap entries per cell have to be strings, like this
    sigma2=['10', '50', '90', '10', '50', '90', '10', '50', '90']
    mean2 = ['-1', '-1', '-1', '5', '5', '5', '10', '10', '10']
    ################################################

    # make heatmap
    heatmap = hv.HeatMap((sigma, mean, cov))



    # declare tap stream with heatmap as source and initial values
    posxy = hv.streams.Tap(source=heatmap, x=10, y=10)
    gauss=param.Parameter(hv.Curve([]))
    # Define function to compute histogram based on tap location
    def tap_histogram(self,x, y):
        points = np.random.normal(int(y), int(x),1000)
        h= hv.Distribution(points).opts(width=500, height=500)
        h.redim.range(x=(int(y)-1.5*int(x),int(y)+1.5*int(x)))
        self.gauss=h

    hoverCustom = HoverTool(tooltips={'mean':'@x','std':'@y','cv':'@z'})
    
    
    def view(self):
        self.posxy.add_subscriber(self.tap_histogram)
        heat=(self.heatmap).opts(opts.HeatMap(cmap='RdBu', tools=[self.hoverCustom, 'tap'], colorbar=True, 
                                width=500, height=500, toolbar='above', clim=(-100,15),
                                title ='coefficient of variation',
                                fontsize={'xticks': '10pt', 'yticks': '10pt', 
                                          'xlabel': '10pt', 'ylabel': '10pt',
                                         'title': '15pt'},
                                xlabel='STD', ylabel='MEAN'


                               ))

        
        
        return pn.Pane(heat)
    
    @param.depends('gauss')
    def dynamic_view(self):
        return pn.Pane(self.gauss)

heat = HeatView()

pn.Row(heat.view,heat.dynamic_view)
3 Likes

Thank you so so much!!!
You have my eternal gratitude.

I will look more into how to implement it into a website (to get the html code)

1 Like