Creating map with date slider to show the progression of values - python

I am trying to create a map showing the progressive lockdown measures globally using a date slider. So far I was able to create the map for a static date but my attempt at the slider doesn't work because it seems to only accept integers or floats instead of a date range (Date_slider = pnw.IntSlider(start=0,end=365,value=0)
Essentially, i want to create the map found here: https://www.bsg.ox.ac.uk/research/research-projects/coronavirus-government-response-tracker
import pandas as pd
import geopandas as gpd
import json
import matplotlib as mpl
import pylab as plt
from bokeh.io import output_file, show, output_notebook, export_png
from bokeh.models import ColumnDataSource, GeoJSONDataSource, LinearColorMapper, ColorBar
from bokeh.plotting import figure
from bokeh.palettes import brewer
import panel as pn
import panel.widgets as pnw
import descartes
output_notebook()
pn.extension()
Read shape file
shapefile = '/Users/sam/Desktop/ne_110m_admin_0_countries/ne_110m_admin_0_countries.shp'
gdf = gpd.read_file(shapefile)[['ADMIN', 'ADM0_A3', 'geometry']]
gdf.columns = ['country', 'country_code', 'geometry']
gdf = gdf.drop(gdf.index[159])
Read data
def get_dataset(name,key=None,Date=None):
df = pd.read_csv(r'/Users/sam/Desktop/Big Data Project/covid-stringency-index-2.csv')
if Date is not None:
df = df[df['Date'] == Date]
#Merge dataframes gdf and df_2016.
if key is None:
key = df.columns[2]
merged = gdf.merge(df, left_on = 'country', right_on = 'Entity', how = 'left')
merged[key] = merged[key].fillna(0)
return merged, key
plot with matplotlib
datasetname='Lockdown Measures'
df,key = get_dataset(datasetname, Date='Jul 10, 2020')
fig, ax = plt.subplots(1, figsize=(14, 8))
df.plot(column=key, cmap='OrRd', linewidth=1.0, ax=ax, edgecolor='Black')
ax.axis('off')
ax.set_title('%s Jul 10, 2020' %datasetname, fontsize=25)
plt.tight_layout()
plt.savefig('test_map.png',dpi=150)
Output: https://i.stack.imgur.com/6GrbF.png
Plot with Bokeh
def get_geodatasource(gdf):
"""Get getjsondatasource from geopandas object"""
json_data = json.dumps(json.loads(gdf.to_json()))
return GeoJSONDataSource(geojson = json_data)
def map_dash():
"""Map dashboard"""
from bokeh.models.widgets import DataTable
map_pane = pn.pane.Bokeh(width=600)
data_select = pnw.Select(name='dataset',options=list(df))
Date_slider = pnw.IntSlider(start=0,end=365,value=0)
def update_map(event):
gdf,key = get_dataset(name=data_select.value,Date=Date_slider.value)
map_pane.object = bokeh_plot_map(gdf, key)
return
Date_slider.param.watch(update_map,'value')
Date_slider.param.trigger('value')
data_select.param.watch(update_map,'value')
app = pn.Column(pn.Row(data_select,Date_slider),map_pane)
return app
app = map_dash()
app

You have to use a pn.widgets.DateRangeSlider(...)
https://panel.holoviz.org/reference/widgets/DateRangeSlider.html#widgets-gallery-daterangeslider

Related

In Bokeh, how can I display different information for points and patches?

