USA County Choropleth Maps in Python

How to create colormaped representations of USA counties by FIPS values in Python.


New to Plotly?

Plotly is a free and open-source graphing library for Python. We recommend you read our Getting Started guide for the latest installation or upgrade instructions, then move on to our Plotly Fundamentals tutorials or dive straight in to some Basic Charts tutorials.

Deprecation warning

This page describes a legacy "figure factory" method for creating map-like figures using self-filled scatter traces. This is no longer the recommended way to make county-level choropleth maps, instead we recommend using a GeoJSON-based approach to making outline choropleth maps or the alternative Mapbox tile-based choropleth maps.

Required Packages

geopandas, pyshp and shapely must be installed for this figure factory.

Run the following commands to install the correct versions of the following modules:

In [1]:
!pip install geopandas==0.3.0
!pip install pyshp==1.2.10
!pip install shapely==1.6.3
Collecting geopandas==0.3.0
  Downloading geopandas-0.3.0-py2.py3-none-any.whl (888 kB)
     |████████████████████████████████| 888 kB 16.2 MB/s 
Requirement already satisfied: shapely in /home/circleci/project/doc/venv/lib/python3.7/site-packages (from geopandas==0.3.0) (1.7.1)
Collecting descartes
  Downloading descartes-1.1.0-py3-none-any.whl (5.8 kB)
Requirement already satisfied: pandas in /home/circleci/project/doc/venv/lib/python3.7/site-packages (from geopandas==0.3.0) (1.0.3)
Requirement already satisfied: fiona in /home/circleci/project/doc/venv/lib/python3.7/site-packages (from geopandas==0.3.0) (1.8.20)
Requirement already satisfied: pyproj in /home/circleci/project/doc/venv/lib/python3.7/site-packages (from geopandas==0.3.0) (3.1.0)
Requirement already satisfied: matplotlib in /home/circleci/project/doc/venv/lib/python3.7/site-packages (from descartes->geopandas==0.3.0) (3.4.2)
Requirement already satisfied: six>=1.7 in /home/circleci/project/doc/venv/lib/python3.7/site-packages (from fiona->geopandas==0.3.0) (1.16.0)
Requirement already satisfied: click-plugins>=1.0 in /home/circleci/project/doc/venv/lib/python3.7/site-packages (from fiona->geopandas==0.3.0) (1.1.1)
Requirement already satisfied: attrs>=17 in /home/circleci/project/doc/venv/lib/python3.7/site-packages (from fiona->geopandas==0.3.0) (21.2.0)
Requirement already satisfied: setuptools in /home/circleci/project/doc/venv/lib/python3.7/site-packages (from fiona->geopandas==0.3.0) (47.1.0)
Requirement already satisfied: munch in /home/circleci/project/doc/venv/lib/python3.7/site-packages (from fiona->geopandas==0.3.0) (2.5.0)
Requirement already satisfied: click>=4.0 in /home/circleci/project/doc/venv/lib/python3.7/site-packages (from fiona->geopandas==0.3.0) (8.0.1)
Requirement already satisfied: cligj>=0.5 in /home/circleci/project/doc/venv/lib/python3.7/site-packages (from fiona->geopandas==0.3.0) (0.7.2)
Requirement already satisfied: certifi in /home/circleci/project/doc/venv/lib/python3.7/site-packages (from fiona->geopandas==0.3.0) (2021.5.30)
Requirement already satisfied: importlib-metadata in /home/circleci/project/doc/venv/lib/python3.7/site-packages (from click>=4.0->fiona->geopandas==0.3.0) (3.10.1)
Requirement already satisfied: zipp>=0.5 in /home/circleci/project/doc/venv/lib/python3.7/site-packages (from importlib-metadata->click>=4.0->fiona->geopandas==0.3.0) (3.5.0)
Requirement already satisfied: typing-extensions>=3.6.4 in /home/circleci/project/doc/venv/lib/python3.7/site-packages (from importlib-metadata->click>=4.0->fiona->geopandas==0.3.0) (3.10.0.0)
Requirement already satisfied: kiwisolver>=1.0.1 in /home/circleci/project/doc/venv/lib/python3.7/site-packages (from matplotlib->descartes->geopandas==0.3.0) (1.3.1)
Requirement already satisfied: cycler>=0.10 in /home/circleci/project/doc/venv/lib/python3.7/site-packages (from matplotlib->descartes->geopandas==0.3.0) (0.10.0)
Requirement already satisfied: pyparsing>=2.2.1 in /home/circleci/project/doc/venv/lib/python3.7/site-packages (from matplotlib->descartes->geopandas==0.3.0) (2.4.7)
Requirement already satisfied: python-dateutil>=2.7 in /home/circleci/project/doc/venv/lib/python3.7/site-packages (from matplotlib->descartes->geopandas==0.3.0) (2.8.2)
Requirement already satisfied: pillow>=6.2.0 in /home/circleci/project/doc/venv/lib/python3.7/site-packages (from matplotlib->descartes->geopandas==0.3.0) (8.3.1)
Requirement already satisfied: numpy>=1.16 in /home/circleci/project/doc/venv/lib/python3.7/site-packages (from matplotlib->descartes->geopandas==0.3.0) (1.19.5)
Requirement already satisfied: pytz>=2017.2 in /home/circleci/project/doc/venv/lib/python3.7/site-packages (from pandas->geopandas==0.3.0) (2021.1)
Installing collected packages: descartes, geopandas
  Attempting uninstall: geopandas
    Found existing installation: geopandas 0.8.1
    Uninstalling geopandas-0.8.1:
      Successfully uninstalled geopandas-0.8.1
