ENV859 - Problem Set 5 
Fall 2026

GeoPandas Exercise¶

  • AI code assistants are NOT to be used in this assignment
  • Use the gis Conda environment created in class, as you will be using GeoPandas in this assignment.
  • Strive to make your code as clear and easy to follow as possible (organize and use comments).

Overview¶

Your team is interested in exploring the relationship between permitted animal operations, specifically swine operations, and water quality across North Carolina at the HUC8 subbasin level.

Your task is to prepare spatial data for subsequent analysis using a transparent, reproducible, and well-documented workflow. You will identify permitted swine operations, aggregate watershed features to the HUC8 level, and calculate the number of permitted operations and allowable animal density within each subbasin.

The datasets and specific tasks are described below.

Data¶

You have been provided two datasets in the data folder:

  • Permitted_Animal_Facilities.csv [Source] - A dataset listing permitted animal facilities including allowable count of animals and the type of operation (Cattle, Horses, Pits, etc.). This dataset has been cleaned (see its metadata) but has not yet been converted to a GeoDataFrame.)
  • NC_HUC12.geojson [Source] - A feature class of all 12-digit HUCs in North Carolina.

Tasks¶

  1. Import & Explore the Animal Permit Data
  2. Import & Explore the North Carolina HUC 12 Data
  3. Spatial Analysis:
    1. Aggregate HUC12 features to the HUC8 level
    2. Spatially join the HUC8 data to the swine operation points
    3. Tabulate aggregated swine data
    4. Join aggregated swine data back to the HUC8 GeoDataFrame
    5. Display results
In [1]:
#Read in the packages
import pandas as pd
import geopandas as gpd

1. Import & Explore the Animal Permit Data¶

Our first task is to import the NC DEQ Animal Permit data, explore its contents, and convert it into a GeoDataFrame. Then we want to subset the swine records into its own GeoDataFrame.

1.1 Read the animal permit dataset and report its shape.¶

In [ ]:
#Read the CSV into a Pandas dataframe & report its shape
Out[ ]:
(2344, 20)

1.2 Convert the animal permits dataframe to a GeoDataFrame¶

  • Set the name of the GeoDataFrame to be gdf_permits.
  • Use the two coordinate columns to create point geometries.
  • The dataset should use the WGS 1984 crs (EPSG 4326).
  • After importing, display information on the geodatframe using info().
In [ ]:
#Construct geometries from the dataframe's coordinate columns



#Create a GeoDataFrame from the dataframe, the geometry, and the coordinate reference system
gdf_permits = 

#Reveal the columns in the data
<class 'geopandas.geodataframe.GeoDataFrame'>
RangeIndex: 2344 entries, 0 to 2343
Data columns (total 21 columns):
 #   Column               Non-Null Count  Dtype   
---  ------               --------------  -----   
 0   Permit Number        2344 non-null   object  
 1   Facility Name        2344 non-null   object  
 2   Combined Owner       2344 non-null   object  
 3   Regulated Operation  2340 non-null   object  
 4   Permit Type          2344 non-null   object  
 5   Regulated Activity   2344 non-null   object  
 6   Allowable Count      2344 non-null   float64 
 7   Number Of Lagoons    2302 non-null   float64 
 8   Issued Date          2344 non-null   object  
 9   Effective Date       2344 non-null   object  
 10  Expiration Date      2344 non-null   object  
 11  Admin Region         2344 non-null   object  
 12  County Name          2344 non-null   object  
 13  Location Lat Num     2344 non-null   float64 
 14  Location Long Num    2344 non-null   float64 
 15  Address 1            2271 non-null   object  
 16  Address 2            90 non-null     object  
 17  City                 2273 non-null   object  
 18  State                2273 non-null   object  
 19  Zip                  2273 non-null   float64 
 20  geometry             2344 non-null   geometry
dtypes: float64(5), geometry(1), object(15)
memory usage: 384.7+ KB

1.3 Plot the GeoDataFrame.¶

  • Show the locations spatially by plotting the GeoDataFrame.
  • Color the points by values in the Regulated Operation column.
  • Apply the Set3 color map to the points.
  • Set the marker size to be 10 and make the points 50% transparent.
In [ ]:
#Plot the GeoDataFrame.
No description has been provided for this image

1.4 Subset sites with active swine operations¶

  • Create a subset GeoDataFrame named gdf_swine
  • Select only records where Regulated Operation is "Swine" and Allowable Count is greater than zero.
  • Display the shape of the gdf_swine
In [ ]:
# Subset active swine operations
gdf_swine = 


# Report the shape of the result
Out[ ]:
(2018, 21)

