Skip to content

Volume Estimation

Estimate standing tree volume with various volume types and filters.

Overview

The volume() function calculates tree volume estimates following EVALIDator methodology.

import pyfia

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

# Total net volume
total = pyfia.volume(db, land_type="forest")

# Volume by species
by_species = pyfia.volume(db, grp_by="SPCD")

Function Reference

volume

volume(db: str | FIA, grp_by: str | list[str] | None = None, by_species: bool = False, by_size_class: bool = False, land_type: str = 'forest', tree_type: str = 'live', vol_type: str = 'net', 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, eval_type: str | None = None) -> DataFrame

Estimate tree volume from FIA data.

Calculates volume estimates using FIA's design-based estimation methods with proper expansion factors and stratification. Automatically handles EVALID selection to prevent overcounting from multiple evaluations.

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

grp_by

Column name(s) to group results by. Can be any column from the TREE, COND, and PLOT tables. Common grouping columns include:

Tree Characteristics: - 'SPCD': Species code (see REF_SPECIES table) - 'SPGRPCD': Species group code (hardwood/softwood groups) - 'DIA': Diameter at breast height (continuous, use with caution) - 'HT': Total tree height in feet - 'CR': Crown ratio (percent of bole with live crown) - 'CCLCD': Crown class code (1=Open grown, 2=Dominant, 3=Codominant, 4=Intermediate, 5=Overtopped) - 'TREECLCD': Tree class code (2=Growing stock, 3=Rough cull, 4=Rotten cull) - 'DECAYCD': Decay class for standing dead trees

Ownership and Management: - 'OWNGRPCD': Ownership group (10=National Forest, 20=Other Federal, 30=State/Local, 40=Private) - 'OWNCD': Detailed ownership code (see REF_RESEARCH_STATION) - 'ADFORCD': Administrative forest code - 'RESERVCD': Reserved status (0=Not reserved, 1=Reserved)

Forest Characteristics: - 'FORTYPCD': Forest type code (see REF_FOREST_TYPE) - 'STDSZCD': Stand size class (1=Large diameter, 2=Medium diameter, 3=Small diameter, 4=Seedling/sapling, 5=Nonstocked) - 'STDORGCD': Stand origin (0=Natural, 1=Planted) - 'STDAGE': Stand age in years

Site Characteristics: - 'SITECLCD': Site productivity class (1=225+ cu ft/ac/yr, 2=165-224, 3=120-164, 4=85-119, 5=50-84, 6=20-49, 7=0-19) - 'PHYSCLCD': Physiographic class code - 'SLOPE': Slope in percent - 'ASPECT': Aspect in degrees (0-360)

Location: - 'STATECD': State FIPS code - 'UNITCD': FIA survey unit code - 'COUNTYCD': County code - 'INVYR': Inventory year

Disturbance and Treatment: - 'DSTRBCD1', 'DSTRBCD2', 'DSTRBCD3': Disturbance codes - 'TRTCD1', 'TRTCD2', 'TRTCD3': Treatment codes

For complete column descriptions, see USDA FIA Database User Guide.

TYPE: str or list of str DEFAULT: None

by_species

If True, group results by species code (SPCD). This is a convenience parameter equivalent to adding 'SPCD' to grp_by.

TYPE: bool DEFAULT: False

by_size_class

If True, group results by diameter size classes. Size classes are defined as: 1.0-4.9", 5.0-9.9", 10.0-19.9", 20.0-29.9", 30.0+".

TYPE: bool DEFAULT: False

land_type

Land type to include in estimation:

  • 'forest': All forestland (COND_STATUS_CD = 1)
  • 'timber': Timberland only (unreserved, productive forestland with SITECLCD < 7 and RESERVCD = 0)
  • 'all': All land types including non-forest

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

tree_type

Tree type to include:

  • 'live': All live trees (STATUSCD = 1)
  • 'dead': Standing dead trees (STATUSCD = 2)
  • 'gs': Growing stock trees (live, TREECLCD = 2, no defects)
  • 'all': All trees regardless of status

