2025, Dec 29 05:00
How to Plot Multiple GeoPandas Layers Across Subplots: Choose the Right Axes, Set Limits, and Use zorder
Learn GeoPandas subplots the right way: target Matplotlib axes with ax, control layer draw order via zorder, and set per-axis limits for clear, consistent maps.
When plotting multiple layers and subplots with GeoPandas (for example, in GeoPandas 1.0.1), the most common stumbling block is how to target a specific Matplotlib axis and control draw order. If you simply call plot repeatedly without managing axes and z-order, results look inconsistent: layers hide one another, and maps don’t end up where you expect.
Repro case: plotting points across three subplots
The scenario starts with reading a CSV containing lon/lat, creating a GeoDataFrame of points, and trying to render three subplots. The code below illustrates that setup.
import pandas as pd
import geopandas as gpd
from geopandas import GeoDataFrame
import matplotlib.pyplot as plt
csv_tbl = pd.read_csv('/home/ruby/Desktop/degree.csv')
world_shape = gpd.read_file("https://naciscdn.org/naturalearth/110m/cultural/ne_110m_admin_0_countries.zip")
canvas, axis_set = plt.subplots(nrows=3, ncols=1, figsize=(20,10))
pts_geom = gpd.points_from_xy(csv_tbl.lon, csv_tbl.lat)
points_layer = GeoDataFrame(csv_tbl, crs="EPSG:4326", geometry=pts_geom)
points_layer.plot(ax=axis_set[0], column='degree', cmap='Blues', alpha=0.6, legend=True, markersize=10, legend_kwds={'label':"Avg degree", 'orientation': "horizontal"})
points_layer.plot(ax=axis_set[1], column='degree', cmap='Blues', alpha=0.6, legend=True, markersize=10, legend_kwds={'label':"Avg degree", 'orientation': "horizontal"})
points_layer.plot(ax=axis_set[2], column='degree', cmap='Blues', alpha=0.6, legend=True, markersize=10, legend_kwds={'label':"Avg degree", 'orientation': "horizontal"})
plt.xlim([50.0, 130.0])
plt.ylim([0.0, 40.0])
plt.show()
What’s going on and why
Subplots in Matplotlib are independent axes, so every call to GeoDataFrame.plot must be told exactly which axis to draw on by passing ax=.... This part is crucial and often overlooked. Likewise, when you overlay multiple GeoDataFrames on the same axis, the paint order is last-in-wins unless you explicitly control it. That is where zorder makes the difference: it lets you state which layer should appear on top.
There’s another subtlety: axis limits should be applied to the intended axis, not globally. When you have a grid of subplots, use set_xlim and set_ylim on the target axes so the zoom applies to the correct pane.
If you intend to plot many dataframes on top of each other you should use zorder to specify the order.
Solution: target axes explicitly and order layers with zorder
The example below demonstrates a 2×2 grid where one subplot shows a world polygon layer, another shows only points, and the remaining two combine both with z-ordering. The last one also constrains the view to the extent of the points.
import matplotlib.pyplot as plt
import geopandas as gpd
# Read a polygon dataset of the world
globe_polys = gpd.read_file(r"C:\Users\bera\Desktop\gistest\world.geojson")
# Build a point GeoDataFrame from a CSV of coordinates
csv_path = r"C:\Users\bera\Desktop\gistest\degree.csv"
measure_pts = gpd.pd.read_csv(csv_path)
measure_pts["geometry"] = gpd.points_from_xy(x=measure_pts.lon, y=measure_pts.lat)
measure_pts = gpd.GeoDataFrame(data=measure_pts, geometry=measure_pts.geometry, crs=4326)
fig_pad, ax_grid = plt.subplots(nrows=2, ncols=2, figsize=(10, 7))
# World on the first axis
globe_polys.plot(ax=ax_grid[0][0], color="green", edgecolor="gray", linewidth=0.1)
ax_grid[0][0].set_title("World on ax_grid[0][0]")
# Points on the second axis
measure_pts.plot(ax=ax_grid[0][1], cmap="cool", column="degree", markersize=1)
ax_grid[0][1].set_title("Points on ax_grid[0][1]")
# Overlay with draw order: world first, points above
globe_polys.plot(ax=ax_grid[1][0], color="gray", edgecolor="white", linewidth=0.1, zorder=1)
measure_pts.plot(ax=ax_grid[1][0], cmap="cool", column="degree", markersize=0.2, zorder=2)
ax_grid[1][0].set_title("Both on ax_grid[1][0]")
# Overlay with draw order and axis limits from the point extent
xmin, ymin, xmax, ymax = measure_pts.total_bounds
ax_grid[1][1].set_xlim(xmin, xmax)
ax_grid[1][1].set_ylim(ymin, ymax)
globe_polys.plot(ax=ax_grid[1][1], color="khaki", edgecolor="gray", linewidth=0.4, zorder=1)
measure_pts.plot(ax=ax_grid[1][1], cmap="cool", column="degree", markersize=3, zorder=2)
ax_grid[1][1].set_title("With limits on ax_grid[1][1]")
Why this matters
In geospatial plotting, clarity and control are everything. Passing the correct ax for each subplot guarantees predictability. Adding zorder ensures your thematic data sits on top of base layers rather than being obscured. And constraining axis limits where appropriate keeps the focus on the region of interest instead of the entire world, which is especially important when your points cover a small area.
Takeaways
Think of each subplot as a separate canvas and always direct GeoPandas where to draw by providing the axis. When combining multiple layers on one axis, define their stacking via zorder so your points are visible above polygons. If you need a specific spatial window, set axis limits on the exact subplot you care about. With those pieces in place, you’ll avoid the typical subplot and layering pitfalls and get consistent, readable maps.