1.5 Explore the swine operations¶

  • Apply the describe() function to display summary stats of the dataframe.
  • Create a histogram of the "Allowable Count" values, setting the plot to use 50 bins.
  • Plot the swine locations on a map, setting the makersize to 8 and the opacity to 30%
In [ ]:
#Show summary stats of the swine operation columns
Out[ ]:
Allowable Count Number Of Lagoons Location Lat Num Location Long Num Zip
count 2018.000000 2004.000000 2018.000000 2018.000000 1.966000e+03
mean 4326.602081 1.633733 35.085029 -78.148941 6.089244e+06
std 3849.021229 1.097621 0.422271 0.600934 4.103445e+07
min 8.000000 0.000000 33.966700 -81.358600 2.701600e+04
25% 2347.000000 1.000000 34.825930 -78.433269 2.832800e+04
50% 3520.000000 1.000000 35.042445 -78.118094 2.838400e+04
75% 5600.000000 2.000000 35.270975 -77.779240 2.847800e+04
max 64680.000000 13.000000 36.538850 -76.210126 2.862172e+08
In [ ]:
#Plot a histogram of Allowable Count
No description has been provided for this image
In [ ]:
#Plot swine operations
No description has been provided for this image

2. Import & Explore the North Carolina HUC 12 Data¶

Now that we have prepared our swine operation points, our next task is to import and explore the NC HUC12 datasets provided to us in GeoJSON format.

2.1 Import the HUC12 feature class¶

  • Read the HUC12 feature class (NC_HUC12.geojson) into a GeoDataFrame object named gdf_HUC12
  • Show info on the GeoDataFrame
In [ ]:
#Read in the HUC12 feature class
gdf_HUC12 = 

#Reveal the columns
<class 'geopandas.geodataframe.GeoDataFrame'>
RangeIndex: 1761 entries, 0 to 1760
Data columns (total 16 columns):
 #   Column           Non-Null Count  Dtype   
---  ------           --------------  -----   
 0   OBJECTID         1761 non-null   int64   
 1   huc12            1761 non-null   object  
 2   subwatershed     1761 non-null   object  
 3   huc10            1761 non-null   object  
 4   watershed        1761 non-null   object  
 5   huc8             1761 non-null   object  
 6   subbasin         1761 non-null   object  
 7   huc6             1761 non-null   object  
 8   basin            1761 non-null   object  
 9   to_huc           1761 non-null   object  
 10  area_sq_mi       1761 non-null   float64 
 11  area_acres       1761 non-null   float64 
 12  population_2000  240 non-null    float64 
 13  population_2010  1761 non-null   int64   
 14  population_2020  1761 non-null   int64   
 15  geometry         1761 non-null   geometry
dtypes: float64(3), geometry(1), int64(3), object(9)
memory usage: 220.3+ KB
  • Show what coordinate reference system (crs) is used by the dataset.
In [ ]:
#Display the coordinate reference system
Out[ ]:
<Geographic 2D CRS: EPSG:4326>
Name: WGS 84
Axis Info [ellipsoidal]:
- Lat[north]: Geodetic latitude (degree)
- Lon[east]: Geodetic longitude (degree)
Area of Use:
- name: World.
- bounds: (-180.0, -90.0, 180.0, 90.0)
Datum: World Geodetic System 1984 ensemble
- Ellipsoid: WGS 84
- Prime Meridian: Greenwich

2.2 Display the HUC12 features¶

  • Create a map of the HUC12 features, coloring them by the basin attribute.
In [ ]:
#Map the HUC12 features
No description has been provided for this image

3. Spatial Analysis¶

Now that we have prepared the swine operations and NC HUC datasets, we can begin our analysis. The questions we want to explore are:

  • Which subbasins (HUC8) have the highest number of individual swine operations?
  • Which subbasins (HUC8) have the highest permitted swine density (allowable animals per square mile)?

To answer this, we'll first need to aggregate our HUC12 features to the HUC8 level, i.e., dissolve our features using the HUC8/subbasin attribute. Then we spatially join the HUC8 attributes to the swine permit locations. From there we can answer these questions.

3.1 Aggregate HUC12 features to the HUC8 level¶

The HUC features provided to us are at the HUC12 level, but we want to do our analysis at the HUC8 level, so we need to aggregate the features.

  • Dissolve the features on the huc8 attribute.
    • Retain the first of "subbasin" value for each HUC8 group.
    • Compute total area in sq miles and total population in 2020.
  • Display the first 5 records of the result.
In [ ]:
# Dissolve HUC12 features on the HUC8 attribute, computing aggregate values for subbasin, area_sq_mi, and population_2020
gdf_HUC8 = 