TYPE: ('live', 'dead', 'gs', 'all') DEFAULT: 'live'

vol_type

Volume type to estimate:

  • 'net': Net cubic foot volume (VOLCFNET) - gross minus defects
  • 'gross': Gross cubic foot volume (VOLCFGRS) - total stem volume
  • 'sound': Sound cubic foot volume (VOLCFSND) - gross minus rot
  • 'sawlog': Sawlog board foot volume (VOLBFNET) - net board feet

TYPE: ('net', 'gross', 'sound', 'sawlog') DEFAULT: 'net'

tree_domain

SQL-like filter expression for tree-level attributes. Examples:

  • "DIA >= 10.0": Trees 10 inches DBH and larger
  • "SPCD IN (131, 110)": Specific species (loblolly and Virginia pine)
  • "DIA BETWEEN 10 AND 20": Mid-sized trees
  • "HT > 50 AND CR > 30": Tall trees with good crowns

TYPE: str DEFAULT: None

area_domain

SQL-like filter expression for COND-level attributes. Examples:

  • "STDAGE > 50": Stands older than 50 years
  • "FORTYPCD IN (161, 162)": Specific forest types
  • "OWNGRPCD == 40": Private lands only
  • "SLOPE < 30 AND ASPECT BETWEEN 135 AND 225": Gentle south-facing slopes

TYPE: str DEFAULT: None

totals

If True, include total volume estimates expanded to population level. If False, only return per-acre values.

TYPE: bool DEFAULT: True

variance

If True, return variance instead of standard error.

TYPE: bool DEFAULT: False

most_recent

If True, automatically select the most recent evaluation for each state/region. Equivalent to calling db.clip_most_recent() first.

TYPE: bool DEFAULT: False

eval_type

Evaluation type to select if most_recent=True. Options: 'ALL', 'VOL', 'GROW', 'MORT', 'REMV', 'CHANGE', 'DWM', 'INV'. Default is 'VOL' for volume estimation.

TYPE: str DEFAULT: None

RETURNS DESCRIPTION
DataFrame

Volume estimates with the following columns:

  • YEAR : int Inventory year
  • [grouping columns] : varies Any columns specified in grp_by parameter
  • VOLCFNET_ACRE : float (if vol_type='net') Net cubic foot volume per acre
  • VOLCFGRS_ACRE : float (if vol_type='gross') Gross cubic foot volume per acre
  • VOLCFSND_ACRE : float (if vol_type='sound') Sound cubic foot volume per acre
  • VOLBFNET_ACRE : float (if vol_type='sawlog') Net board foot volume per acre
  • VOLCFNET_ACRE_SE : float (if variance=False) Standard error of per-acre volume estimate
  • VOLCFNET_ACRE_VAR : float (if variance=True) Variance of per-acre volume estimate
  • N_PLOTS : int Number of plots in estimate
  • N_TREES : int Number of trees in estimate
  • AREA_TOTAL : float Total area (acres) represented by the estimation
  • VOLCFNET_TOTAL : float (if totals=True) Total volume expanded to population level
  • VOLCFNET_TOTAL_SE : float (if totals=True and variance=False) Standard error of total volume
See Also

pyfia.area : Estimate forest area pyfia.biomass : Estimate tree biomass pyfia.tpa : Estimate trees per acre pyfia.mortality : Estimate annual mortality pyfia.growth : Estimate annual growth pyfia.constants.SpeciesCodes : Species code definitions pyfia.constants.ForestTypes : Forest type code definitions pyfia.constants.StateCodes : State FIPS code definitions pyfia.utils.reference_tables : Functions for adding species/forest type names

Notes

The volume estimation follows USDA FIA's design-based estimation procedures as described in Bechtold & Patterson (2005). The basic formula is:

Volume per acre = Σ(VOLCFNET × TPA_UNADJ × ADJ_FACTOR × EXPNS) / Σ(AREA)

