最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

bokeh - How do I validate Hovertool with the correct renderer in Python >= 3.9.7? I shifted from Python 3.8.5 to 3.9.7; m

programmeradmin5浏览0评论

I have a script that plots a few series voltage vs. speed on a bokeh plot for comparison. It includes hover functionality for easier review of the data. It ran successfully when i used python 3.8.5 but recently, I've needed to move to a more recent version of python, so I started using 3.9.7.

The way the script works is it pulls data from an excel file I've already populated, after which it plots each series that I've specified the voltage and speed column names for. It creates a linear regression line for the data, then adds a 95% confidence interval to the plot by calculating an upper and lower bound for the dataset and passing a linear regression line through each of those bounds. The regression line and confidence band are based on a different set of columns in the excel. Those columns are comprehensive so they basically just include all the data from all the series that are being plotted. And thats what's used for the regression line & confidence interval that is representative of the whole the dataset. Then the hover tool is added for the confidence band (this is where my issue is). Finally, I append my renderers and open the plot in the browser.

That's the gist of the script, minus some unrelated extras like printing a data table for the confidence band values and shading the confidence interval.

This is the error i get:

ValueError: failed to validate HoverTool(id='1986', ...).renderers: expected an element of either Auto or List(Instance(DataRenderer)), got [Band(id='1908', ...)]

This is the script:

import pandas as pd
import numpy as np
from scipy import stats
from bokeh.plotting import figure, show, output_file, output_notebook
from bokeh.models import HoverTool, ColumnDataSource, Band, CustomJS, Label
from bokeh.layouts import column, gridplot
from bokeh.palettes import Category10
from bokeh.models.widgets import CheckboxGroup
import matplotlib.pyplot as plt
import seaborn as sns
from scipy.stats import norm
from bokeh.io import push_notebook
from sklearn.linear_model import LinearRegression

def plot_voltage_vs_speed(excel_file, sheet_name, series_info, plot_title, ci_info):
    df = pd.read_excel(excel_file, sheet_name=sheet_name)

    specified_speeds = np.array([3000, 5250, 10500, 14500, 15750, 16250, 18000, 20581, 21000])
    
    renderers = []
    checkbox_labels = []
    
    p = figure(title=plot_title, x_axis_label='Speed (RPM)', y_axis_label='Voltage (V)', width=800, height=600)
    
    for series in series_info:
        voltage_col = series['voltage_col']
        speed_col = series['speed_col']
        label = series['label']
        
        x = df[speed_col]
        y = df[voltage_col]
        
        circle_renderer = p.circle(x, y, legend_label=label, fill_color="white", size=6, color=Category10[10][series_info.index(series)])
        renderers.append((circle_renderer,))
        checkbox_labels.append(label)
        
        # Adding hover tool for the circles; this hover doesnt give me errors
        hover = HoverTool(tooltips=[
            ("Speed", "@x"),
            ("Voltage", "@y")
        ])
        p.add_tools(hover) 

... # skipping to next relevant portion of script

    # Perform linear regression
    model = LinearRegression(fit_intercept=False)
    model.fit(x_filtered.values.reshape(-1, 1), y_filtered)
    line = model.predict(x_filtered.values.reshape(-1, 1))

    # Calc standard deviation and mean for each speed
    std_devs = df_filtered.groupby('mapped_speed')[ci_info['voltage_col']].std()
    means = df_filtered.groupby('mapped_speed')[ci_info['voltage_col']].mean()
    
    # Calculate confidence bands
    upper_bound = means.loc[x_filtered].values + 1.96 * std_devs.loc[x_filtered].values
    lower_bound = means.loc[x_filtered].values - 1.96 * std_devs.loc[x_filtered].values
   
    # Perform linear regression for upper and lower bounds
    model_upper = LinearRegression(fit_intercept=False)
    model_upper.fit(x_filtered.values.reshape(-1, 1), upper_bound)
    upper_line = model_upper.predict(x_filtered.values.reshape(-1, 1))

    model_lower = LinearRegression(fit_intercept=False)
    model_lower.fit(x_filtered.values.reshape(-1, 1), lower_bound)
    lower_line = model_lower.predict(x_filtered.values.reshape(-1, 1))

    sorted_indices = np.argsort(x_filtered)
    x_sorted = x_filtered.values[sorted_indices]
    line_sorted = line[sorted_indices]
    lower_sorted = lower_bound[sorted_indices]
    upper_sorted = upper_bound[sorted_indices]
    
    # Create regression line and confidence band
    source = ColumnDataSource(data={
        'x': x_sorted,
        'line': line_sorted,
        'lower': lower_sorted,
        'upper': upper_sorted
    })
    
    line_renderer = p.line('x', 'line', source=source, legend_label="Regression Line", line_width=1, color="black")
    
    # This is where band comes in. I used it later when adding the hover tool for the confidence band. 
    band = Band(base='x', lower='lower', upper='upper', source=source, level='underlay', fill_alpha=0.2, line_width=1, line_color="black", fill_color="gray")
    p.add_layout(band)

    
    ''' Adding hover tool for confidence band; this is the hover that gives me an error, specifically the renderers=[band] part of it.''' 

    hover_band = HoverTool(renderers=[band], tooltips=[
        ("Speed", "@x"),
        ("Lower Bound", "@lower"),
        ("Upper Bound", "@upper")
    ])
    p.add_tools(hover_band)
    
    renderers.append((line_renderer,))
    renderers.append((band,))
    checkbox_labels.append("Regression Line")
    checkbox_labels.append("Prediction Interval")

    checkbox_group = CheckboxGroup(labels=checkbox_labels, active=list(range(len(renderers))))
    
    checkbox_callback = CustomJS(args=dict(renderers=renderers, checkbox_group=checkbox_group), code="""
        for (let i = 0; i < renderers.length; i++) {
            const elements = renderers[i];
            const visible = checkbox_group.active.includes(i);
            for (let elem of elements) {
                elem.visible = visible;
            }
        }
    """)

    checkbox_group.js_on_change('active', checkbox_callback)
    
    layout = column(checkbox_group, p)

    # Show plot in notebook + browser
    output_notebook()
    show(layout)
    output_file("Machine_Compare.html")

