Skip to contents

Overview

VasGBIF provides a reproducible, tracheophyte-optimized, and computationally efficient framework for transforming GBIF records into analysis-ready datasets. The package functions are organized into 8 steps. This basic example demonstrates the complete workflow using the built-in example dataset.

Each step below progressively filters records through taxonomic, quality, and coordinate checks, so the dataset shrinks from the raw download to a high-quality, analysis-ready set.

library(VasGBIF)
library(data.table)
VasGBIF_summary <- list()

Import Records

import_records() reads a GBIF occurrence download ZIP (‘SIMPLE_CSV’ or Darwin Core Archive), extracts the occurrence table, and returns an "import" data.table of the fields required by the workflow. No records are filtered at this stage — the raw issue column is preserved for the next step.

The built-in example dataset is a GBIF download of Medicago sativa (preserved specimens with coordinates, obtained 23 July 2026): https://doi.org/10.15468/dl.nt5exp.

gbif_file <- system.file(
  "extdata", "0003386-260721160103020.zip",
  package = "VasGBIF"
)
occ_import <- import_records(path = gbif_file)
VasGBIF_summary$initial_records <- nrow(occ_import)
VasGBIF_summary$initial_taxa <- uniqueN(occ_import$scientificName)
print(occ_import)

The returned "import" object is a data.table with the GBIF fields used by the workflow (gbifID coerced to character, plus the raw issue column).

Check Taxon Name

check_taxon() submits species- and infraspecific-rank names to the Taxonomic Name Resolution Service (TNRS; Boyle et al. 2013) for resolution against the World Checklist of Vascular Plants (WCVP) or World Flora Online (WFO). Synonyms are resolved to accepted names; records that fail the match-score threshold or lack an accepted/synonym status are excluded from the downstream table and reported in the summary for manual review.

taxa_checked <- check_taxon(occ_import = occ_import, accuracy = 0.85)
VasGBIF_summary$after_taxon_resolved <- nrow(taxa_checked$occ_taxa_checked)
VasGBIF_summary$final_taxa <- uniqueN(taxa_checked$occ_taxa_checked$Accepted_name)
print(taxa_checked)

The occ_taxa object contains:

  • occ_taxa_checked: records with resolved taxonomy
  • summary: one row per submitted name, for reviewing match quality
  • runtime: execution time

Customized Filter

customized_filter() joins the imported records with the resolved taxonomy and the parsed issue flags, then applies the enabled filter rules to retain only high-quality records. By default the countryCode, coordinateUncertainty (<= 10,000 m), and gbif_issues_max (<= 5) rules are enabled; date, identifiedBy, and recordedBy are opt-in. Every step is recorded in the per-rule summary table.

filtered <- customized_filter(
  occ_import = occ_import,
  taxa_checked = taxa_checked
)
VasGBIF_summary$after_customized_filter <- nrow(filtered$occ_filtered)
print(filtered)

The customFiltered object contains:

  • occ_filtered: the filtered occurrence table
  • occ_marked: the records failed customized quality rules
  • summary: per-rule rule, dropped, and remaining counts

Clean Coordinates

par_clean_coordinates() validates coordinates with CoordinateCleaner (Zizka et al. 2019) to flag spatial errors such as centroids, capitals, marine coordinates, and zero coordinates, splitting records into cleaned and problematic tables. Validation is parallelized across user-specified threads.

cleaned_coordinates <- par_clean_coordinates(
  input = filtered$occ_filtered,
  species = 'Accepted_name_id',
  latitude = 'decimalLatitude',
  longitude = 'decimalLongitude',
  threads = 8,tests = c("capitals", "centroids", "equal", "gbif", "institutions", "outliers", "seas", "zeros"))
VasGBIF_summary$cleaned <- nrow(cleaned_coordinates$CoordinateCleaned)
VasGBIF_summary$problematic <- nrow(cleaned_coordinates$CoordinateProblematic)
print(cleaned_coordinates)

The CoordinateRefined object contains:

  • CoordinateCleaned: records with valid coordinates
  • CoordinateProblematic: records failing one or more CoordinateCleaner tests (including those lack coordinates)
  • runtime: execution time