Successfully installed descartes-1.1.0 geopandas-0.3.0
Collecting pyshp==1.2.10
  Downloading pyshp-1.2.10.tar.gz (176 kB)
     |████████████████████████████████| 176 kB 38.4 MB/s 
Using legacy 'setup.py install' for pyshp, since package 'wheel' is not installed.
Installing collected packages: pyshp
  Attempting uninstall: pyshp
    Found existing installation: pyshp 2.1.2
    Uninstalling pyshp-2.1.2:
      Successfully uninstalled pyshp-2.1.2
    Running setup.py install for pyshp ... - done
Successfully installed pyshp-1.2.10
Collecting shapely==1.6.3
  Downloading Shapely-1.6.3.tar.gz (223 kB)
     |████████████████████████████████| 223 kB 12.0 MB/s 
    ERROR: Command errored out with exit status 1:
     command: /home/circleci/project/doc/venv/bin/python3 -c 'import io, os, sys, setuptools, tokenize; sys.argv[0] = '"'"'/tmp/pip-install-yqdk6387/shapely_d4f6bfdbe99e4460b46cafd76b9322f6/setup.py'"'"'; __file__='"'"'/tmp/pip-install-yqdk6387/shapely_d4f6bfdbe99e4460b46cafd76b9322f6/setup.py'"'"';f = getattr(tokenize, '"'"'open'"'"', open)(__file__) if os.path.exists(__file__) else io.StringIO('"'"'from setuptools import setup; setup()'"'"');code = f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' egg_info --egg-base /tmp/pip-pip-egg-info-9zxpzkju
         cwd: /tmp/pip-install-yqdk6387/shapely_d4f6bfdbe99e4460b46cafd76b9322f6/
    Complete output (11 lines):
    Failed `CDLL(libgeos_c.so.1)`
    Failed `CDLL(libgeos_c.so)`
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/tmp/pip-install-yqdk6387/shapely_d4f6bfdbe99e4460b46cafd76b9322f6/setup.py", line 80, in <module>
        from shapely._buildcfg import geos_version_string, geos_version, \
      File "/tmp/pip-install-yqdk6387/shapely_d4f6bfdbe99e4460b46cafd76b9322f6/shapely/_buildcfg.py", line 167, in <module>
        fallbacks=['libgeos_c.so.1', 'libgeos_c.so'])
      File "/tmp/pip-install-yqdk6387/shapely_d4f6bfdbe99e4460b46cafd76b9322f6/shapely/_buildcfg.py", line 161, in load_dll
        libname, fallbacks or []))
    OSError: Could not find library geos_c or load any of its variants ['libgeos_c.so.1', 'libgeos_c.so']
    ----------------------------------------
