Quick Start: Create Your First Symbolic Cartogram¶
Get started with carto-flow by creating a population cartogram in just a few lines of code.
What You'll Learn¶
- How to load the bundled US Census population and geography data
- How to create a basic symbol cartogram from geographic data using the
create_symbol_cartogramfunction - How to visualize the symbol cartogram
Prerequisites¶
Install carto-flow first. The examples use a US Census Bureau snapshot that ships with carto-flow, so load_us_census needs no API key and no network access.
# Install prerequisites (run this once in your terminal)
# pip install carto-flow
Step 1: Import Libraries¶
import matplotlib.pyplot as plt
import carto_flow.data as examples
import carto_flow.symbol_cartogram as smb
Step 2: Load Geographic Data¶
Load population and geometry data for the lower 48 US States from the US Census Bureau data repository.
us_states = examples.load_us_census(population=True)
The us_states variable is a GeoPandas DataFrame, with for each US State the requested variables, the computed population density, and its geometry.
Let's plot a color map of the population in each state. GeoPandas DataFrames have a convenient plot method that we use here to create a choropleth map in which colors represent the size of a State's population.
fig, ax = plt.subplots(1, 1, figsize=(7, 6))
us_states.plot(
"Population (Millions)",
ax=ax,
vmin=0,
legend=True,
legend_kwds={"shrink": 0.6, "label": "Population (Millions)"},
cmap="YlOrRd",
edgecolor="black",
linewidth=0.5,
)
ax.axis("off")
plt.tight_layout()
plt.show()
Step 3: Create Symbolic Cartogram¶
In a symbolic cartogram, each geographical region is represented by a symbol, such as a circle, a square, or another shape. The symbols are either sized uniformly or proportionally to a variable of interest, such as the number of people living in the region.
A major challenge is to determine where to place the symbols to create a clear and informative cartogram. Positioning symbols at the centroid of their corresponding geographical region preserves the spatial arrangement that makes the map recognizable, but also often produces overlapping symbols or large spacing between symbols. Other options for the placement of the symbols include symbol packing to remove overlap and large spacing while attempting to preserve some of the topological relations between the symbols, and creating a more schematic cartogram by positioning symbols on a regular grid or by giving each region a block of tiles on a shared lattice. The module supports all of these.
Symbols at Region Centroids¶
Here we construct a cartogram in which each US State is represented by a circle that is located at the State's centroid, and has an area that is proportional to the values in the Population column of the us_states Dataframe. We let the CentroidLayout remove any overlap between the circles by locally pushing the circles apart.
symbol_carto = smb.create_symbol_cartogram(
us_states,
# provide a column for proportional sizing of the symbols
"Population",
# place symbols at the state centroids and remove overlap
layout=smb.CentroidLayout(remove_overlap=True),
show_progress=False,
)
fig, ax = plt.subplots(1, 1, figsize=(8, 6))
_ = symbol_carto.plot(
ax=ax,
# color by population size
column="Population (Millions)",
# shrink the colorbar
legend_kwds={"shrink": 0.5},
# label the largest states
# "State Abbreviation" is a column of the loaded census data
label=[
row["State Abbreviation"] if row["State Abbreviation"] in ["CA", "TX", "FL", "NY"] else ""
for _, row in us_states.iterrows()
],
)
Symbols on a Grid¶
Here we construct a cartogram in which each US State is represented by a (hexagon-shaped) symbol in a hexagon tiling grid. Each State is assigned to a tile in the grid such that it is close to its geographical centroid and close to its neighbors. By not passing in a Dataframe column name, we choose to give all symbols the same size.
symbol_carto = smb.create_symbol_cartogram(
us_states,
# provide a column for proportional sizing of the symbols
# "Population",
layout=smb.GridBasedLayout(tiling=smb.HexagonTiling()),
show_progress=False,
)
fig, ax = plt.subplots(1, 1, figsize=(8, 6))
_ = symbol_carto.plot(
ax=ax,
# color by population size
column="Population (Millions)",
# shrink the colorbar
legend_kwds={"shrink": 0.75},
# label all states
# "State Abbreviation" is a column of the loaded census data
label=us_states["State Abbreviation"],
# set label color to white and use bold font
label_color="w",
label_kwargs={"fontweight": "bold"},
)
Circle Packing¶
Here we construct a cartogram in which US States are represented by circles that are closely packed in such a way to preserve the topological structure of the original geometries. Circles are sized proportionally to the values in the Population column of the us_states Dataframe.
symbol_carto = smb.create_symbol_cartogram(
us_states,
# provide a column for proportional sizing of the symbols
"Population",
# use a force-based circle packing layout
layout=smb.CirclePackingLayout(),
size_normalization="total",
show_progress=False,
)
fig, ax = plt.subplots(1, 1, figsize=(9, 6))
_ = symbol_carto.plot(
ax=ax,
# color by population size
column="Population (Millions)",
# shrink the colorbar
legend_kwds={"shrink": 0.75},
# label all states
# "State Abbreviation" is a column of the loaded census data
label=us_states["State Abbreviation"],
# set label color to white and use small bold font
label_color="w",
label_kwargs={"fontweight": "bold"},
label_fontsize=7,
)
Flow Density Layout¶
Here we construct a cartogram using the flow density layout (FlowDensityLayout). This layout builds a divergence field from Gaussian blobs at predicted contact points between circles and advects the circles through the resulting velocity field until they are well-spaced. Unlike force-based layouts, the field covers the full domain so there is no background sink: circles spread only in response to actual packing pressure, which tends to produce good geographic preservation with minimal configuration.
symbol_carto = smb.create_symbol_cartogram(
us_states,
# provide a column for proportional sizing of the symbols
"Population",
# use flow density layout
layout=smb.FlowDensityLayout(spacing=0.2, sigma_perp_factor=0.25, dt_factor=0.3, force_balance=2),
size_normalization="total",
show_progress=False,
)
fig, ax = plt.subplots(1, 1, figsize=(9, 6))
_ = symbol_carto.plot(
ax=ax,
# color by population size
column="Population (Millions)",
# shrink the colorbar
legend_kwds={"shrink": 0.75},
# label all states
label=us_states["State Abbreviation"],
# set label color to white and use small bold font
label_color="w",
label_kwargs={"fontweight": "bold"},
label_fontsize=7,
)
Tiles on a Mosaic¶
Here each US State is a block of hexagons on one shared lattice, and the number of hexagons carries the data instead of the symbol size. tile_count names the column holding the per-State counts; below, one tile stands for two million people, with a floor of one tile so the smallest States stay on the map. MosaicLayout keeps the tiles of a State together as a single connected block, so the blocks read as the States themselves.
us_states["Tiles"] = (us_states["Population"] / 2e6).round().clip(lower=1).astype(int)
symbol_carto = smb.create_symbol_cartogram(
us_states,
# provide a column with the number of tiles per state
tile_count="Tiles",
# place the tiles on a shared hexagon lattice
layout=smb.MosaicLayout(spacing=0.06),
show_progress=False,
)
fig, ax = plt.subplots(1, 1, figsize=(9, 6))
_ = symbol_carto.plot(
ax=ax,
# color by population size
column="Population (Millions)",
# shrink the colorbar
legend_kwds={"shrink": 0.75},
# label each tile with the abbreviation of the state it belongs to
label="State Abbreviation",
# set label color to white and use small bold font
label_color="w",
label_kwargs={"fontweight": "bold"},
label_fontsize=6,
edgecolor="white",
linewidth=0.5,
)
Additional Resources¶
This notebook is part of the carto-flow documentation. Report issues at https://github.com/bright-fakl/carto-flow/issues