Share @ LinkedIn Facebook  choropleth-maps, ipyleaflet
Choropleth Maps using ipyleaflet [Python]

Choropleth Maps using ipyleaflet

The ipyleaflet is a python library for plotting maps. It's built on top of leaflet.js and ipywidgets. We have already covered a detailed tutorial about the usage of ipyleaflet as a separate tutorial. If you are interested in learning about various maps creation using ipyleaflet then we suggest you go through that tutorial.

As a part of this tutorial, we'll be concentrating on how to create choropleth maps using ipyleaflet. The ipyleaflet provides two different constructors which we can use to create choropleth maps.

  • Choropleth()
  • GeoJSON()

We'll be explaining how to create choropleth maps using both constructors. Both methods require a little bit of data preparation which we'll explain with different examples. We'll be using various datasets for creating choropleth maps.

We'll start by importing necessary libraries.

In [1]:
import pandas as pd
import numpy as np
import json

1. Choropleth Maps Using Choropleth() Constructor

The first way of creating choropleth maps using ipyleaflet is using the Choropleth() constructor. We'll be explaining the usage of this constructor with various examples.

1.1 World Happiness Choropleth Map

The choropleth maps that we'll create using ipyleaflet is the world happiness choropleth map. We have used the world happiness dataset available from kaggle for this map.

  • Worldwide Happiness and Other attributes dataset - It has information about each countries happiness score and many other attributes like GDP per capita, social support, healthy life expectancy, freedom to make life choices, generosity, and perception of corruption.

We suggest that you download the dataset to follow along. We'll start by loading the dataset as a pandas dataframe.

In [21]:
happiness_report = pd.read_csv("datasets/world_happiness_2019.csv")
happiness_report.head()
Out[21]:
Overall rank Country or region Score GDP per capita Social support Healthy life expectancy Freedom to make life choices Generosity Perceptions of corruption
0 1 Finland 7.769 1.340 1.587 0.986 0.596 0.153 0.393
1 2 Denmark 7.600 1.383 1.573 0.996 0.592 0.252 0.410
2 3 Norway 7.554 1.488 1.582 1.028 0.603 0.271 0.341
3 4 Iceland 7.494 1.380 1.624 1.026 0.591 0.354 0.118
4 5 Netherlands 7.488 1.396 1.522 0.999 0.557 0.322 0.298

We'll also need geo JSON data for each country of the world which has information about each countries boundaries.

We have loaded that geo JSON dataset below. It has information about each country in the features key of the dictionary. The value with the features key is a list of dictionaries representing each country. One dictionary represents one country. We have moved the name of each country which is stored in the properties key of each dictionary to one level up so that they are directly available with the name key at the top level in the dictionary. This is required by the Choropleth() constructor of ipyleaflet.

In [22]:
with open('datasets/custom.geo.json', 'r') as f:
    geo_json_data = json.load(f)
    for d in geo_json_data["features"]:
        d["name"] = d["properties"]["name_sort"]

Below we have created a mapping dictionary which is mapping from country name to their happiness scores. We also have created one temporary dictionary which maps country name from geo JSON to country name from pandas dataframe.

There is a discrepancy in the country name between geo JSON and pandas dataframe. We have hence included small code which loops through each geo JSON country name and checks whether there is mapping for it present in the proper_name_mapping dictionary then it takes that mapping to fill value of that country name. If the country is not available in the proper_name_mapping dictionary then it'll put 0 as a happiness score for that country. This small code is written so that if the country happiness score is available with a different name then it'll be mapped to the name which is present in the geo JSON dataset.

In [23]:
mapping  = dict(zip(happiness_report["Country or region"].str.strip(), happiness_report["Score"]))

proper_name_mapping = {
    "Russian Federation":"Russia",
    "Czech Republic":"Czechia",
    "Macedonia, FYR":"Macedonia",
    "Central African Republic":"Central African Rep.",
    "Bosnia and Herzegovina":"Bosnia and Herz.",
    "Slovak Republic":"Slovakia",
    "Iran, Islamic Rep.":"Iran",
    "Somaliland":"Somalia",
    "Dominican Republic":"Dominican Rep.",
    "Venezuela, RB":"Venezuela",
    "Lao PDR":"Laos",
    "Yemen, Rep.":"Yemen",
    "South Sudan":"S. Sudan",
    "Papua New Guinea":"Guinea",
    "Congo, Rep.":"Congo",
    "Congo, Dem. Rep.":"Dem. Rep. Congo",
    "Cyprus, Northern":"N. Cyprus",
    "Kyrgyz Republic":"Kyrgyzstan",
    "Korea, Dem. Rep.":"South Korea",
    "Palestine (West Bank and Gaza)":"Palestinian",
    "Syrian Arab Republic":"Syria",
    "Egypt, Arab Rep.":"Egypt",
    "Gambia, The":"Gambia"

}

