Search code examples
pythonplotlyplotly-pythonchoropleth

How to create a choropleth map by district with Plotly


I have the geojson data about districts in Mauritius and I want to plot a choropleth map.

The properties of the geojson file are as follows:

{'id_0': 143, 'iso': 'MUS', 'name_0': 'Mauritius', 'id_1': 1, 'name_1': 'Agalega', 'hasc_1': 'MU.AG', 'ccn_1': 0, 'cca_1': None, 'type_1': 'Region', 'engtype_1': 'Region', 'nl_name_1': None, 'varname_1': None}

{'id_0': 143, 'iso': 'MUS', 'name_0': 'Mauritius', 'id_1': 2, 'name_1': 'Black River', 'hasc_1': 'MU.BL', 'ccn_1': 0, 'cca_1': None, 'type_1': 'District', 'engtype_1': 'District', 'nl_name_1': None, 'varname_1': None}

{'id_0': 143, 'iso': 'MUS', 'name_0': 'Mauritius', 'id_1': 3, 'name_1': 'Flacq', 'hasc_1': 'MU.FL', 'ccn_1': 0, 'cca_1': None, 'type_1': 'District', 'engtype_1': 'District', 'nl_name_1': None, 'varname_1': None}

{'id_0': 143, 'iso': 'MUS', 'name_0': 'Mauritius', 'id_1': 4, 'name_1': 'Grand Port', 'hasc_1': 'MU.GP', 'ccn_1': 0, 'cca_1': None, 'type_1': 'District', 'engtype_1': 'District', 'nl_name_1': None, 'varname_1': None}

{'id_0': 143, 'iso': 'MUS', 'name_0': 'Mauritius', 'id_1': 5, 'name_1': 'Moka', 'hasc_1': 'MU.MO', 'ccn_1': 0, 'cca_1': None, 'type_1': 'District', 'engtype_1': 'District', 'nl_name_1': None, 'varname_1': None}

{'id_0': 143, 'iso': 'MUS', 'name_0': 'Mauritius', 'id_1': 6, 'name_1': 'Pamplemousses', 'hasc_1': 'MU.PA', 'ccn_1': 0, 'cca_1': None, 'type_1': 'District', 'engtype_1': 'District', 'nl_name_1': None, 'varname_1': None}

{'id_0': 143, 'iso': 'MUS', 'name_0': 'Mauritius', 'id_1': 7, 'name_1': 'Plaines Wilhems', 'hasc_1': 'MU.PW', 'ccn_1': 0, 'cca_1': None, 'type_1': 'District', 'engtype_1': 'District', 'nl_name_1': None, 'varname_1': None}

{'id_0': 143, 'iso': 'MUS', 'name_0': 'Mauritius', 'id_1': 8, 'name_1': 'Port Louis', 'hasc_1': 'MU.PL', 'ccn_1': 0, 'cca_1': None, 'type_1': 'District', 'engtype_1': 'District', 'nl_name_1': None, 'varname_1': None}

{'id_0': 143, 'iso': 'MUS', 'name_0': 'Mauritius', 'id_1': 9, 'name_1': 'Riviere du Rempart', 'hasc_1': 'MU.RR', 'ccn_1': 0, 'cca_1': None, 'type_1': 'District', 'engtype_1': 'District', 'nl_name_1': None, 'varname_1': None}

{'id_0': 143, 'iso': 'MUS', 'name_0': 'Mauritius', 'id_1': 10, 'name_1': 'Rodrigues', 'hasc_1': 'MU.RO', 'ccn_1': 0, 'cca_1': None, 'type_1': 'Autonomous island', 'engtype_1': 'Autonomous island', 'nl_name_1': None, 'varname_1': 'Rodrigues'}

{'id_0': 143, 'iso': 'MUS', 'name_0': 'Mauritius', 'id_1': 11, 'name_1': 'Saint Brandon', 'hasc_1': 'MU.CC', 'ccn_1': 0, 'cca_1': None, 'type_1': 'Region', 'engtype_1': 'Region', 'nl_name_1': None, 'varname_1': 'Cargados Carajos)'}

