Skip to content

Standing Dead Carbon Estimation

Estimate standing dead tree carbon using the NSVB biomass framework with decay-class reductions and dead-tree carbon fractions.

Overview

The standing_dead() function recomputes above-ground standing dead tree biomass from scratch using the National Scale Volume and Biomass (NSVB) framework and applies the FIADB REF_TREE_DECAY_PROP density and structural-loss reductions by decay class. The reduced biomass is converted to carbon via S10b dead-tree carbon fractions (hardwood/softwood x DECAYCD). This produces carbon estimates that align with the EPA NGHGI LULUCF standing dead pool.

import pyfia

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

# Above-ground standing dead carbon
result = pyfia.standing_dead(db, pool="ag")

# Standing dead carbon by decay class
by_decay = pyfia.standing_dead(db, pool="ag", grp_by="DECAYCD")

Function Reference

standing_dead

standing_dead(db: str | FIA, pool: str = 'ag', grp_by: str | list[str] | None = None, by_species: bool = False, by_size_class: bool = False, land_type: str = 'forest', tree_domain: str | None = None, area_domain: str | None = None, plot_domain: str | None = None, totals: bool = True, variance: bool = False, most_recent: bool = False) -> DataFrame

Estimate standing dead tree carbon from FIA data using the NSVB framework.

Recomputes above-ground standing dead tree biomass from scratch using the National Scale Volume and Biomass (NSVB) framework of Westfall et al. (2023, GTR-WO-104) and applies the FIADB REF_TREE_DECAY_PROP decay reductions (DENSITY_PROP × wood, BARK_LOSS_PROP × bark, BRANCH_LOSS_PROP × branch) keyed by hardwood/softwood × DECAYCD. The reduced biomass is then converted to carbon via species-class S10b dead-tree carbon fractions from GTR-WO-104, replacing the flat ~0.47 multiplier and producing carbon estimates that align with the EPA NGHGI LULUCF standing dead pool.

Broken-top corrections apply the Appendix K crown-proportion adjustment to branch biomass and a volume-ratio adjustment to wood/bark for trees with ACTUALHT < HT, using the mean intact crown ratio from Table S11 (REF_TREE_STND_DEAD_CR_PROP) keyed by Bailey ecoregion province × hardwood/softwood.

Belowground carbon for standing dead trees is bridged directly to the FIADB pre-computed TREE.CARBON_BG column; a native NSVB coarse-root model for dead trees is deferred.

The standing-dead population is filtered as STATUSCD = 2 AND STANDING_DEAD_CD = 1 AND DECAYCD IS NOT NULL, which matches the trees FIADB itself populates CARBON_AG for. Trees with STANDING_DEAD_CD = 0 (downed dead) belong to the down dead wood pool and are excluded.

PARAMETER DESCRIPTION
db

Database connection or path to FIA database. Can be either a path string to a DuckDB/SQLite file or an existing FIA connection object.

TYPE: str | FIA

pool

Standing dead carbon pool to estimate:

  • 'ag': Above-ground standing dead carbon via the NSVB pipeline + REF_TREE_DECAY_PROP reductions + broken-top corrections + S10b dead carbon fractions.
  • 'bg': Below-ground standing dead carbon (coarse roots) via the bridge to FIADB TREE.CARBON_BG.
  • 'total': 'ag' + 'bg' (NSVB AG + FIADB BG bridge).

TYPE: ('ag', 'bg', 'total') DEFAULT: 'ag'

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

land_type

Land type to include in estimation.

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

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

plot_domain

SQL-like filter expression for plot-level filtering.

TYPE: str DEFAULT: None

totals

If True, include population-level total estimates.

TYPE: bool DEFAULT: True

variance

If True, calculate variance and standard error estimates.

TYPE: bool DEFAULT: False

most_recent

If True, auto-filter to the most recent EXPVOL evaluation.

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
DataFrame

