Create Persistent Identifiers for Longitudinal Spatial Data

Creates dataset-local persistent identifiers for polygon units observed across time. Configurable overlap metrics, thresholds, and one-to-one matching rules identify continuity while separate identifiers track boundary versions and broader lineages through splits, mergers, and replacements. Candidate-link diagnostics, validation checks, registry reconciliation, and lineage summaries keep the resulting decisions inspectable and reproducible.


spatpersist

R-CMD-check test-coverage

spatpersist creates persistent identifiers for polygon units observed over time. It is designed for datasets in which names, labels, and boundaries may change and reliable longitudinal IDs do not already exist.

Identity is determined from configurable spatial continuity rules. Names and other descriptive attributes are preserved in the result but are not used for matching.

spatpersist is under active development. Review transition diagnostics and validation results before using generated IDs in analysis.

Installation

Install the development version from GitHub:

# install.packages("remotes")
remotes::install_github("emre-cebeci/spatpersist")

Quick start

library(spatpersist)

polygons <- example_units()

result <- persist_ids(
  polygons,
  time = "year",
  threshold = 0.75,
  metric = "share_old"
)

sf::st_drop_geometry(result)[
  , c(
    "year",
    "name",
    "spatial_id",
    "spatial_version_id",
    "lineage_id",
    "parent_id",
    "transition_type"
  )
]

The result remains an sf object in its original row order.

Output identifiers

Column Meaning
spatial_id One continuing spatial identity. It occurs at most once per time period.
spatial_version_id A consecutive boundary spell within a spatial_id.
lineage_id A broader family connecting related identities through splits, mergers, and replacements.
parent_id The strongest predecessor when a related observation receives a new identity.
transition_type initial, continuation, new, split, merger, replacement, or complex.

Selected links also receive match_score, match_confidence, and match_ambiguous. Registry recovery is recorded in registry_matched.

Identifiers are dataset-local, not globally unique. Independent projects can both contain values such as SID000001; persistence is guaranteed only within one dataset and its registry chain. Add a project-specific key when combining outputs from unrelated datasets.

Matching controls

The core identity rule is controlled by:

  • metric: "share_old", "share_new", or "iou";
  • threshold: minimum accepted value of that metric;
  • match_rule: greedy one-to-one matching or stricter mutual-best matching;
  • ambiguity_tolerance: the score difference treated as a near tie;
  • ambiguity_action: flag, reject, or stop on ambiguous candidates.

For example, this rejects near ties instead of selecting one deterministically:

conservative <- persist_ids(
  polygons,
  time = "year",
  metric = "iou",
  threshold = 0.75,
  match_rule = "mutual_best",
  ambiguity_action = "new"
)

event_threshold, lineage_threshold, and version_threshold separately control event detection, lineage links, and geometry-version changes.

Audit and validate

Every positive-area overlap considered by the matching engine is available as a regular data frame:

transitions <- id_transitions(result)
lineages <- id_lineages(result, time = "year")
issues <- validate_ids(result, time = "year")

nrow(issues) # zero means all implemented checks passed

Diagnostics distinguish spatial eligibility, rule eligibility, ambiguity, mutual-best status, selection, confidence, and lineage membership.

Plot a lineage

plot_lineage(
  result,
  time = "year",
  lineage_id = "LID000004"
)

Solid edges are selected identity continuations. Dashed edges are additional lineage relationships, such as branches created by a split or merger.

Preserve IDs across reruns

A previous result can be supplied as an optional registry. Matching same-time geometries retain their issued IDs, while new identities are allocated above the registry's existing range.

updated_result <- persist_ids(
  updated_polygons,
  time = "year",
  registry = result
)

Conflicting registry identity or geometry-version evidence stops reconciliation rather than silently renumbering identities. Lineages are different: if new spatial evidence connects observations anchored to multiple registry lineages, the complete connected component is consolidated under the lexicographically smallest existing lineage_id.

Input requirements

  • An sf object containing only POLYGON or MULTIPOLYGON geometries.
  • One observation per spatial unit and time period.
  • No exact coextensive geometries within the same time period. Geometry-only matching cannot distinguish their identities reproducibly, so they are rejected with an error.
  • A non-missing, sortable time column.
  • Valid, non-empty geometries with positive area.
  • A consistent CRS across current data and any registry.

Invalid polygons can be repaired explicitly with geometry_action = "repair". Optional geometry precision and maximum time-gap controls are also available.

Current scope

  • Matching is spatial and one-to-one; names do not determine identity.
  • Consecutive observed periods are compared unless max_time_gap prevents a link.
  • A single parent_id cannot encode every predecessor in a merger; use id_transitions() for complete many-to-many evidence.
  • A reproducible scalability benchmark is included under inst/benchmarks; performance still depends strongly on polygon complexity and the number of candidate intersections.
  • The package contains no bundled administrative datasets or domain-specific assumptions.

See vignette("spatpersist-workflow") for a complete walkthrough.

Contributing

Bug reports and contributions are welcome. Please use only synthetic or public data in issues, examples, and pull requests; do not upload confidential or restricted datasets. See CONTRIBUTING.md for the development and reporting workflow.

Reference manual

It appears you don't have a PDF plugin for this browser. You can click here to download the reference manual.

install.packages("spatpersist")

0.1.0 by Emre Cebeci, 2 months ago


https://github.com/emre-cebeci/spatpersist


Report a bug at https://github.com/emre-cebeci/spatpersist/issues


Browse source code at https://github.com/cran/spatpersist


Authors: Emre Cebeci [aut, cre]


Documentation:   PDF Manual  


MIT + file LICENSE license


Imports sf

Suggests knitr, rmarkdown, testthat


See at CRAN