Skip to content

Area Change Estimation

Estimate annual changes in forest land area using remeasured plots.

Overview

The area_change() function calculates net and gross changes in forest land area by tracking land use transitions between measurement periods. This uses the SUBP_COND_CHNG_MTRX table which records subplot condition changes.

import pyfia

db = pyfia.FIA("georgia.duckdb")
db.clip_most_recent()

# Net annual area change
net_change = pyfia.area_change(db)

# Gross forest loss by ownership
loss_by_owner = pyfia.area_change(db, change_type="gross_loss", grp_by="OWNGRPCD")

Function Reference

area_change

area_change(db: FIA, land_type: Literal['forest', 'timber'] = 'forest', change_type: Literal['net', 'gross_gain', 'gross_loss'] = 'net', annual: bool = True, grp_by: Optional[Union[str, List[str]]] = None, area_domain: Optional[str] = None, variance: bool = False, totals: bool = False) -> DataFrame

Estimate area change for forest or timberland.

Calculates net or gross change in forest/timberland area using remeasured plots from the SUBP_COND_CHNG_MTRX table. Only plots measured at two time points contribute to the estimate.

PARAMETER DESCRIPTION
db

FIA database connection with EVALID set

TYPE: FIA

land_type

Land classification to track changes for: - 'forest': All forest land (COND_STATUS_CD = 1) - 'timber': Timberland only (productive, unreserved forest)

TYPE: (forest, timber) DEFAULT: 'forest'

change_type

Type of change to calculate: - 'net': Net change (gains minus losses) - 'gross_gain': Only area gained (non-forest to forest) - 'gross_loss': Only area lost (forest to non-forest)

TYPE: (net, gross_gain, gross_loss) DEFAULT: 'net'

annual

If True, return annualized rate in acres/year If False, return total change over remeasurement period

TYPE: bool DEFAULT: True

grp_by

Column(s) to group results by (e.g., 'OWNGRPCD', 'FORTYPCD')

TYPE: str or list of str DEFAULT: None

area_domain

SQL-like filter expression for conditions

TYPE: str DEFAULT: None

variance

Whether to calculate sampling variance and standard error

TYPE: bool DEFAULT: False

totals

Whether to include totals row

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
DataFrame

Area change estimates with columns: - STATECD: State FIPS code - AREA_CHANGE_TOTAL: Area change (acres/year if annual=True, else acres) - N_PLOTS: Number of remeasured plots - SE: Standard error (if variance=True) - VARIANCE: Sampling variance (if variance=True) - Grouping columns (if grp_by specified)

Examples:

>>> from pyfia import FIA, area_change
>>> with FIA("path/to/db.duckdb") as db:
...     db.clip_most_recent()
...     # Net annual forest area change
...     result = area_change(db, land_type="forest")
...     print(f"Annual change: {result['AREA_CHANGE_TOTAL'][0]:+,.0f} acres/year")
>>> # Gross forest loss by ownership
>>> result = area_change(
...     db,
...     change_type="gross_loss",
...     grp_by="OWNGRPCD",
...     variance=True
... )
Notes

Area change estimation requires remeasured plots (plots with both current and previous measurements). States with newer FIA programs may have fewer remeasured plots, resulting in higher sampling errors.

The REMPER (remeasurement period) varies by plot but averages approximately 5-7 years in most states.

References

Bechtold & Patterson (2005), "The Enhanced Forest Inventory and Analysis Program - National Sampling Design and Estimation Procedures", Chapter 4.

Source code in src/pyfia/estimation/estimators/area_change.py
def area_change(
    db: FIA,
    land_type: Literal["forest", "timber"] = "forest",
    change_type: Literal["net", "gross_gain", "gross_loss"] = "net",
    annual: bool = True,
    grp_by: Optional[Union[str, List[str]]] = None,
    area_domain: Optional[str] = None,
    variance: bool = False,
    totals: bool = False,
) -> pl.DataFrame:
    """
    Estimate area change for forest or timberland.

    Calculates net or gross change in forest/timberland area using remeasured
    plots from the SUBP_COND_CHNG_MTRX table. Only plots measured at two time
    points contribute to the estimate.

    Parameters
    ----------
    db : FIA
        FIA database connection with EVALID set
    land_type : {'forest', 'timber'}, default 'forest'
        Land classification to track changes for:
        - 'forest': All forest land (COND_STATUS_CD = 1)
        - 'timber': Timberland only (productive, unreserved forest)
    change_type : {'net', 'gross_gain', 'gross_loss'}, default 'net'
        Type of change to calculate:
        - 'net': Net change (gains minus losses)
        - 'gross_gain': Only area gained (non-forest to forest)
        - 'gross_loss': Only area lost (forest to non-forest)
    annual : bool, default True
        If True, return annualized rate in acres/year
        If False, return total change over remeasurement period
    grp_by : str or list of str, optional
        Column(s) to group results by (e.g., 'OWNGRPCD', 'FORTYPCD')
    area_domain : str, optional
        SQL-like filter expression for conditions
    variance : bool, default False
        Whether to calculate sampling variance and standard error
    totals : bool, default False
        Whether to include totals row

    Returns
    -------
    pl.DataFrame
        Area change estimates with columns:
        - STATECD: State FIPS code
        - AREA_CHANGE_TOTAL: Area change (acres/year if annual=True, else acres)
        - N_PLOTS: Number of remeasured plots
        - SE: Standard error (if variance=True)
        - VARIANCE: Sampling variance (if variance=True)
        - Grouping columns (if grp_by specified)

    Examples
    --------
    >>> from pyfia import FIA, area_change
    >>> with FIA("path/to/db.duckdb") as db:
    ...     db.clip_most_recent()
    ...     # Net annual forest area change
    ...     result = area_change(db, land_type="forest")
    ...     print(f"Annual change: {result['AREA_CHANGE_TOTAL'][0]:+,.0f} acres/year")

    >>> # Gross forest loss by ownership
    >>> result = area_change(
    ...     db,
    ...     change_type="gross_loss",
    ...     grp_by="OWNGRPCD",
    ...     variance=True
    ... )

    Notes
    -----
    Area change estimation requires remeasured plots (plots with both current
    and previous measurements). States with newer FIA programs may have fewer
    remeasured plots, resulting in higher sampling errors.

    The REMPER (remeasurement period) varies by plot but averages approximately
    5-7 years in most states.

    References
    ----------
    Bechtold & Patterson (2005), "The Enhanced Forest Inventory and Analysis
    Program - National Sampling Design and Estimation Procedures", Chapter 4.
    """
    config = {
        "land_type": land_type,
        "change_type": change_type,
        "annual": annual,
        "grp_by": grp_by,
        "area_domain": area_domain,
        "variance": variance,
        "totals": totals,
    }

    estimator = AreaChangeEstimator(db, config)
    return estimator.estimate()

