Skip to content

Trees Per Acre

Estimate tree density (TPA) and basal area (BAA).

Overview

The tpa() function calculates trees per acre and basal area estimates.

import pyfia

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

# Total TPA
result = pyfia.tpa(db, land_type="forest")

# TPA by size class
by_size = pyfia.tpa(db, by_size_class=True)

Function Reference

tpa

tpa(db: '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', tree_domain: str | None = None, area_domain: str | None = None, plot_domain: str | None = None, totals: bool = False, variance: bool = False) -> DataFrame

Estimate trees per acre (TPA) and basal area per acre (BAA) from FIA data.

Calculates tree density and basal area 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

FIA database connection object. If EVALID is not set, the function automatically selects the most recent EXPVOL evaluation with a warning.

TYPE: FIA

grp_by

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

Tree Attributes: - 'SPCD': Species code (see REF_SPECIES) - 'SPGRPCD': Species group code - 'DIA': Diameter at breast height (inches) - 'HT': Total tree height (feet) - 'CR': Compacted crown ratio (percent) - '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) - 'STATUSCD': Tree status (1=Live, 2=Dead, 3=Removed)

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) - 'STDAGE': Stand age in years - '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)

Ownership and Location: - 'OWNGRPCD': Ownership group (10=National Forest, 20=Other Federal, 30=State/Local, 40=Private) - '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 2-inch DBH classes: 0-1.9", 2-3.9", 4-5.9", etc.

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 in [1,2,3,4,5,6] and RESERVCD = 0)
  • 'all': All land types including non-forest

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

tree_type

Tree type to include in estimation:

  • 'live': All live trees (STATUSCD = 1)
  • 'dead': Standing dead trees (STATUSCD = 2)
  • 'gs': Growing stock trees (live trees meeting merchantability standards, typically TREECLCD = 2)
  • 'all': All trees regardless of status

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

tree_domain

SQL-like filter expression for tree-level attributes. Applied to the TREE table. Examples:

  • "DIA >= 10.0": Trees 10 inches DBH and larger
  • "SPCD IN (131, 110)": Specific species (loblolly and Virginia pine)
  • "HT > 50 AND CR > 30": Tall trees with good crowns
  • "TREECLCD == 2": Growing stock trees only

TYPE: str DEFAULT: None

area_domain

SQL-like filter expression for area/condition-level attributes. Applied to the COND table. Examples:

  • "STDAGE > 50": Stands older than 50 years
  • "FORTYPCD IN (161, 162)": Specific forest types
  • "OWNGRPCD == 40": Private lands only
  • "PHYSCLCD == 31 AND STDSZCD == 1": Xeric sites with large trees

TYPE: str DEFAULT: None

totals

If True, include population-level total estimates (TPA_TOTAL, BAA_TOTAL) in addition to per-acre values. Total estimates are expanded using stratification factors.

TYPE: bool DEFAULT: False

variance

If True, return variance instead of standard error. Standard error is calculated as the square root of variance.

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
DataFrame

Trees per acre and basal area estimates with the following columns:

  • YEAR : int Representative inventory year
  • [grouping columns] : varies Any columns specified in grp_by parameter
  • TPA : float Trees per acre
  • BAA : float Basal area per acre (square feet)
  • TPA_SE : float (if variance=False) Standard error of TPA estimate
  • BAA_SE : float (if variance=False) Standard error of BAA estimate
  • TPA_VAR : float (if variance=True) Variance of TPA estimate
  • BAA_VAR : float (if variance=True) Variance of BAA estimate
  • TPA_TOTAL : float (if totals=True) Total trees expanded to population level
  • BAA_TOTAL : float (if totals=True) Total basal area expanded to population level
  • TPA_TOTAL_SE : float (if totals=True and variance=False) Standard error of total TPA
  • BAA_TOTAL_SE : float (if totals=True and variance=False) Standard error of total BAA
  • N_PLOTS : int Number of FIA plots in estimate
  • N_TREES : int Number of individual tree records
See Also

pyfia.volume : Estimate tree volume per acre pyfia.biomass : Estimate tree biomass per acre pyfia.area : Estimate forest area

External References

FIA EVALIDator : USDA Forest Service online tool for validation https://apps.fs.usda.gov/Evalidator/evalidator.jsp rFIA : R package for FIA analysis (independent validation) https://cran.r-project.org/package=rFIA Bechtold & Patterson (2005) : The enhanced FIA national program https://doi.org/10.2737/SRS-GTR-80 pyfia.mortality : Estimate annual tree mortality pyfia.growth : Estimate annual tree growth pyfia.constants.SpeciesCodes : Species code definitions pyfia.constants.ForestTypes : Forest type code definitions pyfia.utils.reference_tables : Functions for adding species/forest type names

