Bokeh: Slider is not updating results on Hbar plot - widget

I wrote the following code for using a slider to filter and update values on a Hbar plot in Bokeh.
The plot (as shown in the picture) outputs correctly, but when I move the slider nothing happens.
I'd greatly appreciate any feedback.
import pandas as pd
from bokeh.core.properties import value
from IPython.display import display, HTML
from bokeh.plotting import figure, show
from bokeh.layouts import row, column, gridplot
from bokeh.io import output_notebook, save, curdoc
from bokeh.models import ColumnDataSource, HoverTool, DatetimeTickFormatter, FactorRange, DataTable, TableColumn, DateFormatter
from bokeh.models.widgets import Panel, Tabs, Slider
import matplotlib.pyplot as plt
xls=pd.ExcelFile(path)
test_data=pd.read_excel(xls, 'test_data')
display(test_data)
AREA counts
A 500
B 100
C 70
D 50
E 40
F 20
G 10
H 2
def myplot(doc):
source = ColumnDataSource(pd.DataFrame(data=test_data))
area_list=source.data['AREA'].tolist()[::-1]
# Creating the Bar Chart
p = figure(y_range=area_list ,plot_height=500, plot_width=500, title="Total counts per area",
x_axis_label='counts', y_axis_label='AREA')
p.hbar(y='AREA', right='counts', height=1,
line_color="black", fill_color='red',line_width=1,
source=source)
def update_plot(attr, old, new):
Number_of_counts = slider.value
new_data = test_data.loc[test_data['counts'] >=Number_of_counts]
source = ColumnDataSource(data=new_data)
# Make a slider object: slider
slider = Slider(start=1, end=100, step=1, value=1, title='counts')
# Attach the callback to the 'value' property of slider
slider.on_change('value', update_plot)
doc.add_root(column(slider, p))
show(myplot)

You're replacing the value of the source variable, but the old source is still there, being used by all the created models.
Instead of recreating the source, try to reassign the data attribute of the old source:
# source = ColumnDataSource(data=new_data)
source.data = ColumnDataSource.from_df(new_data)

Related

How to scrape only texts from specific HTML elements?

I have a problem with selecting the appropriate items from the list.
For example - I want to omit "1." then the first "5" (as in the example)
Additionally, I would like to write a condition that the letter "W" should be changed to "WIN".
import re
from selenium import webdriver
from bs4 import BeautifulSoup as BS2
from time import sleep
driver = webdriver.Chrome()
driver.get("https://www.flashscore.pl/druzyna/ajax/8UOvIwnb/tabela/")
sleep(10)
page = driver.page_source
soup = BS2(page,'html.parser')
content = soup.find('div',{'class':'ui-table__body'})
content_list = content.find_all('span',{"table__cell table__cell--value"})
res = []
for i in content:
line = i.text.split()[0]
if re.search('Ajax', line):
res.append(line)
print(res)
results
['1.Ajax550016:315?WWWWW']
I need
Ajax;5;5;0;16;3;W;W;W;W;W
I would recommend to select your elements more specific:
for e in soup.select('.ui-table__row'):
Iterate the ResultSet and decompose() unwanted tag:
e.select_one('.wld--tbd').decompose()
Extract texts with stripped_strings and join() them to your expected string:
data.append(';'.join(e.stripped_strings))
Example
Also making some replacements, based on dict just to demonstrate how this would work, not knowing R or P.
...
soup = BS2(page,'html.parser')
data = []
for e in soup.select('.ui-table__row'):
e.select_one('.wld--tbd').decompose()
e.select_one('.tableCellRank').decompose()
e.select_one('.table__cell--points').decompose()
e.select_one('.table__cell--score').string = ';'.join(e.select_one('.table__cell--score').text.split(':'))
pattern = {'W':'WIN','R':'RRR','P':'PPP'}
data.append(';'.join([pattern.get(i,i) for i in e.stripped_strings]))
data
To only get result for Ajax:
data = []
for e in soup.select('.ui-table__row:-soup-contains("Ajax")'):
e.select_one('.wld--tbd').decompose()
e.select_one('.tableCellRank').decompose()
e.select_one('.table__cell--points').decompose()
e.select_one('.table__cell--score').string = ';'.join(e.select_one('.table__cell--score').text.split(':'))
pattern = {'W':'WIN','R':'RRR','P':'PPP'}
data.append(';'.join([pattern.get(i,i) for i in e.stripped_strings]))
data
Output
Based on actually data it may differ from questions example.
['Ajax;6;6;0;0;21;3;WIN;WIN;WIN;WIN;WIN']
you had the right start by using bs4 to find the table div, but then you gave up and just tried to use re to extract from the text. as you can see that's not going to work. Here is a simple way to hack and get what you want. I keep grabinn divs from the table div you find, and the grab the text of the next eight divs after finding Ajax. then I do some dirty string manipulation thing because the WWWWW is all in the same toplevel div.
import re
from selenium import webdriver
from bs4 import BeautifulSoup as BS2
from time import sleep
from webdriver_manager.chrome import ChromeDriverManager
driver = webdriver.Chrome(ChromeDriverManager().install())
#driver = webdriver.Chrome()
driver.get("https://www.flashscore.pl/druzyna/ajax/8UOvIwnb/tabela/")
driver.implicitly_wait(10)
page = driver.page_source
soup = BS2(page,'html.parser')
content = soup.find('div',{'class':'ui-table__body'})
content_list = content.find_all('span',{"table__cell table__cell--value"})
res = []
found = 0
for i in content.find('div'):
line = i.text.split()[0]
if re.search('Ajax', line):
found = 8
if found:
found -= 1
res.append(line)
# change field 5 into separate values and skip field 6
res = res[:4] +res[5].split(':') + res[7:]
# break the last field into separate values and drop the first '?'
res = res[:-1] + [ i for i in res[-1]][1:]
print(";".join(res))
returns
Ajax;5;5;0;16;3;W;W;W;W;W
This works, but it is very brittle, and will break as soon as the website changes their content. you should put in a lot of error checking. I also replaced the sleep with a wait call, and added chromedrivermamager, which allows me to use selenium with chrome.