Standing dead carbon estimates with columns: YEAR, POOL, CARBON_ACRE, CARBON_TOTAL (if totals), CARBON_ACRE_SE (if variance), CARBON_TOTAL_SE (if variance and totals), N_PLOTS, N_TREES, plus any grouping columns.

See Also

live_tree : Estimate live tree carbon via the NSVB framework.

Examples:

Above-ground standing dead carbon per acre on forestland:

>>> results = standing_dead(db, pool="ag")
>>> print(f"SD Carbon: {results['CARBON_ACRE'][0]:.1f} tons/acre")

Standing dead carbon by decay class on timberland:

>>> results = standing_dead(
...     db,
...     pool="ag",
...     grp_by="DECAYCD",
...     land_type="timber",
... )
Source code in src/pyfia/carbon/standing_dead.py
def standing_dead(
    db: str | FIA,
    pool: str = "ag",
    grp_by: str | list[str] | None = None,
    by_species: bool = False,
    by_size_class: bool = False,
    land_type: str = "forest",
    tree_domain: str | None = None,
    area_domain: str | None = None,
    plot_domain: str | None = None,
    totals: bool = True,
    variance: bool = False,
    most_recent: bool = False,
) -> pl.DataFrame:
    """
    Estimate standing dead tree carbon from FIA data using the NSVB framework.

    Recomputes above-ground standing dead tree biomass from scratch using
    the National Scale Volume and Biomass (NSVB) framework of Westfall et
    al. (2023, GTR-WO-104) and applies the FIADB ``REF_TREE_DECAY_PROP``
    decay reductions (DENSITY_PROP × wood, BARK_LOSS_PROP × bark,
    BRANCH_LOSS_PROP × branch) keyed by hardwood/softwood × DECAYCD. The
    reduced biomass is then converted to carbon via species-class S10b
    dead-tree carbon fractions from GTR-WO-104, replacing the flat ~0.47
    multiplier and producing carbon estimates that align with the EPA
    NGHGI LULUCF standing dead pool.

    Broken-top corrections apply the Appendix K crown-proportion
    adjustment to branch biomass and a volume-ratio adjustment to wood/bark
    for trees with ``ACTUALHT < HT``, using the mean intact crown ratio
    from Table S11 (``REF_TREE_STND_DEAD_CR_PROP``) keyed by Bailey
    ecoregion province × hardwood/softwood.

    Belowground carbon for standing dead trees is bridged directly to the
    FIADB pre-computed ``TREE.CARBON_BG`` column; a native NSVB coarse-root
    model for dead trees is deferred.

    The standing-dead population is filtered as
    ``STATUSCD = 2 AND STANDING_DEAD_CD = 1 AND DECAYCD IS NOT NULL``,
    which matches the trees FIADB itself populates ``CARBON_AG`` for.
    Trees with ``STANDING_DEAD_CD = 0`` (downed dead) belong to the down
    dead wood pool and are excluded.

    Parameters
    ----------
    db : str | FIA
        Database connection or path to FIA database. Can be either a path
        string to a DuckDB/SQLite file or an existing FIA connection object.
    pool : {'ag', 'bg', 'total'}, default 'ag'
        Standing dead carbon pool to estimate:

        - 'ag': Above-ground standing dead carbon via the NSVB pipeline +
          REF_TREE_DECAY_PROP reductions + broken-top corrections + S10b
          dead carbon fractions.
        - 'bg': Below-ground standing dead carbon (coarse roots) via the
          bridge to FIADB ``TREE.CARBON_BG``.
        - 'total': ``'ag' + 'bg'`` (NSVB AG + FIADB BG bridge).
    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.
    land_type : {'forest', 'timber', 'all'}, default 'forest'
        Land type to include in 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.
    plot_domain : str, optional
        SQL-like filter expression for plot-level filtering.
    totals : bool, default True
        If True, include population-level total estimates.
    variance : bool, default False
        If True, calculate variance and standard error estimates.
    most_recent : bool, default False
        If True, auto-filter to the most recent EXPVOL evaluation.

    Returns
    -------
    pl.DataFrame
        Standing dead carbon estimates with columns: YEAR, POOL,
        CARBON_ACRE, CARBON_TOTAL (if totals), CARBON_ACRE_SE (if
        variance), CARBON_TOTAL_SE (if variance and totals), N_PLOTS,
        N_TREES, plus any grouping columns.

    See Also
    --------
    live_tree : Estimate live tree carbon via the NSVB framework.

    Examples
    --------
    Above-ground standing dead carbon per acre on forestland:

    >>> results = standing_dead(db, pool="ag")
    >>> print(f"SD Carbon: {results['CARBON_ACRE'][0]:.1f} tons/acre")

    Standing dead carbon by decay class on timberland:

    >>> results = standing_dead(
    ...     db,
    ...     pool="ag",
    ...     grp_by="DECAYCD",
    ...     land_type="timber",
    ... )
    """
    from ..validation import (
        validate_boolean,
        validate_domain_expression,
        validate_grp_by,
        validate_land_type,
    )

    pool = pool.lower()
    valid_pools = {"ag", "bg", "total"}
    if pool not in valid_pools:
        raise ValueError(
            f"Invalid pool '{pool}'. Must be one of: {sorted(valid_pools)}"
        )

    land_type = validate_land_type(land_type)
    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")
    plot_domain = validate_domain_expression(plot_domain, "plot_domain")
    by_species = validate_boolean(by_species, "by_species")
    by_size_class = validate_boolean(by_size_class, "by_size_class")
    totals = validate_boolean(totals, "totals")
    variance = validate_boolean(variance, "variance")
    most_recent = validate_boolean(most_recent, "most_recent")

    db, owns_db = ensure_fia_instance(db)
    if most_recent and db.evalid is None:
        db.clip_most_recent(eval_type="VOL")
    else:
        ensure_evalid_set(db, eval_type="VOL", estimator_name="standing_dead")

    config = {
        "pool": pool,
        "grp_by": grp_by,
        "by_species": by_species,
        "by_size_class": by_size_class,
        "land_type": land_type,
        "tree_type": "dead",
        "tree_domain": tree_domain,
        "area_domain": area_domain,
        "plot_domain": plot_domain,
        "totals": totals,
        "variance": variance,
        "most_recent": most_recent,
    }

    try:
        estimator = StandingDeadEstimator(db, config)
        if pool == "total":
            # Best-effort cross-era warning; see live_tree.py for rationale.
            try:
                year = estimator._extract_evaluation_year()
                if int(year) < 2024:
                    logger.warning(
                        "standing_dead(pool='total'): selected EVALID year "
                        "(%d) pre-dates the NSVB framework transition "
                        "(September 2023). The BG bridge reads FIADB "
                        "TREE.CARBON_BG directly, which for pre-NSVB "
                        "inventories was computed via legacy CRM-based "
                        "allometry — combining it with NSVB-recomputed AG "
                        "may produce cross-era inconsistencies. Use "
                        "pool='ag' if you need NSVB-only consistency.",
                        int(year),
                    )
            except (ValueError, TypeError, AttributeError, IndexError, KeyError) as exc:
                logger.debug("Skipping standing_dead year warning: %s", exc)
        return estimator.estimate()
    finally:
        if owns_db and hasattr(db, "close"):
            db.close()