Notes

Trees per acre (TPA) and basal area per acre (BAA) are fundamental forest inventory metrics. TPA represents tree density, while BAA represents the cross-sectional area of trees at breast height (4.5 feet).

Calculation Formulas (Two-Stage Aggregation):

Stage 1 - Plot-Condition Aggregation: CONDITION_TPA = Σ(TPA_UNADJ × ADJ_FACTOR) for each tree in condition CONDITION_BAA = Σ(π × (DIA/24)² × TPA_UNADJ × ADJ_FACTOR) for each tree

Stage 2 - Population Expansion: TPA = Σ(CONDITION_TPA × EXPNS) / Σ(CONDPROP_UNADJ × EXPNS) BAA = Σ(CONDITION_BAA × EXPNS) / Σ(CONDPROP_UNADJ × EXPNS)

Where: - TPA_UNADJ: Unadjusted trees per acre from plot design - DIA: Diameter at breast height in inches - ADJ_FACTOR: Plot size adjustment factor (SUBP, MICR, or MACR) - EXPNS: Stratification expansion factor - CONDPROP_UNADJ: Proportion of plot in the condition

The DIA/24 term converts diameter in inches to radius in feet: - DIA/12 converts inches to feet - Divide by 2 to get radius - Simplified: (DIA/24)²

CRITICAL - FUNDAMENTAL REQUIREMENT: The two-stage aggregation is not optional - it is mathematically required for statistically valid FIA estimates. Any deviation from this order (applying expansion factors before condition-level aggregation) will produce fundamentally incorrect results that can be orders of magnitude wrong. This is a core requirement of FIA's design-based estimation methodology, not an implementation choice.

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 tpa().

Plot Size Adjustments: FIA uses different plot sizes for different tree sizes: - Microplot (6.8 ft radius): Trees 1.0-4.9" DBH - Subplot (24.0 ft radius): Trees 5.0"+ DBH (or to breakpoint) - Macroplot (58.9 ft radius): Trees above breakpoint diameter

The adjustment factors account for these different sampling intensities.

Valid Grouping Columns: The function joins TREE, COND, and PLOT tables, so any column from these tables can be used for grouping. Continuous variables (LAT, LON, ELEV) should not be used for grouping. Some columns may contain NULL values.

Size Class Definition: When by_size_class=True, trees are grouped into 2-inch diameter classes based on DBH. The size class value represents the lower bound of each 2-inch class (0, 2, 4, 6, 8, etc.).

Warnings

BREAKING CHANGE (v1.0.0+): This version fixes a critical aggregation bug in previous releases. The two-stage aggregation now correctly sums trees to condition level before applying expansion factors. Previous versions may have produced estimates that were orders of magnitude incorrect (up to 26x higher than correct values). Users upgrading should validate their results against FIA EVALIDator or rFIA. Historical analyses using pyfia <1.0.0 should be rerun with corrected aggregation.

The variance calculation follows Bechtold & Patterson (2005) methodology for ratio-of-means estimation with stratified sampling. The calculation accounts for covariance between the numerator (TPA/BAA) and denominator (area). Small sample sizes (<10 plots) will trigger additional warnings. For applications requiring the most precise variance estimates, consider also validating against the FIA EVALIDator tool or rFIA R package.

Examples:

Basic trees per acre on forestland:

>>> from pyfia import FIA, tpa
>>> db = FIA("path/to/fia.duckdb")
>>> db.clip_by_state(37)  # North Carolina
>>> results = tpa(db, land_type="forest")  # Auto-selects EVALID
>>> print(f"TPA: {results['TPA'][0]:.1f} trees/acre")
>>> print(f"BAA: {results['BAA'][0]:.1f} sq ft/acre")

TPA and BAA by species:

>>> results = tpa(db, by_species=True)
>>> # Top 5 species by trees per acre
>>> top_species = results.sort(by='TPA', descending=True).head(5)

Large trees only (≥10 inches DBH):

>>> results = tpa(
...     db,
...     tree_domain="DIA >= 10.0",
...     land_type="forest"
... )

By size class on timberland:

>>> results = tpa(
...     db,
...     by_size_class=True,
...     land_type="timber",
...     tree_type="live"
... )
>>> # Shows distribution across diameter classes

Multiple grouping variables:

>>> results = tpa(
...     db,
...     grp_by=["OWNGRPCD", "FORTYPCD"],
...     land_type="forest",
...     totals=True
... )

