pyforestry.base.imputation package#

Submodules#

pyforestry.base.imputation.height module#

Height imputation from a fitted height-diameter curve.

A thin adapter over the existing height machinery in pyforestry.base.helpers.height_models: the curve, its fit and its linearising transform all live there and are unchanged. This module only teaches them the Imputer contract, so an imputed height records that Naslund’s curve produced it.

Source:

Naslund, M. (1936). Skogsforsoksanstaltens gallringsforsok i tallskog. Meddelanden fran Statens skogsforsoksanstalt 29(1), 1-169.

class pyforestry.base.imputation.height.NaslundHeightImputer(spec: str | Callable[[float], float | None] | HeightSource | NaslundHeightCurve = 'naslund', naslund_exponent: int | float | str = 2, _source: HeightSource | None = None)[source]#

Bases: object

Impute height_m from a height-diameter curve fitted to the stand.

The curve is fitted from the measured (diameter_cm, height_m) pairs of the trees it is given, so this imputer must be fit() before use – impute() does that for you.

Variables:
attribute: str = 'height_m'#
property component_id: str#

Stable identifier for this imputer.

fit(trees: Sequence[Any], context: Mapping[str, Any]) → NaslundHeightImputer | None[source]#

Fit the curve from trees.

Parameters:
  • trees – Trees whose measured height-diameter pairs the curve is fitted from. Ignored when spec is already a curve or a callable.

  • context – Unused; present for the Imputer contract.

Returns:

A bound copy ready to impute, or None if the curve could not be fitted – too few usable pairs, a single diameter, or a fit implying a non-monotone curve.

Raises:

ValueError – If spec resolves to measured heights, which cannot impute anything.

impute(tree: Any, context: Mapping[str, Any]) → float | None[source]#

Return the curve height for tree, or None without a diameter.

Parameters:
  • tree – The tree to impute for; needs diameter_cm.

  • context – Unused; present for the Imputer contract.

Returns:

The modelled height in metres, or None.

Raises:

RuntimeError – If the imputer has not been fitted.

naslund_exponent: int | float | str = 2#
property source: SourceReference#

Näslund (1936), the curve this imputer evaluates.

spec: str | Callable[[float], float | None] | HeightSource | NaslundHeightCurve = 'naslund'#

pyforestry.base.imputation.imputer module#

The imputer contract, and a wrapper for ad-hoc callables.

An imputer produces one tree attribute from whatever the tree already carries. Like every other model in this package it is Describable: it declares a component_id and a SourceReference, so the value it produces can be traced back to a publication.

Some imputers are fitted from the stand they are applied to – the Naslund height curve is fitted from the stand’s own measured height-diameter pairs – which is what Imputer.fit() is for. Stateless imputers return themselves.

class pyforestry.base.imputation.imputer.CallableImputer(attribute: str, function: Callable[[Any], float | None], label: str = 'caller-supplied callable')[source]#

Bases: object

Wrap a plain callable so ad-hoc imputers carry provenance too.

The value it produces is recorded as uncited, which is the honest label: a lambda has no publication. That keeps caller-supplied values distinguishable from ones a published model produced.

Variables:
  • attribute (str) – The attribute produced, e.g. "crown_radius_m".

  • function (Callable[[Any], float | None]) – f(tree) -> value | None.

  • label (str) – Short description used as the citation title and, slugified, as the component_id.

property component_id: str#

Identifier derived from the attribute and label.

fit(trees: Sequence[Any], context: Mapping[str, Any]) → CallableImputer[source]#

Return self; a callable needs no fitting.

impute(tree: Any, context: Mapping[str, Any]) → float | None[source]#

Evaluate the callable for tree.

label: str = 'caller-supplied callable'#
property source: SourceReference#

The not-applicable citation; a callable has no publication.

class pyforestry.base.imputation.imputer.Imputer(*args, **kwargs)[source]#

Bases: Protocol

Produces one tree attribute from the rest of the tree record.

property attribute: str#

Name of the tree attribute produced, e.g. "height_m".

property component_id: str#

Stable identifier for this imputer.

fit(trees: Sequence[Any], context: Mapping[str, Any]) → Imputer | None[source]#