Carbon Pools

Pool Description Method
"ag" Above-ground (default) NSVB pipeline with REF_TREE_DECAY_PROP reductions + S10b dead carbon fractions
"bg" Below-ground (coarse roots) Bridge to FIADB TREE.CARBON_BG (same as live tree)
"total" AG + BG NSVB dead above-ground + FIADB below-ground bridge

Decay-Class Reductions

Standing dead trees lose mass through decomposition. The FIADB REF_TREE_DECAY_PROP table provides three multiplicative reduction factors applied to each gross NSVB component by hardwood/softwood classification and decay class (1-5):

Factor Applied to Description
DENSITY_PROP Stem wood Fraction of wood biomass remaining after density loss
BARK_LOSS_PROP Stem bark Fraction of bark biomass remaining
BRANCH_LOSS_PROP Branches Fraction of branch biomass remaining

Reduction Factors by Decay Class

Decay Class Description HW Density HW Bark HW Branch SW Density SW Bark SW Branch
1 All limbs present, intact 0.99 1.00 1.00 0.97 1.00 1.00
2 Few limbs, no fine branches 0.80 0.80 0.50 1.00 0.80 0.50
3 Limb stubs only, top broken 0.54 0.50 0.10 0.92 0.50 0.10
4 Few stubs, top broken 0.43 0.20 0.00 0.55 0.20 0.00
5 No limbs, <20% bark 0.43 0.00 0.00 0.55 0.00 0.00

