Create Choropleth Maps by Using Plotly
Tags: Choropleth, Dataviz, Python, Dash, Plotly

Create Choropleth Maps by Using Plotly Baysan Follow Jul 26 · 6 min read

Hello everyone. In this story we will try to cover, how can we create choropleth maps by using Plotly.

If we want to create choropleth we need to have GeoJSON file which related our target area. And I prepared before the jupyter notebook which we will use in this story. You can access from here.

About Our Jupyter Notebook

We will use this dataset while we create our notebook. This dataset contains the estimate of the total population in Turkey in certain years. You can access the dataset from here. And we will use this GeoJSON file for our target area. The choropleth which we will create will demonstrate the estimated population.

Let’s Start

Preparing and Importing Dependencies

import pandas as pd

import numpy as np

import plotly.express as px

import plotly.graph_objects as go

from plotly.subplots import make_subplots

import requests

import json

As unlike the Plotly packages we need to import requests and json packages. We will use requests for getting GeoJSON data from GitHub gists and use json for accessing data.

Import Datasets

Import the dataset which contains estimated population from CSV file.

df = pd.read_csv('Turkey Districts - Population Estimate - Sayfa1.csv')

For accessing the data from GitHub gist we will use requests and we will send GET request to URL which has our GeoJSON file.

As you know our response is storing in the response object. And now we need to extract the json data which we need to access as a GeoJSON from response object.

geojson = response.json()

If you print geojson variable which we stored the data in that, you can see the json data.

Inspecting Datasets and Preparing to Visualization

Print the data types and look how they looks like.

You see, the estimated columns are object type. But they are storing numerical data. We need to change their data type. Firstly I’m going to change their column names for easily access.

df.rename(columns={'Population

Estimate

2009': 'Tahmin-2009',

'Population

Estimate

2013': 'Tahmin-2013',

'Population

Estimate

2019': 'Tahmin-2019'

}, inplace=True)

I’m going to look first 2 rows by using df.head(2)

And now it seems better. But still there is a problem. They separated float values with ‘,’ in Excel file but we are separating by using ‘.’ in Python. We need to solve that thing. I will use apply function on the columns by using lambda function. I will split all values with ‘,’ and after that I will concatenate the all parts which I separated for each row as a string.

df['Tahmin-2009'] = df['Tahmin-2009'].apply(lambda x: ''.join(x.split(',')))

df['Tahmin-2013'] = df['Tahmin-2013'].apply(lambda x: ''.join(x.split(',')))

df['Tahmin-2019'] = df['Tahmin-2019'].apply(lambda x: ''.join(x.split(',')))

Now if you try to convert the columns as numerical data you will see the error. Because they wrote ‘…’ instead of null values. I’m replacing ‘…’ values with numpy.nan.

df.replace('...', np.nan, inplace=True)

Now I can convert the estimated columns as numerical data.

df['Tahmin-2009'] = pd.to_numeric(df['Tahmin-2009'])

df['Tahmin-2013'] = pd.to_numeric(df['Tahmin-2013'])

df['Tahmin-2019'] = pd.to_numeric(df['Tahmin-2019'])

I’m going to check data types.

Look. It seems like what we want. This dataset contains cities and districts in Turkey. But I want to choose just cities. So I’m going to filter dataset by using State column.

df = df[(df['Status'] == 'Metropolitan Province') | (df['Status'] == 'Province')]

Some Informations About Our GeoJSON Data

In this section we will use geojson variable which we created at the start of story.

geojson[‘features’] = we can access the array of storing cities’s coordinates

geojson[‘features’][0] = we can access the first item of array which storing cities’s coordinates

geojson[‘features’][0][‘id’] = we can access the first city’s geojson id

geojson[‘features’][0][‘properties’] = we can access the first city’s geojson properties

geojson[‘features’][0][‘properties’][‘name’] = we can access the first city’s name property in geojson properties

Plotly Choropleth looks GeoJSON’s feature (geojson[‘feature’]) objects’s id by default. If we want we can change that, we can say “you have to look geojson[‘feature’][‘properties’]”.

Let’s imagine. We don’t know that. We will create a variable as geoDict for binding city name and city id from geojson. That dict will contain city name as key and city id as value.

geoDict = {}

for i in geojson['features']:

geoDict[i['properties']['name']] = i['id']

Bind Dataset with GeoJSON IDs

I need to change some values’s value as what we want. There is spelling problem. Because these values are what we changed by using replace function in geojson.

df.loc[df['Name'] == 'Afyonkarahisar'] = df.loc[df['Name'] == 'Afyonkarahisar'].replace('Afyonkarahisar', 'Afyon') df.loc[df['Name'] == 'Elâzığ'] = df.loc[df['Name'] == 'Elâzığ'].replace('Elâzığ', 'Elazığ') df.loc[df['Name'] == 'Hakkâri'] = df.loc[df['Name'] == 'Hakkâri'].replace('Hakkâri', 'Hakkari')

As a start point I created a column and I set it’s default value as ‘Yok’.

