Programmatic HPS Analysis
This guide shows how to drive the Horizontal Point Sampling (HPS) workflow directly from Python.
It highlights the richer goodness-of-fit metrics exposed by fit_hps_inventory and demonstrates
how to turn FitResult objects into tidy reports for downstream analysis.
Prerequisites
Install
nemorain an environment with pandas and numpy available (pip install -e ".[dev]").Obtain an HPS tally CSV with
dbh_cm,tally, and the associated basal area factor (baf) metadata. The repository ships examples underexamples/hps_baf12/.
from pathlib import Path
import pandas as pd
from nemora.core import FitSummary, InventorySpec
from nemora.workflows import fit_hps_inventory
csv_path = Path("examples/hps_baf12/4000002_PSP1_v1_p1.csv")
data = pd.read_csv(csv_path)
dbh_cm = data["dbh_cm"].to_numpy()
tally = data["tally"].to_numpy()
baf = 12.0
Fit candidate distributions
Pass the tallies into fit_hps_inventory. The helper expands the tallies to stand tables, applies
the HPS compression weights, and returns a list of FitResult objects – one entry per candidate.
results = fit_hps_inventory(
dbh_cm=dbh_cm,
tally=tally,
baf=baf,
distributions=("weibull", "gamma", "gb2", "gsm3", "gsm6"),
)
Inspect the goodness-of-fit metrics (RSS, AIC/AICc, BIC, chi-square, KS, CvM, AD) and residual
summaries that are now populated automatically; any gsmN string (with N ≥ 2) will route through
the generalized secant grouped estimator:
for result in results:
gof = result.gof
residuals = result.diagnostics["residual_summary"]
print(
f"{result.distribution:8s}"
f" RSS={gof['rss']:.2f}"
f" AICc={gof.get('aicc', float('nan')):.2f}"
f" Chi^2={gof.get('chisq', float('nan')):.2f}"
f" KS={gof.get('ks', float('nan')):.3f}"
f" CvM={gof.get('cvm', float('nan')):.3f}"
f" AD={gof.get('ad', float('nan')):.3f}"
f" Max|res|={residuals.get('max_abs', float('nan')):.2f}"
)
Choose the best candidate (e.g. by minimum RSS or AICc) and inspect the fitted parameters:
best = min(results, key=lambda item: item.gof["rss"])
print(best.distribution, best.parameters)
Summarise results as a table
FitSummary can collate the results into a tidy DataFrame for notebooks, CSV exports, or further
analysis.
summary = FitSummary(
inventory=InventorySpec(
name="psp-4000002",
sampling="hps",
bins=dbh_cm,
tallies=tally,
metadata={"baf": baf, "source": csv_path.name},
),
results=results,
best=best,
)
summary_frame = summary.to_frame()
summary_frame.loc[:, ["distribution", "gof_rss", "gof_aicc", "gof_chisq"]]
Save the table or plot the residuals from result.diagnostics["residuals"] to match the parity
notebooks.
Next steps
Run
nemora fit-hps --show-parametersfor a CLI view that mirrors the report above.Use the HPS dataset guide to fetch the manuscript dataset via
nemora fetch-reference-datawhen DataLad is available.Combine the scripted workflow with
matplotliborpolarsto build automated QA reports for multiple plots.