calling a function which shows sub category list in a select box using observe function is working only first time

I have simple df have main category and its sub items. I made two select boxes to select main category and it automatically shows belonged items at the other select box using 'observe'
But it works only at the first time category selection and after I selected another category it didn't show sub items in an another select box.
If i put 'observe' inside the 'def' the function is worked as I wanted.
But this approach is troublesome if I have many other 'def' executing on observe on change.
I am also looking for the way to make this code simple without using global.
And also welcome any ideas to make this code simpler and lighter.
import pandas as pd
import ipywidgets as wg
from ipywidgets import *
from IPython.display import display, clear_output
df_list=[{'Category':'fruit','name':['banana','apple','mango']},\
{'Category':'animal','name':['lion','monkey','tiger','cat','dog']},\
{'Category':'body','name':['eyes','hands','ears','arms']},\
{'Category':'office','name':['table','computer','printer']}]
df=pd.DataFrame(df_list)
Category_box=wg.Select(options=list(df.Category),continuous_update=False,layout=Layout(width='30%'))
name_box=wg.Select(options=list(df[df['Category']==Category_box.value].name)[0],continuous_update=False,\
layout=Layout(width='30%'))
hbox=wg.HBox(children=[Category_box,name_box])
display(hbox)
def select_on_change(change):
if change['name'] == 'value':
global Category_box
global name_box
global hbox
Category_box=wg.Select(options=list(df.Category),continuous_update=False,layout=Layout(width='30%'),value=Category_box.value)
name_box=wg.Select(options=list(df[df['Category']==Category_box.value].name)[0],continuous_update=False,\
layout=Layout(width='30%'))
hbox=wg.HBox(children=[Category_box,name_box])
clear_output()
display(hbox)
#Category_box.observe(select_on_change)
Category_box.observe(select_on_change)
Firstly, thanks for the complete and clear example.
The solution is to not create new widgets in your function that you are observing. Instead just update the .options of your name_box widget. This way, no globals required.
import pandas as pd
import ipywidgets as wg
from ipywidgets import *
from IPython.display import display, clear_output
df_list=[{'Category':'fruit','name':['banana','apple','mango']},\
{'Category':'animal','name':['lion','monkey','tiger','cat','dog']},\
{'Category':'body','name':['eyes','hands','ears','arms']},\
{'Category':'office','name':['table','computer','printer']}]
df=pd.DataFrame(df_list)
Category_box=wg.Select(options=list(df.Category),continuous_update=False,layout=Layout(width='30%'))
name_box=wg.Select(options=list(df[df['Category']==Category_box.value].name)[0],continuous_update=False,\
layout=Layout(width='30%'))
hbox=wg.HBox(children=[Category_box,name_box])
display(hbox)
def select_on_change(change):
if change['name'] == 'value':
name_box.options = list(df[df['Category']==Category_box.value].name)[0]
Category_box.observe(select_on_change)

Understand python code to create population pyramid