Bind to a set of trees, returning an imputer ready to use.

Parameters:
  • trees – Every tree the imputer may be applied to.

  • context – Stand-level values an imputer may need.

Returns:

An imputer bound to trees – often self for a stateless model – or None if it could not be fitted from what is available.

impute(tree: Any, context: Mapping[str, Any]) → float | None[source]#

Return a value for tree, or None if it cannot be produced.

property source: SourceReference#

Bibliographic provenance for whatever this imputer computes.

pyforestry.base.imputation.imputer.UNCITED = '(none)'#

Author string marking a value that traces to no publication. Matches the sentinel used by retained_trees, the Swedish mortality engine and base.competition.

pyforestry.base.imputation.imputer.uncited_source(label: str) → SourceReference[source]#

Build the not-applicable citation for an imputer with no publication.

Parameters:

label – Short description of what the callable does.

Returns:

A SourceReference with author "(none)" and year 0 – the sentinel for “not applicable”, not a citation date.

pyforestry.base.imputation.registry module#

Which imputers are available for which attribute.

Keeps the mapping from an attribute name to the imputers that can produce it, so stand.impute("height_m") finds one without the caller naming a class. New imputers register themselves here; a caller can always bypass the registry by passing an imputer instance or a plain callable.

pyforestry.base.imputation.registry.available_attributes() → List[str][source]#

Return every attribute with at least one registered imputer.

pyforestry.base.imputation.registry.imputers_for(attribute: str) → List[str][source]#

Return the registered imputer names for attribute.

pyforestry.base.imputation.registry.register_imputer(attribute: str, name: str, factory: Callable[[], Imputer], *, default: bool = False, rejects: Dict[str, str] | None = None) → None[source]#

Register an imputer factory under an attribute.

Parameters:
  • attribute – The attribute produced, e.g. "height_m".

  • name – Short name callers pass to stand.impute, e.g. "naslund".

  • factory – Zero-argument callable returning a fresh imputer.

  • default – Whether this becomes the attribute’s default, used when the caller asks for the attribute without naming an imputer. The first imputer registered for an attribute becomes its default whether or not this is set, so a lone registration never leaves the attribute without one.

  • rejects – Names that are recognised but invalid for imputation, mapped to the reason. Requesting one raises ValueError with that reason rather than an unhelpful “unknown name”.

pyforestry.base.imputation.registry.resolve_imputer(attribute: str, spec: str | Imputer | Callable[[Any], float | None] | None = None) → Imputer[source]#

Turn a user-facing spec into an imputer.

Parameters:
  • attribute – The attribute to impute.

  • spec – A registered name, an imputer instance, a bare callable, or None/"auto" for the attribute’s default.

Returns:

An imputer for attribute.

Raises:
  • KeyError – If no imputer is registered for the attribute, or the named one is unknown.

  • ValueError – If an imputer instance produces a different attribute, or the name is recognised but cannot impute (e.g. "measured").

Module contents#

Filling in tree attributes a record does not carry, with provenance.

Models keep needing attributes a Tree was not measured for: the influence-zone competition indices need crown_radius_m, Marklund (1988) T12/T15/T16 need crown_base_height_m, and height is missing on most inventory trees. This package supplies one mechanism for all of them.

The rule is that plain attributes are measurements. A modelled value never overwrites one; it goes in Tree.imputed as an ImputedValue carrying the imputer that produced it and that imputer’s citation, and value_of() resolves the two:

>>> stand.impute("height_m")                  # Naslund curve, fitted to the stand
>>> tree.height_m                             # None -- never measured
>>> tree.value_of("height_m")                 # 13.28
>>> tree.provenance("height_m")               # 'imputed'
>>> tree.imputed_source("height_m").author    # 'Näslund, M.'

Attributes with no published model are supplied by the caller, and are recorded as uncited rather than silently unattributed:

>>> stand.impute("crown_radius_m", lambda t: 0.15 * t.diameter_cm)
>>> tree.imputed["crown_radius_m"].is_cited   # False

This package holds no science of its own: it is plumbing plus one adapter over the Näslund curve that already lives in pyforestry.base.helpers.height_models.