for d in geo_json_data["features"]:
    if d["name"] not in mapping:
        if d["name"] in proper_name_mapping.keys():
            mapping[d["name"]] = mapping[proper_name_mapping[d["name"]]]
        else:
            mapping[d["name"]] = 0

list(mapping.items())[:10]
Out[23]:
[('Finland', 7.769),
 ('Denmark', 7.6),
 ('Norway', 7.553999999999999),
 ('Iceland', 7.494),
 ('Netherlands', 7.4879999999999995),
 ('Switzerland', 7.48),
 ('Sweden', 7.343),
 ('New Zealand', 7.307),
 ('Canada', 7.278),
 ('Austria', 7.246)]

Below we have created our first choropleth map representing the happiness score for each country of the world. We have created Map() as usual way. We have then created a choropleth layer using the Choropleth() constructor. We have given geo JSON data loaded to the geo_data parameter and mapping dictionary created to the choro_data parameter. We also have provided colormap to use. We have provided which key to use to identify features which is the name key for each feature in the dictionary. This is the reason we had earlier moved name one level up when loading the geo JSON dataset as a python dictionary.

As each map and component of ipyleaflet is ipywidgets widget we can provide style information as a dictionary to the style parameter of Choropleth().

We then need to add this choropleth layer created on top of the map using the add_layer() method of map passing it choropleth layer created.

In [ ]:
from ipyleaflet import Choropleth, Map
from branca.colormap import linear

m = Map(zoom=2)

layer = Choropleth(
                    geo_data=geo_json_data,
                    choro_data=mapping,
                    colormap=linear.Blues_05,
                    style={'fillOpacity': 1.0, "color":"black"},
                    key_on="name")

m.add_layer(layer)
m.layout.height="700px"

m

Choropleth Maps using ipyleaflet

1.2 World GDP Per Capita Choropleth Map

Below we have created another Choropleth map which represents GDP per capita for each country. We have used almost the same code as the last choropleth map with the only difference in the column name used for creating mapping which is GDP per capita now.

We have created mapping the same as last time and also corrected a few mappings due to discrepancy in country name across datasets.

In [ ]:
mapping  = dict(zip(happiness_report["Country or region"].str.strip(), happiness_report["GDP per capita"]))

for d in geo_json_data["features"]:
    if d["name"] not in mapping:
        if d["name"] in proper_name_mapping.keys():
            mapping[d["name"]] = mapping[proper_name_mapping[d["name"]]]
        else:
            mapping[d["name"]] = 0


m = Map(zoom=2)

layer = Choropleth(
                    geo_data=geo_json_data,
                    choro_data=mapping,
                    colormap=linear.RdYlBu_09,
                    style={'fillOpacity': 1.0, 'dashArray': '5, 5', 'color':"black"},
                    key_on="name")

m.add_layer(layer)
m.layout.height="700px"

m

Choropleth Maps using ipyleaflet

1.3 World Healthy Life Expectancy Choropleth Map

The third choropleth map that we have created using ipyleaflet is the healthy life expectancy for each country of the world. We have followed the same code and steps as the previous choropleth with the only difference in column name which is Healthy life expectancy now.

In [ ]:
mapping  = dict(zip(happiness_report["Country or region"].str.strip(), happiness_report["Healthy life expectancy"]))

for d in geo_json_data["features"]:
    if d["name"] not in mapping:
        if d["name"] in proper_name_mapping.keys():
            mapping[d["name"]] = mapping[proper_name_mapping[d["name"]]]
        else:
            mapping[d["name"]] = 0

m = Map(zoom=2)

layer = Choropleth(
                    geo_data=geo_json_data,
                    choro_data=mapping,
                    colormap=linear.BuPu_08,
                    style={'fillOpacity': 1.0, 'color':"purple"},
                    key_on="name")

