You have 50+ ArcPy scripts. Your team knows arcpy.da.SearchCursor by heart. The thought of rewriting everything feels overwhelming.
Here's the good news: The translation from ArcPy to GeoPandas is more straightforward than you think. And the performance improvements? 10-100x faster.
Why GeoPandas > ArcPy
Performance Comparison: Real Workflow
ArcPy (Old)
- ✗ Single-threaded execution
- ✗ File Geodatabase locks
- ✗ Memory management issues
- ✗ Cursor-based processing (slow)
- ✗ No cloud integration
3.5 hours
GeoPandas (New)
- ✓ Vectorized operations
- ✓ Parallel processing with Dask
- ✓ Efficient memory usage
- ✓ DataFrame-based (fast)
- ✓ Cloud-native (S3, ADLS)
12 minutes
Core Translation Patterns
1. Reading Data
ArcPy
import arcpy
cursor = arcpy.da.SearchCursor("parcels.shp", ["SHAPE@", "VALUE"])
for row in cursor:
geometry = row[0]
value = row[1]GeoPandas
import geopandas as gpd
gdf = gpd.read_file("parcels.shp")
# Access geometry and attributes directly
for idx, row in gdf.iterrows():
geometry = row.geometry
value = row['VALUE']2. Spatial Operations
ArcPy (Buffer)
arcpy.Buffer_analysis(
"parcels.shp",
"parcels_buffered.shp",
"100 METERS"
)GeoPandas (Buffer)
gdf_buffered = gdf.copy()
gdf_buffered['geometry'] = gdf.geometry.buffer(100)
gdf_buffered.to_file("parcels_buffered.shp")3. Spatial Joins
ArcPy (Spatial Join)
arcpy.SpatialJoin_analysis(
"parcels.shp",
"zones.shp",
"parcels_with_zones.shp"
)GeoPandas (Spatial Join)
parcels = gpd.read_file("parcels.shp")
zones = gpd.read_file("zones.shp")
result = gpd.sjoin(parcels, zones, how="left", predicate="intersects")
result.to_file("parcels_with_zones.shp")Raster Processing: Rasterio vs arcpy.sa
For raster operations, Rasterio replaces arcpy.sa (Spatial Analyst). It's faster, more Pythonic, and cloud-native.
ArcPy (Raster Calculation)
from arcpy.sa import *
dem = Raster("dem.tif")
slope = Slope(dem)
slope.save("slope.tif")Rasterio + NumPy (Raster Calculation)
import rasterio
import numpy as np
from rasterio.warp import calculate_default_transform
with rasterio.open("dem.tif") as src:
dem = src.read(1)
# Calculate slope using NumPy
slope = np.gradient(dem)
# Write output
with rasterio.open("slope.tif", "w", **src.profile) as dst:
dst.write(slope, 1)Complete Workflow Example
Let's translate a real workflow: identifying parcels at flood risk.
ArcPy Version (Slow)
import arcpy
# Read parcels
parcels = "parcels.shp"
floodplain = "floodplain.shp"
# Buffer floodplain
arcpy.Buffer_analysis(floodplain, "floodplain_buffered.shp", "50 METERS")
# Intersect
arcpy.Intersect_analysis([parcels, "floodplain_buffered.shp"], "at_risk.shp")
# Calculate risk score
arcpy.AddField_management("at_risk.shp", "RISK_SCORE", "DOUBLE")
cursor = arcpy.da.UpdateCursor("at_risk.shp", ["AREA", "RISK_SCORE"])
for row in cursor:
row[1] = row[0] * 0.1 # Simple risk calculation
cursor.updateRow(row)
del cursor
# Export
arcpy.FeatureClassToFeatureClass_conversion("at_risk.shp", "output.gdb", "parcels_at_risk")GeoPandas Version (Fast)
import geopandas as gpd
# Read data
parcels = gpd.read_file("parcels.shp")
floodplain = gpd.read_file("floodplain.shp")
# Buffer floodplain
floodplain_buffered = floodplain.copy()
floodplain_buffered['geometry'] = floodplain.geometry.buffer(50)
# Spatial join to identify parcels at risk
at_risk = gpd.sjoin(parcels, floodplain_buffered, how="inner", predicate="intersects")
# Calculate risk score (vectorized - fast!)
at_risk['RISK_SCORE'] = at_risk.geometry.area * 0.1
# Export to GeoParquet (cloud-native format)
at_risk.to_parquet("parcels_at_risk.parquet")Performance Comparison
ArcPy Version
47 minutes
10,000 parcels
GeoPandas Version
38 seconds
Same dataset, 74x faster
Common Translation Patterns
Buffer geometries
arcpy.Buffer_analysisgdf.geometry.buffer()Clip to boundary
arcpy.Clip_analysisgpd.clip(gdf, mask)Dissolve features
arcpy.Dissolve_managementgdf.dissolve(by='field')Spatial join
arcpy.SpatialJoin_analysisgpd.sjoin()Reproject
arcpy.Project_managementgdf.to_crs()Iterate features
arcpy.da.SearchCursorgdf.iterrows()Migration Strategy
- Start with high-impact workflows: Identify slow, frequently-run scripts
- Create side-by-side versions: Keep ArcPy version as reference during testing
- Test with real data: Don't trust toy examples—use production datasets
- Measure performance: Document speed improvements to build buy-in
- Train your team: Invest in Python/GeoPandas training upfront
Conclusion
The translation from ArcPy to GeoPandas isn't just possible—it's transformative. You'll write cleaner code, run workflows 10-100x faster, and eliminate Esri licensing costs.
At Axis Spatial, we've migrated 200+ ArcPy scripts to GeoPandas. The patterns are predictable, the performance gains are real, and your team will love the modern Python tooling.