Growing stock trees by forest type:

>>> results = tpa(
...     db,
...     grp_by="FORTYPCD",
...     tree_type="gs",
...     tree_domain="TREECLCD == 2"
... )

Standing dead trees by species:

>>> results = tpa(
...     db,
...     by_species=True,
...     tree_type="dead",
...     tree_domain="DIA >= 5.0"
... )

Validation against FIA EVALIDator:

>>> # Using Texas data (STATECD=48, EVALID=482300)
>>> # Corrected two-stage aggregation produces:
>>> # TPA: 23.8 trees/acre (matches EVALIDator)
>>> # Previous incorrect aggregation would have produced:
>>> # TPA: 619.3 trees/acre (26x higher - INCORRECT)
>>> #
>>> # This demonstrates the critical importance of proper
>>> # condition-level aggregation before expansion
Source code in src/pyfia/estimation/estimators/tpa.py
def tpa(
    db: "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",
    tree_domain: str | None = None,
    area_domain: str | None = None,
    plot_domain: str | None = None,
    totals: bool = False,
    variance: bool = False,
) -> pl.DataFrame:
    """
    Estimate trees per acre (TPA) and basal area per acre (BAA) from FIA data.

    Calculates tree density and basal area 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 : FIA
        FIA database connection object. If EVALID is not set, the function
        automatically selects the most recent EXPVOL evaluation with a warning.
    grp_by : str or list of str, optional
        Column name(s) to group results by. Can be any column from the
        TREE, PLOT, and COND tables. Common grouping columns include:

        **Tree Attributes:**
        - 'SPCD': Species code (see REF_SPECIES)
        - 'SPGRPCD': Species group code
        - 'DIA': Diameter at breast height (inches)
        - 'HT': Total tree height (feet)
        - 'CR': Compacted crown ratio (percent)
        - '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)
        - 'STATUSCD': Tree status (1=Live, 2=Dead, 3=Removed)

        **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)
        - 'STDAGE': Stand age in years
        - '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)

        **Ownership and Location:**
        - 'OWNGRPCD': Ownership group (10=National Forest, 20=Other Federal,
          30=State/Local, 40=Private)
        - '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 2-inch DBH classes: 0-1.9", 2-3.9", 4-5.9", etc.
    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 in [1,2,3,4,5,6] and RESERVCD = 0)
        - 'all': All land types including non-forest
    tree_type : {'live', 'dead', 'gs', 'all'}, default 'live'
        Tree type to include in estimation:

        - 'live': All live trees (STATUSCD = 1)
        - 'dead': Standing dead trees (STATUSCD = 2)
        - 'gs': Growing stock trees (live trees meeting merchantability standards,
          typically TREECLCD = 2)
        - 'all': All trees regardless of status
    tree_domain : str, optional
        SQL-like filter expression for tree-level attributes. Applied to
        the TREE table. Examples:

        - "DIA >= 10.0": Trees 10 inches DBH and larger
        - "SPCD IN (131, 110)": Specific species (loblolly and Virginia pine)
        - "HT > 50 AND CR > 30": Tall trees with good crowns
        - "TREECLCD == 2": Growing stock trees only
    area_domain : str, optional
        SQL-like filter expression for area/condition-level attributes.
        Applied to the COND table. Examples:

        - "STDAGE > 50": Stands older than 50 years
        - "FORTYPCD IN (161, 162)": Specific forest types
        - "OWNGRPCD == 40": Private lands only
        - "PHYSCLCD == 31 AND STDSZCD == 1": Xeric sites with large trees
    totals : bool, default False
        If True, include population-level total estimates (TPA_TOTAL, BAA_TOTAL)
        in addition to per-acre values. Total estimates are expanded using
        stratification factors.
    variance : bool, default False
        If True, return variance instead of standard error. Standard error
        is calculated as the square root of variance.

    Returns
    -------
    pl.DataFrame
        Trees per acre and basal area estimates with the following columns:

        - **YEAR** : int
            Representative inventory year
        - **[grouping columns]** : varies
            Any columns specified in grp_by parameter
        - **TPA** : float
            Trees per acre
        - **BAA** : float
            Basal area per acre (square feet)
        - **TPA_SE** : float (if variance=False)
            Standard error of TPA estimate
        - **BAA_SE** : float (if variance=False)
            Standard error of BAA estimate
        - **TPA_VAR** : float (if variance=True)
            Variance of TPA estimate
        - **BAA_VAR** : float (if variance=True)
            Variance of BAA estimate
        - **TPA_TOTAL** : float (if totals=True)
            Total trees expanded to population level
        - **BAA_TOTAL** : float (if totals=True)
            Total basal area expanded to population level
        - **TPA_TOTAL_SE** : float (if totals=True and variance=False)
            Standard error of total TPA
        - **BAA_TOTAL_SE** : float (if totals=True and variance=False)
            Standard error of total BAA
        - **N_PLOTS** : int
            Number of FIA plots in estimate
        - **N_TREES** : int
            Number of individual tree records

    See Also
    --------
    pyfia.volume : Estimate tree volume per acre
    pyfia.biomass : Estimate tree biomass per acre
    pyfia.area : Estimate forest area

    External References
    -------------------
    FIA EVALIDator : USDA Forest Service online tool for validation
        https://apps.fs.usda.gov/Evalidator/evalidator.jsp
    rFIA : R package for FIA analysis (independent validation)
        https://cran.r-project.org/package=rFIA
    Bechtold & Patterson (2005) : The enhanced FIA national program
        https://doi.org/10.2737/SRS-GTR-80
    pyfia.mortality : Estimate annual tree mortality
    pyfia.growth : Estimate annual tree growth
    pyfia.constants.SpeciesCodes : Species code definitions
    pyfia.constants.ForestTypes : Forest type code definitions
    pyfia.utils.reference_tables : Functions for adding species/forest type names

    Notes
    -----
    Trees per acre (TPA) and basal area per acre (BAA) are fundamental forest
    inventory metrics. TPA represents tree density, while BAA represents the
    cross-sectional area of trees at breast height (4.5 feet).

    **Calculation Formulas (Two-Stage Aggregation):**

    Stage 1 - Plot-Condition Aggregation:
        CONDITION_TPA = Σ(TPA_UNADJ × ADJ_FACTOR) for each tree in condition
        CONDITION_BAA = Σ(π × (DIA/24)² × TPA_UNADJ × ADJ_FACTOR) for each tree

    Stage 2 - Population Expansion:
        TPA = Σ(CONDITION_TPA × EXPNS) / Σ(CONDPROP_UNADJ × EXPNS)
        BAA = Σ(CONDITION_BAA × EXPNS) / Σ(CONDPROP_UNADJ × EXPNS)

    Where:
    - TPA_UNADJ: Unadjusted trees per acre from plot design
    - DIA: Diameter at breast height in inches
    - ADJ_FACTOR: Plot size adjustment factor (SUBP, MICR, or MACR)
    - EXPNS: Stratification expansion factor
    - CONDPROP_UNADJ: Proportion of plot in the condition

    The DIA/24 term converts diameter in inches to radius in feet:
    - DIA/12 converts inches to feet
    - Divide by 2 to get radius
    - Simplified: (DIA/24)²

    **CRITICAL - FUNDAMENTAL REQUIREMENT**: The two-stage aggregation is not
    optional - it is mathematically required for statistically valid FIA
    estimates. Any deviation from this order (applying expansion factors before
    condition-level aggregation) will produce **fundamentally incorrect results**
    that can be orders of magnitude wrong. This is a core requirement of FIA's
    design-based estimation methodology, not an implementation choice.

    **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 tpa().

    **Plot Size Adjustments:**
    FIA uses different plot sizes for different tree sizes:
    - Microplot (6.8 ft radius): Trees 1.0-4.9" DBH
    - Subplot (24.0 ft radius): Trees 5.0"+ DBH (or to breakpoint)
    - Macroplot (58.9 ft radius): Trees above breakpoint diameter

    The adjustment factors account for these different sampling intensities.

    **Valid Grouping Columns:**
    The function joins TREE, COND, and PLOT tables, so any column from these
    tables can be used for grouping. Continuous variables (LAT, LON, ELEV)
    should not be used for grouping. Some columns may contain NULL values.

    **Size Class Definition:**
    When by_size_class=True, trees are grouped into 2-inch diameter classes
    based on DBH. The size class value represents the lower bound of each
    2-inch class (0, 2, 4, 6, 8, etc.).

    Warnings
    --------
    **BREAKING CHANGE (v1.0.0+)**: This version fixes a critical aggregation bug
    in previous releases. The two-stage aggregation now correctly sums trees to
    condition level before applying expansion factors. Previous versions may have
    produced estimates that were **orders of magnitude incorrect** (up to 26x
    higher than correct values). Users upgrading should validate their results
    against FIA EVALIDator or rFIA. Historical analyses using pyfia <1.0.0 should
    be rerun with corrected aggregation.

    The variance calculation follows Bechtold & Patterson (2005) methodology
    for ratio-of-means estimation with stratified sampling. The calculation
    accounts for covariance between the numerator (TPA/BAA) and denominator
    (area). Small sample sizes (<10 plots) will trigger additional warnings.
    For applications requiring the most precise variance estimates, consider
    also validating against the FIA EVALIDator tool or rFIA R package.

    Examples
    --------
    Basic trees per acre on forestland:

    >>> from pyfia import FIA, tpa
    >>> db = FIA("path/to/fia.duckdb")
    >>> db.clip_by_state(37)  # North Carolina
    >>> results = tpa(db, land_type="forest")  # Auto-selects EVALID
    >>> print(f"TPA: {results['TPA'][0]:.1f} trees/acre")
    >>> print(f"BAA: {results['BAA'][0]:.1f} sq ft/acre")

    TPA and BAA by species:

    >>> results = tpa(db, by_species=True)
    >>> # Top 5 species by trees per acre
    >>> top_species = results.sort(by='TPA', descending=True).head(5)

    Large trees only (≥10 inches DBH):

    >>> results = tpa(
    ...     db,
    ...     tree_domain="DIA >= 10.0",
    ...     land_type="forest"
    ... )

    By size class on timberland:

    >>> results = tpa(
    ...     db,
    ...     by_size_class=True,
    ...     land_type="timber",
    ...     tree_type="live"
    ... )
    >>> # Shows distribution across diameter classes

    Multiple grouping variables:

    >>> results = tpa(
    ...     db,
    ...     grp_by=["OWNGRPCD", "FORTYPCD"],
    ...     land_type="forest",
    ...     totals=True
    ... )

    Growing stock trees by forest type:

    >>> results = tpa(
    ...     db,
    ...     grp_by="FORTYPCD",
    ...     tree_type="gs",
    ...     tree_domain="TREECLCD == 2"
    ... )

    Standing dead trees by species:

    >>> results = tpa(
    ...     db,
    ...     by_species=True,
    ...     tree_type="dead",
    ...     tree_domain="DIA >= 5.0"
    ... )

    Validation against FIA EVALIDator:

    >>> # Using Texas data (STATECD=48, EVALID=482300)
    >>> # Corrected two-stage aggregation produces:
    >>> # TPA: 23.8 trees/acre (matches EVALIDator)
    >>> # Previous incorrect aggregation would have produced:
    >>> # TPA: 619.3 trees/acre (26x higher - INCORRECT)
    >>> #
    >>> # This demonstrates the critical importance of proper
    >>> # condition-level aggregation before expansion
    """
    # 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,
    )

    # Validate tpa-specific parameters
    tree_type = validate_tree_type(tree_type)
    by_species = validate_boolean(by_species, "by_species")
    by_size_class = validate_boolean(by_size_class, "by_size_class")

    # Ensure EVALID is set using shared utility
    # Use "VOL" for TPA/BAA estimation (EXPVOL evaluations)
    ensure_evalid_set(db, eval_type="VOL", estimator_name="tpa")

    # 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,
        "tree_domain": inputs.tree_domain,
        "area_domain": inputs.area_domain,
        "plot_domain": inputs.plot_domain,
        "totals": inputs.totals,
        "variance": inputs.variance,
    }

    # Create and run estimator - simple and clean
    estimator = TPAEstimator(db, config)
    return estimator.estimate()

Output Columns

The tpa() function returns these columns:

Column Description
TPA Trees per acre estimate
BAA Basal area (sq ft/acre) estimate
TPA_SE TPA standard error
BAA_SE BAA standard error
TPA_TOTAL Total trees (if totals=True)
BAA_TOTAL Total basal area (if totals=True)

Formulas

Trees Per Acre: $$TPA = TPA_UNADJ \times ADJ_FACTOR$$

Basal Area Per Acre: $$BAA = \pi \times (DIA/24)^2 \times TPA_UNADJ \times ADJ_FACTOR$$

Examples

Total TPA on Forest Land

result = pyfia.tpa(db, land_type="forest")
print(f"TPA: {result['TPA'][0]:.1f} trees/acre")
print(f"BAA: {result['BAA'][0]:.1f} sq ft/acre")

TPA by 2-Inch Diameter Classes

result = pyfia.tpa(db, by_size_class=True)
print(result)

TPA by Species

result = pyfia.tpa(db, grp_by="SPCD")
result = pyfia.join_species_names(result, db)
print(result.sort("TPA", descending=True).head(10))

Large Trees Only

result = pyfia.tpa(
    db,
    tree_domain="DIA >= 12.0",
    land_type="timber"
)