Show/Hide Bokeh widgets based on another widget - python

I have a checkbox A that has two options, and I want to display different widgets based on the options. So when click option 1 only widgets B shows, and click option 2 only widget C shows. I was able to hide widgets B and C with css_classes = ['hidden'] but don't know how to make them show conditionally. I figured it to be something like this:
if 0 in Checkbox_A.active:
wiget_B.show()
else:
widget_C.show()

In Bokeh, you can either link the whole models together or link signals. Assuming you're using bokeh serve, something like this will work:
from bokeh.io import curdoc
from bokeh.layouts import column
from bokeh.models import Div, CheckboxGroup
d1 = Div(text="First widget")
d2 = Div(text="Second widget", visible=False)
c = CheckboxGroup(labels=["Display the first widget"], active=[0])
def checkbox_changed(attr, old, new):
b = (new == [0])
d1.visible = b
d2.visible = not b
c.on_change('active', checkbox_changed)
curdoc().add_root(column(c, d1, d2))
But it's possible to do something like that purely on the JavaScript side.

Related

Use Holoviz Panel Dropdown value to query dataframe

I am trying to use a Holoviz Panel dropdown widget value to query a dataframe. The dataframe however does not reflect the change in the dropdown value. I added a markdown widget to check if the change in the dropdown value is being captured - It seems to be. However, I can't figure out how to update the dataframe. I am a complete beginner to programming, just trying to learn. Any help is appreciated.
import pandas as pd
import panel as pn
pn.extension()
# Dataframe
df = pd.DataFrame({'CcyPair':['EUR/USD', 'AUD/USD' ,'USD/JPY'],
'Requester':['Client1', 'Client2' ,'Client3'],
'Provider':['LP1', 'LP2' ,'LP3']})
# Dropdown
a2 = pn.widgets.Select(options=list(df.Provider.unique()))
# Query dataframe based on value in Provider dropdown
def query(x=a2):
y = pn.widgets.DataFrame(df[(df.Provider==x)])
return y
# Test Markdown Panel to check if the dropdown change returns value
s = pn.pane.Markdown(object='')
# Register watcher and define callback
w = a2.param.watch(callback, ['value'], onlychanged=False)
def callback(*events):
print(events)
for event in events:
if event.name == 'value':
df1 = query(event.new)
s.object = event.new
# Display Output
pn.Column(query, s)
Output Image
Inspired by the self-answer, the following code produces a select box containing the list of providers and a dataframe filtered on that selection. It was tested on Panel version 0.13.1.
Note that the watch=True suggestion in the self-answer wasn't necessary.
import pandas as pd
import panel as pn
pn.extension()
# Dataframe
df = pd.DataFrame({
'CcyPair':['EUR/USD', 'AUD/USD' ,'USD/JPY'],
'Requester':['Client1', 'Client2' ,'Client3'],
'Provider':['LP1', 'LP2' ,'LP3']
})
# Dropdown
providers = list(df.Provider.unique())
select_widget = pn.widgets.Select(options=providers)
# Query dataframe based on value in Provider dropdown
#pn.depends(select_widget)
def query(x):
filtered_df = pn.widgets.DataFrame(df[df.Provider==x])
return filtered_df
# Display Output
pn.Column(select_widget, query)
Figured it out, turned out I just needed to add #pn.depends above my query function. Once I added pn.depends(a2, watch=True), the dataframe was filtered based on a2 input. The callback and watcher were unnecessary.

How to display two different legends in hconcat chart using altair

