data-structures.md
207 lines · skills/geopandas/references/data-structures.md at 5f2bc03
Most marks come from matching words in the file, so treat them as pointers.
GeoPandas data structures
GeoPandas 1.2.0 extends pandas with a geometry extension dtype backed by Shapely 2. A GeoSeries is one geometry-valued pandas Series; a GeoDataFrame is a DataFrame with one active geometry column and may contain additional geometry columns.
Construction
Always assign CRS at construction when it is known from authoritative metadata. Do not infer it from coordinate ranges.
import geopandas as gpd
import pandas as pd
from shapely import Point, box
points = gpd.GeoSeries(
[Point(0, 0), Point(1, 1), None],
index=["feature-a", "feature-b", "feature-c"],
crs="EPSG:3857",
name="location",
)
gdf = gpd.GeoDataFrame(
{
"feature_id": ["feature-a", "feature-b"],
"value": [10, 20],
"geometry": [Point(0, 0), Point(1, 1)],
},
geometry="geometry",
crs="EPSG:3857",
)
table = pd.DataFrame({"x": [0.0, 1.0], "y": [0.0, 1.0]})
from_xy = gpd.GeoDataFrame(
table,
geometry=gpd.points_from_xy(table["x"], table["y"]),
crs="EPSG:3857",
)points_from_xy interprets arguments as x then y. For geographic data, that is normally longitude then latitude in the coordinate array, even though the authority definition of EPSG:4326 advertises latitude-first axes.
Active and additional geometry columns
Frame-level spatial methods act on one active geometry column:
gdf["buffered"] = gdf.geometry.buffer(10)
gdf = gdf.set_geometry("buffered")
assert gdf.active_geometry_name == "buffered"
assert gdf.geometry.name == "buffered"
gdf = gdf.rename_geometry("analysis_geometry")Important distinctions:
gdf.geometryalways returns the active geometry, not necessarily a column literally named"geometry".rename_geometry()updates the active-column bookkeeping. A plain pandasrename(columns=...)must be followed byset_geometry().- A
GeoDataFramecan hold multiple geometry columns with different CRS metadata. Switching the active column switches the CRS exposed asgdf.crs. - Ordinary vector formats generally support only one geometry per layer. GeoParquet and Feather can preserve multiple geometry columns.
- GeoPandas 1.0 changed
set_geometry(named_series): the Series name becomes the active column name and the old geometry column is preserved. Avoid the deprecateddrop=parameter; rename/drop explicitly.
Check every geometry column independently:
geometry_columns = [
name for name, dtype in gdf.dtypes.items() if str(dtype) == "geometry"
]
column_crs = {name: gdf[name].crs for name in geometry_columns}Raw lines 47 to 83Missing, empty, and invalid are different
Treat these states separately:
| State | Test | Meaning |
|---|---|---|
| Missing | series.isna() | Unknown geometry, represented by None |
| Empty | series.is_empty | A geometry object with no coordinates |
| Invalid | ~series.is_valid after excluding missing | Coordinates violate topology rules |
missing = gdf.geometry.isna()
empty = gdf.geometry.is_empty
invalid = (~missing) & (~empty) & (~gdf.geometry.is_valid)
usable = ~(missing | empty | invalid)Missing values generally propagate through element-wise operations and are ignored by reductions such as union_all(). Empty geometries participate as geometries: they may have area 0.0 and remain empty after intersection. Never use only dropna() to remove unusable geometries.
Index alignment
Binary geometry methods are row-wise, not all-pairs operations. With a GeoSeries argument, align=None defaults to label alignment:
left = gpd.GeoSeries([Point(0, 0), Point(1, 1)], index=["a", "b"])
right = gpd.GeoSeries([Point(1, 1), Point(0, 0)], index=["b", "a"])
by_label = left.intersects(right, align=True)
by_position = left.intersects(right, align=False)Use align=False only after proving equal lengths and intended row order. GeoPandas 1.0 raises on some unaligned pandas Series method arguments to avoid ambiguous automatic alignment. For all-pairs matching use a spatial join or spatial-index query.
Assignment also aligns by index:
result = gdf.copy()
derived = result.geometry.buffer(10)
result.loc[:, "buffered"] = derived # label-alignedReset or preserve indices deliberately before positional work. Never assume the pandas index is a stable feature identifier.
Raw lines 106 to 134Feature identity and duplicate controls
Keep a non-null, stable feature-ID column across reads, joins, explode, overlay, dissolve, and exports:
ids = gdf["feature_id"]
if ids.isna().any() or ids.duplicated(keep=False).any():
raise ValueError("feature_id must be non-null and unique for this workflow")Cardinality-changing operations need explicit provenance:
explode(ignore_index=False, index_parts=False)defaults to no part-level MultiIndex in GeoPandas 1.0+. Create a part number if parts need identity.- Spatial joins can repeat either side; retain both source IDs.
- Overlay can split one feature into many. Add source IDs before overlay and generate a derived ID afterward.
- Dissolve intentionally combines IDs; record group keys and aggregation rules.
pd.concatrequires compatible geometry-column CRS and can preserve duplicate indices unlessignore_index=True.
Geometry type and dimensionality
geom_type, has_z, and (with Shapely 2.1) has_m describe different properties. Mixed geometry types are valid in memory but can break overlay or export contracts. Z and M ordinates are not used by GeoPandas' planar topology:
summary = {
"types": gdf.geometry.geom_type.value_counts(dropna=False).to_dict(),
"has_z": int(gdf.geometry.has_z.sum()),
"has_m": int(gdf.geometry.has_m.sum()),
}Do not silently drop Z/M. If a target format or operation is 2D-only, record the loss and create a new derived artifact.
Raw lines 157 to 173Copying and conversion
- Use
gdf.copy()before replacing an active geometry. - Call
merge()from the GeoDataFrame side;plain_df.merge(gdf, ...)can return a non-spatial DataFrame. - Reading a non-spatial layer with
read_file()returns a pandas DataFrame in GeoPandas 1.0+. np.asarray(gdf.geometry)orgdf.geometry.to_numpy()replaces removed access toGeometryArray.data.- Do not serialize geometries with pickle for exchange. Use GeoPackage, GeoParquet, WKB, or WKT with an explicit CRS contract.
Minimum structure audit
Record, without emitting coordinates or identifiers:
- row and column counts;
- active and additional geometry-column names;
- CRS per geometry column;
- counts of missing, empty, invalid, Z/M, and each geometry type;
- index uniqueness and stable-ID null/duplicate counts;
- source hash, parser/driver, package/native versions, and operation timestamp.
The bundled scripts/vector_inventory.py emits a redacted metadata inventory; scripts/geometry_validity_report.py adds bounded geometry-state counts.
Sources (verified 2026-10-01)
- GeoPandas data structures.
- GeoSeries API.
- GeoDataFrame API.
- Missing and empty geometries.
- GeoSeries.intersects alignment.
- GeoPandas 1.0.0 release and migrations — released 2024-06-24.