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: Union[str, FIA], grp_by: Optional[Union[str, List[str]]] = None, by_species: bool = False, by_size_class: bool = False, land_type: str = 'forest', tree_type: str = 'live', vol_type: str = 'net', tree_domain: Optional[str] = None, area_domain: Optional[str] = None, plot_domain: Optional[str] = None, totals: bool = True, variance: bool = False, most_recent: bool = False, eval_type: Optional[str] = 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: Union[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
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
def volume(
    db: Union[str, FIA],
    grp_by: Optional[Union[str, List[str]]] = None,
    by_species: bool = False,
    by_size_class: bool = False,
    land_type: str = "forest",
    tree_type: str = "live",
    vol_type: str = "net",
    tree_domain: Optional[str] = None,
    area_domain: Optional[str] = None,
    plot_domain: Optional[str] = None,
    totals: bool = True,
    variance: bool = False,
    most_recent: bool = False,
    eval_type: Optional[str] = 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 : Union[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.
    """
    # Import validation functions
    from ...validation import (
        validate_boolean,
        validate_domain_expression,
        validate_grp_by,
        validate_land_type,
        validate_tree_type,
        validate_vol_type,
    )

    # Validate inputs
    land_type = validate_land_type(land_type)
    tree_type = validate_tree_type(tree_type)
    vol_type = validate_vol_type(vol_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")

    # Ensure db is a FIA instance
    if isinstance(db, str):
        db = FIA(db)
        owns_db = True
    else:
        owns_db = False

    # Handle EVALID selection for volume estimation
    if db.evalid is None and most_recent:
        db.clip_most_recent(eval_type=eval_type or "VOL")
    elif db.evalid is None:
        # Auto-select most recent EXPVOL evaluation for volume
        import warnings

        warnings.warn(
            "No EVALID specified. Automatically selecting most recent EXPVOL evaluations. "
            "For explicit control, use db.clip_most_recent() or db.clip_by_evalid() before calling volume()."
        )
        db.clip_most_recent(eval_type="VOL")

        # If still no EVALID, warn about potential issues
        if db.evalid is None:
            warnings.warn(
                "WARNING: No EXPVOL evaluations found. Results may be incorrect due to "
                "inclusion of multiple overlapping evaluations. Consider using db.clip_by_evalid() "
                "to explicitly select appropriate EVALIDs."
            )

    # Create config
    config = {
        "grp_by": grp_by,
        "by_species": by_species,
        "by_size_class": by_size_class,
        "land_type": land_type,
        "tree_type": tree_type,
        "vol_type": vol_type,
        "tree_domain": tree_domain,
        "area_domain": area_domain,
        "plot_domain": plot_domain,
        "totals": totals,
        "variance": variance,
        "most_recent": 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("estimate", descending=True).head(10))

Sawlog Volume on Timberland

result = pyfia.volume(
    db,
    vol_type="sawlog",
    land_type="timber",
    tree_type="sl"  # Sawtimber only
)
print(f"Sawlog Volume: {result['estimate'][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
)