Creating an interactive map in Python using Bokeh and pandas | by Craig Dickson | Towards Data Science

This article can be seen as an extension of my earlier piece dealing with avocado pricing data (what can I say, I love avocados!) which you can find here. I recommend checking it out!
As Data Analysts / Data Scientists / other manipulators of data, we often find ourselves using visualisations to effectively communicate our findings to others (and to better understand them ourselves). Visualisations have many advantages when used wisely — they can summarise data, show trends through time, show geographic data and much more.
Sometimes a static visualisation is perfect, but in other cases creating something interactive can be clearer, more useful to the end user, or simply be cooler and more fun!
In this article, we will walk through the process of creating an interactive heatmap showing avocado prices in the United States, which can easily be viewed and manipulated in any modern web browser. We will do this using Python, primarily using Bokeh and a pandas DataFrame.
I was inspired to pick up Bokeh after seeing Nadine Amersi-Bolton’s excellent project ‘Predicting House Prices in King County’, which used it very effectively. I recommend checking the whole Notebook out — there’s some very interesting stuff in there.
To start we’re going to need some geographical data in the form of a Python dict, a pandas DataFrame or a pandas GroupBy object. Conveniently I happened to have an old DataFrame lying around, which I’ll be using for this post. I generated this in my previous article and exported it using pd.to_csv(). If you’re interested in the data-wrangling that I did to get to this point, check it out.
All the code for this post can be found in this GitHub repo, if you want to code along without installing anything, then check it out using Binder here. If you’d just like to follow along while reading I recommend using Jupyter Notebook Viewer.
Bokeh can help you simply and easily make nice-looking and useful interactive visualisations, which can then be displayed in all modern browsers. This means you can embed something nice and useful in a blog post, an internal dashboard or anywhere that you can view HTML. As almost everyone has experience using a browser, this is very useful for getting your visualisations in front of your audience.
The main selling point for my purposes is the interactivity. In our example today we’re using a geographical map, so the ability to allow a user to zoom in and out at will to see the physical distribution of our data points, as well as choosing which data highlights when the user hovers over a point, let us create an intuitive visualisation that our audience can interact with and manipulate at their own pace. That should, in turn, make it easier for them to engage with our findings, which is a plus for any data professional!
Installing Bokeh on your system is relatively simple if you have a working Python setup. I’m using the Anaconda ecosystem myself so a simple conda install bokeh is all that’s required. If you prefer pip, then pip install bokeh does the trick too. More information can be found in their Quickstart guide.
Bokeh is built on four main concepts: the Plot, Glyphs, Guides and Annotations, and Ranges. For full information, I strongly recommend reading the documentation (always read the documentation!), but I’ll provide a very brief run-down of what these mean here.
The Plot. This is the container for everything you will see in your output. You’ll create a figure which is presented to users. This idea will be familiar to anyone who’s used Matplotlib or Seaborn or other visualisation libraries in Python.
Glyphs. This is how Bokeh displays your visualisation. These can be as simple as a line or a circle, or more complex like a multipolygon, image url (nice for getting your logo on the viz!) or text.
Guides and Annotations. Guides are things like axes, grid lines, etc, which help the user understand the scale and make comparisons. Annotations are things like titles, legends, etc, which provide extra information to help the user take the visualisation in.
Ranges. This is the range of the data. You’ll notice if you click on my visualisation that by default it opens with all data points visible, centred over the United States (where all my data is from). This can be modified when creating a visualisation, should you choose.
OK, let’s get down to business.
First, we want to import our libraries. Bokeh is a little different from other Python visualisation libraries that you might be familiar with such as Matplotlib or Seaborn, in that it is arranged as many different sub-modules from which you can import the functions you need. It does not have a single all-encompassing package that can be imported to access all the functions.
This does make it a little more intimidating to get to know, but you can follow along with the code below. It’s not too scary once you get used to it!
from bokeh.io import output_notebook, show, output_filefrom bokeh.plotting import figure, ColumnDataSourcefrom bokeh.tile_providers import get_provider, Vendorsfrom bokeh.palettes import PRGn, RdYlGnfrom bokeh.transform import linear_cmap,factor_cmapfrom bokeh.layouts import row, columnfrom bokeh.models import GeoJSONDataSource, LinearColorMapper, ColorBar, NumeralTickFormatterimport numpy as npimport pandas as pd
OK, now we’re ready to begin. First, we’ll need some data. We’ll use our CSV file, which we can read into pandas as a DataFrame using pd.read_csv():
import pandas as pddf = pd.read_csv('avocado_df.csv', index_col=0)display(df.head())
This gives us the following output:
For reasons best known to themselves, Bokeh plots use Mercator coordinates. Our locations are listed in latitude and longitude. This means we’ll need to convert our latitudes and longitudes to Mercator coordinates.
One of the great things about participating in such an interesting and challenging activity as programming and Data Analysis, is that for most of the difficult problems you encounter, some other smart person has probably encountered them before. To make progress and improve your skills, you also need to develop the skill of effective googling to find the answers those smart people have found before you!
In our case, our trail-blazer Nadine Amersi-Bolton already solved this problem and created a function to do this conversion for us. This works very nicely, so we’re going to work smarter instead of harder and use this existing solution. Thanks Nadine!
# Define function to switch from lat/long to mercator coordinatesdef x_coord(x, y):        lat = x    lon = y        r_major = 6378137.000    x = r_major * np.radians(lon)    scale = x/lon    y = 180.0/np.pi * np.log(np.tan(np.pi/4.0 +         lat * (np.pi/180.0)/2.0)) * scale    return (x, y)# Define coord as tuple (lat,long)df['coordinates'] = list(zip(df['latitude'], df['longitude']))# Obtain list of mercator coordinatesmercators = [x_coord(x, y) for x, y in df['coordinates'] ]
Now we just need to add this column to our DataFrame and split it into two separate columns (one for our x coordinate, one for the y coordinate). We can do that using pandas in two easy steps.
# Create mercator column in our dfdf['mercator'] = mercators# Split that column out into two separate columns - mercator_x and mercator_ydf[['mercator_x', 'mercator_y']] = df['mercator'].apply(pd.Series)
When I ran this I found that Bokeh very much did not want to cooperate with the geometry column in the DataFrame. This came from the Shapely files we were using in our previous visualisation, and it’s not needed here. We can get rid of it by using df.drop().
df = df.drop(columns=['geometry']# Examine our modified DataFramedf.head()
Which gives us:
Now we have everything we need to start the plotting. Before we create our figure, there are a few preparatory steps we’ll need to take first. These include: selecting which tiles to use for the map; choosing our colour palette; instructing Bokeh which DataFrame to use as an information source; creating a colormapper; and setting up our tooltips.
Don’t worry, we’ll go over these as we go!
First — selecting our tile provider. There are various organisations which provide map tiles which can be used in Bokeh. These could be Google Maps (great, but requires an API so we won’t use it here) or CartoDB (also great) or Stamen. Most of these use OpenStreetMap data, which is generally highly reliable. We’re going to use Stamen Toner, because it looks great and the monochrome styling will allow our coloured data points to show up nicely.
# Select tile set to usechosentile = get_provider(Vendors.STAMEN_TONER)
Next, we need to choose a colour palette. As we’re dealing with avocados, I chose the Purple — Green PRGn. We can select between 3 and 11 shades by using PRGn[3] to PRGn[11]. We’re going to use the maximum to get the most variety on our visualisation.
# Choose palettepalette = PRGn[11]
Now we want to point Bokeh at our DataFrame by using ColumnDataSource.
# Tell Bokeh to use df as the source of the datasource = ColumnDataSource(data=df)
Next we need to define the color mapper. In this case, we want to let Bokeh know that it’s our 'AveragePrice' field which should determine the colour of our data point.
# Define color mapper - which column will define the colour of the data pointscolor_mapper = linear_cmap(field_name = 'AveragePrice', palette = palette, low = df['AveragePrice'].min(), high = df['AveragePrice'].max())
And the last piece of prep to do before we build our figure is choose which fields should be used as tooltips — i.e. should be shown when the user hovers over a data point on our map. In this case we will select the 'AveragePrice' field and the 'Region' as these are probably of most interest to our audience.
# Set tooltips - these appear when we hover over a data point in our map, very nifty and very usefultooltips = [("Price","@AveragePrice"), ("Region","@region")]
We’re almost there! Stay with me!
The next step is to define our figure. The figure is the holder for all of our elements. We set it up like so:
# Create figurep = figure(title = 'Avocado Prices by region in the United States', x_axis_type="mercator", y_axis_type="mercator", x_axis_label = 'Longitude', y_axis_label = 'Latitude', tooltips = tooltips)
You can see that we have given our figure a title, set the axis type to mercator, given both axes a label, and told Bokeh where to find the info regarding our tootltips. All that in one line of code — not bad!
Now we need to add our tiles, otherwise we’d end up with our data points floating on a background of white space. Not such a compelling visualisation!
# Add map tilep.add_tile(chosentile)
The next step is to create the data points themselves:
# Add points using mercator coordinatesp.circle(x = 'mercator_x', y = 'mercator_y', color = color_mapper, source=source, size=30, fill_alpha = 0.7)
In this case we’ve adjusted the fill_alpha, this reduces the opacity of the circles, and allows the user to make sense of overlapping data points more easily when zoomed out. We also set the size of the circles to 30. The size stays constant at every level of zoom, so experiment with this and see what suits your data.
Our next step is to create a color bar — this will act as a legend at the side of our visualisation, providing important information regarding the meaning of the colour used on our data points. Is green expensive or cheap? (Expensive!) By giving our users this information they will be able to understand at a glance what the visualisation is showing them.
#Defines color barcolor_bar = ColorBar(color_mapper=color_mapper['transform'],                      formatter = NumeralTickFormatter(format='0.0[0000]'),                      label_standoff = 13, width=8, location=(0,0))# Set color_bar locationp.add_layout(color_bar, 'right')
Now just a couple of steps to make sure we can see the visualisation in our Jupyter Notebook, and to save the file as an HTML file which we can then embed anywhere we desire.
# Display in notebookoutput_notebook()# Save as HTMLoutput_file('avocado.html', title='Avocado Prices by region in the United States')
And there we are. We are ready to admire the fruits of our labour! Just one more step:
# Show mapshow(p)
And voila!
Ta-da! Now we’ve gone from a CSV file with some location data to a fully-fledged interactive map which we can embed in our dashboards or blog posts or anywhere else we might want to.
I hope this article has given you the inspiration to make something great using Bokeh yourself.
Thanks very much for taking the time to explore this topic with me. As always, I strongly welcome your feedback — you can contact me on Twitter at @craigdoesdata to let me know how I could have done this analysis more effectively.
You can see what else I’ve been working on at my website at www.craigdoesdata.de.
Again, the whole Jupyter Notebook and associated files are available on my Github page and on Binder, and I encourage you to play around with them.
Until next time!