Change Types

Type Description
"net" Net change = (gains from non-forest) - (losses to non-forest)
"gross_gain" Area converted from non-forest to forest
"gross_loss" Area converted from forest to non-forest

Technical Notes

Data Requirements

Area change estimation requires remeasured plots:

  • SUBP_COND_CHNG_MTRX table tracks subplot-level condition changes
  • COND table provides current and previous condition status
  • PLOT table provides REMPER (remeasurement period)
  • Only plots measured at two time points contribute to estimates

Condition Status Codes

COND_STATUS_CD Description
1 Forest land
2 Non-forest land
3 Non-census water
4 Census water
5 Denied access

Transition Logic

  • Gain: Previous COND_STATUS_CD != 1, Current COND_STATUS_CD == 1
  • Loss: Previous COND_STATUS_CD == 1, Current COND_STATUS_CD != 1
  • Net: Gains minus losses

Annualization

By default, results are annualized by dividing by REMPER (remeasurement period, typically 5-7 years). Set annual=False to get total change over the measurement period.

Examples

Net Annual Forest Area Change

result = pyfia.area_change(db, land_type="forest")
net = result["AREA_CHANGE_TOTAL"][0]
print(f"Annual Net Change: {net:+,.0f} acres/year")
# Positive = net gain, Negative = net loss

Gross Gain and Loss

# Area gained (non-forest to forest)
gain = pyfia.area_change(db, change_type="gross_gain")
print(f"Annual Gain: {gain['AREA_CHANGE_TOTAL'][0]:,.0f} acres/year")

# Area lost (forest to non-forest)
loss = pyfia.area_change(db, change_type="gross_loss")
print(f"Annual Loss: {loss['AREA_CHANGE_TOTAL'][0]:,.0f} acres/year")

# Verify: net = gain - loss
net = pyfia.area_change(db, change_type="net")
assert abs(net["AREA_CHANGE_TOTAL"][0] - (gain["AREA_CHANGE_TOTAL"][0] - loss["AREA_CHANGE_TOTAL"][0])) < 1

Total Change (Non-Annualized)

# Total change over remeasurement period (not per year)
result = pyfia.area_change(db, annual=False)
print(f"Total Change: {result['AREA_CHANGE_TOTAL'][0]:+,.0f} acres")

Area Change by Ownership

result = pyfia.area_change(
    db,
    change_type="gross_loss",
    grp_by="OWNGRPCD"
)
# OWNGRPCD: 10=Forest Service, 20=Other Federal, 30=State/Local, 40=Private
print(result)

Area Change by Forest Type

result = pyfia.area_change(
    db,
    grp_by="FORTYPCD"
)
result = pyfia.join_forest_type_names(result, db)
print(result.sort("AREA_CHANGE_TOTAL").head(10))  # Top losers

With Variance Estimation

result = pyfia.area_change(
    db,
    variance=True
)
print(f"Net Change: {result['AREA_CHANGE_TOTAL'][0]:+,.0f} acres/year")
print(f"SE: {result['SE'][0]:,.0f}")

Comparison with EVALIDator

EVALIDator provides area change estimates through the following snum codes:

snum Description
127 Forest land area change (remeasured conditions, both measurements)
128 Forest land area change (at least one measurement is forest)
136 Annual forest land area change (both measurements forest)
137 Annual forest land area change (either measurement forest)

EVALIDator Interpretation

EVALIDator's "area change" estimates (snum 136, 137) report the total area meeting the criteria on remeasured plots, not the net transition. The difference between snum 137 (either) and snum 136 (both) represents the total transition area.

pyFIA's area_change() calculates the net transition (gains minus losses), which is typically what users want for trend analysis.

References

  • Bechtold & Patterson (2005), "The Enhanced Forest Inventory and Analysis Program - National Sampling Design and Estimation Procedures", Chapter 4
  • FIA Database User Guide, SUBP_COND_CHNG_MTRX table documentation