matplotlibgeopandas

True comparison of country sizes when plotting GeoDataFrame of countries with matplotlib


When plotting subplots of different countries, the axis ratios are not the same among subplots, showing the countries with incorrect sizes.

Kenya is much bigger than the others but shows as big as the others.

GeoJson files can be accessed here: https://file.io/v7fuma1EXwEa

strCountries = ["ghana", "kenya", "zimbabwe"]
admin1Countries = []
for country in strCountries:
    regions = gpd.read_file(f"../data/admin1{country}.geojson")
    admin1Countries.append(regions)

# real size in one plot
fig, ax = plt.subplots()
for regions in admin1Countries:
    regions.plot(ax=ax)
plt.show()

fig, ax = plt.subplots(1, 3, figsize=(15, 5))
for i, regions in enumerate(admin1Countries):
    regions.plot(ax=ax[i])
    ax[i].set_title(strCountries[i].capitalize())
    ax[i].set_axis_off()
plt.show()

One plot with true sizeOne plot with true size

I tried changing the CRS to those corresponding to the regions of the countries. Nothing changed. I also tried with axs[i].set_aspect('equal')


Solution

  • You need to set the axis range of all subplots to the same value. Don't forget to draw the figure before getting the actual axis limits.

    import geopandas as gpd
    import matplotlib.pyplot as plt
    
    strCountries = ["GHA", "KEN", "ZWE"]
    admin1Countries = []
    for country in strCountries:
        regions = gpd.read_file(f"https://raw.githubusercontent.com/johan/world.geo.json/master/countries/{country}.geo.json")
        admin1Countries.append(regions)
    
    fig, axes = plt.subplots(1, len(admin1Countries), figsize=(15, 5))
    for region, ax, country in zip(admin1Countries, axes, strCountries):
        region.plot(ax=ax)
        ax.set_title(country)
        ax.set_axis_off()
        ax.set_aspect('equal')
    
    fig.draw_without_rendering()
    max_range = 0
    centers = []
    for ax in axes:
        range = ax.get_ylim()
        centers.append((range[0] + range[1]) / 2)
        if range[1] - range[0] > max_range:
            max_range = range[1] - range[0]
    
    for center, ax in zip(centers, axes):
        ax.set_ylim(center - max_range / 2, center + max_range / 2)
    

    enter image description here

    Update (see comment):
    The above is not generally correct: the countries will be shown with different heights for the same y-range in order to fit into the space reserved for the Axes considering the aspect ratio (you can see it if you leave the axes visible, especially when you set ax.set_adjustable('datalim') ).
    To fix this you'll need to reserve space for the Axes according to their proportions:

    import geopandas as gpd
    import matplotlib.pyplot as plt
    
    strCountries = ["GHA", "CIV", "BFA", "TGO"]
    admin1Countries = []
    for country in strCountries:
        regions = gpd.read_file(f"https://raw.githubusercontent.com/johan/world.geo.json/master/countries/{country}.geo.json")
        admin1Countries.append(regions)
    
    width_ratios = []
    for region in admin1Countries:
        bounds = region.geometry.values.bounds[0]
        width_ratios.append(bounds[2] - bounds[0])  
    
    fig, axes = plt.subplots(1, len(admin1Countries), figsize=(15, 5), width_ratios=width_ratios)
    
    for region, ax, country in zip(admin1Countries, axes, strCountries):
        region.plot(ax=ax)
        ax.set_title(country)
        ax.set_axis_off()
        ax.set_aspect('equal')
    
    fig.draw_without_rendering()
    max_range = 0
    centers = []
    for ax in axes:
        range = ax.get_ylim()
        centers.append((range[0] + range[1]) / 2)
        if range[1] - range[0] > max_range:
            max_range = range[1] - range[0]
    
    for center, ax in zip(centers, axes):
        ax.set_ylim(center - max_range / 2, center + max_range / 2)
    

    enter image description here

    Update 2

    Another way is to first translate all the countries to a common center (e.g. Point(0, 0)) and then use shared Axes for display:

    import geopandas as gpd
    import matplotlib.pyplot as plt
    
    strCountries = ["GHA", "CIV", "BFA", "TGO"]
    admin1Countries = []
    for country in strCountries:
        regions = gpd.read_file(f"https://raw.githubusercontent.com/johan/world.geo.json/master/countries/{country}.geo.json")
        center = regions.geometry.values[0].centroid
        admin1Countries.append(regions.set_geometry(regions.geometry.translate(-center.x, -center.y)))
    
    fig, axes = plt.subplots(1, len(admin1Countries), figsize=(15, 5), sharey=True, sharex=True)
    
    for region, ax in zip(admin1Countries, axes):
        region.plot(ax=ax)
        ax.set(title=region.loc[0, 'name'], aspect='equal')
        ax.set_axis_off()
    

    enter image description here