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"))
ERROR 1: PROJ: proj_create_from_database: Open of /home/docs/checkouts/readthedocs.org/user_builds/geopandas/conda/latest/share/proj failed

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/latest/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/latest/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/latest/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/latest/lib/python3.11/site-packages/geopandas/plotting.py:130, in _PolygonPatch(polygon, **kwargs)
    126 from matplotlib.patches import PathPatch
    127 from matplotlib.path import Path
    129 path = Path.make_compound_path(
--> 130     Path(np.asarray(polygon.exterior.coords)[:, :2]),
    131     *[Path(np.asarray(ring.coords)[:, :2]) for ring in polygon.interiors],
    132 )
    133 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/latest/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/latest/lib/python3.11/site-packages/pandas/core/indexes/base.py:3790, in Index.get_loc(self, key)
   3789 try:
-> 3790     return self._engine.get_loc(casted_key)
   3791 except KeyError as err:

File index.pyx:152, in pandas._libs.index.IndexEngine.get_loc()

File index.pyx:181, 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/latest/lib/python3.11/site-packages/geopandas/geodataframe.py:1474, in GeoDataFrame.__getitem__(self, key)
   1468 def __getitem__(self, key):
   1469     """
   1470     If the result is a column containing only 'geometry', return a
   1471     GeoSeries. If it's a DataFrame with any columns of GeometryDtype,
   1472     return a GeoDataFrame.
   1473     """
-> 1474     result = super().__getitem__(key)
   1475     # Custom logic to avoid waiting for pandas GH51895
   1476     # result is not geometry dtype for multi-indexes
   1477     if (
   1478         pd.api.types.is_scalar(key)
   1479         and key == ""
   (...)
   1482         and not is_geometry_type(result)
   1483     ):

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/latest/lib/python3.11/site-packages/pandas/core/frame.py:3896, in DataFrame.__getitem__(self, key)
   3894 if self.columns.nlevels > 1:
   3895     return self._getitem_multilevel(key)
-> 3896 indexer = self.columns.get_loc(key)
   3897 if is_integer(indexer):
   3898     indexer = [indexer]

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/latest/lib/python3.11/site-packages/pandas/core/indexes/base.py:3797, in Index.get_loc(self, key)
   3792     if isinstance(casted_key, slice) or (
   3793         isinstance(casted_key, abc.Iterable)
   3794         and any(isinstance(x, slice) for x in casted_key)
   3795     ):
   3796         raise InvalidIndexError(key)
-> 3797     raise KeyError(key) from err
   3798 except TypeError:
   3799     # If we have a listlike key, _check_indexing_error will raise
   3800     #  InvalidIndexError. Otherwise we fall through and re-raise
   3801     #  the TypeError.
   3802     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/latest/lib/python3.11/site-packages/pandas/core/computation/scope.py:231, in Scope.resolve(self, key, is_local)
    230 if self.has_resolvers:
--> 231     return self.resolvers[key]
    233 # if we're here that means that we have no locals and we also have
    234 # no resolvers

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

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/latest/lib/python3.11/collections/__init__.py:998, in ChainMap.__missing__(self, key)
    997 def __missing__(self, key):
--> 998     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/latest/lib/python3.11/site-packages/pandas/core/computation/scope.py:242, in Scope.resolve(self, key, is_local)
    238 try:
    239     # last ditch effort we look in temporaries
    240     # these are created when parsing indexing expressions
    241     # e.g., df[df > 0]
--> 242     return self.temps[key]
    243 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/latest/lib/python3.11/site-packages/pandas/core/frame.py:4602, in DataFrame.query(self, expr, inplace, **kwargs)
   4600 kwargs["level"] = kwargs.pop("level", 0) + 1
   4601 kwargs["target"] = None
-> 4602 res = self.eval(expr, **kwargs)
   4604 try:
   4605     result = self.loc[res]

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

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/latest/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/latest/lib/python3.11/site-packages/pandas/core/computation/expr.py:808, in Expr.__init__(self, expr, engine, parser, env, level)
    806 self.parser = parser
    807 self._visitor = PARSERS[parser](self.env, self.engine, self.parser)
--> 808 self.terms = self.parse()

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

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

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

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

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

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

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

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

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/latest/lib/python3.11/site-packages/pandas/core/computation/expr.py:531, in BaseExprVisitor.visit_BinOp(self, node, **kwargs)
    530 def visit_BinOp(self, node, **kwargs):
--> 531     op, op_class, left, right = self._maybe_transform_eq_ne(node)
    532     left, right = self._maybe_downcast_constants(left, right)
    533     return self._maybe_evaluate_binop(op, op_class, left, right)

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/latest/lib/python3.11/site-packages/pandas/core/computation/expr.py:451, in BaseExprVisitor._maybe_transform_eq_ne(self, node, left, right)
    449 def _maybe_transform_eq_ne(self, node, left=None, right=None):
    450     if left is None:
--> 451         left = self.visit(node.left, side="left")
    452     if right is None:
    453         right = self.visit(node.right, side="right")

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

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

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

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

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/latest/lib/python3.11/site-packages/pandas/core/computation/scope.py:244, in Scope.resolve(self, key, is_local)
    242     return self.temps[key]
    243 except KeyError as err:
