CRS Not Found Error in GeoPandas: Causes and Fixes
Problem statement
A common CRS problem in GeoPandas happens when a layer loads without coordinate reference system metadata. In practice, this usually shows up in one of these ways:
gdf.crsreturnsNonegdf.to_crs(...)fails- spatial joins, overlays, or distance calculations produce wrong results
- a shapefile loads, but the projection is missing because the
.prjfile is absent
This matters because GeoPandas needs a defined CRS to interpret coordinates correctly. If a layer has longitude and latitude values but no CRS, GeoPandas cannot reproject it until the source CRS is defined. If projected coordinates are mislabeled, later analysis will also be wrong.
A missing CRS is usually caused by one of these situations:
- the source file was saved without projection metadata
- a shapefile is missing its
.prjfile - a GeoJSON file is assumed to be WGS84 longitude/latitude, but the source data may have been exported incorrectly or interpreted with the wrong CRS
- the GeoDataFrame was created manually from coordinates and CRS was never assigned
- an export step dropped CRS metadata
- data moved between tools such as QGIS, CSV-to-point workflows, and Python scripts without preserving CRS information
Quick answer
If the source data has a known CRS but GeoPandas did not detect it, assign it with set_crs():
gdf = gdf.set_crs("EPSG:4326")
If the data already has a CRS and you need to convert it to another one, use to_crs():
gdf = gdf.to_crs("EPSG:3857")
Use set_crs() only to define the current CRS of existing coordinates. Use to_crs() only to reproject after the source CRS is correct. If the file is missing projection metadata, verify the CRS from documentation or the data provider before assigning anything.
Step-by-step solution
Step 1: Check whether the GeoDataFrame has a CRS
Start by inspecting the CRS directly.
import geopandas as gpd
gdf = gpd.read_file("data/parcels.shp")
print(gdf.crs)
Possible results:
- valid CRS, for example:
EPSG:4326 - missing CRS:
None
If gdf.crs is None, GeoPandas has no projection information for that layer.
Step 2: Identify where the missing CRS problem comes from
Check how the data was created or exported.
Common causes:
- Shapefile missing
.prj: shapefiles store CRS in a separate sidecar file - GeoJSON workflow issues: modern GeoJSON generally uses WGS84 longitude/latitude, but exported data can still be mislabeled or interpreted incorrectly
- Manual geometry creation: points were created from
xandycolumns, but no CRS was assigned - Export loss: a previous script wrote features but dropped CRS metadata
For shapefiles, confirm that files such as these exist together:
parcels.shpparcels.shxparcels.dbfparcels.prj
If .prj is missing, GeoPandas may read the geometry but report gdf.crs as None.
Step 3: If the CRS is known, assign it correctly
If you know the original CRS, define it with set_crs().
import geopandas as gpd
gdf = gpd.read_file("data/points_without_prj.shp")
if gdf.crs is None:
gdf = gdf.set_crs("EPSG:4326")
print(gdf.crs)
You can also assign another known CRS, such as British National Grid:
gdf = gdf.set_crs("EPSG:27700")
Important: set_crs() does not change coordinate values. It only labels the existing coordinates with the CRS they already use.
This is the correct fix when you need to assign a CRS in GeoPandas for data that is already in a known coordinate system.
Step 4: Reproject after assigning the CRS
After the source CRS is correctly defined, reproject with to_crs().
gdf = gdf.set_crs("EPSG:4326")
gdf_projected = gdf.to_crs("EPSG:3857")
This is the usual workflow:
- load data
- check
gdf.crs - assign source CRS with
set_crs()if missing - convert to target CRS with
to_crs()
If you try to_crs() while gdf.crs is None, you may get errors such as Cannot transform naive geometries. Please set a crs on the object first.
Step 5: Verify the fix
After assigning or reprojecting, confirm the result.
print(gdf_projected.crs)
print(gdf_projected.total_bounds)
print(gdf_projected.head())
Check that:
gdf.crsis no longerNone- bounds look reasonable for the expected CRS
- coordinates are in the right range
For example:
EPSG:4326should usually have longitude and latitude rangesEPSG:3857should have meter-based values around thousands or millions
Then save the corrected dataset:
gdf_projected.to_file("output/parcels_3857.shp")
Or save to GeoPackage:
gdf_projected.to_file("output/parcels_3857.gpkg", driver="GPKG")
Code examples
Example 1: Read a file and confirm CRS is missing
import geopandas as gpd
gdf = gpd.read_file("data/roads_missing_prj.shp")
print("CRS:", gdf.crs)
if gdf.crs is None:
print("No CRS found.")
Example 2: Assign a known CRS with set_crs()
import geopandas as gpd
gdf = gpd.read_file("data/roads_missing_prj.shp")
# Confirmed from the data provider that this layer uses WGS84
gdf = gdf.set_crs("EPSG:4326")
print("Assigned CRS:", gdf.crs)
Example 3: Reproject after assigning CRS
import geopandas as gpd
gdf = gpd.read_file("data/roads_missing_prj.shp")
gdf = gdf.set_crs("EPSG:4326")
roads_utm = gdf.to_crs("EPSG:32633")
print("Reprojected CRS:", roads_utm.crs)
Example 4: Create a GeoDataFrame from coordinates and set CRS
import pandas as pd
import geopandas as gpd
df = pd.DataFrame({
"site": ["A", "B"],
"longitude": [12.4924, 12.4964],
"latitude": [41.8902, 41.9028]
})
gdf = gpd.GeoDataFrame(
df,
geometry=gpd.points_from_xy(df["longitude"], df["latitude"]),
crs="EPSG:4326"
)
print(gdf.crs)
This is the correct pattern when using longitude and latitude columns. Without the crs argument, you may end up with gdf.crs = None later.
Example 5: Handle a shapefile missing its .prj file
from pathlib import Path
import geopandas as gpd
shp_path = Path("data/parcels.shp")
prj_path = shp_path.with_suffix(".prj")
print("PRJ exists:", prj_path.exists())
gdf = gpd.read_file(shp_path)
print("Loaded CRS:", gdf.crs)
# Only assign after confirming the correct CRS from metadata or provider documentation
if gdf.crs is None:
gdf = gdf.set_crs("EPSG:27700")
print("Final CRS:", gdf.crs)
This is a typical workflow for a shapefile that is missing its .prj file.
Explanation
There are three different states to understand:
- Missing CRS:
gdf.crsisNone. GeoPandas does not know what the coordinates mean. - Assigned CRS: you use
set_crs()to define the CRS of the existing coordinates. - Reprojected CRS: you use
to_crs()to transform coordinates into a different CRS.
These are not interchangeable.
set_crs() fixes missing metadata. It tells GeoPandas what CRS the current coordinate values already use.
to_crs() transforms coordinates from one CRS into another.
If you use to_crs() before setting the source CRS, GeoPandas cannot transform the geometry because the source coordinate system is unknown.
Assigning the wrong CRS is also dangerous. A layer in meters labeled as EPSG:4326 may look valid in code, but every later measurement, overlay, and spatial join may be wrong.
Edge cases or notes
Do not guess the CRS
Do not assign a CRS just because the layer looks right on a map. Verify with:
- data provider metadata
- source documentation
- known coordinate ranges
- another trusted GIS project
Shapefile-specific issue: missing .prj
A shapefile stores projection information outside the .shp file itself. If the .prj file is missing, GeoPandas may read geometry but not CRS.
GeoJSON note
Modern GeoJSON generally uses WGS84 longitude/latitude. Even so, exported data can still be mislabeled or interpreted incorrectly in a workflow, so verify before assigning a CRS manually.
Error after to_crs()
If you see errors related to naive geometries, first check:
print(gdf.crs)
If it returns None, assign the source CRS before reprojecting.
Invalid geometries are a separate issue
A missing CRS and invalid geometry are different problems. If reprojection still fails after fixing CRS, check geometry validity:
print(gdf.is_valid.value_counts())
Verify after export
After fixing the layer, save it to a format that preserves CRS and verify after export by reading it back.
Internal links
For the broader concept, see What Is a CRS in GIS? Coordinate Reference Systems Explained.
Related task guides:
If reprojection still fails, see Cannot Transform Naive Geometries in GeoPandas.
FAQ
Why does gdf.crs return None in GeoPandas?
It usually means the source data has no readable CRS metadata. Common causes are a missing .prj file, manual geometry creation without a CRS, or a previous export that dropped projection information.
What is the difference between set_crs() and to_crs()?
set_crs() defines the current CRS of existing coordinates. It does not move geometry. to_crs() transforms coordinates into a new CRS.
Can I assign a CRS if my shapefile is missing the .prj file?
Yes, but only if you know the correct CRS from a reliable source. In that case, use set_crs() to define it. Do not guess.
Why are my coordinates wrong after fixing the CRS?
The most common reason is that the wrong CRS was assigned with set_crs(). Another possibility is that you reprojected from an incorrect source CRS. Check the original coordinate system first, then assign, then reproject.