How to Remove Null and Empty Geometries in GeoPandas
Problem statement
A GeoDataFrame can contain unusable geometry rows in two different ways:
- null geometries: missing values such as
None - empty geometries: geometry objects that exist but contain no coordinates
This is a common cleanup step in GIS workflows. You may see these rows after:
- reading shapefiles or GeoJSON files with incomplete features
- creating geometries from coordinates where some rows failed
- running
clip(),overlay(), or similar operations that return empty results
If you do not remove these rows, they can cause inconsistent behavior or unnecessary errors in:
- spatial joins
- overlays
- plotting
- exports to shapefile or GeoJSON
- downstream validation and automation scripts
The practical goal is to keep only rows with real, usable geometries before analysis or export.
Quick answer
Use a two-step filter:
clean_gdf = gdf[gdf.geometry.notna()].copy()
clean_gdf = clean_gdf[~clean_gdf.geometry.is_empty].copy()
This does two things:
gdf.geometry.notna()removes null geometries~clean_gdf.geometry.is_emptyremoves empty geometries
If you want a clean index after filtering:
clean_gdf = clean_gdf.reset_index(drop=True)
Step-by-step solution
Inspect the GeoDataFrame for null geometries
In GeoPandas, a null geometry is a missing value in the geometry column, usually None.
Use isna() to find them:
null_count = gdf.geometry.isna().sum()
print(f"Null geometries: {null_count}")
You can also preview those rows:
print(gdf[gdf.geometry.isna()])
This matters because null geometry rows often pass through file imports or data merges without obvious errors.
Inspect the GeoDataFrame for empty geometries
An empty geometry is different. It is still a geometry object, but it contains no coordinates.
Use is_empty to identify these rows:
non_null_gdf = gdf[gdf.geometry.notna()]
empty_count = non_null_gdf.geometry.is_empty.sum()
print(f"Empty geometries: {empty_count}")
Preview them if needed:
print(non_null_gdf[non_null_gdf.geometry.is_empty])
This often appears after spatial operations where a feature has no resulting geometry.
Remove both null and empty geometries
To keep only usable geometry rows, filter null geometries first and empty geometries second:
clean_gdf = gdf[gdf.geometry.notna()].copy()
clean_gdf = clean_gdf[~clean_gdf.geometry.is_empty].copy()
This is the safest cleanup pattern for GeoPandas scripts because it treats null and empty geometry checks separately.
Reset the index after filtering
After removing rows, the original index may contain gaps. Resetting the index makes exports and later processing cleaner:
clean_gdf = clean_gdf.reset_index(drop=True)
This is especially useful before saving a cleaned layer or looping through rows in an automation workflow.
Confirm the cleaned result
Check row counts before and after cleanup:
print(f"Rows before: {len(gdf)}")
print(f"Rows after: {len(clean_gdf)}")
print(f"Remaining null geometries: {clean_gdf.geometry.isna().sum()}")
print(f"Remaining empty geometries: {clean_gdf.geometry.is_empty.sum()}")
You can also preview the cleaned result:
print(clean_gdf.head())
Code examples
Example 1: Remove null geometries from a GeoDataFrame
This example removes rows where the geometry is missing (None).
import geopandas as gpd
from shapely.geometry import Polygon
gdf = gpd.GeoDataFrame(
{
"parcel_id": [1, 2, 3],
"owner": ["A", "B", "C"],
"geometry": [
Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]),
None,
Polygon([(2, 2), (3, 2), (3, 3), (2, 3)])
]
},
crs="EPSG:4326"
)
clean_gdf = gdf[gdf.geometry.notna()].copy()
print(clean_gdf)
Use this only when the problem is missing geometry values and you know the remaining geometries are not empty.
Example 2: Remove empty geometries from a GeoDataFrame
This example removes geometry objects that exist but are empty.
import geopandas as gpd
from shapely.geometry import Polygon
gdf = gpd.GeoDataFrame(
{
"site_id": [101, 102, 103],
"geometry": [
Polygon([(0, 0), (2, 0), (2, 2), (0, 2)]),
Polygon(),
Polygon([(3, 3), (4, 3), (4, 4), (3, 4)])
]
},
crs="EPSG:3857"
)
clean_gdf = gdf[~gdf.geometry.is_empty].copy()
print(clean_gdf)
This is useful when you already know there are no null geometries.
Example 3: Remove both null and empty geometries in one workflow
This is the most practical cleanup pattern for imported GIS data.
import geopandas as gpd
from shapely.geometry import Point, Polygon
gdf = gpd.GeoDataFrame(
{
"feature_id": [1, 2, 3, 4],
"name": ["A", "B", "C", "D"],
"geometry": [
Point(0, 0),
None,
Polygon(),
Point(5, 5)
]
},
crs="EPSG:4326"
)
print("Before cleanup")
print(f"Rows: {len(gdf)}")
print(f"Null geometries: {gdf.geometry.isna().sum()}")
non_null_gdf = gdf[gdf.geometry.notna()]
print(f"Empty geometries: {non_null_gdf.geometry.is_empty.sum()}")
clean_gdf = gdf[gdf.geometry.notna()].copy()
clean_gdf = clean_gdf[~clean_gdf.geometry.is_empty].copy()
clean_gdf = clean_gdf.reset_index(drop=True)
print("\nAfter cleanup")
print(f"Rows: {len(clean_gdf)}")
print(f"Null geometries: {clean_gdf.geometry.isna().sum()}")
print(f"Empty geometries: {clean_gdf.geometry.is_empty.sum()}")
print(clean_gdf)
Example 4: Read a file, clean geometries, and save the result
This example shows a realistic preprocessing step before analysis.
import geopandas as gpd
input_file = "data/parcels_raw.geojson"
output_file = "data/parcels_clean.geojson"
gdf = gpd.read_file(input_file)
print("Before cleanup")
print(f"Rows: {len(gdf)}")
print(f"Null geometries: {gdf.geometry.isna().sum()}")
non_null_gdf = gdf[gdf.geometry.notna()]
print(f"Empty geometries: {non_null_gdf.geometry.is_empty.sum()}")
clean_gdf = gdf[gdf.geometry.notna()].copy()
clean_gdf = clean_gdf[~clean_gdf.geometry.is_empty].copy()
clean_gdf = clean_gdf.reset_index(drop=True)
print("\nAfter cleanup")
print(f"Rows: {len(clean_gdf)}")
print(f"Remaining null geometries: {clean_gdf.geometry.isna().sum()}")
print(f"Remaining empty geometries: {clean_gdf.geometry.is_empty.sum()}")
clean_gdf.to_file(output_file, driver="GeoJSON")
The same cleanup pattern works for shapefiles:
gdf = gpd.read_file("data/roads_raw.shp")
clean_gdf = gdf[gdf.geometry.notna()].copy()
clean_gdf = clean_gdf[~clean_gdf.geometry.is_empty].copy()
clean_gdf = clean_gdf.reset_index(drop=True)
clean_gdf.to_file("data/roads_clean.shp")
Explanation
The key point is that null and empty are not the same condition.
isna()checks for missing values in the geometry column, such asNoneis_emptychecks for geometry objects that exist but have no coordinates
That is why dropna() or geometry.notna() alone is not enough for many datasets. A row with an empty polygon is not null, so it stays unless you also filter ~geometry.is_empty.
Filtering in two steps is also more reliable across different GeoPandas and Shapely environments because the empty check only runs on rows that already have geometry objects.
In practice, removing both is a good default before:
sjoin()overlay()clip()- plotting with
.plot() - writing cleaned outputs for other GIS tools
This cleanup is also different from geometry validity. A polygon can be non-null and non-empty but still invalid because of self-intersections or topology errors. Removing missing and empty rows is usually the first cleanup step, not the last one.
Edge cases and notes
Null and empty geometries are not the same
Do not assume one check covers both cases. Many imported datasets contain a mix of None values and empty Shapely geometries.
Some workflows may keep empty results temporarily
After clip() or overlay(), empty geometries may be expected as an intermediate result. That is fine, but remove them before final export or downstream analysis.
Geometry validity is separate
A geometry can still be invalid after this cleanup. If you need topology-safe data, check validity next.
CRS issues
Removing null and empty geometries does not change CRS, but you should still confirm the GeoDataFrame has the correct CRS before further analysis:
print(gdf.crs)
If you plan to run distance or area calculations after cleanup, reproject to an appropriate projected CRS first.
Watch for export behavior
Some drivers, file formats, and GIS tools handle missing or empty geometries inconsistently. Cleaning geometry rows before writing files helps avoid silent data loss, write errors, or confusing outputs in QGIS and other GIS software.
Common pitfall
dropna() on the full DataFrame may remove rows for non-geometry nulls you want to keep. If your goal is only geometry cleanup, target the geometry column directly.
Internal links
For the broader background on how GeoPandas works with spatial data, see How to Reproject Spatial Data in Python (GeoPandas).
If you are working with input and output files around this cleanup step, read How to Read a Shapefile in Python with GeoPandas and How to Export GeoJSON in Python with GeoPandas.
If this cleanup is part of a failing file workflow, start with How to Read a Shapefile in Python with GeoPandas to verify the layer loaded correctly.
FAQ
What is the difference between null and empty geometries in GeoPandas?
A null geometry is a missing value such as None. An empty geometry is a real geometry object with no coordinates. Use isna() for null values and is_empty for empty geometry objects.
Do I need to remove both None and empty geometries before spatial joins?
Usually, yes. Spatial joins, overlays, and plotting are more reliable when the geometry column contains only usable geometries.
Why does isna() not catch empty geometries?
Because empty geometries are not missing values. They are geometry objects, so they are not considered null by pandas or GeoPandas.
Can I save a shapefile or GeoJSON if some geometries are null?
Sometimes yes, but behavior depends on the driver, file format, and GIS tool reading the output. Cleaning null and empty geometries first is safer and more predictable.