Skip to content

Mortality Estimation

Estimate annual tree mortality rates and volumes.

Overview

The mortality() function calculates annual mortality estimates using GRM (Growth, Removals, Mortality) methodology.

import pyfia

db = pyfia.FIA("georgia.duckdb")
db.clip_by_state("GA")

# Mortality volume
result = pyfia.mortality(db, measure="volume")

# Mortality by cause
by_agent = pyfia.mortality(db, measure="volume", grp_by="AGENTCD")

Function Reference

mortality

mortality(db: str | FIA, grp_by: str | list[str] | None = None, by_species: bool = False, by_size_class: bool = False, size_class_type: str = 'standard', land_type: str = 'timber', tree_type: str = 'gs', measure: str = 'volume', tree_domain: str | None = None, area_domain: str | None = None, as_rate: bool = False, totals: bool = True, variance: bool = False, most_recent: bool = False) -> DataFrame

Estimate annual tree mortality from FIA data using GRM methodology.

Uses TREE_GRM_COMPONENT and TREE_GRM_MIDPT tables to calculate annual mortality following FIA's Growth-Removal-Mortality approach. This is the correct FIA statistical methodology for mortality estimation.

PARAMETER DESCRIPTION
db

Database connection or path to FIA database.

TYPE: str | FIA

grp_by

Column name(s) to group results by.

TYPE: str or list of str DEFAULT: None

by_species

If True, group results by species code (SPCD).

TYPE: bool DEFAULT: False

by_size_class

If True, group results by diameter size classes.

TYPE: bool DEFAULT: False

size_class_type

Type of size class grouping to use (only applies when by_size_class=True): - "standard": FIA numeric ranges (1.0-4.9, 5.0-9.9, etc.) - "descriptive": Text labels (Saplings, Small, Medium, Large) - "market": Timber market categories (Pre-merchantable, Pulpwood, Chip-n-Saw, Sawtimber)

TYPE: ('standard', 'descriptive', 'market') DEFAULT: 'standard'

land_type

Land type to include in estimation.

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

tree_type

Tree type to include.

TYPE: ('gs', 'al', 'sawtimber', 'live') DEFAULT: 'gs'

measure

What to measure in the mortality estimation.

TYPE: ('volume', 'sawlog', 'biomass', 'tpa', 'count', 'basal_area') DEFAULT: 'volume'

tree_domain

SQL-like filter expression for tree-level filtering.

TYPE: str DEFAULT: None

area_domain

SQL-like filter expression for area/condition-level filtering.

TYPE: str DEFAULT: None

as_rate

If True, return mortality as a rate (mortality/live).

TYPE: bool DEFAULT: False

totals

If True, include population-level total estimates.

TYPE: bool DEFAULT: True

variance

If True, calculate and include variance and standard error estimates.

TYPE: bool DEFAULT: False

most_recent

If True, automatically filter to the most recent evaluation.

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
DataFrame

Mortality estimates with columns: - MORT_ACRE: Annual mortality per acre - MORT_TOTAL: Total annual mortality (if totals=True) - MORT_ACRE_SE: Standard error of per-acre estimate (if variance=True) - MORT_TOTAL_SE: Standard error of total estimate (if variance=True) - Additional grouping columns if specified

See Also

growth : Estimate annual growth using GRM tables removals : Estimate annual removals/harvest using GRM tables

Examples:

Basic volume mortality on forestland:

>>> results = mortality(db, measure="volume", land_type="forest")

Mortality by species (tree count):

>>> results = mortality(db, by_species=True, measure="tpa")