excel_file = 'Machine_Comparison.xlsx'
sheet_name = 'Voltage_Comparison'  # Specify sheet 

series_info = [
    {'voltage_col': 'Voltage1', 'speed_col': 'Speed1', 'label': 'Machine 1'},
    {'voltage_col': 'Voltage2', 'speed_col': 'Speed2', 'label': 'Machine 2'}    
]

plot_title = 'Machine Comparison with 95% Prediction Interval'

# Columns for prediction interval calc
ci_info = {'voltage_col': 'Voltages', 'speed_col': 'Speeds'}

plot_voltage_vs_speed(excel_file, sheet_name, series_info, plot_title, ci_info) 

Sorry its a bit long. The relevant parts were spread out around the script and only copying those over made it somewhat confusing to read, without the steps in between.

I tried removing renderers=[band], from the hover tool that causes the error to ensure I had the cause of the error right, and though it did run and generate the plot successfully, the hover information showed ?? for both speed and voltage values.

I have a script that plots a few series voltage vs. speed on a bokeh plot for comparison. It includes hover functionality for easier review of the data. It ran successfully when i used python 3.8.5 but recently, I've needed to move to a more recent version of python, so I started using 3.9.7.

The way the script works is it pulls data from an excel file I've already populated, after which it plots each series that I've specified the voltage and speed column names for. It creates a linear regression line for the data, then adds a 95% confidence interval to the plot by calculating an upper and lower bound for the dataset and passing a linear regression line through each of those bounds. The regression line and confidence band are based on a different set of columns in the excel. Those columns are comprehensive so they basically just include all the data from all the series that are being plotted. And thats what's used for the regression line & confidence interval that is representative of the whole the dataset. Then the hover tool is added for the confidence band (this is where my issue is). Finally, I append my renderers and open the plot in the browser.

That's the gist of the script, minus some unrelated extras like printing a data table for the confidence band values and shading the confidence interval.

This is the error i get:

ValueError: failed to validate HoverTool(id='1986', ...).renderers: expected an element of either Auto or List(Instance(DataRenderer)), got [Band(id='1908', ...)]

This is the script:

import pandas as pd
import numpy as np
from scipy import stats
from bokeh.plotting import figure, show, output_file, output_notebook
from bokeh.models import HoverTool, ColumnDataSource, Band, CustomJS, Label
from bokeh.layouts import column, gridplot
from bokeh.palettes import Category10
from bokeh.models.widgets import CheckboxGroup
import matplotlib.pyplot as plt
import seaborn as sns
from scipy.stats import norm
from bokeh.io import push_notebook
from sklearn.linear_model import LinearRegression

def plot_voltage_vs_speed(excel_file, sheet_name, series_info, plot_title, ci_info):
    df = pd.read_excel(excel_file, sheet_name=sheet_name)

    specified_speeds = np.array([3000, 5250, 10500, 14500, 15750, 16250, 18000, 20581, 21000])
    
    renderers = []
    checkbox_labels = []
    
    p = figure(title=plot_title, x_axis_label='Speed (RPM)', y_axis_label='Voltage (V)', width=800, height=600)
    
    for series in series_info:
        voltage_col = series['voltage_col']
        speed_col = series['speed_col']
        label = series['label']
        
        x = df[speed_col]
        y = df[voltage_col]
        
        circle_renderer = p.circle(x, y, legend_label=label, fill_color="white", size=6, color=Category10[10][series_info.index(series)])
        renderers.append((circle_renderer,))
        checkbox_labels.append(label)
        
        # Adding hover tool for the circles; this hover doesnt give me errors
        hover = HoverTool(tooltips=[
            ("Speed", "@x"),
            ("Voltage", "@y")
        ])
        p.add_tools(hover) 

