Note

This page was generated from gallery/plotting_with_geoplot.ipynb.
Interactive online version: Binder badge

Plotting with Geoplot and GeoPandas#

Geoplot is a Python library providing a selection of easy-to-use geospatial visualizations. It is built on top of the lower-level CartoPy, covered in a separate section of this tutorial, and is designed to work with GeoPandas input.

This example is a brief tour of the geoplot API. For more details on the library refer to its documentation.

First we’ll load in the data using GeoPandas.

[1]:
import geopandas
import geodatasets
import geoplot

world = geopandas.read_file("https://naciscdn.org/naturalearth/110m/cultural/ne_110m_admin_0_countries.zip")
boroughs = geopandas.read_file(geoplot.datasets.get_path("nyc_boroughs"))
collisions = geopandas.read_file(geoplot.datasets.get_path("nyc_injurious_collisions"))

Plotting with Geoplot#

We start out by replicating the basic GeoPandas world plot using Geoplot.

[2]:
geoplot.polyplot(world, figsize=(8, 4))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.13.1/lib/python3.11/site-packages/geoplot/geoplot.py:885, in polyplot.<locals>.PolyPlot.draw(self)
    884 try:  # Duck test for MultiPolygon.
--> 885     for subgeom in geom:
    886         feature = GeopandasPolygonPatch(
    887             subgeom, facecolor=facecolor, edgecolor=edgecolor, zorder=zorder,
    888             **kwargs
    889         )

TypeError: 'MultiPolygon' object is not iterable

During handling of the above exception, another exception occurred:

AttributeError                            Traceback (most recent call last)
Cell In[2], line 1
----> 1 geoplot.polyplot(world, figsize=(8, 4))

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.13.1/lib/python3.11/site-packages/geoplot/geoplot.py:901, in polyplot(df, projection, extent, figsize, ax, **kwargs)
    898         return ax
    900 plot = PolyPlot(df, figsize=figsize, ax=ax, extent=extent, projection=projection, **kwargs)
--> 901 return plot.draw()

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.13.1/lib/python3.11/site-packages/geoplot/geoplot.py:892, in polyplot.<locals>.PolyPlot.draw(self)
    890                 ax.add_patch(feature)
    891         except (TypeError, AssertionError):  # Shapely Polygon.
--> 892             feature = GeopandasPolygonPatch(
    893                 geom, facecolor=facecolor, edgecolor=edgecolor, zorder=zorder,
    894                 **kwargs
    895             )
    896             ax.add_patch(feature)
    898 return ax

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.13.1/lib/python3.11/site-packages/geopandas/plotting.py:129, in _PolygonPatch(polygon, **kwargs)
    125 from matplotlib.patches import PathPatch
    126 from matplotlib.path import Path
    128 path = Path.make_compound_path(
--> 129     Path(np.asarray(polygon.exterior.coords)[:, :2]),
    130     *[Path(np.asarray(ring.coords)[:, :2]) for ring in polygon.interiors],
    131 )
    132 return PathPatch(path, **kwargs)

AttributeError: 'MultiPolygon' object has no attribute 'exterior'
../_images/gallery_plotting_with_geoplot_3_1.png

Geoplot can re-project data into any of the map projections provided by CartoPy (see the list here).