{'id_0': 143, 'iso': 'MUS', 'name_0': 'Mauritius', 'id_1': 12, 'name_1': 'Savanne', 'hasc_1': 'MU.SA', 'ccn_1': 0, 'cca_1': None, 'type_1': 'District', 'engtype_1': 'District', 'nl_name_1': None, 'varname_1': None}

The data to be plotted is in a LocationJobCount.csv file and tab is used as separator:

             Location  JobCount
0                Moka        55
1          Port Louis        45
2       Pamplemousses        13
3             Savanne         3
4  Riviere du Rempart         8
5         Black River         6
6     Plaines Wilhems       200

The location column contains the district names.

My code

"""
Python : 3.9.7
Plotly : 5.8.0
"""
import pandas as pd
import json
import plotly.express as px

districts = json.load(open("stanford-ph377fn8728-geojson.json", 'r'))
df = pd.read_csv("LocationJobCount.csv", sep='\t')

# map each location in dataframe to location in geojson
district_id_map = {}
for feature in districts['features']:
    district_id_map[feature['properties']['name_1']] = feature['properties']['id_1']
df['id'] = df['Location'].apply(lambda x: district_id_map[x])

# create choropleth map
fig = px.choropleth(df, geojson=districts,
                    locations=df['id'],
                    color='JobCount',
                    color_continuous_scale="algae",
                    range_color=[0, max(df['JobCount'])],
                    labels={"Value": "Count"}
                    )
fig.update_layout(geo_scope="world", geo_resolution=50)
# fig.update_geos(fitbounds="locations", visible=False)
fig.show()

After mapping locations between geojson and source file, df looks like this :

             Location  JobCount  id
0                Moka        55   5
1          Port Louis        45   8
2       Pamplemousses        13   6
3             Savanne         3  12
4  Riviere du Rempart         8   9
5         Black River         6   2
6     Plaines Wilhems       200   7

Result

After I zoomed in to Mauritius I see this:

enter image description here

No district is seen.

What I tried

  • Reference 1 : I read this post and ensured that the spelling of districts in the geojson file match that in the dataframe.
  • Uncommenting fig.update_geos(fitbounds="locations", visible=False) outputs a white screen.
  • Reference2 : Adding locationmode='country names' as parameter does not do anything.
  • Reference 3 : fig_map.update_layout(geo_scope="africa", geo_resolution=50) does not work because the island Mauritius is not found in this scope.

My goal is to create something similar to this : enter image description here


Solution

  • The problem in code is due to a wrong geojson object/data-file pairing.

    Solution

    In my case, I can directly pair the name_1 property in my GeoJSON file to the Location column in my dataframe.

    More info: https://plotly.com/python/choropleth-maps/#indexing-by-geojson-properties

    import pandas as pd
    import json
    import plotly.express as px
    import plotly.io as pio
    pio.renderers.default = 'browser'  # to show geojson map in web browser
    
    districts = json.load(open("stanford-ph377fn8728-geojson.json", 'r'))
    df = pd.read_csv("LocationJobCount.csv", sep='\t')
    
    fig = px.choropleth(df, geojson = districts,
                        featureidkey='properties.name_1',
                        locations='Location',  # column in dataframe which contains districts names
                        color='JobCount',  # data from this column in dataframe is plotted
                        color_continuous_scale="algae",
                        )
    fig.update_geos(fitbounds="locations") 
    fig.show()
    
    

    This worked but some districts where JobCount is 0 were missing from the map.

    I modified my dataframe to include all districts and set 0 as default value :

    Location    JobCount
    Black River 6
    Flacq   0
    Grand Port  0
    Moka    60
    Pamplemousses   17
    Port Louis  53
    Riviere du Rempart  10
    Savanne 3
    Plaines Wilhems 233
    

    The choropleth map now works:

    enter image description here