I want to display different information for different layers (points and patches) using bokeh.
I downloaded the shapefile and the population information of Haitian cities respectively from here and from here and I merged them.
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import geopandas as gpd
import osmnx as ox
from bokeh.layouts import row, column
from bokeh.models import Select
from bokeh.palettes import Spectral5
from bokeh.plotting import curdoc, figure, save
from bokeh.sampledata.autompg import autompg_clean as df
from bokeh.io import show
from bokeh.models import LogColorMapper
from bokeh.palettes import Viridis6 as palette
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource
from bokeh.sampledata.us_counties import data as counties
from bokeh.sampledata.unemployment import data as unemployment
import pandas as pd
import geopandas as gpd
import shapely
color_mapper = LogColorMapper(palette=palette)
Some functions
def getPolyCoords(row, geom, coord_type):
"""Returns the coordinates ('x' or 'y') of edges of a Polygon exterior"""
# Parse the exterior of the coordinate
exterior = row[geom].exterior
if coord_type == 'x':
# Get the x coordinates of the exterior
return list( exterior.coords.xy[0] )
elif coord_type == 'y':
# Get the y coordinates of the exterior
return list( exterior.coords.xy[1] )
def getPointCoords(row, geom, coord_type):
"""Calculates coordinates ('x' or 'y') of a Point geometry"""
if coord_type == 'x':
return row[geom].x
elif coord_type == 'y':
return row[geom].y
Cities data
haiti = gpd.read_file(hti_admbnda_adm2_cnigs_20181129.shp')
haiti = haiti.to_crs({'init': 'epsg:32618'})
haiti = haiti[haiti.index != 98].reset_index(drop=True) ## i=98 is corrupted
pop = pd.read_csv('hti_admnbnda_adm2_cnigs2013c.csv')
level = 2
left = 'adm%dcode'%level
right = 'ADM%d_PCODE'%level
h_geom = pd.merge(pop, haiti, left_on=left, right_on=right)
Then I created a data for bokeh
grid = pd.DataFrame()
grid['x'] = h_geom.apply(getPolyCoords, geom='geometry', coord_type='x', axis=1)
grid['y'] = h_geom.apply(getPolyCoords, geom='geometry', coord_type='y', axis=1)
grid['Name'] = h_geom['adm2_en']
grid['Population'] = h_geom['TOTAL']
data=dict(
x=list(grid['x'].values),
y=list(grid['y'].values),
name=list(grid['Name'].values),
rate=list(grid['Population'].values),
)
From osmnx I get points of schools
selected_amenities = ['school']
place = 'Haiti'
schoolOSM = ox.pois_from_place(place=place, amenities=selected_amenities)
schools = gpd.GeoDataFrame(schoolOSM)
idxok = []
for i in schools.index:
if type(schools['geometry'][i]) == shapely.geometry.point.Point:
idxok.append(i)
schools = schools[schools.index.isin(idxok)]
schools['x'] = schools.apply(getPointCoords, geom='geometry', coord_type='x', axis=1)
schools['y'] = schools.apply(getPointCoords, geom='geometry', coord_type='y', axis=1)
data1=dict(
x=list(schools['x'].values),
y=list(schools['y'].values),
)
Then I want to show the information: I would like to show Name, Population and coordinates for cities while only coordinates for schools.
TOOLS = "pan,wheel_zoom,reset,hover,save"
p = figure(title="Schools Point in Haiti", tools=TOOLS,
x_axis_location=None, y_axis_location=None,
tooltips=[("Name", "#name"), ("Population", "#rate"), ("(Long, Lat)", "($x, $y)")])
p.hover.point_policy = "follow_mouse"
p.patches('x', 'y', source=data,
fill_color={'field': 'rate', 'transform': color_mapper},
fill_alpha=1.0, line_color="black", line_width=1)
# Add points on top (as black points)
p.circle('x', 'y', size=3, source=data1, color="black")
show(p)
In doing so I get the information of Name, Population, Long, Lat for both Schools and Cities. But Schools do not have the info Name and Population, so I get something like
You need to create two separate data sources and two separate HoverTools.
from bokeh.models import HoverTool
data_cities = dict(x = list(cities['x'].values), y = list(cities['y'].values))
data_schools = dict(x = list(schools['x'].values), y = list(schools['y'].values))
cities = p.circle('x', 'y', size = 3, source = data_cities, color = "green")
schools = p.circle('x', 'y', size = 3, source = data_schools, color = "blue")
hover_cities = HoverTool(renderers = [cities], tooltips = [("Name", "#name"), ("Population", "#rate"), ("(Long, Lat)", "($x, $y)")]))
hover_schools = HoverTool(renderers = [schools], tooltips = [("(Long, Lat)", "($x, $y)")]))
p.add_tools(hover_cities)
p.add_tools(hover_schools)

Bokeh figure doesn't updates on dropdown.on_change