# Display the first 5 records
Out[ ]:
geometry subbasin area_sq_mi population_2020
huc8
03010102 POLYGON ((-78.43639 36.54272, -78.38369 36.543... Middle Roanoke 298.418534 21887
03010103 POLYGON ((-79.99079 36.24530, -79.98892 36.244... Upper Dan 914.654400 127112
03010104 POLYGON ((-79.19427 36.22730, -79.19459 36.227... Lower Dan 716.098882 51261
03010106 POLYGON ((-78.20934 36.44432, -78.21000 36.444... Roanoke Rapids 254.496692 15599
03010107 POLYGON ((-77.01231 35.67864, -77.01435 35.679... Lower Roanoke 1309.688863 66452
  • Show a map of HUC8 features colored by population
    • Set the map size to be 8 x 4 inches.
    • Add the swine permit locations to this map as small, fairly transparent dots
In [ ]:
#Plot HUC8s colored by population, adding the swine features on top
No description has been provided for this image

3.2 Spatially join the HUC8 data to the swine operation points¶

Next, we need to tag each swine operation point with the HUC8 in which it occurs so that we can tabulate stats at the HUC8 level.

  • Ensure that the two spatial dataframes use a common coordinate reference system (crs), transforming a dataset if required.
  • Spatially join the HUC12 attributes to the swine point features, and display the column names in the resulting data set.
    • Set the join such that all the counties are included in the result, even those not overlapping a swine operation feature.
In [ ]:
#Check whether the data share a common crs; transform if necessary.
Out[ ]:
True
In [ ]:
#Spatially join HUC8 subbasins to the swine operation points



#Display the column names
Out[ ]:
Index(['index_left', 'Permit Number', 'Facility Name', 'Combined Owner',
       'Regulated Operation', 'Permit Type', 'Regulated Activity',
       'Allowable Count', 'Number Of Lagoons', 'Issued Date', 'Effective Date',
       'Expiration Date', 'Admin Region', 'County Name', 'Location Lat Num',
       'Location Long Num', 'Address 1', 'Address 2', 'City', 'State', 'Zip',
       'geometry', 'subbasin', 'area_sq_mi', 'population_2020'],
      dtype='object')

3.3 Tabulate aggregated swine data¶

With the HUC8 attributes now included in the swine permit table, we can group permit data on HUC8 and compute aggregate values.

  1. Group the spatial join result on the huc8 attribute
  2. Compute the aggregate values:
    • Count of permit numbers
    • Sum of allowable count
  3. Reset the index of the resulting dataframe.
In [ ]:
#Group by HUC8/subbasin and compute aggregate values



#Display the records
Out[ ]:
huc8 Permit Number Allowable Count
0 03010102 1 1256.0
1 03010103 1 800.0
2 03010104 2 4286.0
3 03010106 6 9372.0
4 03010107 11 47913.0

3.4 Join the above result back to the HUC8 GeoDataFrame¶

To be able to plot the aggregate values, we need to join the data back to the HUC8 features

  • Merge the aggregated dataframe to the HUC8 GeoDataFrame.
  • Compute Swine Density by dividing allowable count by area.
  • Display the first 5 records.
In [ ]:
#Join the data back to the dissolved HUC8 spatial dataframe



#Compute swine density


#Display the records
gdf_HUC8_update.head()
Out[ ]:
huc8 geometry subbasin area_sq_mi population_2020 Permit Number Allowable Count Swine Density
0 03010102 POLYGON ((-78.43639 36.54272, -78.38369 36.543... Middle Roanoke 298.418534 21887 1 1256.0 4.208854
1 03010103 POLYGON ((-79.99079 36.24530, -79.98892 36.244... Upper Dan 914.654400 127112 1 800.0 0.874647
2 03010104 POLYGON ((-79.19427 36.22730, -79.19459 36.227... Lower Dan 716.098882 51261 2 4286.0 5.985207
3 03010106 POLYGON ((-78.20934 36.44432, -78.21000 36.444... Roanoke Rapids 254.496692 15599 6 9372.0 36.825626
4 03010107 POLYGON ((-77.01231 35.67864, -77.01435 35.679... Lower Roanoke 1309.688863 66452 11 47913.0 36.583498

3.5 Display Results¶

Finally, we'll view our two results as maps

  • Create a map of HUC8s colored by total permitted operation count
In [ ]:
# Map the permit counts
No description has been provided for this image
  • Create a map of HUC8s colored by swine density
In [ ]:
# Map the swine density by HUC8
No description has been provided for this image