[3]:
# use the Orthographic map projection (e.g. a world globe)
ax = geoplot.polyplot(world, projection=geoplot.crs.Orthographic(), figsize=(8, 4))
ax.outline_patch.set_visible(True)
/home/docs/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.13.1/lib/python3.11/site-packages/geoplot/geoplot.py:703: UserWarning: Plot extent lies outside of the Orthographic projection's viewport. Defaulting to global extent.
  warnings.warn(
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[3], line 3
      1 # use the Orthographic map projection (e.g. a world globe)
      2 ax = geoplot.polyplot(world, projection=geoplot.crs.Orthographic(), figsize=(8, 4))
----> 3 ax.outline_patch.set_visible(True)

AttributeError: 'GeoAxes' object has no attribute 'outline_patch'
../_images/gallery_plotting_with_geoplot_5_2.png

polyplot is trivial and can only plot the geometries you pass to it. If you want to use color as a visual variable, specify a choropleth. Here we sort GDP per person by country into five buckets by color, using “quantiles” binning from the Mapclassify library.

[4]:
import mapclassify

gpd_per_person = world["gdp_md_est"] / world["pop_est"]
scheme = mapclassify.Quantiles(gpd_per_person, k=5)

# Note: this code sample requires geoplot>=0.4.0.
geoplot.choropleth(
    world, hue=gpd_per_person, scheme=scheme, cmap="Greens", figsize=(8, 4)
)
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.13.1/lib/python3.11/site-packages/pandas/core/indexes/base.py:3652, in Index.get_loc(self, key)
   3651 try:
-> 3652     return self._engine.get_loc(casted_key)
   3653 except KeyError as err:

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.13.1/lib/python3.11/site-packages/pandas/_libs/index.pyx:147, in pandas._libs.index.IndexEngine.get_loc()

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.13.1/lib/python3.11/site-packages/pandas/_libs/index.pyx:176, in pandas._libs.index.IndexEngine.get_loc()

File pandas/_libs/hashtable_class_helper.pxi:7080, in pandas._libs.hashtable.PyObjectHashTable.get_item()

File pandas/_libs/hashtable_class_helper.pxi:7088, in pandas._libs.hashtable.PyObjectHashTable.get_item()

KeyError: 'gdp_md_est'

The above exception was the direct cause of the following exception:

KeyError                                  Traceback (most recent call last)
Cell In[4], line 3
      1 import mapclassify
----> 3 gpd_per_person = world["gdp_md_est"] / world["pop_est"]
      4 scheme = mapclassify.Quantiles(gpd_per_person, k=5)
      6 # Note: this code sample requires geoplot>=0.4.0.

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.13.1/lib/python3.11/site-packages/geopandas/geodataframe.py:1475, in GeoDataFrame.__getitem__(self, key)
   1469 def __getitem__(self, key):
   1470     """
   1471     If the result is a column containing only 'geometry', return a
   1472     GeoSeries. If it's a DataFrame with any columns of GeometryDtype,
   1473     return a GeoDataFrame.
   1474     """
-> 1475     result = super().__getitem__(key)
   1476     # Custom logic to avoid waiting for pandas GH51895
   1477     # result is not geometry dtype for multi-indexes
   1478     if (
   1479         pd.api.types.is_scalar(key)
   1480         and key == ""
   (...)
   1483         and not is_geometry_type(result)
   1484     ):

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.13.1/lib/python3.11/site-packages/pandas/core/frame.py:3761, in DataFrame.__getitem__(self, key)
   3759 if self.columns.nlevels > 1:
   3760     return self._getitem_multilevel(key)
-> 3761 indexer = self.columns.get_loc(key)
   3762 if is_integer(indexer):
   3763     indexer = [indexer]

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.13.1/lib/python3.11/site-packages/pandas/core/indexes/base.py:3654, in Index.get_loc(self, key)
   3652     return self._engine.get_loc(casted_key)
   3653 except KeyError as err:
-> 3654     raise KeyError(key) from err
   3655 except TypeError:
   3656     # If we have a listlike key, _check_indexing_error will raise
   3657     #  InvalidIndexError. Otherwise we fall through and re-raise
   3658     #  the TypeError.
   3659     self._check_indexing_error(key)

KeyError: 'gdp_md_est'

If you want to use size as a visual variable, use a cartogram. Here are population estimates for countries in Africa.

[5]:
africa = world.query('continent == "Africa"')
ax = geoplot.cartogram(
    africa, scale="pop_est", limits=(0.2, 1), edgecolor="None", figsize=(7, 8)
)
geoplot.polyplot(africa, edgecolor="gray", ax=ax)
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.13.1/lib/python3.11/site-packages/pandas/core/computation/scope.py:233, in Scope.resolve(self, key, is_local)
    232 if self.has_resolvers:
--> 233     return self.resolvers[key]
    235 # if we're here that means that we have no locals and we also have
    236 # no resolvers

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.13.1/lib/python3.11/collections/__init__.py:1004, in ChainMap.__getitem__(self, key)
   1003         pass
-> 1004 return self.__missing__(key)

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.13.1/lib/python3.11/collections/__init__.py:996, in ChainMap.__missing__(self, key)
    995 def __missing__(self, key):
--> 996     raise KeyError(key)

KeyError: 'continent'

During handling of the above exception, another exception occurred:

KeyError                                  Traceback (most recent call last)
File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.13.1/lib/python3.11/site-packages/pandas/core/computation/scope.py:244, in Scope.resolve(self, key, is_local)
    240 try:
    241     # last ditch effort we look in temporaries
    242     # these are created when parsing indexing expressions
    243     # e.g., df[df > 0]
--> 244     return self.temps[key]
    245 except KeyError as err:

KeyError: 'continent'

The above exception was the direct cause of the following exception:

UndefinedVariableError                    Traceback (most recent call last)
Cell In[5], line 1
----> 1 africa = world.query('continent == "Africa"')
      2 ax = geoplot.cartogram(
      3     africa, scale="pop_est", limits=(0.2, 1), edgecolor="None", figsize=(7, 8)
      4 )
      5 geoplot.polyplot(africa, edgecolor="gray", ax=ax)

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.13.1/lib/python3.11/site-packages/pandas/core/frame.py:4440, in DataFrame.query(self, expr, inplace, **kwargs)
   4438 kwargs["level"] = kwargs.pop("level", 0) + 1
   4439 kwargs["target"] = None
-> 4440 res = self.eval(expr, **kwargs)
   4442 try:
   4443     result = self.loc[res]

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.13.1/lib/python3.11/site-packages/pandas/core/frame.py:4566, in DataFrame.eval(self, expr, inplace, **kwargs)
   4563     kwargs["target"] = self
   4564 kwargs["resolvers"] = tuple(kwargs.get("resolvers", ())) + resolvers
-> 4566 return _eval(expr, inplace=inplace, **kwargs)

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.13.1/lib/python3.11/site-packages/pandas/core/computation/eval.py:336, in eval(expr, parser, engine, local_dict, global_dict, resolvers, level, target, inplace)
    327 # get our (possibly passed-in) scope
    328 env = ensure_scope(
    329     level + 1,
    330     global_dict=global_dict,
   (...)
    333     target=target,
    334 )
--> 336 parsed_expr = Expr(expr, engine=engine, parser=parser, env=env)
    338 if engine == "numexpr" and (
    339     is_extension_array_dtype(parsed_expr.terms.return_type)
    340     or getattr(parsed_expr.terms, "operand_types", None) is not None
   (...)
    344     )
    345 ):
    346     warnings.warn(
    347         "Engine has switched to 'python' because numexpr does not support "
    348         "extension array dtypes. Please set your engine to python manually.",
    349         RuntimeWarning,
    350         stacklevel=find_stack_level(),
    351     )

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.13.1/lib/python3.11/site-packages/pandas/core/computation/expr.py:809, in Expr.__init__(self, expr, engine, parser, env, level)
    807 self.parser = parser
    808 self._visitor = PARSERS[parser](self.env, self.engine, self.parser)