Per FIADB User Guide v9.1 Appendix K, TREE.CULL is not applied to standing dead tree biomass. The decay reductions above are the only mass adjustments.

Dead Carbon Fractions (S10b)

Unlike live trees (which use per-species S10a fractions), dead tree carbon fractions come from S10b and vary only by hardwood/softwood and decay class:

Decay Class Hardwood Softwood
1 0.470 0.501
2 0.473 0.504
3 0.481 0.506
4 0.480 0.520
5 0.472 0.527

Population Filter

The standing-dead population is automatically filtered as:

  • STATUSCD = 2 (dead tree)
  • STANDING_DEAD_CD = 1 (standing, not downed)
  • DECAYCD IS NOT NULL (required for the decay-proportion lookup)
  • DIA >= 1.0 (NSVB floor)

Trees with STANDING_DEAD_CD = 0 (downed dead) belong to the down dead wood pool and are excluded.

Broken-Top Corrections

Approximately 75% of standing dead trees have broken tops (ACTUALHT < HT). The pipeline applies two adjustments for these trees per FIADB User Guide v9.1 Appendix K:

Adjustment Component Formula
Crown proportion Branch biomass Broken_crn_prop = max(0, (ACTUALHT - (1 - CRprop_HT) * HT) / (CRprop_HT * HT))
Volume ratio Wood & bark (ACTUALHT / HT) ^ (2/3) — paraboloid taper approximation

The mean intact crown ratio (CR_MEAN) is looked up from Table S11 (REF_TREE_STND_DEAD_CR_PROP) by Bailey ecoregion province and hardwood/softwood classification. When the province is unknown, the UNDEFINED fallback (softwood: 46.8%, hardwood: 38.0%) is used.

The volume-ratio adjustment uses a paraboloid taper exponent (2/3) rather than FIADB's Model 6 (Schumacher-Hall) volume-ratio model, which is not implemented. This approximation accounts for the fact that the wider lower stem contains a disproportionately large fraction of total stem volume.

Technical Notes

Examples

Standing Dead Carbon Per Acre

result = pyfia.standing_dead(db, pool="ag")
print(f"SD Carbon: {result['CARBON_ACRE'][0]:.2f} tons/acre")

Carbon by Decay Class

result = pyfia.standing_dead(
    db,
    pool="ag",
    grp_by="DECAYCD",
)
for row in result.iter_rows(named=True):
    print(f"Decay {row['DECAYCD']}: {row['CARBON_ACRE']:.3f} tons/acre")

Carbon by Species

result = pyfia.standing_dead(db, pool="ag", by_species=True)
result = pyfia.join_species_names(result, db)
print(result.sort("CARBON_ACRE", descending=True).head(10))

Large Snag Carbon by Ownership

result = pyfia.standing_dead(
    db,
    pool="ag",
    grp_by="OWNGRPCD",
    tree_domain="DIA >= 20.0",
    totals=True,
)
print(result)

Total Standing Dead Carbon with Variance

result = pyfia.standing_dead(
    db,
    pool="total",
    land_type="forest",
    variance=True,
    totals=True,
)
print(f"SD Carbon: {result['CARBON_TOTAL'][0]:,.0f} +/- "
      f"{result['CARBON_TOTAL_SE'][0]:,.0f} tons")