... # skipping to next relevant portion of script

    # Perform linear regression
    model = LinearRegression(fit_intercept=False)
    model.fit(x_filtered.values.reshape(-1, 1), y_filtered)
    line = model.predict(x_filtered.values.reshape(-1, 1))

    # Calc standard deviation and mean for each speed
    std_devs = df_filtered.groupby('mapped_speed')[ci_info['voltage_col']].std()
    means = df_filtered.groupby('mapped_speed')[ci_info['voltage_col']].mean()
    
    # Calculate confidence bands
    upper_bound = means.loc[x_filtered].values + 1.96 * std_devs.loc[x_filtered].values
    lower_bound = means.loc[x_filtered].values - 1.96 * std_devs.loc[x_filtered].values
   
    # Perform linear regression for upper and lower bounds
    model_upper = LinearRegression(fit_intercept=False)
    model_upper.fit(x_filtered.values.reshape(-1, 1), upper_bound)
    upper_line = model_upper.predict(x_filtered.values.reshape(-1, 1))

    model_lower = LinearRegression(fit_intercept=False)
    model_lower.fit(x_filtered.values.reshape(-1, 1), lower_bound)
    lower_line = model_lower.predict(x_filtered.values.reshape(-1, 1))

    sorted_indices = np.argsort(x_filtered)
    x_sorted = x_filtered.values[sorted_indices]
    line_sorted = line[sorted_indices]
    lower_sorted = lower_bound[sorted_indices]
    upper_sorted = upper_bound[sorted_indices]
    
    # Create regression line and confidence band
    source = ColumnDataSource(data={
        'x': x_sorted,
        'line': line_sorted,
        'lower': lower_sorted,
        'upper': upper_sorted
    })
    
    line_renderer = p.line('x', 'line', source=source, legend_label="Regression Line", line_width=1, color="black")
    
    # This is where band comes in. I used it later when adding the hover tool for the confidence band. 
    band = Band(base='x', lower='lower', upper='upper', source=source, level='underlay', fill_alpha=0.2, line_width=1, line_color="black", fill_color="gray")
    p.add_layout(band)

    
    ''' Adding hover tool for confidence band; this is the hover that gives me an error, specifically the renderers=[band] part of it.''' 

    hover_band = HoverTool(renderers=[band], tooltips=[
        ("Speed", "@x"),
        ("Lower Bound", "@lower"),
        ("Upper Bound", "@upper")
    ])
    p.add_tools(hover_band)
    
    renderers.append((line_renderer,))
    renderers.append((band,))
    checkbox_labels.append("Regression Line")
    checkbox_labels.append("Prediction Interval")

    checkbox_group = CheckboxGroup(labels=checkbox_labels, active=list(range(len(renderers))))
    
    checkbox_callback = CustomJS(args=dict(renderers=renderers, checkbox_group=checkbox_group), code="""
        for (let i = 0; i < renderers.length; i++) {
            const elements = renderers[i];
            const visible = checkbox_group.active.includes(i);
            for (let elem of elements) {
                elem.visible = visible;
            }
        }
    """)

    checkbox_group.js_on_change('active', checkbox_callback)
    
    layout = column(checkbox_group, p)

    # Show plot in notebook + browser
    output_notebook()
    show(layout)
    output_file("Machine_Compare.html")

excel_file = 'Machine_Comparison.xlsx'
sheet_name = 'Voltage_Comparison'  # Specify sheet 

series_info = [
    {'voltage_col': 'Voltage1', 'speed_col': 'Speed1', 'label': 'Machine 1'},
    {'voltage_col': 'Voltage2', 'speed_col': 'Speed2', 'label': 'Machine 2'}    
]

plot_title = 'Machine Comparison with 95% Prediction Interval'

# Columns for prediction interval calc
ci_info = {'voltage_col': 'Voltages', 'speed_col': 'Speeds'}

plot_voltage_vs_speed(excel_file, sheet_name, series_info, plot_title, ci_info) 

Sorry its a bit long. The relevant parts were spread out around the script and only copying those over made it somewhat confusing to read, without the steps in between.

I tried removing renderers=[band], from the hover tool that causes the error to ensure I had the cause of the error right, and though it did run and generate the plot successfully, the hover information showed ?? for both speed and voltage values.

Share Improve this question edited Mar 25 at 17:17 Zay asked Mar 24 at 13:11 ZayZay 114 bronze badges 1
  • Please add al the necessary imports so that this code can actually be run to be investigated. Otherwise, all I can say generally is that Band is not a valid target for a hover tool, only glyphs (e.g. scatter, line, patch, bars, etc) are. And that ??? in the hover output means that the column name you configured for the tool is missing from the ColumnDataSource backing the glyph. – bigreddot Commented Mar 24 at 16:42
Add a comment  | 

1 Answer 1

Reset to default 0

So what wound up working for me was replacing [band] with [line_renderer]. I assume the problem was because Band was no longer a valid target for bokeh's hover tool as bigreddot mentioned in their comment.

So that section of the code now looks like this:

    # Adding hover tool for confidence band
    hover_band = HoverTool(renderers=[line_renderer], tooltips=[
        ("Speed", "@x"),
        ("Lower Bound", "@lower"),
        ("Upper Bound", "@upper")
    ])
    p.add_tools(hover_band)

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论