Does Hovertool/tooltip work with pandas df or only with ColumnDataSource - bokeh

Pretty new to Bokeh. Plotting a barplot (after importing pandas_bokey) works well.
But... I want to change the hoover tooltips.
Question: should hoover tooltip work with a pandas df in Bokeh or must ColumnDataSource be used?
thanks

One option in pandas_bokeh to modify the HoverTool is passing a custom string to hovertool_string.
import pandas as pd
import pandas_bokeh
from bokeh.plotting import output_notebook
output_notebook()
df = pd.DataFrame({'a':[1,2], 'b':[3,4]})
df.plot_bokeh.bar(hovertool_string=r"""At index #{__x__values}: a is #{a} and b is #{b}""")
default output
modified tooltop
To see a more complex example check the 2. example in the line plot documentation
Comment
Because your question is very open, I am not sure if the answer is satisfying. Please provide some Minimal Working Example and some example data in future.

Related

Seaborn code Anscombe’s quartet does not work

The Seaborn code does not work.
I use jupyterlite to execute seaborn python code. first, i import seaborn in the following way --
import piplite
await piplite.install('seaborn')
import matplotlib.pyplot as plt
import seaborn as sn
%matplotlib inline
But when I insert seaborn code like the following one then it shows many errors that i do not understand yet --
link of the code
the problem that I face
But I insert this code in the google colab it works nicely
google colab
The issue is getting the example dataset as I point out in my comments.
The problem step is associated with:
# Load the example dataset for Anscombe's quartet
df = sns.load_dataset("anscombe")
You need to replace the line df = sns.load_dataset("anscombe") with the following:
url = 'https://raw.githubusercontent.com/mwaskom/seaborn-data/master/anscombe.csv' # based on [Data repository for seaborn examples](https://github.com/mwaskom/seaborn-data)
from pyodide.http import open_url
import pandas
df = pandas.read_csv(open_url(url))
That's based on use of open_url() from pyodide.http, see here for more examples.
Alternative with pyfetch and assigning the string obtained
If you've seen pyfetch around, this also works as a replacement of the sns.load_dataset() line based on John Hanley's post, that uses pyfetch to get the CSV data. The code is commented further:
# GET text at URL via pyfetch based on John Hanley's https://www.jhanley.com/blog/pyscript-loading-python-code-in-the-browser/
url = 'https://raw.githubusercontent.com/mwaskom/seaborn-data/master/anscombe.csv' # based on [Data repository for seaborn examples](https://github.com/mwaskom/seaborn-data)
from pyodide.http import pyfetch
response = await pyfetch(url)
content = (await response.bytes()).decode('utf-8')
# READ in string to dataframe based on [farmOS + JupyterLite: Import a CSV of Animals](https://gist.github.com/symbioquine/7641a2ab258726347ec937e8ea02a167)
import io
import pandas
df = pandas.read_csv(io.StringIO(content))

How to modify the DateTimeTickFormatter class?

import pandas as pd
from math import pi
from datetime import datetime as dt
from bokeh.io import show
from bokeh.models import DatetimeTickFormatter
from bokeh.plotting import figure
d = {'col1': [dt(2015, 1, 1), dt(2015, 1, 2), dt(2015, 1, 3)], 'col2': [100, 200, 300]}
df = pd.DataFrame(data=d)
p = figure(plot_width=400, plot_height=400)
p.line(df.col1, df.col2)
p.xaxis.formatter = DatetimeTickFormatter(days=f"%m/%d %H:%M",
months="%m/%d %H:%M",
hours="%m/%d %H:%M",
minutes="%m/%d %H:%M:%S",
minsec="%m/%d %H:%M:%S",
seconds="%m/%d %H:%M:%S")
p.xaxis.major_label_orientation = pi/4
show(p)
I would like to modify the date time ticker class to allow additional data in tick labels. For example, I would like to see the corresponding data in "col2" whenever bokeh places a tick label such as "1/01 00:00 - 100" or "1/01 12:00". There is no added data on second example since it is not a point in the source. For the second example, an interpolation is also acceptable as the data frequency is high in real data set.
Bokeh is actually two separate libraries:
Bokeh the Python Package
A JavaScript library BokehJS
Being a web-plotting tool, the Python part of Bokeh is actually just a very thin wrapper that drives the BokehJS component in the browser. All of the actual work an implementation is in BokehJS (written in TypeScript). Accordingly, it's generally not possible to make pure-Python changes or edits or subclasses to Bokeh that would have much or any effect at all.
It is possible to create custom extensions to Bokeh by supplying your own Javascript implementation of new Bokeh object subclasses. There is in fact specifically an example of a custom tick formatter at the bottom of that docs page. SO is not a code-writing site or service, but if you attempt to create your own custom extension and get stuck, you can provide the complete code for what you try, and ask specific, pointed questions about how to fix that existing code.

