Data cleaning
Turning a messy download into an analysis-ready dataset you can trust.
Cleaning is where an analysis acquires its assumptions, which is why it deserves to be written down rather than done interactively. Every step is a rule applied to a set of rows, and the rule is the part that matters: "removed 312" tells a reader how many, and only the rule tells them why.
The work itself is unglamorous and specific: coordinate columns stored as text, decimal commas, swapped latitude and longitude, duplicate geometries that differ in the eighth decimal place, categories spelled four ways, missing values encoded as −9999 and as the empty string in the same file. Almost none of it raises an error; it produces a dataset that looks finished.
The pages below cover what to check before trusting a file, the operations that fix each defect, and a repeatable report that makes the whole thing reproducible six months later.
🧭 Start with the concepts
The ideas the how-to guides below assume you already have.
- Spatial Data Quality: The Six Dimensions That Matter
- Null, Empty, Missing and Invalid: Four Kinds of Broken Geometry
- Topological Coverage: What "Clean" Means for Adjacent Polygons
- Repair, Reject or Flag? Choosing What Cleaning Should Do
- Choropleth Classification Explained: Quantiles, Equal Interval and Natural Breaks
- Test Fixtures for GIS Code: Building Spatial Test Data You Can Trust
Show every concept in this topic
- Spatial Join Cardinality Explained: One-to-One, One-to-Many and What to Do
- Attribute Join or Spatial Join? Choosing How to Combine Two Layers
- The Modifiable Areal Unit Problem Explained
- Spatial Autocorrelation Explained
- The Address Data Model: Why an Address Is Not a String
- Rooftop, Interpolated or Centroid: Geocoding Precision Levels
- Address Matching Explained: Why Exact String Equality Fails
- Census Identifiers Explained: GEOIDs, Codes and the Leading Zero Problem
- Boundary Changes Over Time: Why Two Census Years Do Not Line Up
- Geoprivacy explained: why coordinates are personal data
- Spatial metadata explained: what a dataset must tell you
- Yield monitor data explained: what the numbers really are
- Dynamic Segmentation Explained
- LRS Conflation Explained: Moving Events Between Networks
- Coordinate Uncertainty Explained
- Air Pollutants and Their Units Explained
- How Many Decimal Places Do Coordinates Need?
- 311 Data Explained: What a Service Request Does and Does Not Measure
- Storm Events, Episodes and County Segments: What One Row Means
- NYC Restaurant Letter Grades Explained: Scores, Re-inspections and Pending Grades
- Why Restaurant Inspection Data Only Shows Restaurants Still Open
⚙️ Then do the task
Step-by-step walkthroughs with code you can copy and adapt.
- How to Remove Null and Empty Geometries in GeoPandas
- The Python GIS Data Cleaning Checklist: From Raw Download to Analysis-Ready
- How to Find and Remove Duplicate Geometries in GeoPandas
- How to Clean Messy CSV Coordinates into a Reliable GeoDataFrame
- How to Standardise and Repair CRS Across a Folder of Files in Python
- How to Clean and Normalise Attribute Columns in a GeoDataFrame
Show every how-to guide in this topic
- How to Handle Missing and Null Values in Spatial Datasets in Python
- How to Snap and Align Geometries to Fix Slivers and Gaps in Python
- How to Validate a GeoDataFrame Against a Schema Before Analysis
- How to Build a Repeatable Data-Cleaning Report in GeoPandas
- How to Split Multipart Geometries into Single Parts in GeoPandas
- How to Fuzzy-Match Place Names When Joining Spatial Data in Python
- How to Find and Remove Spatial Outliers in a Point Dataset
- How to Clean and Standardise Date Columns in Spatial Data
- How to Explore a Spatial Dataset You Have Never Seen Before
- How to Find and Fix Gaps and Overlaps in a Polygon Coverage
- How to Merge Near-Duplicate Features in a Spatial Dataset
- How to Clean a Line Network: Dangles, Overshoots and Disconnects
- How to Standardise Attribute Values Against a Controlled Vocabulary
- How to Set, Assign and Convert CRS in GeoPandas (set_crs vs to_crs)
- How to Reproject Between Datums Correctly (and Why the Grid File Matters)
- How to Geocode Addresses in Python
- How to Clean a GPS Track: Outliers, Duplicates and Jumps
- How to Resample and Interpolate a Track to a Fixed Interval
- How to Parse and Normalise Addresses in Python
- How to Validate Geocoding Results Before You Trust Them
- How to Match Addresses Against a Reference File in Python
- How to Deduplicate an Address List in Python
- How to Join a Census Table to Its Boundaries Without Losing Rows
- How to Normalise Census Counts into Rates and Densities
- How to measure shoreline change between two dates
- How to clean yield monitor data in Python
- How to find the trips running on a given date in GTFS
- How to clip a GTFS feed to a study area
- How to overlay linear event tables in Python
- How to transfer events between two route systems
- How to clean species occurrence records
- How to thin occurrence records spatially
- How to Analyse an Air Quality Time Series in Python
- How to Analyse Crime by Hour and Day of Week in Python
- How to Estimate Alcohol-Impaired Road Deaths with FARS in Python
- How to Measure the Effect of the Clock Change on Road Crashes in Python
- How to Analyse Speeding-Related Crashes with FARS in Python
- How to Convert Degrees, Minutes and Seconds to Decimal Degrees in Python
- How to Find the Time Zone of a Coordinate in Python
- How to Convert Hourly Weather Data to Daily in Python
- How to Estimate Airbnb Occupancy From Inside Airbnb Calendars in Python
- How to Find Airbnb Hosts With Multiple Listings in Python
- How to Build a House Price Time Series From Land Registry Data in Python
- How to Clean a Year of Bike-Share Trip Data in Python
- How to Load and Clean a Year of US Flight Data in Python
- How to Measure Traffic Trends from Count Points That Were Actually Counted
- How to Compare Two Street Tree Censuses: New York 2005 and 2015
- How to Identify Drought Events, Their Duration and Severity, from an Index
- How Old Are America's Bridges? Year Built, Reconstruction and Condition
- How to Measure 311 Response Times by Agency and Complaint Type
🔧 When it breaks
The errors this topic produces, with the cause explained rather than just the command.
- How to Fix Invalid Geometries in Python (GeoPandas)
- Why Are My Shapefile Column Names Truncated? How to Fix It
- buffer(0) Does Not Fix My Invalid Geometry: What to Do Instead
- Garbled Attribute Text and UnicodeDecodeError from a Shapefile: How to Fix Encoding
- GeoPandas to_file() Fails on a Column Type: How to Fix It
- Rasterio Returns the Wrong Values: NoData, Scaling and dtype Fixes
Show every fix in this topic
- My Cleaned Layer Has More Rows Than the Original: How to Fix It
- My GeoPandas Choropleth Colours Look Wrong: How to Fix It
- Geocoding Returns Wrong or Missing Coordinates
- My Time Column Sorts or Joins Wrongly
- My Track Has Impossible Speeds or Teleports
- Fixing Geocodes That All Land in the Country Centroid
- Fixing a Geocoder That Matches the Wrong Town
- Fixing NetCDF Values That Look Wrong: scale_factor, _FillValue and Units
- Fixing cftime and Out-of-Bounds Datetime Errors in xarray
- Fixing Census API Values Like −666666666 and Other Sentinels
- Fixing Census Totals That Do Not Add Up After Aggregation
- Anonymised points still identify individuals
- Tide times are off by hours after a join
- A yield map has impossible values and stripes
- GTFS times after 24:00 break time parsing
- A GTFS query returns no trips, or the wrong trips, on a date
- Earthquake counts rise over time in my catalogue
- An event's measure falls outside the route
- Event lengths do not add up to the route length
- Occurrence records fall in the ocean
- PV Yield Off by a Factor
- Solar Timestamps Shifted by an Hour
- ppb and µg/m³ Mixed Up
- Air Quality Data Gaps and Negative Values
- Snow Depth Data Has Gaps, Zeros and Impossible Values
- Rainfall Totals Come Out Wrong: Gaps, Extra Scans and Zeros That Mean Missing
- Why Do Some of My Flows Vanish When I Join Them to a Map?
- Why Are My Daily Fire Detection Counts on the Wrong Day?
- Why Are My Station Wind Speeds Ten Times Too High?
- Why Are Coordinates Missing from My Crime Data?
- Why Is the First of the Month the Busiest Day in My Crime Data?
- Why Are Some of My Crash Points in the Arctic or the Wrong State?
- Why Did Serious Injuries Jump in My STATS19 Time Series?
- Why Are My Crashes on the Wrong Day of the Week?
- Fixing Negative Populations and Values in Census Grid Data
- Fixing Daily SST Series with Missing Days from ERDDAP
- Fixing a Gutenberg-Richter b-Value That Comes Out Too Low
- Fixing Peak Ground Acceleration Values That Are 100 Times Too Large
- Fixing USGS Earthquake Times That Show Up as 1970
- Fixing a Haversine Function That Returns the Wrong Distance
- Fixing DMS Coordinates That Land in the Wrong Hemisphere
- Fixing Weather Data in the Wrong Time Zone
- Fixing Gaps and Missing Values in Weather Station Data
- Fixing GHCN Temperatures in Tenths of a Degree and -9999 Values
- Fixing Land Registry Price Paid CSVs That Have No Header
- Fixing Airbnb Prices Stored as Dollar Strings in Python
- Bike-Share Trips With Negative Durations: The Daylight-Saving Night
- Bike-Share Trips That Last 25 Hours: Lost Bikes and Capped Durations
- Flights That Arrive Before They Depart: Local Times Across Time Zones
- Average Flight Delay Comes Out Negative or Tiny: Choosing the Right Delay Measure
- Average Traffic Drops When the Survey Adds Minor Roads
- Traffic Counts That All Grow by the Same Percentage: Estimated AADF
- Street Trees 425 Inches Across: Impossible Diameters in Tree Census Data
- Volunteers and Staff Recorded Trees Differently: Measurer Bias in Citizen Data
- Drought Percentages That Add Up to More Than 100%: Cumulative Categories
- SPI Fails in Dry Climates: Months With No Rain and the Gamma Fit
- Drought Monitor Dates Are a Week Off: Map Date, Valid Period and Release Day
- Taxi Tips Are Zero for Cash Trips: Why Average Tips Come Out Wrong
- Taxi Trips With No Passenger Count or Rate Code: Payment Type 0
- EIA-923 Monthly Values Are Dots: Reading Missing Months Correctly
- EIA-923 Plant 99999: Generation That Belongs to No Plant
- Nameplate or Summer Capacity: Why Power Plant Capacity Totals Disagree
- Bridge Rating Averages Come Out Too Low: N Codes Read as Zero
- NBI Inspection Dates That Will Not Parse: Month and Year Without Leading Zeros
- 311 Requests Closed Before They Were Created: Impossible Dates
- 311 Requests With No Location: Missing Coordinates and Placeholder Addresses
- 311 Counts Inflated by Duplicates: One Problem, Many Requests
- Storm Events Damage Columns Will Not Sum: 10.00K, 1.60B and Blanks
- Tornado Counts Too High: One Tornado Split Across County Lines
- ACE Comes Out Too High: Landfall Records Between Six-Hourly Fixes
- Disaster Counts Too High: FEMA Declaration Rows Are One per County
- NFIP Claim Totals Look Wrong: Negative Payments and Impossible Water Depths
- Average EV Range Comes Out Too Low: Zero Means Not Researched
- Counting EVs by VIN Goes Wrong: Ten Characters Are Not One Vehicle
- Restaurant Inspections Dated 1 January 1900: New Places Not Yet Inspected
- Restaurant Inspection Counts Too High: One Row per Violation
Learning paths through this topic
A curated route rather than a list — the guides in the order that builds on itself.
- Finding & Fetching Data
- Spatial Statistics
- Raster Analysis
- Making Maps
- GIS Data Cleaning
- Movement & Time
- Geocoding & Addresses
- Gridded & Climate Data
- Census & Demographics
- Geoprivacy & Anonymisation
- Metadata, Licensing & Provenance
- Coastal & Marine
- Agriculture & Crops
- Public Transit & GTFS
- Biodiversity & Species Data
- Linear Referencing
- Natural Hazards & Risk
- Air Quality Data
- Renewable Energy Siting
Related topics
Subjects that keep coming up alongside this one, most overlap first.