Visualizing Geospatial Data in Python with GeoPandas

Maps are a powerful way to visualize data and convey insights related to geographic locations. When your data contains spatial attributes like coordinates, boundaries, or addresses, a map can be the ideal choice to represent and explore the information.

Python has emerged as a leading platform for data science and provides excellent tools for geospatial analysis and mapping. In particular, the GeoPandas library makes it easy to work with geospatial data and create informative map visualizations, all within the convenience of a Jupyter notebook.

In this guide, we‘ll take a deep dive into visualizing geospatial data as maps using GeoPandas. We‘ll cover the key concepts, walk through a real-world mapping example, and highlight some best practices and limitations to be aware of. By the end, you‘ll be ready to start creating your own expressive maps to visualize your geospatial datasets.

GeoPandas: Pandas for Geospatial Data

GeoPandas is an open source Python library that makes working with geospatial data in Python easier. It takes the popular data analysis library pandas and extends it to be able to handle and analyze geospatial data.

If you‘re already familiar with using pandas DataFrames and Series for tabular data, picking up GeoPandas will feel very natural. GeoPandas provides two main data structures that build on top of the pandas equivalents:

  • GeoSeries – A vector of geospatial geometry objects (e.g. points, lines, polygons) based on pandas Series
  • GeoDataFrame – A tabular data structure for storing geospatial data where one column contains geometry objects, based on pandas DataFrame

So a GeoDataFrame is essentially a DataFrame that always contains one or more special columns containing geometric objects (which are GeoSeries). Having the spatial geometries and attributes tightly coupled allows GeoPandas to provide convenient methods for analysis and visualization that are spatially aware.

The geometries in a GeoSeries or GeoDataFrame are represented using the Shapely library. Some common types of geometries you‘ll encounter are:

  • Point – A single point in space with x, y coordinates
  • LineString – A sequence of points that form a line
  • Polygon – A filled area formed by a sequence of points that form a closed shape
  • MultiPoint/MultiLineString/MultiPolygon – Collections of multiple points, lines or polygons

Another key concept is the Coordinate Reference System or CRS. The CRS defines how the coordinates of the geometries relate to locations on the earth‘s surface. Some common CRS you may encounter are WGS84 (GPS coordinates) and WebMercator (web mapping). GeoPandas allows defining the CRS for the geometries it stores.

Exploring GeoPandas Plotting Functionality

One of the big benefits of using GeoPandas is how easy it makes visualizing your spatial data as maps. The GeoDataFrame and GeoSeries have built-in .plot() methods that allow you to quickly create different types of map plots.

Some of the common plot types you can create with GeoPandas include:

Boundary plots
Boundary plots draw the outlines of the geometries contained in the GeoDataFrame/GeoSeries. This could be used to show the borders between geographic regions like countries, states, counties etc.

Example:
world_df.boundary.plot()

Area plots
Area plots fill in the interiors of the polygon geometries. These are often used to create choropleths where the fill color is mapped to values of some attribute for each geometry. This allows visually encoding data values across different regions.

Example:
world_df.plot(column=‘population‘, legend=True, cmap=‘OrRd‘)

Point plots
If your geometries are points, you can create scatter plots showing the location of each point. The points can be styled and colored based on attributes.

Example:
cities_df.plot(marker=‘*‘, markersize=12, color=‘black‘)

Combining layers
GeoPandas makes it easy to combine multiple vector layers (GeoDataFrames or GeoSeries) together on the same map plot. You can customize the zorder to control the drawing order of layers.

Example:
ax = countries_df.boundary.plot(linewidth=1, edgecolor=‘black‘)
cities_df.plot(ax=ax, color=‘red‘, markersize=12)

We‘ll see more detailed examples of these plot types later on. But first, let‘s make sure we have GeoPandas and the other libraries we‘ll need installed:

!pip install geopandas descartes mapclassify
import geopandas as gpd
import pandas as pd
import matplotlib.pyplot as plt

Example: Visualizing World Population Data

