GeoPandas "No Geometry Column" Error: How to Fix It
On this page
Problem statement
A common GeoPandas problem happens when you call a geospatial method on an object that has no active geometry column. With GeoPandas 1.1 it shows up as one of these:
AttributeError: You are calling a geospatial method on the GeoDataFrame, but the active geometry column to use has not been set.β no column was ever made activeAttributeError: ... but the active geometry column ('geometry') is not present.β the active column was renamed or droppedAttributeError: 'DataFrame' object has no attribute 'to_crs'β the object is not a GeoDataFrame at allTypeError: Input must be valid geometry objectsβset_geometry()was pointed at text, such as WKTKeyError: 'geometry'β plain pandas indexing on a column that does not exist
Older GeoPandas releases said No geometry data set yet (expected in column 'geometry') for the first case; the meaning is the same.
A missing geometry column is usually caused by one of these situations:
- the active geometry column was dropped, or renamed with
rename() - the object is a plain
DataFrame, for example afterpd.read_csv(),pd.read_parquet(), or a merge or concat that started from a plain DataFrame - the geometry lives in a column with a different name that was never set as active
- coordinates exist as
lon/latnumbers, or shapes as WKT text or WKB bytes, that were never converted to geometry
Every example below uses real files: New York City's borough boundaries, which NYC Open Data exports as a CSV with WKT in a the_geom column; the same boroughs written to GeoParquet by DuckDB, which names its geometry column geom; and the UK's airports from OurAirports, a CSV of plain latitude and longitude.
Quick answer
If you have a column that holds geometry but it is not active, set it with set_geometry(). If the column holds text or bytes, parse it first. If you only have coordinates, build points:
import geopandas as gpd
import pandas as pd
boroughs = pd.read_csv("data/nyc_boroughs.csv") # the_geom is WKT text
boroughs = gpd.GeoDataFrame(boroughs, geometry=gpd.GeoSeries.from_wkt(boroughs["the_geom"]), crs="EPSG:4326")
airports = pd.read_csv("data/uk_airports.csv") # latitude_deg, longitude_deg
airports = gpd.GeoDataFrame(airports, crs="EPSG:4326",
geometry=gpd.points_from_xy(airports["longitude_deg"], airports["latitude_deg"]))
print(boroughs.geometry.name, boroughs.geom_type.unique().tolist(), "|", airports.geometry.name, len(airports))
geometry ['MultiPolygon'] | geometry 88
Use set_geometry() when the geometry already exists as shapes. Build a new GeoDataFrame when you only have raw coordinates, WKT strings or WKB bytes.
Choosing the repair path
Find the geometry column and make it active
Step 1: Confirm whether the object is a GeoDataFrame
A frequent cause is that you are working with a plain DataFrame. Check the type first:
raw = pd.read_csv("data/nyc_boroughs.csv")
print(type(raw).__name__, isinstance(raw, gpd.GeoDataFrame))
print(raw.dtypes.to_dict())
DataFrame False
{'BoroCode': dtype('int64'), 'BoroName': <StringDtype(na_value=nan)>, 'Shape_Area': dtype('float64'), 'Shape_Length': dtype('float64'), 'the_geom': <StringDtype(na_value=nan)>}
A CSV never contains geometry, so pd.read_csv() always returns a plain DataFrame, and the_geom is a column of text. If isinstance(obj, gpd.GeoDataFrame) is False, you need to build a GeoDataFrame, not just set the geometry.
Step 2: Find the geometry column
Wrapping that DataFrame in GeoDataFrame() does not help on its own β GeoPandas does not guess which column holds shapes:
wrapped = gpd.GeoDataFrame(raw)
try:
wrapped.geometry
except AttributeError as err:
print("AttributeError:", str(err).splitlines()[0])
print(" ", str(err).splitlines()[1])
AttributeError: You are calling a geospatial method on the GeoDataFrame, but the active geometry column to use has not been set.
There are no existing columns with geometry data type. You can add a geometry column as the active geometry column with df.set_geometry.
Look for a column that holds shapes but is not named geometry β geom, the_geom, shape, wkt and wkb_geometry are all common β and check what is actually in it. The second line of the message tells you whether any column already has the geometry dtype.
Step 3: Set the geometry column as active
set_geometry() needs shapes, not text:
try:
wrapped.set_geometry("the_geom")
except TypeError as err:
print("TypeError:", str(err)[:60], "...")
wrapped["the_geom"] = gpd.GeoSeries.from_wkt(wrapped["the_geom"])
boroughs = wrapped.set_geometry("the_geom").set_crs("EPSG:4326")
print(boroughs.geometry.name, "->", boroughs.rename_geometry("geometry").geometry.name)
TypeError: Input must be valid geometry objects: MULTIPOLYGON (((-74.05 ...
the_geom -> geometry
rename_geometry() renames the active column and keeps it active, if you want it called geometry.
Step 4: Rebuild geometry from coordinates
If you only have coordinate columns, create point geometry with points_from_xy():
airports = pd.read_csv("data/uk_airports.csv")
print(airports[["name", "latitude_deg", "longitude_deg"]].head(2))
airports = gpd.GeoDataFrame(airports, crs="EPSG:4326",
geometry=gpd.points_from_xy(airports["longitude_deg"], airports["latitude_deg"]))
print(airports.geometry.name, airports.total_bounds.round(1))
name latitude_deg longitude_deg
0 Belfast International Airport 54.657501 -6.215830
1 Enniskillen/St Angelo Airport 54.398274 -7.651179
geometry [-7.7 50.1 1.3 59.9]
Note the argument order: points_from_xy(x, y) takes longitude first. OurAirports stores latitude first, which is exactly how the columns get swapped.
Step 5: Verify the fix
After setting or rebuilding the geometry, check that GeoPandas sees it and that it means what you think. The NYC export carries each borough's area in square feet from the city's own GIS, which makes a good test:
check = boroughs.to_crs("EPSG:2263") # NY State Plane, US feet
print(isinstance(check, gpd.GeoDataFrame), check.geometry.name, check.crs.axis_info[0].unit_name)
print((check.area / check["Shape_Area"]).round(4).tolist())
True the_geom US survey foot
[1.0, 1.0, 1.0, 1.0, 1.0]
The areas computed from the parsed WKT match the published Shape_Area to four decimal places for all five boroughs, so the geometry, the CRS and the parsing all agree.
Losing and restoring geometry on New York's boroughs
The data used on this page
- NYC borough boundaries β NYC Open Data's Borough Boundaries (dataset
gthc-hcne) exported as CSV: five rows withBoroCode,BoroName,Shape_Area,Shape_Lengthand the shape as WKT inthe_geom. - Boroughs as GeoParquet β the same boundaries written to GeoParquet by DuckDB's spatial extension, which names the geometry column
geom, as it does for everything it reads withST_Read. - UK airports β the large and medium airports in the UK from OurAirports' public-domain
airports.csv.
import os
import time
import duckdb
import geopandas as gpd
import pandas as pd
import requests
os.makedirs("data", exist_ok=True)
if not os.path.exists("data/nyc_boroughs.csv"):
for attempt in range(5): # NYC Open Data answers 503 now and then
reply = requests.get("https://data.cityofnewyork.us/api/views/gthc-hcne/rows.csv?accessType=DOWNLOAD",
timeout=120)
if reply.status_code != 503:
break
time.sleep(10 * (attempt + 1))
reply.raise_for_status()
open("data/nyc_boroughs.csv", "wb").write(reply.content)
if not os.path.exists("data/nyc_boroughs.parquet"):
con = duckdb.connect()
con.install_extension("spatial")
con.load_extension("spatial")
con.sql("""COPY (SELECT BoroCode, BoroName, ST_GeomFromText(the_geom) AS geom
FROM read_csv('data/nyc_boroughs.csv')) TO 'data/nyc_boroughs.parquet' (FORMAT parquet)""")
if not os.path.exists("data/uk_airports.csv"):
everything = pd.read_csv("https://davidmegginson.github.io/ourairports-data/airports.csv")
uk = everything[(everything["iso_country"] == "GB") & everything["type"].isin(["large_airport", "medium_airport"])]
uk[["ident", "name", "latitude_deg", "longitude_deg"]].to_csv("data/uk_airports.csv", index=False)
for name in ("nyc_boroughs.csv", "nyc_boroughs.parquet", "uk_airports.csv"):
print(f"{name:22} {os.path.getsize('data/' + name):>10,} bytes")
nyc_boroughs.csv 3,085,880 bytes
nyc_boroughs.parquet 1,239,059 bytes
uk_airports.csv 3,967 bytes
Example 1: GeoParquet read with pandas instead of GeoPandas
import geopandas as gpd
import pandas as pd
as_table = pd.read_parquet("data/nyc_boroughs.parquet")
print(type(as_table).__name__, type(as_table["geom"].iloc[0]).__name__)
as_geo = gpd.read_parquet("data/nyc_boroughs.parquet")
print(type(as_geo).__name__, "active:", as_geo.geometry.name, "| CRS:", as_geo.crs)
rebuilt = gpd.GeoDataFrame(as_table, geometry=gpd.GeoSeries.from_wkb(as_table["geom"]), crs="OGC:CRS84")
print(rebuilt.geometry.name, rebuilt.geom_type.iloc[0])
DataFrame bytes
GeoDataFrame active: geom | CRS: OGC:CRS84
geometry MultiPolygon
pd.read_parquet() hands back the geometry as raw WKB bytes; gpd.read_parquet() reads the file's GeoParquet metadata and makes geom active under its original name. Note the CRS: DuckDB never had one, and GeoParquet's default for a file without one is OGC:CRS84 β longitude/latitude, which is right here by luck.
Example 2: Geometry column with a different name
The name of the geometry column depends on how you read the file, not only on what is in it:
import geopandas as gpd
from_parquet = gpd.read_parquet("data/nyc_boroughs.parquet")
from_parquet.to_file("output_boroughs.gpkg") # the GeoPackage stores it as "geom"
from_gpkg = gpd.read_file("output_boroughs.gpkg")
print("read_parquet:", from_parquet.geometry.name, from_parquet.columns.tolist())
print("read_file: ", from_gpkg.geometry.name, from_gpkg.columns.tolist())
read_parquet: geom ['BoroCode', 'BoroName', 'geom']
read_file: geometry ['BoroCode', 'BoroName', 'geometry']
read_file() always calls the geometry geometry; read_parquet() and read_postgis() keep the source's name. Code that does gdf["geometry"] works on one and raises KeyError on the other. gdf.geometry works on both.
Example 3: Rebuild geometry from WKT strings
import geopandas as gpd
import pandas as pd
df = pd.read_csv("data/nyc_boroughs.csv")
print(df["the_geom"].str[:40].iloc[0], "...")
gdf = gpd.GeoDataFrame(df.drop(columns="the_geom"), geometry=gpd.GeoSeries.from_wkt(df["the_geom"]), crs="EPSG:4326")
print(gdf.geometry.name, gdf[["BoroName", "geometry"]].assign(parts=gdf.geometry.count_geometries()).drop(columns="geometry"))
MULTIPOLYGON (((-74.05050806403247 40.56 ...
geometry BoroName parts
0 Staten Island 4
1 Brooklyn 28
2 Queens 23
3 Manhattan 36
4 Bronx 26
Dropping the text column afterwards avoids carrying a multi-megabyte string copy of every shape. The part counts are real: every borough is a multipolygon, because every borough includes islands.
Example 4: The call order decides whether geometry survives
import geopandas as gpd
import pandas as pd
gdf = gpd.read_parquet("data/nyc_boroughs.parquet")
names = pd.read_csv("data/nyc_boroughs.csv")[["BoroCode", "Shape_Area"]]
print("gdf.merge(df): ", type(gdf.merge(names, on="BoroCode")).__name__)
print("df.merge(gdf): ", type(names.merge(gdf, on="BoroCode")).__name__)
print("concat, gdf first:", type(pd.concat([gdf, gdf])).__name__)
print("gdf[[cols]]: ", type(gdf[["BoroCode", "BoroName"]]).__name__)
lost = names.merge(gdf, on="BoroCode")
restored = gpd.GeoDataFrame(lost, geometry="geom", crs=gdf.crs)
print("restored:", type(restored).__name__, restored.geometry.name)
gdf.merge(df): GeoDataFrame
df.merge(gdf): DataFrame
concat, gdf first: GeoDataFrame
gdf[[cols]]: DataFrame
restored: GeoDataFrame geom
Calling merge() on the GeoDataFrame keeps it a GeoDataFrame; calling it on the plain DataFrame does not, though the geometry column is still there to restore.
The active geometry column
A GeoDataFrame is a DataFrame with one extra concept: an active geometry column. This is the column that spatial methods read from. When you call gdf.plot(), gdf.to_crs(), gdf.buffer(), or gpd.sjoin(), GeoPandas looks up the active geometry column by name and operates on it.
By default that column is named geometry, but it does not have to be. A GeoDataFrame can hold geometry in any column name, and even hold more than one geometry column. Only one column is active at a time, and gdf.geometry.name tells you which one it is.
The "active geometry column β¦ has not been set" error means GeoPandas tried to find the active geometry column and could not. Either no column was ever set as active, the active column was dropped or renamed, or the object is a plain DataFrame that has no geometry concept at all.
This is why the fix depends on the situation. If geometry exists in a column, you make that column active with set_geometry(). If the object is a plain DataFrame, you rebuild a GeoDataFrame. If you only have coordinates, WKT or WKB, you create geometry first, then assign it.
Operations that drop the geometry
Only a plain DataFrame on the left loses the geometry
gdf.merge(df) returns a GeoDataFrame; df.merge(gdf) returns a plain DataFrame (Example 4). The same goes for pd.concat(): the type of the first object decides. Rebuild with gpd.GeoDataFrame(result, geometry="<name>", crs=gdf.crs).
Reading a CSV or Parquet file with pandas never gives you geometry
pd.read_csv() returns text and numbers; pd.read_parquet() returns geometry as WKB bytes. Use gpd.points_from_xy(), GeoSeries.from_wkt() or GeoSeries.from_wkb() β or gpd.read_parquet(), which reads the GeoParquet metadata for you.
Multiple geometry columns
A GeoDataFrame can hold several geometry columns, for example the boundary and a centroid. Only the active one is used by spatial methods. Switch with gdf.set_geometry("centroid"); the other columns stay as geometry and can still be used as GeoSeries directly.
Dropping the geometry column
Selecting columns with gdf[["BoroCode", "BoroName"]] returns a plain DataFrame, and so does gdf.drop(columns="geometry"), after which to_crs fails with AttributeError: 'DataFrame' object has no attribute 'to_crs'. Keep the geometry column in the selection if you need to stay spatial.
Renaming the active column with rename()
gdf.rename(columns={"geometry": "geom"}) returns a GeoDataFrame whose active geometry is still recorded as geometry β a column that no longer exists β so the next spatial call fails with "the active geometry column ('geometry') is not present". Use gdf.rename_geometry("geom"), which renames the column and keeps it active.
Internal links
For the underlying concept, read What Is a GeoDataFrame? Structure and Key Concepts.
Related task guides:
- How to Read a CSV with Coordinates as a GeoDataFrame
- How to Read GeoJSON in Python with GeoPandas
- How to Join Attribute Data to a GeoDataFrame in Python
If geometry quality is the next problem, see How to Remove Null and Empty Geometries in GeoPandas.
FAQ
Why does GeoPandas say "No geometry data set" when my data has coordinates?
Coordinates in lon/lat columns are just numbers, and wrapping the DataFrame in GeoDataFrame() does not change that. GeoPandas needs an actual geometry column. Build one with gpd.points_from_xy() and construct a GeoDataFrame, or set an existing geometry column active with set_geometry().
How do I check which column is the active geometry?
Use gdf.geometry.name, which returns the name of the active geometry column. If it raises an AttributeError, no geometry column is set yet, or the one that was set has been renamed or dropped.
Why did my GeoDataFrame turn into a plain DataFrame after a merge?
It did not come from gdf.merge(...), which keeps the GeoDataFrame; it comes from df.merge(gdf, ...) or pd.merge(df, gdf, ...) with the plain DataFrame first. Put the GeoDataFrame first, or rebuild with gpd.GeoDataFrame(merged, geometry="geometry", crs=gdf.crs).
What is the difference between set_geometry() and building a new GeoDataFrame?
set_geometry() activates a column that already contains geometry objects. Building a new GeoDataFrame is for when you only have raw coordinates or WKT strings and need to create the geometry first.
How do I keep the GeoDataFrame type after a pd.concat()?
pd.concat() can return a plain DataFrame, dropping the active geometry. Put a GeoDataFrame first β pd.concat([gdf1, gdf2]) returns a GeoDataFrame β or rebuild it afterward with gpd.GeoDataFrame(combined, geometry="geometry", crs=gdf1.crs).
Why does gpd.sjoin() complain about no geometry on the right frame?
gpd.sjoin() needs a GeoDataFrame on both sides; given a plain DataFrame it raises ValueError: 'right_df' should be GeoDataFrame. If the right side came from a merge or column selection that lost its geometry, rebuild it as a GeoDataFrame before the join, or call set_geometry() on the column that still holds geometry.
Can a GeoDataFrame have geometry but no active geometry column set?
Yes. A GeoDataFrame can hold geometry objects in a column while having no column marked active, which still raises the "active geometry column β¦ has not been set" error. Activate one with gdf.set_geometry("geom").
How do I convert a GeoDataFrame back to a plain DataFrame on purpose?
Drop the geometry column and cast the type: pd.DataFrame(gdf.drop(columns="geometry")). This is useful when you only need the attribute table for a CSV export or a tabular merge.