import geopandas as gpd
import pandas as pd
import json
from bokeh.io import output_notebook, show, output_file, curdoc
from bokeh.plotting import figure
from bokeh.models import GeoJSONDataSource, LinearColorMapper, ColorBar, Slider, HoverTool
from bokeh.palettes import brewer
from bokeh.layouts import widgetbox, row, column
This notebook was created using this tutorial here:A Complete Guide to an Interactive Geographical Map using Python.
shapefile = 'Downloads/ne_110m_admin_0_countries/ne_110m_admin_0_countries.shp'
#Read shapefile using Geopandas
gdf = gpd.read_file(shapefile)[['ADMIN','ADM0_A3','geometry']]
#Rename columns
gdf.columns = ['country','country_code','geometry']
gdf.head()
country | country_code | geometry | |
---|---|---|---|
0 | Fiji | FJI | MULTIPOLYGON (((180.00000 -16.06713, 180.00000... |
1 | United Republic of Tanzania | TZA | POLYGON ((33.90371 -0.95000, 34.07262 -1.05982... |
2 | Western Sahara | SAH | POLYGON ((-8.66559 27.65643, -8.66512 27.58948... |
3 | Canada | CAN | MULTIPOLYGON (((-122.84000 49.00000, -122.9742... |
4 | United States of America | USA | MULTIPOLYGON (((-122.84000 49.00000, -120.0000... |
print(gdf[gdf['country'] == 'Antarctica'])
country country_code \ 159 Antarctica ATA geometry 159 MULTIPOLYGON (((-48.66062 -78.04702, -48.15140...
#Drop row corresponding to Antarctica
gdf = gdf.drop(gdf.index[159])
datafile = 'Datasets/share-of-adults-defined-as-obese.csv'
#Read csv file using pandas
df = pd.read_csv(datafile, names=['entity','code','year','percent_obesity'], skiprows=1)
df.head()
entity | code | year | percent_obesity | |
---|---|---|---|---|
0 | Afghanistan | AFG | 1975 | 0.5 |
1 | Afghanistan | AFG | 1976 | 0.5 |
2 | Afghanistan | AFG | 1977 | 0.6 |
3 | Afghanistan | AFG | 1978 | 0.6 |
4 | Afghanistan | AFG | 1979 | 0.6 |
df.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 8316 entries, 0 to 8315 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 entity 8316 non-null object 1 code 7980 non-null object 2 year 8316 non-null int64 3 percent_obesity 8316 non-null float64 dtypes: float64(1), int64(1), object(2) memory usage: 260.0+ KB
df[df.code.isnull()]
entity | code | year | percent_obesity | |
---|---|---|---|---|
42 | Africa | NaN | 1975 | 2.0 |
43 | Africa | NaN | 1976 | 2.1 |
44 | Africa | NaN | 1977 | 2.2 |
45 | Africa | NaN | 1978 | 2.3 |
46 | Africa | NaN | 1979 | 2.3 |
... | ... | ... | ... | ... |
8185 | Western Pacific | NaN | 2012 | 5.2 |
8186 | Western Pacific | NaN | 2013 | 5.5 |
8187 | Western Pacific | NaN | 2014 | 5.8 |
8188 | Western Pacific | NaN | 2015 | 6.1 |
8189 | Western Pacific | NaN | 2016 | 6.4 |
336 rows × 4 columns
#Filter data for year 2016
df_2016 = df[df['year'] == 2016]
#Merge dataframes gdf and df_2016
merged = gdf.merge(df_2016, left_on='country_code', right_on='code')
#Read data to json
merged_json = json.loads(merged.to_json())
#Convert to String like object
json_data = json.dumps(merged_json)
#Import GeoJSON source that contains features for plotting
geosource = GeoJSONDataSource(geojson = json_data)
#Define a sequential multi-hue color palette
palette = brewer['YlGnBu'][8]
#Reverse color order so that dark blue is highest obesity
palette = palette[::-1]
#Instantiate LinearColorMapper that linearly maps numbers in a range into a sequence of colors
color_mapper = LinearColorMapper(palette=palette, low=0, high=40)
#Define custom tick labels for color bar
tick_labels = {'0':'0%','5':'5%','10':'10%','15':'15%','20':'20%','25':'25%','30':'30%','30':'35%','40':'>40%'}
#Create color bar
color_bar = ColorBar(color_mapper=color_mapper, label_standoff=8, width=500, height=20,
border_line_color=None, location=(0,0), orientation='horizontal',
major_label_overrides=tick_labels)
#Create figure object
fig = figure(title='Share of adults who are obese, 2016', plot_height=600, plot_width=950, toolbar_location=None)
fig.xgrid.grid_line_color=None
fig.ygrid.grid_line_color=None
#Add patch renderer to figure
fig.patches('xs', 'ys', source=geosource, fill_color={'field': 'percent_obesity', 'transform': color_mapper},
line_color='black', line_width=0.25, fill_alpha=1)
#Specify figure layout
fig.add_layout(color_bar, 'below')
#Display figure inline in Jupyter Notebook
output_notebook()
#Display figure
show(fig)
#Perform left merge to preserve every row in gdf
merged = gdf.merge(df_2016, left_on='country_code', right_on='code', how='left')
#Read data to json
merged_json = json.loads(merged.to_json())
#Convert to String like object
json_data = json.dumps(merged_json)
#Import GeoJSON source that contains features for plotting
geosource = GeoJSONDataSource(geojson = json_data)
#Instantiate LinearColorMapper that linearly maps numbers in a range into a sequence of colors
color_mapper = LinearColorMapper(palette=palette, low=0, high=40)
#Create figure object
fig = figure(title='Share of adults who are obese, 2016', plot_height=600, plot_width=950, toolbar_location=None)
fig.xgrid.grid_line_color=None
fig.ygrid.grid_line_color=None
#Add patch renderer to figure
fig.patches('xs', 'ys', source=geosource, fill_color={'field': 'percent_obesity', 'transform': color_mapper},
line_color='black', line_width=0.25, fill_alpha=1)
#Specify figure layout
fig.add_layout(color_bar, 'below')
#Display figure inline in Jupyter Notebook
output_notebook()
#Display figure
show(fig)
#Replace NaN values to string "No Data"
merged.fillna("No Data", inplace=True)
#Read data to json
merged_json = json.loads(merged.to_json())
#Convert to String like object
json_data = json.dumps(merged_json)
#Import GeoJSON source that contains features for plotting
geosource = GeoJSONDataSource(geojson = json_data)
#Instantiate LineaerColorMapper that maps numbers in a range linearly into a sequence of colors. Input nan_color.
color_mapper = LinearColorMapper(palette=palette, low=0, high=40, nan_color = '#d9d9d9')
#Create figure object
fig = figure(title='Share of adults who are obese, 2016', plot_height=600, plot_width=950, toolbar_location=None)
fig.xgrid.grid_line_color=None
fig.ygrid.grid_line_color=None
#Add patch renderer to figure
fig.patches('xs', 'ys', source=geosource, fill_color={'field': 'percent_obesity', 'transform': color_mapper},
line_color='black', line_width=0.25, fill_alpha=1)
#Specify figure layout
fig.add_layout(color_bar, 'below')
#Display figure inline in Jupyter Notebook
output_notebook()
#Display figure
show(fig)
#Define function that returns json_data for year selected by user
def json_data(selectedYear):
yr = selectedYear
df_yr = df[df['year'] == yr]
merged = gdf.merge(df_yr, left_on='country_code', right_on='code', how='left')
merged.fillna('No Data', inplace=True)
merged_json = json.loads(merged.to_json())
json_data = json.dumps(merged_json)
return json_data
#Input GeoJSON source that contains features for plotting
geosource = GeoJSONDataSource(geojson = json_data(2016))
#Add hover tool
hover = HoverTool(tooltips = [('Country/region', '@country'), ('%obesity', '@percent_obesity')])
#Create figure object
fig = figure(title='Share of adults who are obese, 2016', plot_height=600, plot_width=950, toolbar_location=None,
tools=[hover])
fig.xgrid.grid_line_color=None
fig.ygrid.grid_line_color=None
#Add patch renderer to figure
fig.patches('xs', 'ys', source=geosource, fill_color={'field': 'percent_obesity', 'transform': color_mapper},
line_color='black', line_width=0.25, fill_alpha=1)
#Specify figure layout
fig.add_layout(color_bar, 'below')
#Define the callback function: update_plot
def update_plot(attr, old, new):
yr = slider.value
new_data = json_data(yr)
geosource.geojson = new_data
fig.title.text = 'Share of adults who are obese, %d %yr'
#Make a slider object: slider
slider = Slider(title='Year', start=1975, end=2016, step=1, value=2016)
slider.on_change('value', update_plot)
#Make a column layout of widgetbox(slider) and plot, and add it to the current document
layout = column(fig, widgetbox(slider))
curdoc().add_root(layout)
#Display figure inline in Jupyter Notebook
output_notebook()
#Display figure
show(fig)
To view the interactive map on the local host, run the code in this Notebook on local device and type bokeh serve --show filename.ipynb
into the local terminal. A local host page will open up in the browser with the interactive widget bar and map.