To demonstrate GeoPandas plotting in action, let‘s walk through an example of loading some world geospatial data and creating a series of visualizations with it.

Our goal will be to create a map showing population density across the countries of the world. We‘ll start with a basic world map and incrementally add data and styling to arrive at our final visualization.

Loading a base world map

First, we need a base map of the world to plot our data on top of. GeoPandas provides an easy way to load some common baseline datasets, including a low resolution world map. Let‘s load that into a GeoDataFrame:

world_df = gpd.read_file(gpd.datasets.get_path(‘naturalearth_lowres‘))
world_df.head()

Name
iso_a3
geometry

0
Fiji
FJI
MULTIPOLYGON (((180.00000 -16.06713, 180.00000…

1
Tanzania
TZA
POLYGON ((33.90371 -0.95000, 34.07262 -1.05982…

2
W. Sahara
ESH
POLYGON ((-8.66559 27.65643, -8.66512 27.58948…

3
Canada
CAN
MULTIPOLYGON (((-122.84000 49.00000, -122.9742…

4
United States of America
USA
MULTIPOLYGON (((-122.84000 49.00000, -120.0000..

The GeoDataFrame contains columns with the name, ISO code, and geometry of each country. The geometry column contains Polygon or MultiPolygon objects representing the boundaries of each country.

Let‘s plot this GeoDataFrame to see what the base world map looks like:

world_df.plot(figsize=(12,8))

This gives us a solid base map showing the boundaries of all the world‘s countries. The geometries are automatically projected to fit the plot window.

Plotting population density

Now let‘s add some population data to our map. We‘ll use 2021 population estimates from the World Bank, which we can load from a CSV into a pandas DataFrame:

pop_df = pd.read_csv(‘world_population_2021.csv‘)  
pop_df.head()

Country Name
Country Code
2021 Population

0
Aruba
ABW
106766

1
Africa Eastern and Southern
AFE
705450115

2
Afghanistan
AFG
40099462

3
Africa Western and Central
AFW
476752426

4
Angola
AGO
34503774

To plot this population data on our world map, we need to combine the population DataFrame with our world GeoDataFrame. We can do an inner join on the ISO country code column:

world_pop_df = world_df.merge(pop_df, left_on=‘iso_a3‘, right_on=‘Country Code‘)

Now we have a GeoDataFrame that contains both the country geometry data and the population values. Let‘s create a choropleth map plotting the 2021 population:

world_pop_df.plot(column=‘2021 Population‘, 
                  legend=True, 
                  figsize=(12,8),
                  cmap=‘OrRd‘)

This plots each country with a fill color corresponding to its 2021 population value. GeoPandas automatically creates a legend and applies a sequential color scheme.

To represent population density, we need to normalize the population values by the area of each country. We can calculate the area of each country‘s geometry in square km and add it as a new column:

world_pop_df[‘area_sqkm‘] = world_pop_df.geometry.area / 10**6

Then we can calculate the population density and plot again:

world_pop_df[‘pop_density‘] = world_pop_df[‘2021 Population‘] / world_pop_df[‘area_sqkm‘]

ax = world_pop_df.plot(column=‘pop_density‘, 
                       legend=True, 
                       figsize=(12,8),
                       cmap=‘OrRd‘,
                       scheme=‘quantiles‘)

A few things to note here:

  • We‘re now plotting the calculated pop_density column
  • The color scheme is now using quantile breaks to better represent the skewed distribution
  • We‘re capturing the Axes object returned so we can further customize it

Adding plot customization

Let‘s polish up our density map with some improvements to the legend, colors, and overall style.

First, let‘s add a title to the map and move the legend to the bottom:

ax.set_title(‘2021 Population Density by Country‘, fontsize=16)
ax.get_legend().set_bbox_to_anchor((.5, -0.1))  
ax.get_legend().set_title(‘Population per km^2‘)

We can further improve the quantile color scheme by using a logarithmic scale which will handle the density skew better:

ax = world_pop_df.plot(column=‘pop_density‘, 
                       legend=True, 
                       figsize=(12,8),
                       cmap=‘OrRd‘,
                       scheme=‘quantiles‘,
                       k=8,
                       legend_kwds={‘fmt‘: ‘{:.0f}‘},
                       norm=matplotlib.colors.LogNorm())

Finally, let‘s give the map a cleaner look by removing the axes and adding a subtle background:

ax.set_axis_off()
plt.rcParams["axes.facecolor"] = "#d0d8e2"  

And with that, we‘ve created an attractive and informative visualization of population density across the world! This map would be great for identifying the most and least densely populated regions.

Of course, there are many more ways you could customize and enhance this plot. Some ideas:

  • Using different color palettes
  • Highlighting specific countries of interest
  • Adding point layers for major cities
  • Providing an interactive legend for filtering
  • Customizing the map projection

Hopefully this example has given you a taste of the types of maps you can create with GeoPandas and some of the key considerations that go into building them. Next, let‘s discuss a few best practices to keep in mind.

Tips for Effective Geospatial Visualization

Creating effective and impactful geospatial visualizations is a combination of leveraging the right tools and following visualization best practices. Here are some key tips to consider:

Choose appropriate map layers – Think carefully about which spatial features you include in your map. Provide enough context to orient the viewer and support your message, but avoid extraneous clutter. Common base layers include boundaries, water, roads/rails, place labels, and physical features like terrain.

Use meaningful colors – Color is one of the most important visual channels on a map. Use a well-designed color palette that appropriately matches your data. Sequential color schemes are great for continuous numeric data, while qualitative palettes work for categorical data. Always be mindful of color blindness.

Normalize data thoughtfully – When representing data values across different regions (like we did with population density), make sure to normalize your data properly. Typically you‘ll want to use a rate or ratio rather than raw values to account for differences in region sizes.

Leverage classifications – Classifications or "binning" can help to simplify busy data and focus attention. Experiment with different classification schemes (quantiles, equal interval, natural breaks etc.) to find one that best fits your data distribution and message.

Design clear legends – Legends are crucial for explaining the meaning of colors and symbols in your map. Make sure your legend is clearly labeled, positioned in a logical spot, and uses text sizing and formatting for readability.

Maximize data-ink ratio – Avoid chart junk and decorations that don‘t enhance the data. Keep the focus on your data by eliminating unnecessary borders, gridlines, backgrounds etc. Let the data shine!

Invite exploration – Think about how your readers will consume your map. Is there a clear visual hierarchy guiding attention? Are there opportunities to add interactivity like panning, zooming, filtering or details-on-demand? Encourage your audience to explore the geography and discover insights.

When to Go Beyond GeoPandas

While GeoPandas is a fantastic tool for many common geospatial analysis and visualization tasks, it does have some limitations. It‘s important to recognize when you may need to leverage other tools in the Python geospatial ecosystem.

Some cases where you may need to look beyond GeoPandas:

  • Handling very large datasets that don‘t fit in memory
  • Working with raster datasets like satellite imagery
  • Performing complex GIS operations like spatial joins, overlays, and geometry simplification
  • Generating vector tiles for web maps
  • Producing advanced cartographic outputs

For these more advanced geospatial tasks, you‘ll want to explore tools like rasterio, rasterstat, shapely, pyproj, geoviews and cartopy. Don‘t be afraid to mix and match! The beauty of the scientific Python stack is how well the libraries work together.

Conclusion

As data professionals, maps are an indispensable part of our visualization toolkit. They‘re unrivaled for displaying data in a geographic context and engaging audiences to explore. GeoPandas provides a user-friendly interface, close integration with the PyData stack, and expressive plotting functionality that makes it an excellent choice for geospatial analysis and mapping in Python.

I hope this guide has given you the foundation to start creating your own compelling geospatial visualizations with GeoPandas. Remember, a thoughtfully crafted map can reveal powerful geographically-related insights that might otherwise remain hidden. Now it‘s your turn to go make some maps!

How useful was this post?

Click on a star to rate it!

Average rating 0 / 5. Vote count: 0

No votes so far! Be the first to rate this post.

Similar Posts