--> 244     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/latest/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(
/home/docs/checkouts/readthedocs.org/user_builds/geopandas/conda/latest/lib/python3.11/site-packages/seaborn/_oldcore.py:1498: FutureWarning: is_categorical_dtype is deprecated and will be removed in a future version. Use isinstance(dtype, CategoricalDtype) instead
  if pd.api.types.is_categorical_dtype(vector):
/home/docs/checkouts/readthedocs.org/user_builds/geopandas/conda/latest/lib/python3.11/site-packages/seaborn/_oldcore.py:1498: FutureWarning: is_categorical_dtype is deprecated and will be removed in a future version. Use isinstance(dtype, CategoricalDtype) instead
  if pd.api.types.is_categorical_dtype(vector):
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[6], line 1
----> 1 ax = geoplot.kdeplot(
      2     collisions.head(1000),
      3     clip=boroughs.geometry,
      4     shade=True,
      5     cmap="Reds",
      6     projection=geoplot.crs.AlbersEqualArea(),
      7 )
      8 geoplot.polyplot(boroughs, ax=ax, zorder=1)

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/latest/lib/python3.11/site-packages/geoplot/geoplot.py:1320, in kdeplot(df, projection, extent, figsize, ax, clip, **kwargs)
   1315         return ax
   1317 plot = KDEPlot(
   1318     df, projection=projection, extent=extent, figsize=figsize, ax=ax, clip=clip, **kwargs
   1319 )
-> 1320 return plot.draw()

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/latest/lib/python3.11/site-packages/geoplot/geoplot.py:1304, in kdeplot.<locals>.KDEPlot.draw(self)
   1301     return ax
   1303 if self.projection:
-> 1304     sns.kdeplot(
   1305         x=pd.Series([p.x for p in self.df.geometry]),
   1306         y=pd.Series([p.y for p in self.df.geometry]),
   1307         transform=ccrs.PlateCarree(), ax=ax, **self.kwargs
   1308     )
   1309 else:
   1310     sns.kdeplot(
   1311         x=pd.Series([p.x for p in self.df.geometry]),
   1312         y=pd.Series([p.y for p in self.df.geometry]),
   1313         ax=ax, **self.kwargs
   1314     )

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/latest/lib/python3.11/site-packages/seaborn/distributions.py:1698, in kdeplot(data, x, y, hue, weights, palette, hue_order, hue_norm, color, fill, multiple, common_norm, common_grid, cumulative, bw_method, bw_adjust, warn_singular, log_scale, levels, thresh, gridsize, cut, clip, legend, cbar, cbar_ax, cbar_kws, ax, **kwargs)
   1695 p._attach(ax, allowed_types=["numeric", "datetime"], log_scale=log_scale)
   1697 method = ax.fill_between if fill else ax.plot
-> 1698 color = _default_color(method, hue, color, kwargs)
   1700 if not p.has_xy_data:
   1701     return ax

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/latest/lib/python3.11/site-packages/seaborn/utils.py:164, in _default_color(method, hue, color, kws)
    160     return "C0"
    162 kws = _normalize_kwargs(kws, mpl.collections.PolyCollection)
--> 164 scout = method([], [], **kws)
    165 facecolor = scout.get_facecolor()
    166 color = to_rgb(facecolor[0])

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/latest/lib/python3.11/site-packages/matplotlib/__init__.py:1465, in _preprocess_data.<locals>.inner(ax, data, *args, **kwargs)
   1462 @functools.wraps(func)
   1463 def inner(ax, *args, data=None, **kwargs):
   1464     if data is None:
-> 1465         return func(ax, *map(sanitize_sequence, args), **kwargs)
   1467     bound = new_sig.bind(ax, *args, **kwargs)
   1468     auto_label = (bound.arguments.get(label_namer)
   1469                   or bound.kwargs.get(label_namer))

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/latest/lib/python3.11/site-packages/matplotlib/axes/_axes.py:5504, in Axes.fill_between(self, x, y1, y2, where, interpolate, step, **kwargs)
   5502 def fill_between(self, x, y1, y2=0, where=None, interpolate=False,
   5503                  step=None, **kwargs):
-> 5504     return self._fill_between_x_or_y(
   5505         "x", x, y1, y2,
   5506         where=where, interpolate=interpolate, step=step, **kwargs)

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/latest/lib/python3.11/site-packages/matplotlib/axes/_axes.py:5495, in Axes._fill_between_x_or_y(self, ind_dir, ind, dep1, dep2, where, interpolate, step, **kwargs)
   5493 up_x = up_y = True
   5494 if "transform" in kwargs:
-> 5495     up_x, up_y = kwargs["transform"].contains_branch_seperately(self.transData)
   5496 self.update_datalim(pts, updatex=up_x, updatey=up_y)
   5498 self.add_collection(collection, autolim=False)

AttributeError: 'PlateCarree' object has no attribute 'contains_branch_seperately'
../_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.