I've found this really cool plot (matplotlib visualizations) to make a population pyramid. I would like to change the condition for the colors. Instead of men/women, I want to have the whole bars in the same color and this color depends on the tag that I write on Gender. So, some whole bars in blue and the rest in purple.
But I don't understand how the colors are generated.
I imagined that removing the .unique() in df[group_col].unique() would work, but it doesn't plot anything when I switch the tag.
import numpy as np
import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns
# Read data
df = pd.read_csv("https://raw.githubusercontent.com/selva86/datasets/master/email_campaign_funnel.csv")
# Draw Plot
plt.figure(figsize=(13,10), dpi= 80)
group_col = 'Gender'
order_of_bars = df.Stage.unique()[::-1]
colors = [plt.cm.Spectral(i/float(len(df[group_col].unique())-1)) for i in range(len(df[group_col].unique()))]
for c, group in zip(colors, df[group_col].unique()):
sns.barplot(x='Users', y='Stage', data=df.loc[df[group_col]==group, :], order=order_of_bars, color=c, label=group)
# Decorations
plt.xlabel("$Users$")
plt.ylabel("Stage of Purchase")
plt.yticks(fontsize=12)
plt.title("Population Pyramid of the Marketing Funnel", fontsize=22)
plt.legend()
plt.show()
Pyramid plot
Any idea how to do that?

Using clear_output() with jupyter notebook widgets and google maps api

I am using jupyter notebook to do some mapping visualisation stuff with google maps (via the http://jupyter-gmaps.readthedocs.io/en/latest/gmaps.html library)
I want to be able to use the jupyter dropdown widget to pass it an updated string and get a new map based on this to appear on the page.
All of this works well (code below) but when I run the it, the clear_output() does not work. It works fine if I am not using google maps, like just printing something in the cell once when I change the dropdown.
Does anyone have any idea about this?
import ipywidgets as widgets
import gmaps
import pandas as pd
gmaps.configure(api_key="my api key...")
from IPython.display import display
from IPython.display import clear_output
# global scaling for circle size on map
global SCALING_NORMALIZATION
SCALING_NORMALIZATION = 30
#MAP FUNCTIONS
#function to change circle sizes on map
def create_circle_sizes(values):
scaling_values = []
for i in range(0, len(values)):
a = np.asscalar(values[i])
a = int(a / SCALING_NORMALIZATION)
if a == 0:
scaling_values.append(2)
else:
scaling_values.append(a)
return scaling_values
# function to create hover info on map
def create_hover_info(service_types, names):
hover_info = []
for i in range(0, len(service_types)):
hover_info.append(names[i])
return hover_info
#function to draw a map in gmaps
def create_map(value):
map_df = data[data['lga'] == value][['lat', 'long', 'lic_places', 'sta_name', 'distinct_se_type']]
scaling = map_df['lic_places'].tolist()
names = map_df['sta_name'].tolist()
service_types = map_df['distinct_se_type'].tolist()
map_layer = gmaps.symbol_layer(map_df[['lat', 'long']],
fill_color="blue",
stroke_color="blue",
scale=create_circle_sizes(scaling),
hover_text =
create_hover_info(service_types, names))
fig = gmaps.figure()
fig.add_layer(map_layer)
display(fig)
# WIDGET FUNCTIONS
# function to update the map when dropdown choice is changed
def update_map(args):
# clear_output() doesn't seem to work with map rendering
clear_output()
user_choice = args['new']
create_map(user_choice)
# dropdown widget
dd = widgets.Dropdown(
options=['auburn (c)', 'fairfield (c)', 'penrith (c)'],
value='fairfield (c)',
description='Number:',
disabled=False,
button_style=''
)
#display dropdown widget, with function it will call on change
dd.observe(update_map, 'value')

IPython notebook widgets using interactive

I'm having trouble creating widgets in a Jupyter notebook that update when other widget values are changed. This is the code I've been playing around with:
from ipywidgets import interact, interactive, fixed
import ipywidgets as widgets
from IPython.display import display
def func(arg1,arg2):
print arg1
print arg2
choice = widgets.ToggleButtons(description='Choice:',options=['A','B'])
display(choice)
metric = widgets.Dropdown(options=['mercury','venus','earth'],description='Planets:')
text = widgets.Text(description='Text:')
a = interactive(func,
arg1=metric,
arg2=text,
__manual=True)
def update(*args):
if choice.value == 'A':
metric = widgets.Dropdown(options=['mercury','venus','earth'],description='Planets:')
text = widgets.Text(description='Text:')
a.children = (metric,text)
else:
metric = widgets.Dropdown(options=['monday','tuesday','wednesday'],description='Days:')
text2 = widgets.Textarea(description='Text2:')
a.children = (metric,text2)
choice.observe(update,'value')
display(a)
The resulting widgets metric and text do change based whether A or B is selected, but the problem is that the "Run func" button goes away as soon as I change to B. I've tried adding the __manual attribute immediately before display(a), adding it within update, and several other places. How do I change the children of the widget box without overwriting the fact that I want to manually run the function?