I have some problem with updating Bokeh plot. It's simple piece of code, one figure with one curve and one dropdown, which can changes time period, 7,10 and 30 days. When i change dropdown value, nothing happens.
I already have gone through various articles, but i didn't find clear answer for me.
Code example is presented below.
Thanks
from bokeh.plotting import figure
from bokeh.layouts import row
from bokeh.io import curdoc
from bokeh.models import ColumnDataSource
from bokeh.models.widgets import Dropdown
from pandas_datareader import data
import datetime
TIME_PERIOD = 30
def get_data(period):
today = datetime.date.today()
timedelta = datetime.timedelta(days=period)
start = today - timedelta
df = data.DataReader(name="BTC-USD", data_source="yahoo", start=start)
dates = df.loc[str(start):str(today)].index
y = df["Volume"]
data1 = dict(
xaxis=dates,
yaxis=y
)
source = ColumnDataSource(data1)
return source
def update_date(attr, old, new):
global TIME_PERIOD
temp = new
TIME_PERIOD = int(temp)
def get_plot(data_source):
p = figure(title="Cryptocurrencies volumes", x_axis_label="Дни", y_axis_label="Volume 24hr",
x_axis_type="datetime")
p.line(x="xaxis", y="yaxis", color="green", source=data_source)
return p
dropdown_menu = [("7","7"),("10","10"),("30","30")]
dropdown = Dropdown(label="Выбор временного интервала",button_type="success",menu=dropdown_menu, value="30")
dropdown.on_change("value", update_date)
data1 = get_data(TIME_PERIOD)
plot = get_plot(data1)
image = row(dropdown,plot)
curdoc().add_root(image)
curdoc().title = "Plot"
Simply setting your time period is not enough. You have to call the get_data() function again and set the data of the ColumnDataSource it returns as the data of the ColumnDataSource that is used by your line glyph.
from bokeh.plotting import figure
from bokeh.layouts import row
from bokeh.io import curdoc
from bokeh.models import ColumnDataSource
from bokeh.models.widgets import Dropdown
from pandas_datareader import data
import datetime
TIME_PERIOD = 30
def get_data(period):
today = datetime.date.today()
timedelta = datetime.timedelta(days=period)
start = today - timedelta
df = data.DataReader(name="BTC-USD", data_source="yahoo", start=start)
dates = df.loc[str(start):str(today)].index
y = df["Volume"]
data1 = dict(
xaxis=dates,
yaxis=y
)
source = ColumnDataSource(data1)
return source
def update_date(attr, old, new):
TIME_PERIOD = int(new)
newdata = get_data(TIME_PERIOD)
source.data = newdata.data
dropdown_menu = [("7","7"),("10","10"),("30","30")]
dropdown = Dropdown(label="Выбор временного интервала",button_type="success",menu=dropdown_menu, value="30")
dropdown.on_change("value", update_date)
source = get_data(TIME_PERIOD)
p = figure(title="Cryptocurrencies volumes", x_axis_label="Дни", y_axis_label="Volume 24hr",
x_axis_type="datetime")
p.line(x="xaxis", y="yaxis", color="green", source=source)
image = row(dropdown,p)
curdoc().add_root(image)
curdoc().title = "Plot"

Points plotted by DateTimeTick Formatter in a bokeh scatter plot move in xaxis

When using Bokeh plot I find the following issues:
1) The plot does not show the points immediately.
2) When I zoom out using mouse wheel 3 times the points become visible.
3) When I zoom out 7 times the points are shifted to the next/previous minute(In my case they are between 40m:54s and 41m originally after 7th zoom they go to 40:38 to 40:44)
I have tried setting
g.x_range.range_padding = 0.1
to 0 with no luck
import pandas as pd
import bokeh
from bokeh.plotting import *
from bokeh.io import output_file,show,save
from bokeh.resources import CDN,INLINE
from bokeh.embed import file_html
from bokeh.models.ranges import *
from bokeh.palettes import Spectral6
from bokeh.transform import factor_cmap
from bokeh.transform import dodge
from bokeh.core.properties import value
from bokeh.embed import components
from bokeh.layouts import row,column
from bokeh.models import DatetimeTickFormatter
myPandas = pd.read_pickle("myPanda.pickle")
source=ColumnDataSource(data=myPandas)
yaxis="yaxis"
xaxis="xaxis"
def getTitle(graphDet):
return graphDet
graphDet="Dummy"
g = figure(plot_width=450, plot_height=300, y_axis_label=yaxis, x_axis_label=xaxis, output_backend="webgl", title=getTitle(graphDet), x_axis_type="datetime")
x="time"
y="col1"
g.circle(myPandas[x],myPandas[y], size=5,legend=value(y))
g.xaxis[0].formatter=DatetimeTickFormatter(milliseconds = ['%3Nms']
,seconds = ['%Ss']
)
g.x_range.range_padding = 0.1
g.xgrid.grid_line_color = None
g.legend.location = "top_right"
g.legend.orientation = "vertical"
show(g)
The pickle file for input can be found in
https://www.dropbox.com/s/4fe11kdu00nbcjp/myPanda.pickle?dl=0
My expectation is the plot must be visible right from the start.It must not jump across time.
Looks like a bug introduced wheh webgl is used. Removing it solves the problem but is this acceptable for you? (tested on bokeh v1.0.4)
import pandas as pd
import bokeh
import numpy as np
from bokeh.plotting import *
from bokeh.io import output_file, show, save
from bokeh.resources import CDN, INLINE
from bokeh.embed import file_html
from bokeh.models.ranges import *
from bokeh.palettes import Spectral6
from bokeh.transform import factor_cmap
from bokeh.transform import dodge
from bokeh.core.properties import value
from bokeh.embed import components
from bokeh.layouts import row, column
from bokeh.models import DatetimeTickFormatter
from datetime import datetime, timedelta
d_start = datetime(2016, 6, 1)
d_step = timedelta(milliseconds = 100)
t = [d_start + (i * d_step) for i in range(0, 100)]
myPandas = pd.DataFrame(columns = ['time', 'col1'], data = {'time': t, 'col1': np.arange(100)}, index = t)
source = ColumnDataSource(data = myPandas)
def getTitle(graphDet):
return graphDet
graphDet = "Dummy"
g = figure(plot_width = 450, plot_height = 300, y_axis_label = "yaxis", x_axis_label = "xaxis", title = getTitle(graphDet), x_axis_type = "datetime")
x = "time"
y = "col1"
g.circle(myPandas[x], myPandas[y], size = 5, legend = value(y))
g.xaxis[0].formatter = DatetimeTickFormatter(seconds = ['%Ss'], milliseconds = ['%3Nms'])
g.x_range.range_padding = 0.1
g.xgrid.grid_line_color = None
g.legend.location = "top_right"
g.legend.orientation = "vertical"
show(g)

