Use GeoPandas and Bokeh to build a choropleth plot

Germain Salvato Vallverdu germain.vallverdu@gmail.com

This notebook shows how to build a choropleth plot using geopandas in order to read a shapefile and bokeh in order to draw the choropleth plot.

U.S. states and councils are available from core packages of bokeh. In the case of France you have to download the data from a government website. In this example, I used the data from IGN website. I downloaded only metropolitan france departments.

The main strategy is the following :

  1. Read data you want to put on the map with pandas
  2. Read the shapefile with geopandas
  3. Merge the data frames
  4. Set up the plot

Used pacakges

import used pacakges :

  • pandas for data
  • geopandas for Geographic Information System
  • bokeh for the plot

In [1]:
import pandas as pd
import geopandas as gpd

from bokeh.io import show, output_notebook
from bokeh.plotting import figure
import bokeh.models as bm
import bokeh.palettes

# set up bokeh for jupyter notebook
output_notebook()


Loading BokehJS ...

1. Read data from csv

First, read the data from a csv file. Here I did the following :

  1. Read the csv file
  2. extract data for france only
  3. set up an index with the department number as a string with 2 digits.

In [2]:
df = pd.read_csv("membres_par_pays_dep.csv", sep=";")
df = df[df["Pays"] == "FR"]
df.rename(columns={df.columns[2]: "Departement"}, inplace=True)
df.index = df.Departement.apply(lambda x: "%02d" % x)
df.index.name = "CODE_DEPT"
df.head()


Out[2]:
Nombre de membres Pays Departement
CODE_DEPT
01 8 FR 1.0
02 1 FR 2.0
03 1 FR 3.0
04 3 FR 4.0
05 1 FR 5.0

2. Read GIS data in a shapefile

Now we read a shape file and set up the same index with the department number.

The last column of a GeoDataFrame is a geometry object, here a polygon.


In [3]:
gdf = gpd.GeoDataFrame.from_file("DEPARTEMENT/DEPARTEMENT.shp")
gdf = gdf[["CODE_DEPT", "NOM_CHF", "NOM_DEPT", "geometry"]]
gdf.set_index("CODE_DEPT", inplace=True)
gdf.sort_index(inplace=True)
gdf.head()


Out[3]:
NOM_CHF NOM_DEPT geometry
CODE_DEPT
01 BOURG-EN-BRESSE AIN POLYGON ((838243.5999999591 6564210.300001801,...
02 LAON AISNE POLYGON ((708718.999999999 6956305.000000671, ...
03 MOULINS ALLIER POLYGON ((664479.2000000098 6602292.300001685,...
04 DIGNE-LES-BAINS ALPES-DE-HAUTE-PROVENCE POLYGON ((910437.2999999194 6342569.70000242, ...
05 GAP HAUTES-ALPES POLYGON ((933489.9999999163 6411083.800002239,...

3. Merge the data frames

Now we merge the data frames. As they have the same index, you only need to call the join method.

We only keep the column with the number of members and set NaN values to zero.


In [4]:
gdf2 = gdf.join(df["Nombre de membres"])
gdf2.rename(columns={"Nombre de membres": "membres"}, inplace=True)
gdf2.fillna(value=0., inplace=True)
gdf2.head()


Out[4]:
NOM_CHF NOM_DEPT geometry membres
CODE_DEPT
01 BOURG-EN-BRESSE AIN POLYGON ((838243.5999999591 6564210.300001801,... 8.0
02 LAON AISNE POLYGON ((708718.999999999 6956305.000000671, ... 1.0
03 MOULINS ALLIER POLYGON ((664479.2000000098 6602292.300001685,... 1.0
04 DIGNE-LES-BAINS ALPES-DE-HAUTE-PROVENCE POLYGON ((910437.2999999194 6342569.70000242, ... 3.0
05 GAP HAUTES-ALPES POLYGON ((933489.9999999163 6411083.800002239,... 1.0

4. Set up the plot

First we convert the GeoDataFrame in a GeoJSONDataSource bokeh model.


In [5]:
geo_src = bm.GeoJSONDataSource(geojson=gdf2.to_json())

Now we set up a show the plot.


In [6]:
# set up a log colormap
cmap = bm.LogColorMapper(
    palette=bokeh.palettes.BuGn9[::-1], # reverse the palette
    low=0, 
    high=gdf2.membres.max()
)

# define web tools
TOOLS = "pan,wheel_zoom,box_zoom,reset,hover,save"

# set up bokeh figure
p = figure(
    title="Membres de l'AFPY en 2016", 
    tools=TOOLS,
    toolbar_location="below",
    x_axis_location=None, 
    y_axis_location=None, 
    width=500, 
    height=500
)

# remove the grid
p.grid.grid_line_color = None

# core part !
#    * add a patch for each polygon in the geo data frame
#    * fill color from column 'membres' using the color map defined above
p.patches(
    'xs', 'ys', 
    fill_alpha=0.7, 
    fill_color={'field': 'membres', 'transform': cmap},
    line_color='black', 
    line_width=0.5, 
    source=geo_src
)

# set up mouse hover informations
hover = p.select_one(bm.HoverTool)
hover.point_policy = 'follow_mouse'
hover.tooltips = [
    ('Département:', '@NOM_DEPT'), 
    ("Membres:", "@membres"), 
    ("Contact:", "??"), 
    ("Afpyro:", "True/False")
]

# add a color bar
color_bar = bm.ColorBar(
    color_mapper=cmap,
    ticker=bm.LogTicker(),
    title_text_align="left",
    location=(0, 0),
    border_line_color=None,
    title="Membres"
)
p.add_layout(color_bar, 'right')

# show plot
show(p)



In [ ]:
from bokeh.plotting import output_file
output_file("afpy_france.html")