geopandas provides a high-level interface to the matplotlib library for making maps. Mapping shapes is as easy as using the plot() method on a GeoSeries or GeoDataFrame.
matplotlib
plot()
GeoSeries
GeoDataFrame
Loading some example data:
In [1]: world = geopandas.read_file(geopandas.datasets.get_path('naturalearth_lowres')) In [2]: cities = geopandas.read_file(geopandas.datasets.get_path('naturalearth_cities'))
We can now plot those GeoDataFrames:
# Examine country GeoDataFrame In [3]: world.head() Out[3]: pop_est continent name iso_a3 gdp_md_est geometry 0 920938 Oceania Fiji FJI 8374.0 MULTIPOLYGON (((180.000000000 -16.067132664, 1... 1 53950935 Africa Tanzania TZA 150600.0 POLYGON ((33.903711197 -0.950000000, 34.072620... 2 603253 Africa W. Sahara ESH 906.5 POLYGON ((-8.665589565 27.656425890, -8.665124... 3 35623680 North America Canada CAN 1674000.0 MULTIPOLYGON (((-122.840000000 49.000000000, -... 4 326625791 North America United States of America USA 18560000.0 MULTIPOLYGON (((-122.840000000 49.000000000, -... # Basic plot, random colors In [4]: world.plot();
Note that in general, any options one can pass to pyplot in matplotlib (or style options that work for lines) can be passed to the plot() method.
geopandas makes it easy to create Choropleth maps (maps where the color of each shape is based on the value of an associated variable). Simply use the plot command with the column argument set to the column whose values you want used to assign colors.
column
# Plot by GDP per capta In [5]: world = world[(world.pop_est>0) & (world.name!="Antarctica")] In [6]: world['gdp_per_cap'] = world.gdp_md_est / world.pop_est In [7]: world.plot(column='gdp_per_cap');
When plotting a map, one can enable a legend using the legend argument:
legend
# Plot population estimates with an accurate legend In [8]: import matplotlib.pyplot as plt In [9]: fig, ax = plt.subplots(1, 1) In [10]: world.plot(column='pop_est', ax=ax, legend=True) Out[10]: <matplotlib.axes._subplots.AxesSubplot at 0x7fc42cfb7f10>
However, the default appearance of the legend and plot axes may not be desirable. One can define the plot axes (with ax) and the legend axes (with cax) and then pass those in to the plot call. The following example uses mpl_toolkits to vertically align the plot axes and the legend axes:
ax
cax
plot
mpl_toolkits
# Plot population estimates with an accurate legend In [11]: from mpl_toolkits.axes_grid1 import make_axes_locatable In [12]: fig, ax = plt.subplots(1, 1) In [13]: divider = make_axes_locatable(ax) In [14]: cax = divider.append_axes("right", size="5%", pad=0.1) In [15]: world.plot(column='pop_est', ax=ax, legend=True, cax=cax) Out[15]: <matplotlib.axes._subplots.AxesSubplot at 0x7fc3f86c1fd0>
And the following example plots the color bar below the map and adds its label using legend_kwds:
legend_kwds
# Plot population estimates with an accurate legend In [16]: import matplotlib.pyplot as plt In [17]: fig, ax = plt.subplots(1, 1) In [18]: world.plot(column='pop_est', ....: ax=ax, ....: legend=True, ....: legend_kwds={'label': "Population by Country", ....: 'orientation': "horizontal"}) ....: Out[18]: <matplotlib.axes._subplots.AxesSubplot at 0x7fc3f86e9a90>
One can also modify the colors used by plot with the cmap option (for a full list of colormaps, see the matplotlib website):
cmap
In [19]: world.plot(column='gdp_per_cap', cmap='OrRd');
To make the color transparent for when you just want to show the boundary, you have two options. One option is to do world.plot(facecolor="none", edgecolor="black"). However, this can cause a lot of confusion because "none" and None are different in the context of using facecolor and they do opposite things. None does the “default behavior” based on matplotlib, and if you use it for facecolor, it actually adds a color. The second option is to use world.boundary.plot(). This option is more explicit and clear.:
world.plot(facecolor="none", edgecolor="black")
"none"
None
facecolor
world.boundary.plot()
In [20]: world.boundary.plot();
The way color maps are scaled can also be manipulated with the scheme option (if you have mapclassify installed, which can be accomplished via conda install -c conda-forge mapclassify). The scheme option can be set to any scheme provided by mapclassify (e.g. ‘box_plot’, ‘equal_interval’, ‘fisher_jenks’, ‘fisher_jenks_sampled’, ‘headtail_breaks’, ‘jenks_caspall’, ‘jenks_caspall_forced’, ‘jenks_caspall_sampled’, ‘max_p_classifier’, ‘maximum_breaks’, ‘natural_breaks’, ‘quantiles’, ‘percentiles’, ‘std_mean’ or ‘user_defined’). Arguments can be passed in classification_kwds dict. See the mapclassify documentation for further details about these map classification schemes.
scheme
mapclassify
conda install -c conda-forge mapclassify
In [21]: world.plot(column='gdp_per_cap', cmap='OrRd', scheme='quantiles');
In some cases one may want to plot data which contains missing values - for some features one simply does not know the value. Geopandas (from the version 0.7) by defaults ignores such features.
In [22]: import numpy as np In [23]: world.loc[np.random.choice(world.index, 40), 'pop_est'] = np.nan In [24]: world.plot(column='pop_est');
However, passing missing_kwds one can specify the style and label of features containing None or NaN.
missing_kwds
In [25]: world.plot(column='pop_est', missing_kwds={'color': 'lightgrey'}); In [26]: world.plot( ....: column="pop_est", ....: legend=True, ....: scheme="quantiles", ....: figsize=(15, 10), ....: missing_kwds={ ....: "color": "lightgrey", ....: "edgecolor": "red", ....: "hatch": "///", ....: "label": "Missing values", ....: }, ....: ); ....:
There are two strategies for making a map with multiple layers – one more succinct, and one that is a little more flexible.
Before combining maps, however, remember to always ensure they share a common CRS (so they will align).
# Look at capitals # Note use of standard `pyplot` line style options In [27]: cities.plot(marker='*', color='green', markersize=5); # Check crs In [28]: cities = cities.to_crs(world.crs) # Now we can overlay over country outlines # And yes, there are lots of island capitals # apparently in the middle of the ocean!
Method 1
In [29]: base = world.plot(color='white', edgecolor='black') In [30]: cities.plot(ax=base, marker='o', color='red', markersize=5);
Method 2: Using matplotlib objects
In [31]: import matplotlib.pyplot as plt In [32]: fig, ax = plt.subplots() # set aspect to equal. This is done automatically # when using *geopandas* plot on it's own, but not when # working with pyplot directly. In [33]: ax.set_aspect('equal') In [34]: world.plot(ax=ax, color='white', edgecolor='black') Out[34]: <matplotlib.axes._subplots.AxesSubplot at 0x7fc42cc03a90> In [35]: cities.plot(ax=ax, marker='o', color='red', markersize=5) Out[35]: <matplotlib.axes._subplots.AxesSubplot at 0x7fc42cc03a90> In [36]: plt.show();
When plotting multiple layers, use zorder to take control of the order of layers being plotted. The lower the zorder is, the lower the layer is on the map and vice versa.
zorder
Without specified zorder, cities (Points) gets plotted below world (Polygons), following the default order based on geometry types.
In [37]: ax = cities.plot(color='k') In [38]: world.plot(ax=ax);
We can set the zorder for cities higher than for world to move it of top.
In [39]: ax = cities.plot(color='k', zorder=2) In [40]: world.plot(ax=ax, zorder=1);
Links to jupyter Notebooks for different mapping tasks:
Making Heat Maps