Where: - VOLCFNET: Net cubic foot volume per tree (or VOLCFGRS, VOLCFSND, VOLBFNET) - TPA_UNADJ: Unadjusted trees per acre factor - ADJ_FACTOR: Size-based adjustment factor (MICR, SUBP, or MACR) - EXPNS: Expansion factor from stratification - AREA: Total area from condition proportions

Adjustment Factors: Trees are adjusted based on their diameter and sampling method: - Trees < 5.0" DBH: Microplot adjustment (ADJ_FACTOR_MICR) - Trees 5.0" to MACRO_BREAKPOINT_DIA: Subplot adjustment (ADJ_FACTOR_SUBP) - Trees ≥ MACRO_BREAKPOINT_DIA: Macroplot adjustment (ADJ_FACTOR_MACR)

EVALID Handling: If no EVALID is specified, the function automatically selects the most recent EXPVOL evaluation to prevent overcounting from multiple evaluations. For explicit control, use db.clip_by_evalid() before calling volume().

Valid Grouping Columns: The function joins TREE, COND, and PLOT tables, so any column from these tables can be used for grouping. Not all columns are suitable - continuous variables like DIA should be used with caution or binned first.

NULL Value Handling: Some grouping columns may contain NULL values (e.g., HT ~30% NULL for some species). NULL values are handled safely by Polars and will appear as a separate group in results if present.

Growing Stock Definition: Growing stock trees (tree_type='gs') are defined as live trees of commercial species that meet minimum merchantability standards: - Must be live (STATUSCD = 1) - Must be growing stock (TREECLCD = 2) - Excludes rough and rotten culls - Typically ≥ 5.0" DBH for hardwoods and softwoods

Board Foot Conversion: Sawlog volume (vol_type='sawlog') uses board foot measurements which apply only to sawtimber-sized trees: - Softwoods: ≥ 9.0" DBH - Hardwoods: ≥ 11.0" DBH

Trees below these thresholds will have NULL or 0 board foot volume.

Examples:

Basic net volume on forestland:

>>> from pyfia import FIA, volume
>>> with FIA("path/to/fia.duckdb") as db:
...     db.clip_by_state(37)  # North Carolina
...     results = volume(db, land_type="forest", vol_type="net")

Volume by species on timberland:

>>> results = volume(
...     db,
...     by_species=True,
...     land_type="timber",
...     tree_type="gs"  # Growing stock only
... )
>>> # Find top species by volume
>>> if not results.is_empty():
...     top_species = results.sort(by='VOLCFNET_ACRE', descending=True).head(5)

Large tree volume by ownership:

>>> results = volume(
...     db,
...     grp_by="OWNGRPCD",
...     tree_domain="DIA >= 20.0",
...     variance=True
... )

Sawlog volume by forest type:

>>> results = volume(
...     db,
...     grp_by="FORTYPCD",
...     vol_type="sawlog",
...     tree_type="gs",
...     tree_domain="DIA >= 11.0"  # Hardwood sawtimber size
... )

Volume by multiple grouping variables:

>>> results = volume(
...     db,
...     grp_by=["STATECD", "OWNGRPCD", "STDSZCD"],
...     land_type="forest",
...     totals=True
... )

Complex filtering with domain expressions:

>>> # High-value timber on productive sites
>>> results = volume(
...     db,
...     grp_by="SPCD",
...     land_type="timber",
...     tree_domain="DIA >= 16.0 AND TREECLCD == 2",
...     area_domain="SITECLCD <= 3 AND SLOPE < 35"
... )

Dead tree volume assessment:

>>> results = volume(
...     db,
...     tree_type="dead",
...     by_species=True,
...     tree_domain="DIA >= 10.0 AND DECAYCD IN (1, 2)"  # Sound dead trees
... )
Notes

Variance calculations follow Bechtold & Patterson (2005) stratified ratio-of-means methodology. A ValueError is raised if required tree-level data is unavailable for variance calculation.