df.loc[:, 'GeoID'] = 'Yok'

Now I’m binding geojson ids with dataframe by using dataframe’s ‘Name’ column and geoDict’s keys.

df['GeoID'] = df['Name'].apply(lambda x: geoDict[x])

We can see GeoID in DataFrame

Huh. We did preprocess our data for prepare as what we want format. Now we can create choropleths. Let’s start.

Choropleths by Using Plotly Express

Choropleth MapBox

Plotly created Choropleth MapBox for create choropleth with MapBox. You can use your own customized maps by using MapBox tokens which you created from MapBox.

df = which dataframe variable

geojson = which geojson variable

locations = Which column will be used from Plotly for binding geojson and dataframe

color = which column will set the colors

range_color = set the min and max values for set colors

center = which coordinates will using while map starting

labels = change labels which you used in plot

mapbox_style = mapbox map style

custom_data = you can send custom data for using them on hover etc. actions

fig = px.choropleth_mapbox(

df,

geojson=geojson,

locations='GeoID',

color='Tahmin-2019', color_continuous_scale="dense",

range_color=(df['Tahmin-2019'].min(),df['Tahmin-2019'].max()),

center={'lat': 38.7200, 'lon': 34.0000},

labels={'Tahmin-2019': '2019 Nüfus Tahmini'},

mapbox_style="carto-positron",

zoom=4.8,

opacity=0.5,

custom_data=[df['Name'],

df['Tahmin-2019'], df['Status']]

) # Some make-up for plot

fig.update_layout(

title='Türkiye Nüfus Tahminleri 2019',

title_x=0.5

) # I created my own hover template for on hover event

hovertemp = '<i style="color:red;">Şehir Adı:</i> %{customdata[0]}<br>'

hovertemp += '<i>Şehir Statüsü:</i> %{customdata[2]}<br>'

hovertemp += '<i>Tahmini Nüfus:</i> %{customdata[1]:,f}<br>' # I set my own hover template

fig.update_traces(hovertemplate=hovertemp) fig.show()

If you execute the code you can see that output.

Choropleth

As unlike our previous map which we created by using choropleth_mapbox function we used featureidkey parameter. It will bind the geojson and dataframe by using geojson’s properties property.

color_continuous_scale changes color palette.

update_geos function can set map settings. fitbounds can draw map borders and visible parameter can set the visibility.

fig = px.choropleth(

df,

geojson=geojson,

locations='Name',

featureidkey='properties.name',

color='Tahmin-2019',

color_continuous_scale="Viridis",

range_color=(df['Tahmin-2019'].min(),

df['Tahmin-2019'].max()),

center={'lat': 38.7200, 'lon': 34.0000},

labels={'Tahmin-2019': '2019 Nüfus Tahmini'},

custom_data=[df['Name'],

df['Tahmin-2019'],

df['Status']]

) fig.update_geos(

fitbounds="locations",

visible=False

)



hovertemp = '<i>Şehir Adı:</i> %{customdata[0]}<br>'

hovertemp += '<i>Şehir Statüsü:</i> %{customdata[2]}<br>'

hovertemp += '<i>Tahmini Nüfus:</i> %{customdata[1]:,f}<br>'

fig.update_traces(hovertemplate=hovertemp) fig.show()

The map which we created by using choropleth

Choropleths by Using Plotly Graph Objects

In this section I don’t want to explain as unnecessary. Because the technique is almost same. And If you don’t know how can create subplots you can read my story which about that topic.

fig = make_subplots(

rows=1, cols=2,

specs=[

[{"type": "choropleth"},{"type": "choropleth"}],

]

) fig.add_trace(trace=go.Choropleth(

featureidkey='properties.name',

geojson=geojson,

locations=df['Name'],

z=df['Tahmin-2009'],

colorscale='YlGn',

colorbar_title="Tahmin",

zmin=df['Tahmin-2009'].min(),

zmax=df['Tahmin-2009'].max(),

name='2009 Nüfus Tahmini',

hoverinfo='location+z',

showlegend=False,

showscale=False,

), row=1, col=1) fig.add_trace(trace=go.Choropleth(

featureidkey='properties.name',

geojson=geojson,

locations=df['Name'],

z=df['Tahmin-2019'],

colorscale='YlGn',

colorbar_title="Tahmin",

zmin=df['Tahmin-2019'].min(),

zmax=df['Tahmin-2019'].max(),

name='2019 Nüfus Tahmini',

hoverinfo='location+z',

), row=1, col=2) fig.update_geos(fitbounds="locations",

visible=False,

) hovertemp = '<i>Şehir Adı:</i> %{location} <br>'

hovertemp += '<i>Nüfus Tahmini:</i> %{z:,}' fig.update_traces(hovertemplate=hovertemp) fig.update_layout(

title='2009 ve 2019 Arasında Karşılaştırmalı Nüfus Tahminleri', title_x=0.5) fig.show()

We can see different maps from different years

I hope this story can help you for understand “how can we create choropleth maps in Plotly”.

Kind regards.