I need to display two separate charts side by side including their legends in Jupyterlab and the only way I managed to do that was using hconcat.
I've gotten this far:
However even with .resolve_legend(color='independent') I get the entries from both charts displayed in both legends at the top - which is mighty confusing.
The result should look like this:
How can I remove the unwanted legend entries?
Or if anyone knows a good alternative how to have to charts side-by-side in a single jupyterlab cell I would be happy to take a different route.
My code looks like this:
import altair as alt
import pandas as pd
from altair.expr import datum
df_test=pd.read_csv("test_df.csv")
chart_m1=alt.Chart(df_test).mark_bar().encode(
x=alt.X('counts:Q', stack="normalize",axis=None),
y=alt.Y('category:N',sort=['A','B','C'],title=None),
color=alt.Color('grade:N',
sort = alt.EncodingSortField( 'sort:Q', order = 'ascending' ),
scale = alt.Scale(domain=['good <10', 'average 10-20', 'bad >20'], range=['#0cce6b', '#ffa400', '#ff4e42']),
legend = alt.Legend(title="Metric1",orient='top')),
order='sort:Q',
tooltip=['category:N','grade:N','counts:Q']
).transform_filter(datum.metric=='metric1'
).properties(height=50,width=150)
chart_m2=alt.Chart(df_test).mark_bar().encode(
x=alt.X('counts:Q', stack="normalize",axis=None),
y=alt.Y('category:N',sort=['A','B','C'],title=None),
color=alt.Color('grade:N',
sort = alt.EncodingSortField( 'sort:Q', order = 'ascending' ),
scale = alt.Scale(domain=['good <100', 'average 100-350', 'bad >350'], range=['#0cce6b', '#ffa400', '#ff4e42']),
legend = alt.Legend(title="Metric2",orient='top')),
order='sort:Q',
tooltip=['category:N','grade:N','counts:Q']
).transform_filter(datum.metric=='metric2'
).properties(height=50,width=150)
alt.hconcat(chart_m1,chart_m2).resolve_legend(color='independent').configure_view(stroke=None)
The test_df.csv I used is this:
category,metric,sort,grade,counts
A,metric1,1,good <10,345
B,metric1,1,good <10,123
C,metric1,1,good <10,567
A,metric1,2,average 10-20,567
B,metric1,2,average 10-20,678
C,metric1,2,average 10-20,789
A,metric1,3,bad >20,900
B,metric1,3,bad >20,1011
C,metric1,3,bad >20,1122
A,metric2,1,good <100,1122
B,metric2,1,good <100,1011
C,metric2,1,good <100,900
A,metric2,2,average 100-350,789
B,metric2,2,average 100-350,678
C,metric2,2,average 100-350,567
A,metric2,3,bad >350,567
B,metric2,3,bad >350,345
C,metric2,3,bad >350,123
Use resolve_scale(color='independent')
alt.hconcat(
chart_m1, chart_m2
).resolve_scale(
color='independent'
).configure_view(
stroke=None
)
More information at https://altair-viz.github.io/user_guide/scale_resolve.html

Using to_html() to display a dataframe [duplicate]

I am using iPython notebook. When I do this:
df
I get a beautiful table with cells. However, if i do this:
df1
df2
it doesn't print the first beautiful table. If I try this:
print df1
print df2
It prints out the table in a different format that spills columns over and makes the output very tall.
Is there a way to force it to print out the beautiful tables for both datasets?
You'll need to use the HTML() or display() functions from IPython's display module:
from IPython.display import display, HTML
# Assuming that dataframes df1 and df2 are already defined:
print "Dataframe 1:"
display(df1)
print "Dataframe 2:"
display(HTML(df2.to_html()))
Note that if you just print df1.to_html() you'll get the raw, unrendered HTML.
You can also import from IPython.core.display with the same effect
from IPython.display import display
display(df) # OR
print df.to_html()
This answer is based on the 2nd tip from this blog post: 28 Jupyter Notebook tips, tricks and shortcuts
You can add the following code to the top of your notebook
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"
This tells Jupyter to print the results for any variable or statement on it’s own line. So you can then execute a cell solely containing
df1
df2
and it will "print out the beautiful tables for both datasets".
I prefer not messing with HTML and use as much as native infrastructure as possible. You can use Output widget with Hbox or VBox:
import ipywidgets as widgets
from IPython import display
import pandas as pd
import numpy as np
# sample data
df1 = pd.DataFrame(np.random.randn(8, 3))
df2 = pd.DataFrame(np.random.randn(8, 3))
# create output widgets
widget1 = widgets.Output()
widget2 = widgets.Output()
# render in output widgets
with widget1:
display.display(df1)
with widget2:
display.display(df2)
# create HBox
hbox = widgets.HBox([widget1, widget2])
# render hbox
hbox
This outputs:
In order to show the DataFrame in Jupyter Notebook just type:
display(Name_of_the_DataFrame)
for example:
display(df)
It seems you can just display both dfs using a comma in between in display.
I noticed this on some notebooks on github. This code is from Jake VanderPlas's notebook.
class display(object):
"""Display HTML representation of multiple objects"""
template = """<div style="float: left; padding: 10px;">
<p style='font-family:"Courier New", Courier, monospace'>{0}</p>{1}
</div>"""
def __init__(self, *args):
self.args = args
def _repr_html_(self):
return '\n'.join(self.template.format(a, eval(a)._repr_html_())
for a in self.args)
def __repr__(self):
return '\n\n'.join(a + '\n' + repr(eval(a))
for a in self.args)
display('df', "df2")
You can use markdown to create a table. You'll be asked to install tabulate package first if it is not yet available.
from IPython.display import display, Markdown
display(Markdown(df.to_markdown()))
To display dataframes contained in a list:
dfs = [df1, df2]
display(*dfs)
From my other answer,
if you want to using option, you can use context manager combining the display:
from IPython.display import display
with pd.option_context('precision', 3):
display(df1)
display(df2)

