Skip to main content
pyFIA can clip plots to a polygon boundary and join polygon attributes to plots, so you can estimate over custom regions like watersheds, management units, or counties.
MethodPurpose
clip_by_polygon()Restrict plots to a polygon boundary
intersect_polygons()Join polygon attributes to plots for use in grp_by
Both accept any GDAL-supported format — Shapefile (.shp), GeoJSON (.geojson), GeoPackage (.gpkg), or GeoParquet (.parquet).

Clipping to a region

from pyfia import FIA, tpa

with FIA("southeast.duckdb") as db:
    db.clip_by_state(37)                       # North Carolina
    db.clip_most_recent(eval_type="VOL")

    db.clip_by_polygon("my_region.geojson")    # restrict to the polygon

    result = tpa(db, tree_type="live")         # estimates use only plots inside
Under the hood pyFIA loads the DuckDB spatial extension, runs a point-in-polygon test on plot coordinates, and applies the resulting plot filter when loading data.

Grouping by polygon attributes

Use intersect_polygons() to attach polygon attributes, then group by them:
from pyfia import FIA, tpa

with FIA("southeast.duckdb") as db:
    db.clip_by_state(37)
    db.clip_most_recent(eval_type="VOL")

    db.intersect_polygons("counties.shp", attributes=["NAME", "FIPS"])

    result = tpa(db, grp_by=["NAME"], tree_type="live")
NAMETPABAAN_PLOTS
Wake523.489.245
Durham612.1102.338
Orange487.978.629

Combining both

Clip to a study area and group within it:
with FIA("southeast.duckdb") as db:
    db.clip_by_state(37)
    db.clip_most_recent(eval_type="VOL")

    db.clip_by_polygon("study_area.geojson")
    db.intersect_polygons("management_units.shp", attributes=["UNIT_NAME"])

    result = tpa(db, grp_by=["UNIT_NAME"], tree_type="live")

Things to know

FIA public plot coordinates are fuzzed up to ~1 mile for landowner privacy. Spatial precision below ~1 mile is not meaningful, and small polygons may capture few or no plots.
  • Plots outside all polygons get NULL attribute values. They still contribute to non-grouped estimates, but grp_by excludes NULL groups.
  • The first spatial query loads the DuckDB spatial extension; subsequent queries are faster.

Method signatures

db.clip_by_polygon(polygon: str | Path, predicate: str = "intersects") -> FIA
db.intersect_polygons(polygon: str | Path, attributes: list[str]) -> FIA
Both return self, so they chain.

See also