Bokeh plot not showing with show(p) or p.show()

Trying to get a Bokeh chart to show with this code, I either get nothing if I use show(p) or
AttributeError: 'Figure' object has no attribute 'show'
How can I fix this?
from math import pi
import pandas as pd
from bokeh.plotting import figure, show, output_notebook
from bokeh.models.annotations import Title
from nsepy import get_history
from datetime import date
from datetime import datetime
from pykalman import KalmanFilter
df = get_history(symbol="TCS", start = date(2018,1,1),end = date(2018,7,22))
print(df)
kf = KalmanFilter(transition_matrices = [1],
observation_matrices = [1],
initial_state_mean = df['Close'].values[0],
initial_state_covariance = 1,
observation_covariance=1,
transition_covariance=.01)
state_means,_ = kf.filter(df[['Close']].values)
state_means = state_means.flatten()
df["date"] = pd.to_datetime(df.index)
mids = (df.Open + df.Close)/2
spans = abs(df.Close-df.Open)
inc = df.Close > df.Open
dec = df.Open > df.Close
w = 12*60*60*1000 # half day in ms
output_notebook()
TOOLS = "pan,wheel_zoom,box_zoom,reset,save"
#This causes an exception tol with p.show() no show in figure
p = figure(x_axis_type="datetime", tools=TOOLS, plot_width=1000, toolbar_location="left",y_axis_label = "Price",
x_axis_label = "Date")
p.segment(df.date, df.High, df.date, df.Low, color="black")
p.rect(df.date[inc], mids[inc], w, spans[inc], fill_color='green', line_color="green")
p.rect(df.date[dec], mids[dec], w, spans[dec], fill_color='red', line_color="red")
p.line(df.date,state_means,line_width=1,line_color = 'blue',legend="Kalman filter")
t = Title()
t.text = 'Kalman Filter Estimation'
p.title = t
p.xaxis.major_label_orientation = pi/4
p.grid.grid_line_alpha=0.3
p.show() #Throws attribute error show does not exist
#show(p) #Nothing happens on this
Here is the the code I added to the start to get visualization working. I often found that using output_notebook alone will sometimes still not be enough to get show() to display figures as it should. This is a work around using INLINE which has not failed for me yet.
# allows visualisation in notebook
from bokeh.io import output_notebook
from bokeh.resources import INLINE
output_notebook(INLINE)
<your code>
show(p)
There is no show method on plots in Bokeh, and never has been. There is a show function that you can pass plots (or layouts of plots and widgets) to.
from bokeh.io import output_file, show
from bokeh.plotting import figure
p = figure(...)
p.circle(...)
output_file("foo.html")
show(p)
This is the very first thing explained in the Quickstart: Getting Started section and this pattern is also repeated in hundreds of examples throughout the docs.
from math import pi
import pandas as pd
from bokeh.plotting import figure, show, output_file
from bokeh.models.annotations import Title
from nsepy import get_history
from datetime import date
from datetime import datetime
from pykalman import KalmanFilter
df = get_history(symbol="TCS", start = date(2018,1,1),end = date(2018,7,22))
print(df)
kf = KalmanFilter(transition_matrices = [1],
observation_matrices = [1],
initial_state_mean = df['Close'].values[0],
initial_state_covariance = 1,
observation_covariance=1,
transition_covariance=.01)
state_means,_ = kf.filter(df[['Close']].values)
state_means = state_means.flatten()
df["date"] = pd.to_datetime(df.index)
mids = (df.Open + df.Close)/2
spans = abs(df.Close-df.Open)
inc = df.Close > df.Open
dec = df.Open > df.Close
w = 12*60*60*1000 # half day in ms
#output_notebook()
#please note in the import statement above, I have changed it from
output_notebook to output_file
output_file=("TCS.html", title = "Kalman Filter Estimation", mode="cdn")
TOOLS = "pan,wheel_zoom,box_zoom,reset,save"
#This causes an exception tol with p.show() no show in figure
p = figure(x_axis_type="datetime", tools=TOOLS, plot_width=1000,
toolbar_location="left",y_axis_label = "Price",
x_axis_label = "Date")
p.segment(df.date, df.High, df.date, df.Low, color="black")
p.rect(df.date[inc], mids[inc], w, spans[inc], fill_color='green',
line_color="green")
p.rect(df.date[dec], mids[dec], w, spans[dec], fill_color='red', line_color="red")
p.line(df.date,state_means,line_width=1,line_color = 'blue',legend="Kalman filter")
#t = Title()
#t.text = 'Kalman Filter Estimation'
#p.title = t
p.xaxis.major_label_orientation = pi/4
p.grid.grid_line_alpha=0.3
p.show()
This should open up the html file in google or edge or whichever is your default browser you have set
Your import is wrong:
change "from bokeh.plotting import output_notebook" to "from bokeh.io import output_notebook"