WARNING: Discarding https://files.pythonhosted.org/packages/c0/10/1457c46e20b509108a32a5776141d78d410161dae8ab8da74efe67c530eb/Shapely-1.6.3.tar.gz#sha256=14152f111c7711fc6756fd538ec12fc8cdde7419f869b244922f71f61b2a6c6b (from https://pypi.org/simple/shapely/). Command errored out with exit status 1: python setup.py egg_info Check the logs for full command output.
ERROR: Could not find a version that satisfies the requirement shapely==1.6.3 (from versions: 1.0a7, 1.0b1, 1.0b2, 1.0b3, 1.0b4, 1.0rc1, 1.0rc2, 1.0, 1.0.1, 1.0.2, 1.0.3, 1.0.4, 1.0.5, 1.0.6, 1.0.7, 1.0.11, 1.0.12, 1.0.13, 1.0.14, 1.0.15, 1.2b1, 1.2b2, 1.2b3, 1.2b4, 1.2b5, 1.2b6, 1.2b7, 1.2rc1, 1.2rc2, 1.2, 1.2.1, 1.2.2, 1.2.3, 1.2.4, 1.2.5, 1.2.6, 1.2.7, 1.2.8, 1.2.9, 1.2.10, 1.2.12, 1.2.13, 1.2.14, 1.2.15, 1.2.16, 1.2.17, 1.2.18, 1.2.19, 1.3.0, 1.3.1, 1.3.2, 1.3.3, 1.4.0, 1.4.1, 1.4.2, 1.4.3, 1.4.4, 1.5.0, 1.5.1, 1.5.2, 1.5.3, 1.5.4, 1.5.5, 1.5.6, 1.5.7, 1.5.8, 1.5.9, 1.5.10, 1.5.11, 1.5.12, 1.5.13, 1.5.14, 1.5.15, 1.5.16, 1.5.17, 1.6a1, 1.6a2, 1.6b1, 1.6b2, 1.6b3, 1.6b4, 1.6b5, 1.6.0, 1.6.1, 1.6.2, 1.6.2.post1, 1.6.3, 1.6.4, 1.6.4.post1, 1.6.4.post2, 1.7a1, 1.7a2, 1.7a3, 1.7b1, 1.7.0, 1.7.1, 1.8a1, 1.8a2, 1.8a3)
ERROR: No matching distribution found for shapely==1.6.3

If you are using Windows, follow this post to properly install geopandas and dependencies: http://geoffboeing.com/2014/09/using-geopandas-windows/. If you are using Anaconda, do not use PIP to install the packages above. Instead use conda to install them:

conda install plotly conda install geopandas

FIPS and Values

Every US state and county has an assigned ID regulated by the US Federal Government under the term FIPS (Federal Information Processing Standards) codes. There are state codes and county codes: the 2016 state and county FIPS codes can be found at the US Census Website.

Combine a state FIPS code (eg. 06 for California) with a county FIPS code of the state (eg. 059 for Orange county) and this new state-county FIPS code (06059) uniquely refers to the specified state and county.

ff.create_choropleth only needs a list of FIPS codes and a list of values. Each FIPS code points to one county and each corresponding value in values determines the color of the county.

Simple Example

A simple example of this is a choropleth a few counties in California:

In [2]:
import plotly.figure_factory as ff

fips = ['06021', '06023', '06027',
        '06029', '06033', '06059',
        '06047', '06049', '06051',
        '06055', '06061']
values = range(len(fips))

fig = ff.create_choropleth(fips=fips, values=values)
fig.layout.template = None
fig.show()

Change the Scope

Even if your FIPS values belong to a single state, the scope defaults to the entire United States as displayed in the example above. Changing the scope of the choropleth shifts the zoom and position of the USA map. You can define the scope with a list of state names and the zoom will automatically adjust to include the state outlines of the selected states.

By default scope is set to ['USA'] which the API treats as identical to passing a list of all 50 state names:

['AK', 'AL', 'CA', ...]

State abbreviations (eg. CA) or the proper names (eg. California) as strings are accepted. If the state name is not recognized, the API will throw a Warning and indicate which FIPS values were ignored.

Another param used in the example below is binning_endpoints. If your values is a list of numbers, you can bin your values into half-open intervals on the real line.

In [3]:
import plotly.figure_factory as ff

import numpy as np
import pandas as pd

df_sample = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/minoritymajority.csv')
df_sample_r = df_sample[df_sample['STNAME'] == 'California']

values = df_sample_r['TOT_POP'].tolist()
fips = df_sample_r['FIPS'].tolist()

colorscale = [
    'rgb(193, 193, 193)',
    'rgb(239,239,239)',
    'rgb(195, 196, 222)',
    'rgb(144,148,194)',
    'rgb(101,104,168)',
    'rgb(65, 53, 132)'
]

fig = ff.create_choropleth(
    fips=fips, values=values, scope=['CA', 'AZ', 'Nevada', 'Oregon', ' Idaho'],
    binning_endpoints=[14348, 63983, 134827, 426762, 2081313], colorscale=colorscale,
    county_outline={'color': 'rgb(255,255,255)', 'width': 0.5}, round_legend_values=True,
    legend_title='Population by County', title='California and Nearby States'
)
fig.layout.template = None
fig.show()