m.add_layer(layer)
m.layout.height="700px"

m

Choropleth Maps using ipyleaflet

1.4 World Perception of Corruption Choropleth Map

The fourth choropleth map that we have created with ipyleaflet is a choropleth map representing Perceptions of corruption for each country of the world. It has been created with the same code as previous steps with only change in column name which is Perceptions of corruption this time from dataframe.

In [ ]:
mapping  = dict(zip(happiness_report["Country or region"].str.strip(), happiness_report["Perceptions of corruption"]))

for d in geo_json_data["features"]:
    if d["name"] not in mapping:
        if d["name"] in proper_name_mapping.keys():
            mapping[d["name"]] = mapping[proper_name_mapping[d["name"]]]
        else:
            mapping[d["name"]] = 0

m = Map(zoom=2)

layer = Choropleth(
                    geo_data=geo_json_data,
                    choro_data=mapping,
                    colormap=linear.YlOrBr_09,
                    style={'fillOpacity': 1.0, 'color':"tomato"},
                    key_on="name")

m.add_layer(layer)
m.layout.height="700px"

m

Choropleth Maps using ipyleaflet

1.5 US State-wise Population Choropleth Map

The fifth choropleth map that we'll create using ipyleaflet is the US states population choropleth map. The dataset which has information about each state population is available on kaggle.

We have loaded the dataset as a pandas dataframe.

In [9]:
us_state_pop = pd.read_csv("datasets/State Populations.csv")
us_state_pop.head()
Out[9]:
State 2018 Population
0 California 39776830
1 Texas 28704330
2 Florida 21312211
3 New York 19862512
4 Pennsylvania 12823989

Below we have created a choropleth map for the US state-wise population. It has the same steps as a previous choropleth map. We have loaded the geo JSON dataset which has boundary information about each US state.

We have then created mapping from state name to state population. We have then created a map object and choropleth layer with geo JSON and mapping data. We have then added the choropleth layer the same way as previous examples.

In [ ]:
with open('datasets/us-states.json', 'r') as f:
    us_geo_json_data = json.load(f)
    for d in us_geo_json_data["features"]:
        d["name"] = d["properties"]["name"]

mapping  = dict(zip(us_state_pop["State"].str.strip(), us_state_pop["2018 Population"]))

for d in geo_json_data["features"]:
    if d["name"] not in mapping:
        mapping[d["name"]] = 0


m = Map(zoom=4, center=(43,-100))

layer = Choropleth(
                    geo_data=us_geo_json_data,
                    choro_data=mapping,
                    colormap=linear.YlGn_04,
                    style={'fillOpacity': 1.0, 'color':"black"},
                    key_on="name")

m.add_layer(layer)
m.layout.height="700px"

m

Choropleth Maps using ipyleaflet

1.6 US State-wise Starbucks Store Count Choropleth Map

The sixth choropleth map that we have created using ipyleaflet is state-wise Starbucks store count for the US. The dataset which has information about Starbucks store locations is available from kaggle.

We have loaded the dataset as a pandas dataframe.

In [11]:
starbucks_stores = pd.read_csv("datasets/starbucks_store_locations.csv")
starbucks_stores.head()
Out[11]:
Brand Store Number Store Name Ownership Type Street Address City State/Province Country Postcode Phone Number Timezone Longitude Latitude
0 Starbucks 47370-257954 Meritxell, 96 Licensed Av. Meritxell, 96 Andorra la Vella 7 AD AD500 376818720 GMT+1:00 Europe/Andorra 1.53 42.51
1 Starbucks 22331-212325 Ajman Drive Thru Licensed 1 Street 69, Al Jarf Ajman AJ AE NaN NaN GMT+04:00 Asia/Dubai 55.47 25.42
2 Starbucks 47089-256771 Dana Mall Licensed Sheikh Khalifa Bin Zayed St. Ajman AJ AE NaN NaN GMT+04:00 Asia/Dubai 55.47 25.39
3 Starbucks 22126-218024 Twofour 54 Licensed Al Salam Street Abu Dhabi AZ AE NaN NaN GMT+04:00 Asia/Dubai 54.38 24.48
4 Starbucks 17127-178586 Al Ain Tower Licensed Khaldiya Area, Abu Dhabi Island Abu Dhabi AZ AE NaN NaN GMT+04:00 Asia/Dubai 54.54 24.51