--> 809 self.terms = self.parse()

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.13.1/lib/python3.11/site-packages/pandas/core/computation/expr.py:828, in Expr.parse(self)
    824 def parse(self):
    825     """
    826     Parse an expression.
    827     """
--> 828     return self._visitor.visit(self.expr)

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.13.1/lib/python3.11/site-packages/pandas/core/computation/expr.py:415, in BaseExprVisitor.visit(self, node, **kwargs)
    413 method = f"visit_{type(node).__name__}"
    414 visitor = getattr(self, method)
--> 415 return visitor(node, **kwargs)

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.13.1/lib/python3.11/site-packages/pandas/core/computation/expr.py:421, in BaseExprVisitor.visit_Module(self, node, **kwargs)
    419     raise SyntaxError("only a single expression is allowed")
    420 expr = node.body[0]
--> 421 return self.visit(expr, **kwargs)

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.13.1/lib/python3.11/site-packages/pandas/core/computation/expr.py:415, in BaseExprVisitor.visit(self, node, **kwargs)
    413 method = f"visit_{type(node).__name__}"
    414 visitor = getattr(self, method)
--> 415 return visitor(node, **kwargs)

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.13.1/lib/python3.11/site-packages/pandas/core/computation/expr.py:424, in BaseExprVisitor.visit_Expr(self, node, **kwargs)
    423 def visit_Expr(self, node, **kwargs):
--> 424     return self.visit(node.value, **kwargs)

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.13.1/lib/python3.11/site-packages/pandas/core/computation/expr.py:415, in BaseExprVisitor.visit(self, node, **kwargs)
    413 method = f"visit_{type(node).__name__}"
    414 visitor = getattr(self, method)
