Categorical maps#
Categorical maps visualize distinct categories linked to individual observations. As such, they usually use different colormaps and users have different expectations about their behavior. GeoPandas can create categorical maps natively by passing the column containing those values to the column argument of the plot() method. The method infers whether the value is categorical based on its dtype. Alternatively, you can force a numeric column to be treated as categorical by passing categorical=True.
In [1]: import geodatasets
In [2]: guerry = geopandas.read_file(
...: geodatasets.get_path("geoda.guerry")
...: )
...:
Default behavior#
By default, when GeoPandas detects categorical data, it switches the default colormap to "tab10" (or "tab20" for more than 10 unique categories) for better distinction between the classes.
In [3]: guerry.plot("Region");
You can request a legend using legend=True, which shows one legend item for each unique category, with a label reflecting the style mapped to the class. Additional keywords to adapt the legend can be passed in a legend_kwds dictionary.
In [4]: guerry.plot("Region", legend=True, legend_kwds={"loc": "lower left"});
Alternatively, you can use Matplotlib’s tooling to generate the legend from the figure using matplotlib.pyplot.legend().
In [5]: guerry.plot("Region")
Out[5]: <Axes: xlabel='Easting [metre]', ylabel='Northing [metre]'>
In [6]: plt.legend(loc="lower left");
Colormap treatment#
While GeoPandas uses a categorical colormap by default, any colormap understood by Matplotlib can be used. GeoPandas attempts to detect whether the colormap is categorical or continuous and adapts the mapping of categories to colors accordingly. With categorical colormaps (those with less than 32 unique colors), it iterates over colors sequentially. For continuous colormaps, it retrieves colors proportionally from the entire extent of the colormap for optimal contrast.
In [7]: fig, axs = plt.subplots(1, 2)
In [8]: cmaps = [
...: "Set2", # categorical
...: "Reds", # continuous
...: ]
...:
In [9]: for cmap, ax in zip(cmaps, axs):
...: guerry.plot("Region", cmap=cmap, ax=ax, legend=True)
...: ax.set_title(cmap)
...: ax.set_axis_off()
...:
Mapping styles to categories#
However, a common use case is mapping custom colors to individual categories. This can be done using a dictionary with unique categories as keys and colors as values, where colors can be specified in any way understood by matplotlib.
In [10]: colors = {
....: 'C': '#f8da20',
....: 'E': '#924b4e',
....: 'N': '#99cfba',
....: 'S': '#332737',
....: 'W': '#aaa08c',
....: }
....:
In [11]: guerry.plot("Region", cmap=colors, legend=True, legend_kwds={"loc": "lower left"});
Other styles can be mapped in an analogous way.
In [12]: hatches = {
....: 'C': '//',
....: 'E': r'\\',
....: 'N': '||',
....: 'S': 'O',
....: 'W': 'o',
....: }
....:
In [13]: edgecolors = {
....: 'C': 'black',
....: 'E': 'black',
....: 'N': 'white',
....: 'S': 'white',
....: 'W': 'white',
....: }
....:
In [14]: guerry.plot(
....: "Region",
....: cmap=colors,
....: hatch=hatches,
....: edgecolor=edgecolors,
....: linewidth=0,
....: legend=True,
....: legend_kwds={"loc": "lower left"},
....: );
....:
Another option is to pass an array of the same length as the GeoDataFrame to be mapped to individual geometries.
Maps with multiple layers#
When creating a map with multiple layers, legend entries for all data plotted on the same Axes are merged. You can also see that the legend handles reflect the geometry types and their specific symbology for easier identification.
In [15]: ax = guerry.plot("Region", cmap=colors)
In [16]: ax = guerry.set_geometry(guerry.centroid).iloc[:10].plot(
....: "Dprtmnt",
....: ax=ax,
....: cmap="tab10",
....: edgecolor="k",
....: markersize="Wealth",
....: marker="X",
....: )
....:
In [17]: ax.legend(bbox_to_anchor=(1.45, 1), frameon=False);