Below we have filtered the original dataset to keep only rows where the country is the US. We have then grouped the dataset based on the state name to get a count of stores per each state.

In [12]:
us_stores = starbucks_stores[starbucks_stores.Country=="US"]
us_stores_statewise = us_stores.groupby("State/Province").count()[["Store Name"]].rename(columns={"Store Name":"Count"})
us_stores_statewise = us_stores_statewise.reset_index()
us_stores_statewise.head()
Out[12]:
State/Province Count
0 AK 49
1 AL 85
2 AR 55
3 AZ 488
4 CA 2821

Below we have created a choropleth map with exactly the same steps as the previous example with the only difference that this time we have used different key in the Choropleth() constructor. The dataset has state name available as code and the geo JSON dataset also has state name available as code as a part of the id key of it. This will allow state code matching between mapping data and id key data of the geo JSON dataset.

In [ ]:
with open('datasets/us-states.json', 'r') as f:
    us_geo_json_data = json.load(f)

mapping  = dict(zip(us_stores_statewise["State/Province"].str.strip(), us_stores_statewise["Count"]))

m = Map(zoom=4, center=(43,-100))

layer = Choropleth(
                    geo_data=us_geo_json_data,
                    choro_data=mapping,
                    colormap=linear.RdPu_03,
                    style={'fillOpacity': 1.0, 'color':"black"},
                    key_on="id",
                    hover_style={
                            'dashArray': '1', 'fillOpacity': 0.1
                        },
                    )

m.add_layer(layer)
m.layout.height="700px"

m

Choropleth Maps using ipyleaflet

2. Choropleth Maps Using GeoJSON() Constructor

The second way of creating choropleth maps using ipyleaflet is using the GeoJSON() constructor. This step requires a little bit of modification to data than the previous step. We'll explain the usage of this way to construct choropleth maps with few examples.

2.1 World Happiness Choropleth Map

The first choropleth map that we'll create using the GeoJSON() constructor is the happiness choropleth map for each country of the world. We'll start with loading world countries geo JSON data the same as earlier examples.

In [14]:
with open('datasets/custom.geo.json', 'r') as f:
    geo_json_data = json.load(f)
    for d in geo_json_data["features"]:
        d["name"] = d["properties"]["name_sort"]

Below is one extra step which is needed for creating a choropleth map with GeoJSON(). We have normalized happiness score data for each country so that score of each country is between 0-1. The reason behind doing this is that we'll be using various colormaps available from branca and it has colors mapped between values 0-1. We need to map each happiness score value to different colors according to their intensity hence we have a normalized happiness score so that branca colormap can map each happiness score to different colors.

In [15]:
min_val = min(happiness_report.Score.values)
max_val = max(happiness_report.Score.values)
diff = max_val-min_val

normalized_vals = (happiness_report.Score.values - min_val)/diff
normalized_vals[:20]
Out[15]:
array([1.        , 0.96562246, 0.95626526, 0.94406021, 0.94283971,
       0.94121237, 0.91334418, 0.90602116, 0.90012205, 0.89361269,
       0.88995118, 0.87754272, 0.87184703, 0.86187958, 0.85455655,
       0.84784378, 0.84052075, 0.82790887, 0.82160293, 0.81346623])

Below we have created mapping from country name to normalized happiness scores. We have also tried to correct the happiness score when the country name has a discrepancy between geo JSON and dataframe like previous examples.

In [16]:
mapping  = dict(zip(happiness_report["Country or region"].str.strip(), normalized_vals))

for d in geo_json_data["features"]:
    if d["name"] not in mapping:
        if d["name"] in proper_name_mapping.keys():
            mapping[d["name"]] = mapping[proper_name_mapping[d["name"]]]
        else:
            mapping[d["name"]] = 0

list(mapping.items())[:10]
Out[16]:
[('Finland', 1.0),
 ('Denmark', 0.9656224572823433),
 ('Norway', 0.9562652563059395),
 ('Iceland', 0.944060211554109),
 ('Netherlands', 0.9428397070789258),
 ('Switzerland', 0.9412123677786819),
 ('Sweden', 0.9133441822620016),
 ('New Zealand', 0.9060211554109032),
 ('Canada', 0.900122050447518),
 ('Austria', 0.893612693246542)]