Bokeh server - How to manipulate a selection in a callback function

I am plotting several patches that are grouped by a category "group" in the data source. What would I like to achieve is the following: By clicking on one patch, not only the patch itself but all patches of the same group should be highlighted as selected.
I found that ColumnDataSource has an attribute selected. However, manipulating this attribute in the callback function does not have the desired effect.
import os
from bokeh.models import ColumnDataSource, Patches
from bokeh.plotting import figure
from bokeh.layouts import row
from bokeh.io import output_file, curdoc
import pandas as pd
x = [[1,2,4], [3,5,6], [7,9,7], [5,7,6]]
y = [[4,2,1], [6,5,8], [3,9,6], [2,2,1]]
group = ['A', 'A', 'B', 'B']
id = [0,1,2,3]
df = pd.DataFrame(data=dict(x=x, y=y, group=group, id=id))
source = ColumnDataSource(df)
p = figure(tools="tap")
renderer = p.patches('x', 'y', source=source)
# Event handler
def my_tap_handler(attr,old,new):
global source
group_name = source.data['group'][new['1d']['indices'][0]]
group_indices = df['id'][df['group'] == group_name]
source.selected.indices = list(group_indices)
print("source.selected.indices", source.selected.indices)
selected_patches = Patches(fill_color="#a6cee3")
renderer.selection_glyph = selected_patches
# Event
renderer.data_source.on_change("selected", my_tap_handler)
#######################################
# Set up layouts and add to document
curdoc().add_root(row(p, width=800))
You can do the selection in Javascript, but if you really want to do this in Python, here is an example:
import os
from bokeh.models import ColumnDataSource, Patches, CustomJS
from bokeh.plotting import figure
from bokeh.layouts import row
from bokeh.io import output_file, curdoc
import pandas as pd
def app(doc):
x = [[1,2,4], [3,5,6], [7,9,7], [5,7,6]]
y = [[4,2,1], [6,5,8], [3,9,6], [2,2,1]]
group = ['A', 'A', 'B', 'B']
id = [0,1,2,3]
df = pd.DataFrame(data=dict(x=x, y=y, group=group, id=id))
source = ColumnDataSource(df)
p = figure(tools="tap")
renderer = p.patches('x', 'y', source=source)
def my_tap_handler(attr,old,new):
indices = source.selected.indices
if len(indices) == 1:
group = source.data["group"][indices[0]]
new_indices = [i for i, g in enumerate(source.data["group"]) if g == group]
if new_indices != indices:
source.selected = Selection(indices=new_indices)
selected_patches = Patches(fill_color="#a6cee3")
renderer.selection_glyph = selected_patches
source.on_change("selected", my_tap_handler)
doc.add_root(row(p, width=800))
show(app)

Categories