Interactive Maps using geopandas.geodataframe.GeoDataFrame

I am unclear if Streamlit can render interactive maps if the underlying map data is of type geopandas.geodataframe.GeoDataFrame.

I am trying to create an app that lets users view how various small-area demographics changed during Covid-19. As a first step, I have code like this, which generates an interactive map of median household income in Census County Subdivisions in San Francisco County in 2022. This code works fine in a Jupyter Notebook:

import censusdis.data as ced
from censusdis.datasets import ACS5

MEDIAN_INCOME = "B19013_001E"
gdf_counties = ced.download(
    dataset=ACS5,
    vintage=2022,
    download_variables=["NAME", MEDIAN_INCOME],
    state='06', # California State
    county='075', # San Francisco County
    county_subdivision='*', # The geography that interests me - "County Subdivisions"
    with_geometry=True,
)
# The output of this cell should be an interactive map.
gdf_counties.explore(
    column=MEDIAN_INCOME,
    legend_kwds={"caption": "Median Income"},
)

I am fairly new to Python, and the creator of the censusdis package was kind enough to recently add support for .explore() for me (link).

I assumed that I would be able to simply pipe gdf_counties to st.map and get a similar experience in my streamlit app:

import streamlit as st
import censusdis.data as ced
from censusdis.datasets import ACS5

MEDIAN_INCOME = "B19013_001E"

gdf_counties = ced.download(
    dataset=ACS5,
    vintage=2022,
    download_variables=["NAME", MEDIAN_INCOME],
    state='06', # California State
    county='075', # San Francisco County
    county_subdivision='*', # The geography that interests me - "County Subdivisions"
    with_geometry=True,
)

st.map(gdf_counties)

However it instead generates this error:

StreamlitAPIException: Map data must contain a latitude column named: 'LAT', 'LATITUDE', 'lat', 'latitude'. Existing columns: 'STATE', 'COUNTY', 'COUNTY_SUBDIVISION', 'NAME', 'B19013_001E', 'geometry'

As I googled this error I saw a lot of activity, some dating from several years ago. But I am unclear if what I am trying to do is possible. In my case I think that the interactivity is very important, because people often don’t know the names of the various County Subdivisions, and so having it appear on click or on rollover will be very helpful (in addition to displaying the specific value).

Is there a way to have streamlit render gdf_counties as a choropleth with basic interactivity (i.e. tooltips)?

Thank you.

Update: On LinkedIn Ramnath Vaidyanathan was kind enough to make a recommendation that worked:

In short: Create the choropleth with plotly and then use st.plotly_chart. That worked for me, and I plan to write the solution up as a blog post as other solutions I found online simply did not work for me.

I just wrote a blog post on the solution that worked for me, including code.

It looks like this is a problem that people have had for several years. But unfortunately a few of the solutions I’d seen written about in blog posts simply did not work for me.

Hopefully this post helps someone in the future who has a similar problem:

After delving into your code,

import streamlit as st
import plotly.express as px

df = df.set_index('County')
fig = px.choropleth(df, geojson=df.geometry, locations=df.index, color=var_label, color_continuous_scale="Viridis", projection="mercator")
fig.update_geos(fitbounds="locations", visible=False)

st.plotly_chart(fig)

I also featured out other ways

  • Set County as index but keep the column for location reference
df = df.set_index('County', drop=False)
fig = px.choropleth(df, geojson=df.geometry, locations='County', color=var_label, color_continuous_scale="Viridis", projection="mercator")
  • Use geojson instead of geometry
# df = df.set_index('County', drop=False) # no need to set index
fig = px.choropleth(df, geojson=df.__geo_interface__, locations='County', featureidkey="properties.County", color=var_label, color_continuous_scale="Viridis", projection="mercator")
2 Likes

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.