Below we have created our world happiness choropleth map using GeoJSON(). We have created a map object first. We have then created a geo json layer using the GeoJSON() constructor passing it geo json data and a callback function which will be used to style each country.

We have declared callback function named feature_color() which returns style information for each feature (country). It maps the normalized happiness score for each country to color available for that value in BuGn_04 colormap. We have also provided information about the hover style for each feature.

In [ ]:
from ipyleaflet import GeoJSON

def feature_color(feature):
    feature_name = feature["properties"]["name_sort"]
    return {
        'color': 'black',
        'fillColor': linear.BuGn_04(mapping[feature_name]),
    }

m = Map(zoom=2)

geo_json = GeoJSON(
    data=geo_json_data,
    style={
        'opacity': 1, 'fillOpacity': 1.0, 'weight': 1
    },
    hover_style={
        'color': 'white', 'fillOpacity': 0.95
    },
    style_callback=feature_color
)

m.add_layer(geo_json)

m.layout.height = "600px"

m

Choropleth Maps using ipyleaflet

2.2 World Healthy Life Expectancy Choropleth Map

Below we have created another choropleth map using the GeoJSON() constructor but this time using Healthy life expectancy of pandas dataframe. The code is totally the same as the previous step with only a change in column name to use for the choropleth map.

In [ ]:
min_val = min(happiness_report["Healthy life expectancy"].values)
max_val = max(happiness_report["Healthy life expectancy"].values)
diff = max_val-min_val

normalized_vals = (happiness_report["Healthy life expectancy"].values - min_val)/diff

mapping  = dict(zip(happiness_report["Country or region"].str.strip(), normalized_vals))

for d in geo_json_data["features"]:
    if d["name"] not in mapping:
        if d["name"] in proper_name_mapping.keys():
            mapping[d["name"]] = mapping[proper_name_mapping[d["name"]]]
        else:
            mapping[d["name"]] = 0

def feature_color(feature):
    feature_name = feature["properties"]["name_sort"]
    return {
        'color': 'black',
        'fillColor': linear.PuBuGn_04(mapping[feature_name]),
    }

m = Map(zoom=2)

geo_json = GeoJSON(
    data=geo_json_data,
    style={
        'opacity': 1, 'fillOpacity': 1.0, 'weight': 1
    },
    hover_style={
        'color': 'white', 'fillOpacity': 0.1
    },
    style_callback=feature_color
)

m.add_layer(geo_json)

m.layout.height = "600px"

m

Choropleth Maps using ipyleaflet

2.3 US State-wise Population Choropleth Map

The third choropleth map that we have created using GeoJSON() is the US states population choropleth map. The steps to create a map is almost the same as the previous two examples with only changes in geo json and pandas dataframe.

In [ ]:
m = Map(zoom=4, center=(43,-100))

with open('datasets/us-states.json', 'r') as f:
    us_geo_json_data = json.load(f)
    for d in geo_json_data["features"]:
        d["name"] = d["properties"]["name"]

min_val = min(us_state_pop["2018 Population"].values)
max_val = max(us_state_pop["2018 Population"].values)
diff = max_val-min_val

normalized_vals = (us_state_pop["2018 Population"].values - min_val)/diff

mapping  = dict(zip(us_state_pop["State"].str.strip(), normalized_vals))

for d in geo_json_data["features"]:
    if d["name"] not in mapping:
        mapping[d["name"]] = 0

def feature_color(feature):
    feature_name = feature["properties"]["name"]
    return {
        'color': 'black',
        'fillColor': linear.YlOrRd_04(mapping[feature_name]),
    }

m = Map(zoom=4, center=(43,-100))

geo_json = GeoJSON(
    data=us_geo_json_data,
    style={
        'opacity': 1, 'fillOpacity': 1.0, 'weight': 1
    },
    hover_style={
        'color': 'white', 'fillOpacity': 0.0, "text":"test"
    },
    style_callback=feature_color
)

m.add_layer(geo_json)

m.layout.height = "600px"

m

Choropleth Maps using ipyleaflet

This ends our small tutorial explaining ways to create choropleth maps using ipyleaflet. Please feel free to let us know your views in the comments section.



Sunny Solanki  Sunny Solanki