How to format a bokeh DataTable cell based on another column value?

Salutations.
I am developing an application using bokeh server (version 0.12.13) and I have a DataTable widget with several columns. One of them are a measure of an issue open days and another is an estimate of days to close such issue.
In some situations, the amount of days that an issue is open surpasses the estimated amount and I would like to colour the estimated days column red if that happens.
I have tried using "widget.HTMLTemplateFormatter", but I haven't figured out how to access another column value to make the comparison and decide whether paint the cell red or not.
Does anyone know how to get around this?
You can either define a javascript function within the underscore js code to conditionally color each cell. Each of the fields within the data source linked to the table can be accessed.
from bokeh.models import ColumnDataSource
from bokeh.models.widgets import DataTable, TableColumn, HTMLTemplateFormatter
from bokeh.io import show
dict1 = {'estd':[1]*6,
'actd':[1, 1, 1, 2, 2, 2],
'z' :[3, 3, 3, 3, 3, 3]}
source = ColumnDataSource(data=dict1)
template="""
<b><div style="background:<%=
(function colorfromint(){
if(actd > estd){
return("Red")
}
else{
return("White")
}
}()) %>;">
<%= (value).toFixed(1) %></div></b>
"""
formater = HTMLTemplateFormatter(template=template)
columns = [
TableColumn(field="estd", title="Estimated Days"),
TableColumn(field="actd", title="Actual days",formatter=formater),
TableColumn(field="z", title="z")
]
data_table = DataTable(source=source, columns=columns, width=800)
show(data_table)
If the data does not change you can define the colors using python code, see the second example here: How do I adjust number format in bokeh data table using HTMLTemplateFormatter?.

Jupyter Notebook Widgets: Create dependent dropdowns

I want to create 2 dropdown widgets in my Jupyter Notebook. The dropdown content is taken from a dataframe.
Let's say I have a pandas dataframe consisting of 3 categorical variables 'a', 'b', 'c'. 'a' has 3 subtypes 'a1','a2' and 'a3'. 'b' and 'c' are similar to a in the sense that they also have their own subtypes. I want to create 2 dropdown widgets: the first dropdown widget will have ['a','b','c'], and the second dropdown widget will display subtypes depending on what variable the user selects for the first widget.
I honestly have any idea how to do this. I'll try to write out some codes for this:
import pandas as pd
from IPython.display import *
import ipywidgets as widgets
from ipywidgets import *
# Create the dataframe
df = pd.DataFrame([['a1','a2','a3'],
['b1','b2','b3'],
['c1','c2','c3']], index = ['a','b','c']).transpose()
# Widgets
widget1 = Dropdown(options = ['a','b','c'])
display(widget1)
widget2 = Dropdown(???????)
display(widget2)
And depending on what I select for the two dropdown widgets, I want some function executed.
Any help is appreciated.
I found out how to do this. I hope this helps for anyone else who's also looking to do the same thing.
x_widget = Dropdown(options = ['a','b','c'])
y_widget = Dropdown()
# Define a function that updates the content of y based on what we select for x
def update(*args):
y_widget.options = df[x_widget.value].unique().tolist()
x_widget.observe(update)
# Some function you want executed
def random_function():
...
interact(random_function,
x = x_widget,
y = y_widget);

Categories