Single State

In [4]:
import plotly.figure_factory as ff

import numpy as np
import pandas as pd

df_sample = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/minoritymajority.csv')
df_sample_r = df_sample[df_sample['STNAME'] == 'Florida']

values = df_sample_r['TOT_POP'].tolist()
fips = df_sample_r['FIPS'].tolist()

endpts = list(np.mgrid[min(values):max(values):4j])
colorscale = ["#030512","#1d1d3b","#323268","#3d4b94","#3e6ab0",
              "#4989bc","#60a7c7","#85c5d3","#b7e0e4","#eafcfd"]
fig = ff.create_choropleth(
    fips=fips, values=values, scope=['Florida'], show_state_data=True,
    colorscale=colorscale, binning_endpoints=endpts, round_legend_values=True,
    plot_bgcolor='rgb(229,229,229)',
    paper_bgcolor='rgb(229,229,229)',
    legend_title='Population by County',
    county_outline={'color': 'rgb(255,255,255)', 'width': 0.5},
    exponent_format=True,
)
fig.layout.template = None
fig.show()

Multiple States

In [5]:
import plotly.figure_factory as ff

import pandas as pd

NE_states = ['Connecticut', 'Maine', 'Massachusetts', 'New Hampshire', 'Rhode Island', 'Vermont']
df_sample = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/minoritymajority.csv')
df_sample_r = df_sample[df_sample['STNAME'].isin(NE_states)]

values = df_sample_r['TOT_POP'].tolist()
fips = df_sample_r['FIPS'].tolist()

colorscale = [
    'rgb(68.0, 1.0, 84.0)',
    'rgb(66.0, 64.0, 134.0)',
    'rgb(38.0, 130.0, 142.0)',
    'rgb(63.0, 188.0, 115.0)',
    'rgb(216.0, 226.0, 25.0)'
]

fig = ff.create_choropleth(
    fips=fips, values=values,
    scope=NE_states, county_outline={'color': 'rgb(255,255,255)', 'width': 0.5},
    legend_title='Population per county'

)
fig.update_layout(
    legend_x = 0,
    annotations = {'x': -0.12, 'xanchor': 'left'}
)

fig.layout.template = None
fig.show()

Simplify County, State Lines

Below is a choropleth that uses several other parameters. For a full list of all available params call help(ff.create_choropleth)

  • simplify_county determines the simplification factor for the counties. The larger the number, the fewer vertices and edges each polygon has. See http://toblerity.org/shapely/manual.html#object.simplify for more information.
  • simplify_state simplifies the state outline polygon. See the documentation for more information. Default for both simplify_county and simplify_state is 0.02

Note: This choropleth uses a divergent categorical colorscale. See http://react-colorscales.getforge.io/ for other cool colorscales.

In [6]:
import plotly.figure_factory as ff

import pandas as pd

scope = ['Oregon']
df_sample = pd.read_csv(
    'https://raw.githubusercontent.com/plotly/datasets/master/minoritymajority.csv'
)
df_sample_r = df_sample[df_sample['STNAME'].isin(scope)]

values = df_sample_r['TOT_POP'].tolist()
fips = df_sample_r['FIPS'].tolist()

colorscale = ["#8dd3c7", "#ffffb3", "#bebada", "#fb8072",
              "#80b1d3", "#fdb462", "#b3de69", "#fccde5",
              "#d9d9d9", "#bc80bd", "#ccebc5", "#ffed6f",
              "#8dd3c7", "#ffffb3", "#bebada", "#fb8072",
              "#80b1d3", "#fdb462", "#b3de69", "#fccde5",
              "#d9d9d9", "#bc80bd", "#ccebc5", "#ffed6f",
              "#8dd3c7", "#ffffb3", "#bebada", "#fb8072",
              "#80b1d3", "#fdb462", "#b3de69", "#fccde5",
              "#d9d9d9", "#bc80bd", "#ccebc5", "#ffed6f"]

fig = ff.create_choropleth(
    fips=fips, values=values, scope=scope,
    colorscale=colorscale, round_legend_values=True,
    simplify_county=0, simplify_state=0,
    county_outline={'color': 'rgb(15, 15, 55)', 'width': 0.5},
    state_outline={'width': 1},
    legend_title='pop. per county',
    title='Oregon'
)

fig.layout.template = None
fig.show()