Remeasurement Panels¶
Create t1/t2 linked panel datasets from FIA remeasurement data for harvest analysis, growth tracking, and change detection.
Overview¶
The panel() function creates linked datasets where each row represents a measurement pair:
- t1 (time 1): Previous measurement
- t2 (time 2): Current measurement
This panel data is essential for:
- Harvest probability modeling
- Forest change detection
- Growth and mortality analysis
- Land use transition studies
import pyfia
db = pyfia.FIA("data/nc.duckdb")
db.clip_by_state("NC")
# Condition-level panel for harvest analysis
cond_panel = pyfia.panel(db, level="condition", land_type="timber")
print(f"Harvest rate: {cond_panel['HARVEST'].mean():.1%}")
# Tree-level panel for mortality/cut analysis
tree_panel = pyfia.panel(db, level="tree")
print(tree_panel.group_by("TREE_FATE").len())
Function Reference¶
panel
¶
panel(db: str | FIA, level: Literal['condition', 'tree'] = 'condition', columns: list[str] | None = None, land_type: str = 'forest', tree_type: str = 'gs', tree_domain: str | None = None, area_domain: str | None = None, expand_chains: bool = True, min_remper: float = 0, max_remper: float | None = None, min_invyr: int = 2000, harvest_only: bool = False, expand: bool = False, measure: Literal['tpa', 'volume'] = 'tpa', grp_by: list[str] | None = None, by_fate: bool = False) -> DataFrame
Create a t1/t2 remeasurement panel from FIA data.
Returns a DataFrame where each row represents a measurement pair: - t1 (time 1): Previous measurement - t2 (time 2): Current measurement
This panel data is useful for: - Harvest probability modeling - Forest change detection - Growth and mortality analysis - Land use transition studies
Tree-level panels use GRM (Growth-Removal-Mortality) tables for authoritative tree fate classification, providing consistent definitions aligned with FIA's official estimation methodology.
| PARAMETER | DESCRIPTION |
|---|---|
db
|
Database connection or path to FIA database.
TYPE:
|
level
|
Level of panel to create: - 'condition': Condition-level panel for area/harvest analysis. Each row is a condition measured at two time points. - 'tree': Tree-level panel for individual tree tracking. Each row is a tree with GRM component classification.
TYPE:
|
columns
|
Additional columns to include beyond defaults. Useful for adding specific attributes needed for analysis.
TYPE:
|
land_type
|
Land classification filter: - 'forest': All forest land (COND_STATUS_CD = 1) - 'timber': Timberland (productive, unreserved forest) - 'all': No land type filtering
TYPE:
|
tree_type
|
Tree type filter (tree-level only). Maps to GRM column suffixes: - 'gs': Growing stock (merchantable trees) - uses GS columns - 'all': All trees - uses GS columns (default GRM behavior) - 'live': All live trees - uses AL columns
TYPE:
|
tree_domain
|
SQL-like filter expression for tree-level filtering. Example: "SPCD == 131" (loblolly pine only)
TYPE:
|
area_domain
|
SQL-like filter expression for condition-level filtering. Example: "OWNGRPCD == 40" (private land only)
TYPE:
|
expand_chains
|
If True and multiple remeasurements exist (t1->t2->t3), creates pairs (t1,t2) and (t2,t3). If False, only returns the most recent pair for each location.
TYPE:
|
min_remper
|
Minimum remeasurement period in years. Filters out pairs with shorter intervals.
TYPE:
|
max_remper
|
Maximum remeasurement period in years. Filters out pairs with longer intervals.
TYPE:
|
min_invyr
|
Minimum inventory year for t2 (current measurement). Defaults to 2000 to use only the enhanced annual inventory methodology. FIA transitioned from periodic to annual inventory around 1999-2000, with significant methodology changes. Set to None or 0 to include all years.
TYPE:
|
harvest_only
|
If True, return only records where harvest was detected. For condition-level: uses TRTCD treatment codes. For tree-level: returns trees with TREE_FATE in ['cut', 'diversion'].
TYPE:
|
expand
|
If True, apply expansion factors to produce per-acre estimates comparable to removals(). Requires level='tree'. Uses three-layer expansion: - TPA_UNADJ: Base trees-per-acre - ADJ_FACTOR: Plot-type adjustment (subplot/microplot/macroplot) - EXPNS: Stratum expansion to total acres When expand=True, returns aggregated per-acre estimates instead of tree-level data.
TYPE:
|
measure
|
Measure to expand (only used when expand=True): - 'tpa': Trees per acre - 'volume': Cubic foot volume per acre
TYPE:
|
grp_by
|
Grouping columns for expanded estimates (only used when expand=True). Example: ['SPCD'] for by-species estimates.
TYPE:
|
by_fate
|
If True and expand=True, include TREE_FATE in grouping columns to get separate estimates for survivors, mortality, cut, etc.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
DataFrame
|
Panel dataset with columns: For condition-level: - PLT_CN: Current plot control number - PREV_PLT_CN: Previous plot control number - CONDID: Condition identifier - STATECD, COUNTYCD: Geographic identifiers - INVYR: Current inventory year - REMPER: Remeasurement period (years) - HARVEST: Harvest indicator (1=harvest detected, 0=no harvest) - t1_/t2_: Attributes at time 1 and time 2 For tree-level: - PLT_CN: Plot control number - TRE_CN: Tree control number - TREE_FATE: Tree fate from GRM classification: - 'survivor': Tree alive at both measurements - 'mortality': Tree died naturally - 'cut': Tree removed by harvest - 'diversion': Tree removed due to land use change - 'ingrowth': New tree crossing size threshold - COMPONENT: Raw GRM component (SURVIVOR, CUT1, etc.) - DIA_BEGIN, DIA_MIDPT, DIA_END: Diameter measurements - TPA_UNADJ: Trees per acre expansion factor - t1_/t2_: Tree attributes at time 1 and time 2 |
See Also
removals : Estimate harvest removals (uses same GRM methodology) mortality : Estimate tree mortality growth : Estimate forest growth area_change : Estimate forest area change
Examples:
Basic condition-level panel for harvest analysis:
>>> from pyfia import FIA, panel
>>> with FIA("path/to/db.duckdb") as db:
... db.clip_by_state(37) # North Carolina
... data = panel(db, level="condition", land_type="timber")
... print(f"Panel has {len(data)} condition pairs")
... print(f"Harvest rate: {data['HARVEST'].mean():.1%}")
Tree-level panel with GRM-based fate classification:
>>> with FIA("path/to/db.duckdb") as db:
... db.clip_by_state(37)
... trees = panel(db, level="tree", tree_type="gs")
... fate_counts = trees.group_by("TREE_FATE").len()
... print(fate_counts)
Get all removals (cut + diversion):
>>> data = panel(
... db,
... level="tree",
... harvest_only=True, # Returns cut and diversion trees
... )
Filter remeasurement period to 4-8 years:
Notes
Tree-level panels use GRM (Growth-Removal-Mortality) tables:
Tree fate is determined from TREE_GRM_COMPONENT, which provides
authoritative classification pre-computed by FIA. This ensures
consistency with the removals() function and EVALIDator.
GRM component types: - SURVIVOR: Tree alive at beginning and end of period - MORTALITY1/2: Tree died during measurement period - CUT1/2: Tree harvested during measurement period - DIVERSION1/2: Tree removed due to land use change - INGROWTH: New tree crossing 5" DBH threshold
Condition-level harvest detection uses treatment codes (TRTCD): - 10 = Cutting (harvest) - 20 = Site preparation (implies prior harvest)
Remeasurement availability varies by region: - Southern states typically have 5-7 year remeasurement cycles - Western states may have 10-year cycles - Some plots have 3+ remeasurements (t1->t2->t3)
References
Bechtold & Patterson (2005), "The Enhanced Forest Inventory and Analysis Program", Chapter 4: Change Estimation.
FIA Database User Guide, TREE_GRM_COMPONENT table documentation.
Source code in src/pyfia/estimation/estimators/panel.py
1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 | |
Panel Levels¶
Condition-Level (level="condition")¶
Each row represents a forest condition measured at two time points. Best for:
- Area-based harvest probability models
- Land use change analysis
- Stand-level attribute tracking
Key columns:
| Column | Description |
|---|---|
PLT_CN |
Current plot control number |
PREV_PLT_CN |
Previous plot control number |
CONDID |
Condition identifier |
INVYR |
Current inventory year |
REMPER |
Remeasurement period (years) |
HARVEST |
Harvest indicator (1=detected, 0=no) |
t1_* / t2_* |
Attributes at time 1 and time 2 |
Tree-Level (level="tree")¶
Each row represents an individual tree measured at two time points. Best for:
- Individual tree fate analysis
- Species-specific harvest patterns
- Mortality vs. harvest separation
Key columns:
| Column | Description |
|---|---|
TRE_CN |
Current tree control number |
PREV_TRE_CN |
Previous tree control number |
TREE_FATE |
Tree fate classification |
t1_* / t2_* |
Tree attributes at time 1 and time 2 |
Tree Fate Values:
| Fate | Description | GRM Component |
|---|---|---|
survivor |
Live at t1 and t2 | SURVIVOR |
mortality |
Live at t1, dead at t2 (natural causes) | MORTALITY1, MORTALITY2 |
cut |
Live at t1, removed/harvested at t2 | CUT1, CUT2 |
diversion |
Removed due to land use change | DIVERSION1, DIVERSION2 |
ingrowth |
New tree crossing 5" DBH threshold | INGROWTH |
other |
Unrecognized component type | - |
Harvest Detection¶
Condition-Level Detection¶
Harvest is detected using TRTCD (treatment code) fields:
- TRTCD = 10: Cutting (harvest)
- TRTCD = 20: Site preparation (implies prior harvest)
# Get harvested conditions
harvested = pyfia.panel(db, level="condition", harvest_only=True)
print(f"Harvested conditions: {len(harvested)}")
Tree-Level Detection (GRM-Based)¶
Tree-level panels use FIA's GRM (Growth-Removal-Mortality) tables for authoritative fate classification. The TREE_GRM_COMPONENT table provides pre-computed classifications that align with FIA's official estimation methodology:
# Tree fate comes directly from GRM COMPONENT classification
tree_panel = pyfia.panel(db, level="tree")
cut_trees = tree_panel.filter(pl.col("TREE_FATE") == "cut")
# Get all removals (cut + diversion)
removals = pyfia.panel(db, level="tree", harvest_only=True)
This ensures consistency with the removals() function and EVALIDator results.
Examples¶
Basic Harvest Analysis¶
import pyfia
import polars as pl
db = pyfia.FIA("data/nc.duckdb")
db.clip_by_state("NC")
# Condition-level harvest rates
panel = pyfia.panel(db, level="condition", land_type="forest")
# Overall harvest rate
harvest_rate = panel["HARVEST"].mean()
remper = panel["REMPER"].mean()
annual_rate = 1 - (1 - harvest_rate) ** (1 / remper)
print(f"Period harvest rate: {harvest_rate:.1%}")
print(f"Annualized rate: {annual_rate:.2%}/year")
Harvest by Ownership¶
panel = pyfia.panel(db, level="condition")
harvest_by_owner = (
panel
.group_by("t2_OWNGRPCD")
.agg([
pl.len().alias("n_conditions"),
pl.col("HARVEST").mean().alias("harvest_rate")
])
.sort("t2_OWNGRPCD")
)
# OWNGRPCD: 10=USFS, 20=Other Fed, 30=State/Local, 40=Private
print(harvest_by_owner)
Tree-Level Cut Analysis¶
# Get all trees with fate information
tree_panel = pyfia.panel(db, level="tree", tree_type="all")
# Tree fate distribution
fate_dist = tree_panel.group_by("TREE_FATE").len()
print(fate_dist)
# Cut trees only
cut_trees = pyfia.panel(db, level="tree", harvest_only=True)
# Top species cut
species_cut = (
cut_trees
.group_by("t1_SPCD")
.agg([
pl.len().alias("n_trees"),
pl.col("t1_DIA").mean().alias("avg_dia")
])
.sort("n_trees", descending=True)
.head(10)
)
print(species_cut)
Filtering Options¶
# Timberland only (productive, unreserved forest)
timber_panel = pyfia.panel(db, level="condition", land_type="timber")
# Private land only
private_panel = pyfia.panel(
db,
level="condition",
area_domain="OWNGRPCD == 40"
)
# Specific remeasurement period range
panel_5_10yr = pyfia.panel(
db,
level="condition",
min_remper=5,
max_remper=10
)
# Loblolly pine only (tree-level)
loblolly_panel = pyfia.panel(
db,
level="tree",
tree_domain="SPCD == 131"
)
Multi-Period Chain Analysis¶
# Get panel with chain expansion (default)
panel = pyfia.panel(db, level="condition", expand_chains=True)
# Count plots by number of measurement periods
chain_lengths = (
panel
.group_by("PLT_CN")
.len()
.group_by("len")
.len()
.rename({"len": "periods", "len_right": "n_plots"})
.sort("periods")
)
print("Plots by chain length:")
print(chain_lengths)
Expansion to Per-Acre Estimates¶
Tree-level panels can be expanded to produce per-acre estimates comparable to removals(). This applies the same three-layer expansion methodology:
- TPA_UNADJ: Base trees-per-acre from plot design
- ADJ_FACTOR: Plot-type adjustment (subplot/microplot/macroplot)
- EXPNS: Stratum expansion to total acres
Basic Expansion¶
# Get per-acre removals estimates (comparable to removals())
expanded = pyfia.panel(
db,
level="tree",
harvest_only=True,
expand=True,
measure="tpa" # Trees per acre
)
print(f"Total removals: {expanded['PANEL_TOTAL'][0]:,.0f} trees/year")
print(f"Per acre: {expanded['PANEL_ACRE'][0]:,.1f} trees/acre/year")
Expansion by Species¶
# Removals by species
by_species = pyfia.panel(
db,
level="tree",
harvest_only=True,
expand=True,
measure="volume",
grp_by=["SPCD"]
)
print(by_species.sort("PANEL_TOTAL", descending=True).head(10))
Expansion by Tree Fate¶
# Compare survivor, mortality, cut volumes
by_fate = pyfia.panel(
db,
level="tree",
expand=True,
measure="volume",
by_fate=True # Groups by TREE_FATE automatically
)
print(by_fate)
Expansion Parameters¶
| Parameter | Type | Default | Description |
|---|---|---|---|
expand |
bool | False | Apply expansion factors for per-acre estimates |
measure |
str | "tpa" | Measure to expand: "tpa" or "volume" |
grp_by |
list | None | Grouping columns (e.g., ["SPCD"]) |
by_fate |
bool | False | Include TREE_FATE in grouping |
Note: expand=True requires level="tree" and returns aggregated estimates instead of tree-level data.
Harvest Transition Analysis¶
import polars as pl
panel = pyfia.panel(db, level="condition")
# Find plots with multiple periods
multi_period = panel.filter(
pl.col("PLT_CN").is_in(
panel.group_by("PLT_CN").len().filter(pl.col("len") > 1)["PLT_CN"]
)
).sort(["PLT_CN", "INVYR"])
# Calculate harvest transitions
transitions = (
multi_period
.with_columns([
pl.col("HARVEST").shift(1).over("PLT_CN").alias("PREV_HARVEST")
])
.filter(pl.col("PREV_HARVEST").is_not_null())
.group_by(["PREV_HARVEST", "HARVEST"])
.len()
)
print("Harvest transitions:")
print(transitions)
Technical Notes¶
Inventory Year Filter (min_invyr)¶
FIA transitioned from periodic to annual inventory methodology around 1999-2000. By default, min_invyr=2000 ensures only post-transition data is used, providing:
- Consistent methodology across the panel
- Annual rather than periodic measurements
- Better tree tracking via PREV_TRE_CN
Set min_invyr=0 to include historical data if needed.
Chain Expansion (expand_chains)¶
When expand_chains=True (default), plots with multiple remeasurements (t1→t2→t3) generate all consecutive pairs:
- (t1, t2) and (t2, t3)
This maximizes data utilization for transition modeling.
Data Sources¶
- PLOT table:
PREV_PLT_CN,REMPERfor plot linking - COND table:
TRTCD1-3for harvest detection - TREE table:
PREV_TRE_CN,STATUSCDfor tree tracking
See Also¶
area_change()- Estimate forest area change with variancemortality()- Annual tree mortality estimationremovals()- Timber removals estimationgrowth()- Annual forest growth estimation