--> 415 return visitor(node, **kwargs)

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.13.1/lib/python3.11/site-packages/pandas/core/computation/expr.py:719, in BaseExprVisitor.visit_Compare(self, node, **kwargs)
    717     op = self.translate_In(ops[0])
    718     binop = ast.BinOp(op=op, left=node.left, right=comps[0])
--> 719     return self.visit(binop)
    721 # recursive case: we have a chained comparison, a CMP b CMP c, etc.
    722 left = node.left

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.13.1/lib/python3.11/site-packages/pandas/core/computation/expr.py:415, in BaseExprVisitor.visit(self, node, **kwargs)
    413 method = f"visit_{type(node).__name__}"
    414 visitor = getattr(self, method)
--> 415 return visitor(node, **kwargs)

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.13.1/lib/python3.11/site-packages/pandas/core/computation/expr.py:535, in BaseExprVisitor.visit_BinOp(self, node, **kwargs)
    534 def visit_BinOp(self, node, **kwargs):
--> 535     op, op_class, left, right = self._maybe_transform_eq_ne(node)
    536     left, right = self._maybe_downcast_constants(left, right)
    537     return self._maybe_evaluate_binop(op, op_class, left, right)

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.13.1/lib/python3.11/site-packages/pandas/core/computation/expr.py:455, in BaseExprVisitor._maybe_transform_eq_ne(self, node, left, right)
    453 def _maybe_transform_eq_ne(self, node, left=None, right=None):
    454     if left is None:
--> 455         left = self.visit(node.left, side="left")
    456     if right is None:
    457         right = self.visit(node.right, side="right")

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.13.1/lib/python3.11/site-packages/pandas/core/computation/expr.py:415, in BaseExprVisitor.visit(self, node, **kwargs)
    413 method = f"visit_{type(node).__name__}"
    414 visitor = getattr(self, method)
--> 415 return visitor(node, **kwargs)

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.13.1/lib/python3.11/site-packages/pandas/core/computation/expr.py:548, in BaseExprVisitor.visit_Name(self, node, **kwargs)
    547 def visit_Name(self, node, **kwargs):
--> 548     return self.term_type(node.id, self.env, **kwargs)

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.13.1/lib/python3.11/site-packages/pandas/core/computation/ops.py:86, in Term.__init__(self, name, env, side, encoding)
     84 tname = str(name)
     85 self.is_local = tname.startswith(LOCAL_TAG) or tname in DEFAULT_GLOBALS
---> 86 self._value = self._resolve_name()
     87 self.encoding = encoding

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.13.1/lib/python3.11/site-packages/pandas/core/computation/ops.py:110, in Term._resolve_name(self)
    105 if local_name in self.env.scope and isinstance(
    106     self.env.scope[local_name], type
    107 ):
    108     is_local = False
--> 110 res = self.env.resolve(local_name, is_local=is_local)
    111 self.update(res)
    113 if hasattr(res, "ndim") and res.ndim > 2:

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.13.1/lib/python3.11/site-packages/pandas/core/computation/scope.py:246, in Scope.resolve(self, key, is_local)
    244     return self.temps[key]
    245 except KeyError as err:
--> 246     raise UndefinedVariableError(key, is_local) from err

UndefinedVariableError: name 'continent' is not defined

If we have data in the shape of points in space, we may generate a three-dimensional heatmap on it using kdeplot.

[6]:
ax = geoplot.kdeplot(
    collisions.head(1000),
    clip=boroughs.geometry,
    shade=True,
    cmap="Reds",
    projection=geoplot.crs.AlbersEqualArea(),
)
geoplot.polyplot(boroughs, ax=ax, zorder=1)
/home/docs/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.13.1/lib/python3.11/site-packages/geoplot/geoplot.py:1304: FutureWarning:

`shade` is now deprecated in favor of `fill`; setting `fill=True`.
This will become an error in seaborn v0.14.0; please update your code.

  sns.kdeplot(
[6]:
<GeoAxes: >
../_images/gallery_plotting_with_geoplot_11_2.png

These are just some of the plots you can make with Geoplot. There are many other possibilities not covered in this brief introduction. For more examples, refer to the Gallery in the Geoplot documentation.