Detect Native Status

detect_native_coord() and detect_native_country() match each record against WCVP distribution data (the internal Distributions dataset) via WGSRPD Level 3 areas to classify it as native, introduced, extinct, location_doubtful, or unknown. Records with validated coordinates are matched spatially by detect_native_coord(); records without coordinates or failed with cleaned_coordinates() are matched through their country code by detect_native_country(). Every classification records how it was obtained in native_status_source.

native_detected_coord <- detect_native_coord(
  input = cleaned_coordinates$CoordinateCleaned,
  species = 'Accepted_name',
  latitude = 'decimalLatitude',
  longitude = 'decimalLongitude', buffer_km = 10, buffer_chunk_size = 2000)
native_detected_country <- detect_native_country(
  input = cleaned_coordinates$CoordinateProblematic,
  country = 'countryCode',
  species = 'Accepted_name')
native_detected <- rbind(
  native_detected_coord,
  native_detected_country[is.na(decimalLatitude) | is.na(decimalLongitude),],
  use.names = TRUE,
  fill = TRUE
)
VasGBIF_summary$native <- native_detected[native_status == "native", .N]
VasGBIF_summary$introduced <- native_detected[native_status == "introduced", .N]
VasGBIF_summary$extinct <- native_detected[native_status == "extinct", .N]
VasGBIF_summary$location_doubtful <- native_detected[
  native_status == "location_doubtful", .N
]
VasGBIF_summary$unknown <- native_detected[native_status == "unknown", .N]
print(native_detected)

The nativeDetected objects are data.tables keyed by gbifID. Each retains every column of the input records and appends LEVEL3_COD, native_status, native_status_source, and buffered. The spatial result (native_detected_coord) carries coordinates for every record and can be passed straight to export_records() and map_records() without joining back to cleaned_coordinates.

Map Visualiztion

map_records() renders the records on an interactive map via mapview, with geohash-based decluttering and colour-coding by native status.

map_records(
  native_detected_coord = native_detected_coord,
  precision = 3,
  cex = 3
)

Export Records

export_records() writes the classified records to disk as two gzip-compressed CSV files:

  • all_records.csv.gz: all classified records with validated coordinates, with their native status
  • native_records.csv.gz: the subset classified as native
export_records(
  native_detected_coord = native_detected_coord,
  native_detected_country = native_detected_country,
  export_path = getwd()
)

Summary

The summary statistics provide a quantitative overview of data reduction through the pipeline:

VasGBIF_summary |>
  as.data.frame() |>
  t()
Statistic Description
initial_records Total GBIF occurrence records imported
initial_taxa Unique scientific names before resolution
after_taxon_resolved Records retained after TNRS resolution (score and status thresholds)
after_customized_filter Records retained after the quality filter rules
cleaned Records with valid coordinates after CoordinateCleaner
problematic Records failing one or more coordinate tests
native Records classified as native
introduced Records classified as introduced
extinct Records classified as extinct
location_doubtful Records classified as location_doubtful
unknown Records with no usable distribution evidence
final_taxa Unique accepted taxon names in the final dataset

The workflow progressively filters the dataset through taxonomic resolution, quality rules, and coordinate validation. The drop from initial_records to after_customized_filter reflects unresolved names and records failing the quality rules; the drop from after_customized_filter to cleaned reflects records that failed the coordinate tests being set aside (records without coordinates are not carried by clean_coordinates()); and the transition from initial_taxa to final_taxa reflects taxonomic resolution.

Performance

VasGBIF achieves its speed through:

  • C/C++ backends: core operations delegated to data.table, stringi, and terra
  • Vectorisation: issue-flag detection in extract_issues() and native-status lookups in detect_native_coord() and detect_native_country() process entire columns in compiled calls
  • Memory-efficient design: in-place modification (:=, set()) avoids unnecessary copies
  • Selective parallelisation: par_clean_coordinates() distributes CoordinateCleaner validation across threads

On a standard laptop, VasGBIF compiles one million occurrence records within 15 minutes.

References

GBIF.org (23 July 2026) GBIF Occurrence Download https://doi.org/10.15468/dl.nt5exp