floris.tools.power_rose.load() requires class instantiation

I am writing a script to load a PowerRose object from a file I pickled previously using floris.tools.power_rose.PowerRose.save(). The script looks like this:
# General modules
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
# FLORIS-specific modules
import floris.tools as wfct
import floris.tools.power_rose as pr
power_rose = pr.PowerRose(name, df_power, df_turbine_power_no_wake, df_turbine_power_baseline)
power_rose.load(filename = "PowerRose_All.p")
However, as is clear from the last two lines I have to instantiate the PowerRose class in order to load a PowerRose instance from a pickled PowerRose, which seems to me to be a causality problem. The only solution I can think of would be to create a DataFrames of the same size as "PowerRose_All.p" filled with zeros to use in the instatiation.
Yes, you need to instantiate a power_rose object before you can use the load method. However, you do not need to supply DataFrames to do this. This could be accomplished by:
import floris.tools.power_rose as pr
power_rose = pr.PowerRose()
power_rose.load(filename="PowerRose_All.p")

update chart with ipywidgets:

I'm using seaborn on jupyter notebook and would like a slider to update a chart. My code is as follows:
from ipywidgets import interact, interactive, fixed, interact_manual
import numpy as np
import seaborn as sns
from IPython.display import clear_output
def f(var):
print(var)
clear_output(wait=True)
sns.distplot(list(np.random.normal(1,var,1000)))
interact(f, var=10);
Problem: every time I move the slider, the graph is duplicated. How do I update the chart instead?
Seaborn plots should be handled as regular matplotlib plot. So you need to use plt.show() to display it as explained in this answer for example.
Combined with %matplotlib inline magic command, this works fine for me:
%matplotlib inline
from ipywidgets import interact
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
def f(var):
sns.distplot(np.random.normal(1, var, 1000))
plt.show()
interact(f, var = (1,10))
Another solution would be to update the data of the plot instead of redrawing a new one, as explained here: https://stackoverflow.com/a/4098938/2699660

Can I draw an annotation in bokeh with data from a ColumnDataSource?

I'd like to draw a vertical line on my Bokeh plot which gets moved around by javascript in the browser at runtime. (It's a timebar that marks the current time on a time series plot.)
For drawing a static vertical line, I'm using:
from bokeh.models import Span
timebar = Span(location=where_I_want_the_timebar, dimension=height)
my_figure.add_layout(timebar)
In order to enable the interactivity, I think I need to get the location from a ColumnDataSource. However, I can't figure out how to do that, because Span does not accept a source argument.
Alternatively, is there another way for me to move the timebar at runtime?
I found a workaround. In python:
from bokeh.models import Span
timebar = Span(name='timebar' location=where_I_want_the_timebar, dimension=height)
my_figure.add_layout(timebar)
Then in javascript in the browser:
let timebar = Bokeh.documents[0].get_model_by_name('timebar')
timebar.attributes.location = my_new_desired_location
timebar.change.emit()
If someone posts a way to use a ColumnDataSource I will accept that answer.

Resources