Pre-merchantable tree mortality (trees < 5" DBH):

>>> results = mortality(
...     db,
...     tree_type="live",  # Include all live trees, not just growing stock
...     by_size_class=True,
...     size_class_type="market",  # Returns Pre-merchantable, Pulpwood, etc.
...     measure="tpa",  # TPA recommended for small trees (no volume calculated)
... )
>>> # Filter to pre-merchantable only:
>>> premerch = results.filter(pl.col("SIZE_CLASS") == "Pre-merchantable")
Notes

This function uses FIA's GRM tables which contain pre-calculated annual mortality values. The TPA_UNADJ fields are already annualized.

Pre-merchantable Tree Support: By default (tree_type="gs"), only growing stock trees (≥5" DBH) are included. To include pre-merchantable trees (1.0"-4.9" DBH), use tree_type="live" or tree_type="al". When using market size classes, pre-merchantable trees are automatically categorized as "Pre-merchantable".

Note that FIA does not calculate volume for trees < 5" DBH, so measure="tpa" is recommended for pre-merchantable mortality analysis. For economic valuation of pre-merchantable mortality, consider the "discount timber price" method (Bruck et al.) which values small trees based on their future merchantable potential.

Source code in src/pyfia/estimation/estimators/mortality.py
def mortality(
    db: str | FIA,
    grp_by: str | list[str] | None = None,
    by_species: bool = False,
    by_size_class: bool = False,
    size_class_type: str = "standard",
    land_type: str = "timber",
    tree_type: str = "gs",
    measure: str = "volume",
    tree_domain: str | None = None,
    area_domain: str | None = None,
    as_rate: bool = False,
    totals: bool = True,
    variance: bool = False,
    most_recent: bool = False,
) -> pl.DataFrame:
    """
    Estimate annual tree mortality from FIA data using GRM methodology.

    Uses TREE_GRM_COMPONENT and TREE_GRM_MIDPT tables to calculate
    annual mortality following FIA's Growth-Removal-Mortality approach.
    This is the correct FIA statistical methodology for mortality estimation.

    Parameters
    ----------
    db : str | FIA
        Database connection or path to FIA database.
    grp_by : str or list of str, optional
        Column name(s) to group results by.
    by_species : bool, default False
        If True, group results by species code (SPCD).
    by_size_class : bool, default False
        If True, group results by diameter size classes.
    size_class_type : {'standard', 'descriptive', 'market'}, default 'standard'
        Type of size class grouping to use (only applies when by_size_class=True):
        - "standard": FIA numeric ranges (1.0-4.9, 5.0-9.9, etc.)
        - "descriptive": Text labels (Saplings, Small, Medium, Large)
        - "market": Timber market categories (Pre-merchantable, Pulpwood, Chip-n-Saw, Sawtimber)
    land_type : {'forest', 'timber'}, default 'timber'
        Land type to include in estimation.
    tree_type : {'gs', 'al', 'sawtimber', 'live'}, default 'gs'
        Tree type to include.
    measure : {'volume', 'sawlog', 'biomass', 'tpa', 'count', 'basal_area'}, default 'volume'
        What to measure in the mortality estimation.
    tree_domain : str, optional
        SQL-like filter expression for tree-level filtering.
    area_domain : str, optional
        SQL-like filter expression for area/condition-level filtering.
    as_rate : bool, default False
        If True, return mortality as a rate (mortality/live).
    totals : bool, default True
        If True, include population-level total estimates.
    variance : bool, default False
        If True, calculate and include variance and standard error estimates.
    most_recent : bool, default False
        If True, automatically filter to the most recent evaluation.

    Returns
    -------
    pl.DataFrame
        Mortality estimates with columns:
        - MORT_ACRE: Annual mortality per acre
        - MORT_TOTAL: Total annual mortality (if totals=True)
        - MORT_ACRE_SE: Standard error of per-acre estimate (if variance=True)
        - MORT_TOTAL_SE: Standard error of total estimate (if variance=True)
        - Additional grouping columns if specified

    See Also
    --------
    growth : Estimate annual growth using GRM tables
    removals : Estimate annual removals/harvest using GRM tables

    Examples
    --------
    Basic volume mortality on forestland:

    >>> results = mortality(db, measure="volume", land_type="forest")

    Mortality by species (tree count):

    >>> results = mortality(db, by_species=True, measure="tpa")

    Pre-merchantable tree mortality (trees < 5" DBH):

    >>> results = mortality(
    ...     db,
    ...     tree_type="live",  # Include all live trees, not just growing stock
    ...     by_size_class=True,
    ...     size_class_type="market",  # Returns Pre-merchantable, Pulpwood, etc.
    ...     measure="tpa",  # TPA recommended for small trees (no volume calculated)
    ... )
    >>> # Filter to pre-merchantable only:
    >>> premerch = results.filter(pl.col("SIZE_CLASS") == "Pre-merchantable")

    Notes
    -----
    This function uses FIA's GRM tables which contain pre-calculated annual
    mortality values. The TPA_UNADJ fields are already annualized.

    **Pre-merchantable Tree Support:**
    By default (``tree_type="gs"``), only growing stock trees (≥5" DBH) are
    included. To include pre-merchantable trees (1.0"-4.9" DBH), use
    ``tree_type="live"`` or ``tree_type="al"``. When using market size classes,
    pre-merchantable trees are automatically categorized as "Pre-merchantable".

    Note that FIA does not calculate volume for trees < 5" DBH, so
    ``measure="tpa"`` is recommended for pre-merchantable mortality analysis.
    For economic valuation of pre-merchantable mortality, consider the
    "discount timber price" method (Bruck et al.) which values small trees
    based on their future merchantable potential.
    """
    from ...validation import (
        validate_boolean,
        validate_domain_expression,
        validate_grp_by,
        validate_land_type,
        validate_mortality_measure,
        validate_tree_type,
    )

    land_type = validate_land_type(land_type)
    tree_type = validate_tree_type(tree_type)
    measure = validate_mortality_measure(measure)
    grp_by = validate_grp_by(grp_by)
    tree_domain = validate_domain_expression(tree_domain, "tree_domain")
    area_domain = validate_domain_expression(area_domain, "area_domain")
    by_species = validate_boolean(by_species, "by_species")
    by_size_class = validate_boolean(by_size_class, "by_size_class")
    as_rate = validate_boolean(as_rate, "as_rate")
    totals = validate_boolean(totals, "totals")
    variance = validate_boolean(variance, "variance")
    most_recent = validate_boolean(most_recent, "most_recent")

    valid_size_class_types = ("standard", "descriptive", "market")
    if size_class_type not in valid_size_class_types:
        raise ValueError(
            f"size_class_type must be one of {valid_size_class_types}, got {size_class_type!r}"
        )

    config = {
        "grp_by": grp_by,
        "by_species": by_species,
        "by_size_class": by_size_class,
        "size_class_type": size_class_type,
        "land_type": land_type,
        "tree_type": tree_type,
        "measure": measure,
        "tree_domain": tree_domain,
        "area_domain": area_domain,
        "as_rate": as_rate,
        "totals": totals,
        "variance": variance,
        "most_recent": most_recent,
        "include_cv": False,
    }

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

Measurement Types

Measure Description
"volume" Net cubic-foot volume mortality
"sawlog" Board-foot sawlog mortality
"biomass" Above-ground biomass mortality
"tpa" Trees per acre mortality
"count" Tree count mortality
"basal_area" Basal area mortality

Technical Notes

Mortality estimation uses:

  • TREE_GRM_COMPONENT table for mortality attributes
  • TREE_GRM_MIDPT table for annualized values
  • Trees with TPAMORT_UNADJ > 0 are mortality trees
  • Annual rates calculated using measurement interval

Examples

Total Mortality Volume

result = pyfia.mortality(
    db,
    measure="volume",
    land_type="forest"
)
print(f"Annual Mortality: {result['estimate'][0]:,.0f} cu ft/year")

Mortality by Agent (Tree-Level)

result = pyfia.mortality(
    db,
    measure="volume",
    grp_by="AGENTCD"
)
# AGENTCD: 10=Insect, 20=Disease, 30=Fire, 50=Weather, etc.
print(result)

Mortality by Disturbance (Condition-Level)

result = pyfia.mortality(
    db,
    measure="volume",
    grp_by="DSTRBCD1"
)
# DSTRBCD1: 30=Fire, 52=Hurricane/wind, 54=Drought, etc.
print(result)

Mortality by Species

result = pyfia.mortality(
    db,
    measure="volume",
    grp_by="SPCD"
)
result = pyfia.join_species_names(result, db)
print(result.sort("estimate", descending=True).head(10))

Growing Stock Mortality on Timberland

result = pyfia.mortality(
    db,
    measure="volume",
    land_type="timber",
    tree_type="gs"
)

Biomass Mortality

result = pyfia.mortality(
    db,
    measure="biomass",
    land_type="forest"
)
print(f"Annual Biomass Mortality: {result['estimate'][0]:,.0f} tons/year")

Mortality by Size Class

Group mortality by diameter size classes:

# Standard FIA size classes (1.0-4.9, 5.0-9.9, 10.0-19.9, etc.)
result = pyfia.mortality(db, by_size_class=True)
print(result)

# Descriptive labels (Saplings, Small, Medium, Large)
result = pyfia.mortality(db, by_size_class=True, size_class_type="descriptive")
print(result)

# Timber market classes (Pulpwood, Chip-n-Saw, Sawtimber)
# Based on TimberMart-South categories
result = pyfia.mortality(db, by_size_class=True, size_class_type="market")
print(result)

Size Class Types

Type Description Categories
"standard" FIA numeric ranges 1.0-4.9, 5.0-9.9, 10.0-19.9, 20.0-29.9, 30.0+
"descriptive" Text labels Saplings, Small, Medium, Large
"market" Timber market categories Pre-merchantable, Pulpwood, Chip-n-Saw (pine only), Sawtimber

Market Size Classes

Market size classes use species-aware thresholds based on TimberMart-South:

  • Pre-merchantable (all species): < 5.0" DBH
  • Pine/Softwood (SPCD < 300): Pulpwood (5-8.9"), Chip-n-Saw (9-11.9"), Sawtimber (12"+)
  • Hardwood (SPCD >= 300): Pulpwood (5-10.9"), Sawtimber (11"+)

Pre-merchantable Tree Mortality

By default, mortality() only includes growing stock trees (≥5" DBH). To analyze mortality of smaller trees, use tree_type="live":

# Include pre-merchantable trees (1.0" to 4.9" DBH)
result = pyfia.mortality(
    db,
    tree_type="live",  # Include all live trees, not just growing stock
    by_size_class=True,
    size_class_type="market",
    measure="tpa"  # TPA recommended for small trees
)

# Filter to pre-merchantable only
import polars as pl
premerch = result.filter(pl.col("SIZE_CLASS") == "Pre-merchantable")

Volume not available for small trees

FIA does not calculate volume for trees < 5" DBH. Use measure="tpa" or measure="count" when analyzing pre-merchantable mortality.