RAISES DESCRIPTION
ValueError

If invalid parameter values are provided, if required tables (TREE, COND, PLOT) are not found in the database, or if variance is requested but tree-level data is unavailable.

KeyError

If specified columns in grp_by don't exist in the joined tables.

Source code in src/pyfia/estimation/estimators/volume.py
def volume(
    db: str | FIA,
    grp_by: str | list[str] | None = None,
    by_species: bool = False,
    by_size_class: bool = False,
    land_type: str = "forest",
    tree_type: str = "live",
    vol_type: str = "net",
    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,
    eval_type: str | None = None,
) -> pl.DataFrame:
    """
    Estimate tree volume from FIA data.

    Calculates volume estimates using FIA's design-based estimation methods
    with proper expansion factors and stratification. Automatically handles
    EVALID selection to prevent overcounting from multiple evaluations.

    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.
    grp_by : str or list of str, optional
        Column name(s) to group results by. Can be any column from the
        TREE, COND, and PLOT tables. Common grouping columns include:

        **Tree Characteristics:**
        - 'SPCD': Species code (see REF_SPECIES table)
        - 'SPGRPCD': Species group code (hardwood/softwood groups)
        - 'DIA': Diameter at breast height (continuous, use with caution)
        - 'HT': Total tree height in feet
        - 'CR': Crown ratio (percent of bole with live crown)
        - 'CCLCD': Crown class code (1=Open grown, 2=Dominant, 3=Codominant,
          4=Intermediate, 5=Overtopped)
        - 'TREECLCD': Tree class code (2=Growing stock, 3=Rough cull, 4=Rotten cull)
        - 'DECAYCD': Decay class for standing dead trees

        **Ownership and Management:**
        - 'OWNGRPCD': Ownership group (10=National Forest, 20=Other Federal,
          30=State/Local, 40=Private)
        - 'OWNCD': Detailed ownership code (see REF_RESEARCH_STATION)
        - 'ADFORCD': Administrative forest code
        - 'RESERVCD': Reserved status (0=Not reserved, 1=Reserved)

        **Forest Characteristics:**
        - 'FORTYPCD': Forest type code (see REF_FOREST_TYPE)
        - 'STDSZCD': Stand size class (1=Large diameter, 2=Medium diameter,
          3=Small diameter, 4=Seedling/sapling, 5=Nonstocked)
        - 'STDORGCD': Stand origin (0=Natural, 1=Planted)
        - 'STDAGE': Stand age in years

        **Site Characteristics:**
        - 'SITECLCD': Site productivity class (1=225+ cu ft/ac/yr,
          2=165-224, 3=120-164, 4=85-119, 5=50-84, 6=20-49, 7=0-19)
        - 'PHYSCLCD': Physiographic class code
        - 'SLOPE': Slope in percent
        - 'ASPECT': Aspect in degrees (0-360)

        **Location:**
        - 'STATECD': State FIPS code
        - 'UNITCD': FIA survey unit code
        - 'COUNTYCD': County code
        - 'INVYR': Inventory year

        **Disturbance and Treatment:**
        - 'DSTRBCD1', 'DSTRBCD2', 'DSTRBCD3': Disturbance codes
        - 'TRTCD1', 'TRTCD2', 'TRTCD3': Treatment codes

        For complete column descriptions, see USDA FIA Database User Guide.
    by_species : bool, default False
        If True, group results by species code (SPCD). This is a convenience
        parameter equivalent to adding 'SPCD' to grp_by.
    by_size_class : bool, default False
        If True, group results by diameter size classes. Size classes are
        defined as: 1.0-4.9", 5.0-9.9", 10.0-19.9", 20.0-29.9", 30.0+".
    land_type : {'forest', 'timber', 'all'}, default 'forest'
        Land type to include in estimation:

        - 'forest': All forestland (COND_STATUS_CD = 1)
        - 'timber': Timberland only (unreserved, productive forestland with
          SITECLCD < 7 and RESERVCD = 0)
        - 'all': All land types including non-forest
    tree_type : {'live', 'dead', 'gs', 'all'}, default 'live'
        Tree type to include:

        - 'live': All live trees (STATUSCD = 1)
        - 'dead': Standing dead trees (STATUSCD = 2)
        - 'gs': Growing stock trees (live, TREECLCD = 2, no defects)
        - 'all': All trees regardless of status
    vol_type : {'net', 'gross', 'sound', 'sawlog'}, default 'net'
        Volume type to estimate:

        - 'net': Net cubic foot volume (VOLCFNET) - gross minus defects
        - 'gross': Gross cubic foot volume (VOLCFGRS) - total stem volume
        - 'sound': Sound cubic foot volume (VOLCFSND) - gross minus rot
        - 'sawlog': Sawlog board foot volume (VOLBFNET) - net board feet
    tree_domain : str, optional
        SQL-like filter expression for tree-level attributes. Examples:

        - "DIA >= 10.0": Trees 10 inches DBH and larger
        - "SPCD IN (131, 110)": Specific species (loblolly and Virginia pine)
        - "DIA BETWEEN 10 AND 20": Mid-sized trees
        - "HT > 50 AND CR > 30": Tall trees with good crowns
    area_domain : str, optional
        SQL-like filter expression for COND-level attributes. Examples:

        - "STDAGE > 50": Stands older than 50 years
        - "FORTYPCD IN (161, 162)": Specific forest types
        - "OWNGRPCD == 40": Private lands only
        - "SLOPE < 30 AND ASPECT BETWEEN 135 AND 225": Gentle south-facing slopes
    totals : bool, default True
        If True, include total volume estimates expanded to population level.
        If False, only return per-acre values.
    variance : bool, default False
        If True, return variance instead of standard error.
    most_recent : bool, default False
        If True, automatically select the most recent evaluation for each
        state/region. Equivalent to calling db.clip_most_recent() first.
    eval_type : str, optional
        Evaluation type to select if most_recent=True. Options:
        'ALL', 'VOL', 'GROW', 'MORT', 'REMV', 'CHANGE', 'DWM', 'INV'.
        Default is 'VOL' for volume estimation.

    Returns
    -------
    pl.DataFrame
        Volume estimates with the following columns:

        - **YEAR** : int
            Inventory year
        - **[grouping columns]** : varies
            Any columns specified in grp_by parameter
        - **VOLCFNET_ACRE** : float (if vol_type='net')
            Net cubic foot volume per acre
        - **VOLCFGRS_ACRE** : float (if vol_type='gross')
            Gross cubic foot volume per acre
        - **VOLCFSND_ACRE** : float (if vol_type='sound')
            Sound cubic foot volume per acre
        - **VOLBFNET_ACRE** : float (if vol_type='sawlog')
            Net board foot volume per acre
        - **VOLCFNET_ACRE_SE** : float (if variance=False)
            Standard error of per-acre volume estimate
        - **VOLCFNET_ACRE_VAR** : float (if variance=True)
            Variance of per-acre volume estimate
        - **N_PLOTS** : int
            Number of plots in estimate
        - **N_TREES** : int
            Number of trees in estimate
        - **AREA_TOTAL** : float
            Total area (acres) represented by the estimation
        - **VOLCFNET_TOTAL** : float (if totals=True)
            Total volume expanded to population level
        - **VOLCFNET_TOTAL_SE** : float (if totals=True and variance=False)
            Standard error of total volume

    See Also
    --------
    pyfia.area : Estimate forest area
    pyfia.biomass : Estimate tree biomass
    pyfia.tpa : Estimate trees per acre
    pyfia.mortality : Estimate annual mortality
    pyfia.growth : Estimate annual growth
    pyfia.constants.SpeciesCodes : Species code definitions
    pyfia.constants.ForestTypes : Forest type code definitions
    pyfia.constants.StateCodes : State FIPS code definitions
    pyfia.utils.reference_tables : Functions for adding species/forest type names

    Notes
    -----
    The volume estimation follows USDA FIA's design-based estimation procedures
    as described in Bechtold & Patterson (2005). The basic formula is:

    Volume per acre = Σ(VOLCFNET × TPA_UNADJ × ADJ_FACTOR × EXPNS) / Σ(AREA)

    Where:
    - VOLCFNET: Net cubic foot volume per tree (or VOLCFGRS, VOLCFSND, VOLBFNET)
    - TPA_UNADJ: Unadjusted trees per acre factor
    - ADJ_FACTOR: Size-based adjustment factor (MICR, SUBP, or MACR)
    - EXPNS: Expansion factor from stratification
    - AREA: Total area from condition proportions

    **Adjustment Factors:**
    Trees are adjusted based on their diameter and sampling method:
    - Trees < 5.0" DBH: Microplot adjustment (ADJ_FACTOR_MICR)
    - Trees 5.0" to MACRO_BREAKPOINT_DIA: Subplot adjustment (ADJ_FACTOR_SUBP)
    - Trees ≥ MACRO_BREAKPOINT_DIA: Macroplot adjustment (ADJ_FACTOR_MACR)

    **EVALID Handling:**
    If no EVALID is specified, the function automatically selects the most
    recent EXPVOL evaluation to prevent overcounting from multiple evaluations.
    For explicit control, use db.clip_by_evalid() before calling volume().

    **Valid Grouping Columns:**
    The function joins TREE, COND, and PLOT tables, so any column from these
    tables can be used for grouping. Not all columns are suitable - continuous
    variables like DIA should be used with caution or binned first.

    **NULL Value Handling:**
    Some grouping columns may contain NULL values (e.g., HT ~30% NULL for
    some species). NULL values are handled safely by Polars and will appear
    as a separate group in results if present.

    **Growing Stock Definition:**
    Growing stock trees (tree_type='gs') are defined as live trees of
    commercial species that meet minimum merchantability standards:
    - Must be live (STATUSCD = 1)
    - Must be growing stock (TREECLCD = 2)
    - Excludes rough and rotten culls
    - Typically ≥ 5.0" DBH for hardwoods and softwoods

    **Board Foot Conversion:**
    Sawlog volume (vol_type='sawlog') uses board foot measurements which
    apply only to sawtimber-sized trees:
    - Softwoods: ≥ 9.0" DBH
    - Hardwoods: ≥ 11.0" DBH

    Trees below these thresholds will have NULL or 0 board foot volume.

    Examples
    --------
    Basic net volume on forestland:

    >>> from pyfia import FIA, volume
    >>> with FIA("path/to/fia.duckdb") as db:
    ...     db.clip_by_state(37)  # North Carolina
    ...     results = volume(db, land_type="forest", vol_type="net")

    Volume by species on timberland:

    >>> results = volume(
    ...     db,
    ...     by_species=True,
    ...     land_type="timber",
    ...     tree_type="gs"  # Growing stock only
    ... )
    >>> # Find top species by volume
    >>> if not results.is_empty():
    ...     top_species = results.sort(by='VOLCFNET_ACRE', descending=True).head(5)

    Large tree volume by ownership:

    >>> results = volume(
    ...     db,
    ...     grp_by="OWNGRPCD",
    ...     tree_domain="DIA >= 20.0",
    ...     variance=True
    ... )

    Sawlog volume by forest type:

    >>> results = volume(
    ...     db,
    ...     grp_by="FORTYPCD",
    ...     vol_type="sawlog",
    ...     tree_type="gs",
    ...     tree_domain="DIA >= 11.0"  # Hardwood sawtimber size
    ... )

    Volume by multiple grouping variables:

    >>> results = volume(
    ...     db,
    ...     grp_by=["STATECD", "OWNGRPCD", "STDSZCD"],
    ...     land_type="forest",
    ...     totals=True
    ... )

    Complex filtering with domain expressions:

    >>> # High-value timber on productive sites
    >>> results = volume(
    ...     db,
    ...     grp_by="SPCD",
    ...     land_type="timber",
    ...     tree_domain="DIA >= 16.0 AND TREECLCD == 2",
    ...     area_domain="SITECLCD <= 3 AND SLOPE < 35"
    ... )

    Dead tree volume assessment:

    >>> results = volume(
    ...     db,
    ...     tree_type="dead",
    ...     by_species=True,
    ...     tree_domain="DIA >= 10.0 AND DECAYCD IN (1, 2)"  # Sound dead trees
    ... )

    Notes
    -----
    Variance calculations follow Bechtold & Patterson (2005) stratified
    ratio-of-means methodology. A ValueError is raised if required tree-level
    data is unavailable for variance calculation.

    Raises
    ------
    ValueError
        If invalid parameter values are provided, if required tables
        (TREE, COND, PLOT) are not found in the database, or if variance
        is requested but tree-level data is unavailable.
    KeyError
        If specified columns in grp_by don't exist in the joined tables.
    """
    # Validate common inputs using shared utility
    inputs = validate_estimator_inputs(
        land_type=land_type,
        grp_by=grp_by,
        area_domain=area_domain,
        plot_domain=plot_domain,
        tree_domain=tree_domain,
        totals=totals,
        variance=variance,
        most_recent=most_recent,
    )

    # Validate volume-specific parameters
    tree_type = validate_tree_type(tree_type)
    vol_type = validate_vol_type(vol_type)
    by_species = validate_boolean(by_species, "by_species")
    by_size_class = validate_boolean(by_size_class, "by_size_class")

    # Ensure db is a FIA instance using shared utility
    db, owns_db = ensure_fia_instance(db)

    # Handle EVALID selection for volume estimation
    if inputs.most_recent:
        # User explicitly requested most_recent
        if db.evalid is None:
            db.clip_most_recent(eval_type=eval_type or "VOL")
    else:
        # Auto-select if no EVALID set using shared utility
        # Use "VOL" for volume estimation (EXPVOL evaluations)
        ensure_evalid_set(db, eval_type="VOL", estimator_name="volume")

    # Create config using validated inputs
    config = {
        "grp_by": inputs.grp_by,
        "by_species": by_species,
        "by_size_class": by_size_class,
        "land_type": inputs.land_type,
        "tree_type": tree_type,
        "vol_type": vol_type,
        "tree_domain": inputs.tree_domain,
        "area_domain": inputs.area_domain,
        "plot_domain": inputs.plot_domain,
        "totals": inputs.totals,
        "variance": inputs.variance,
        "most_recent": inputs.most_recent,
    }

    try:
        # Create and run estimator
        estimator = VolumeEstimator(db, config)
        return estimator.estimate()
    finally:
        # Clean up if we created the db
        if owns_db and hasattr(db, "close"):
            db.close()

Volume Types

Type Column Description
"net" VOLCFNET Net cubic-foot volume
"gross" VOLCFGRS Gross cubic-foot volume
"sawlog" VOLBFNET Board-foot sawlog volume
"sound" VOLCSNET Sound wood volume

Examples

Net Volume by Species

result = pyfia.volume(db, vol_type="net", grp_by="SPCD")
result = pyfia.join_species_names(result, db)
print(result.sort("VOLUME_ACRE", descending=True).head(10))

Sawlog Volume on Timberland

result = pyfia.volume(
    db,
    vol_type="sawlog",
    land_type="timber",
    tree_type="sl",  # Sawtimber only
    totals=True
)
print(f"Sawlog Volume: {result['VOLUME_TOTAL'][0]:,.0f} board feet")

Volume by Diameter Class

# Create diameter classes
result = pyfia.volume(
    db,
    grp_by="DIA_CLASS",
    tree_domain="DIA >= 5.0"
)

Pine Volume Only

result = pyfia.volume(
    db,
    land_type="forest",
    tree_domain="SPGRPCD = 10"  # Softwood group
)