Search code examples
animationplotly-dashgeopandasplotly-pythonchoropleth

How to colorize lands which have missing values and get their name in plotly choropleth? Add extra values ​in the matchbox? Animate the plot by year?


I made a world map with specific data using choropleth from plotly. For some countries I have missing values ​​and this gives me uncolored areas and especially without being able to identify them.

Edit: I found how to colorize the countries with missing values ​​by adding this in the "geo=dict()" of "update_layout":

landcolor = 'lightgray',
showland = True,
showcountries = True,
countrycolor = 'gray',
countrywidth = 0.5,

Which gives this code:

import plotly.graph_objects as go

fig = go.Figure(data = go.Choropleth(
    locations = world_map_df_sous_nutrition["Code zone (ISO3)"],
    z = round((world_map_df_sous_nutrition["Proportion pop en sous-nutrition"]),2),
    text = world_map_df_sous_nutrition["Zone"],
    colorscale = "earth",
    autocolorscale = False,
    reversescale = True,
    marker_line_color = "white",
    marker_line_width = .2,
    colorbar_tickprefix = "%",
    colorbar_title = "Proportion de personnes en sous nutrition"
    ))

fig.update_layout(
    title_text="L'état de la sous-nutrition dans le monde en 2017",
    geo=dict(
        landcolor = 'lightgray',
        showland = True,
        showcountries = True,
        countrycolor = 'gray',
        countrywidth = 0.5,
        showframe=False,
        showcoastlines=False,
        projection_type='equirectangular'
    ),
    annotations = [dict(
        x=0.55,
        y=0.1,
        xref='paper',
        yref='paper',
        text='Source: <a href="https://www.fao.org/faostat/fr/#data">\
            FAO</a>',
        showarrow = False
    )],
    margin={"r":0,"t":0,"l":0,"b":0},  
)



fig.show()

And this image result:

Choropleth result update

Choropleth result before

How to get the name of these greyed out countries (having missing values) by hovering the cursor over them?

With GeoPandas I managed to colorize my missing areas with this code:

from geopandas import GeoDataFrame

world_map_df_sous_nutrition = GeoDataFrame(world_map_df_sous_nutrition)
world_map_df_sous_nutrition.plot(column="Proportion pop en sous-nutrition",cmap='cividis', figsize=(20, 20), missing_kwds={
        "color": "lightgrey",
        "edgecolor": "red",
        "hatch": "///",
        "label": "Missing values",
    }).axis('off')
plt.show()

GeoPandas result

How to do the same with plotly?

Is it possible in the matchbox to add values ​​from other columns of my dataframe in addition or I can only display values ​​from one column? If possible how to switch from one colorization to another?

Is it possible too to make an animated world map with the evolution of our data by years?


Solution

  • There are multiple questions

    1. how to color missing countries?
    2. how to have hover text on missing countries?
    3. how to animate years?
    4. how to display other columns in hover?
    • start by using plotly express It's a simpler to use interface

    • simple case of using animation_frame for animation. Answers 3.

    • use hover_data to answer 4.

    • https://plotly.com/python/map-configuration/#physical-base-maps states that countries are from natural earth. A simple way to get this is is use geopandas

      • for each animation frame check which countries are missing and add an additional trace. Answers 1.
      • this trace contains information for answer 2.
    • one strange thing I encountered was need to recreate the figure as final step using go

    import plotly.graph_objects as go
    import plotly.express as px
    import geopandas as gpd
    import pandas as pd
    import numpy as np
    
    # need to know countries that make up natural earth...
    world = gpd.read_file(gpd.datasets.get_path("naturalearth_lowres"))
    
    # synthesize data by year for animation
    df = pd.concat(
        [
            pd.DataFrame(
                {
                    "Code zone (ISO3)": world["iso_a3"].sample(150),
                    "Proportion pop en sous-nutrition": np.random.uniform(0, 100, 150),
                    "Zone": np.random.choice(list("ABCD"), 150),
                    "Product": np.reshape(
                        np.concatenate(
                            [
                                np.random.choice(list("jklmn"), 150),
                                np.random.choice(list("jklmn"), 150),
                            ]
                        ),
                        [150, 2],
                    ).tolist(),
                }
            ).assign(year=y)
            for y in range(2010, 2022)
        ]
    )
    
    # use px, simpler... especially animation
    fig = px.choropleth(
        df,
        locations="Code zone (ISO3)",
        color="Proportion pop en sous-nutrition",
        color_continuous_scale="earth_r",
        animation_frame="year",
        hover_data=["Zone", "Product"],
    ).update_layout(
        title_text="L'état de la sous-nutrition dans le monde en 2017",
        geo=dict(
            # landcolor="lightgray",
            showland=True,
            showcountries=True,
            countrycolor="gray",
            countrywidth=0.5,
            showframe=False,
            showcoastlines=False,
            projection_type="equirectangular",
        ),
        annotations=[
            dict(
                x=0.55,
                y=0.1,
                xref="paper",
                yref="paper",
                text='Source: <a href="https://www.fao.org/faostat/fr/#data">\
                FAO</a>',
                showarrow=False,
            )
        ],
        margin={"r": 0, "t": 0, "l": 0, "b": 0},
        coloraxis2={"colorscale": [[0, "red"], [1, "red"]], "showscale": False},
    )
    
    # update each of the animation frames with missing countries
    for fr in fig.frames:
        tr_missing = (
            px.choropleth(
                world.loc[~world["iso_a3"].isin(fr.data[0]["locations"]), "iso_a3"]
                .to_frame()
                .assign(color=1),
                color="color",
                locations="iso_a3",
                color_continuous_scale=[[0, "red"], [1, "red"]],
            )
            .update_traces(hovertemplate="missing: %{location}", coloraxis="coloraxis2")
            .data[0]
        )
        fr.update(data=[fr.data[0], tr_missing])
    
    
    # re-construct the figure...
    go.Figure(data=fig.frames[0].data, layout=fig.layout, frames=fig.frames)
    

    enter image description here