ENV859 - Problem Set 5
Fall 2026
GeoPandas Exercise¶
- AI code assistants are NOT to be used in this assignment
- Use the
gisConda 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¶
#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.¶
#Read the CSV into a Pandas dataframe & report its shape
(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().
#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 Operationcolumn. - Apply the
Set3color map to the points. - Set the marker size to be 10 and make the points 50% transparent.
#Plot the GeoDataFrame.
1.4 Subset sites with active swine operations¶
- Create a subset GeoDataFrame named
gdf_swine - Select only records where
Regulated Operationis "Swine" andAllowable Countis greater than zero. - Display the shape of the
gdf_swine
# Subset active swine operations
gdf_swine =
# Report the shape of the result
(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%
#Show summary stats of the swine operation columns
| 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 |
#Plot a histogram of Allowable Count
#Plot swine operations
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 namedgdf_HUC12 - Show info on the GeoDataFrame
#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.
#Display the coordinate reference system
<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
basinattribute.
#Map the HUC12 features
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
huc8attribute.- 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.
# 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
| 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
#Plot HUC8s colored by population, adding the swine features on top
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.
#Check whether the data share a common crs; transform if necessary.
True
#Spatially join HUC8 subbasins to the swine operation points
#Display the column names
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.
- Group the spatial join result on the
huc8attribute - Compute the aggregate values:
- Count of permit numbers
- Sum of allowable count
- Reset the index of the resulting dataframe.
#Group by HUC8/subbasin and compute aggregate values
#Display the records
| 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 Densityby dividing allowable count by area. - Display the first 5 records.
#Join the data back to the dissolved HUC8 spatial dataframe
#Compute swine density
#Display the records
gdf_HUC8_update.head()
| 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
# Map the permit counts
- Create a map of HUC8s colored by swine density
# Map the swine density by HUC8