
Apply custom quality filters to occurrence records
Source:R/customized_filter.R
customized_filter.RdJoins the outputs of the VasGBIF import and taxonomic-resolution steps into a single occurrence table, then progressively removes records according to a user-selected set of quality rules.
Usage
customized_filter(
occ_import = NA,
taxa_checked = NA,
filter_countryCode = TRUE,
filter_coordinateUncertainty = 10000,
filter_date = FALSE,
filter_identifiedBy = FALSE,
filter_recordedBy = FALSE,
filter_gbif_issues_max = 5
)Arguments
- occ_import
An
"import"data.tablereturned byimport_records().gbifIDandissueare always required; every other column is required only by the rule that reads it, so a column backing a disabled rule may be absent. The columns aredecimalLatitudeandcountryCodeforfilter_countryCode,coordinateUncertaintyInMetersforfilter_coordinateUncertainty,eventDate,month,year, anddayforfilter_date,identifiedBy, andrecordedBy.- taxa_checked
An
"occ_taxa"object returned bycheck_taxon().- filter_countryCode
Logical scalar. If
TRUE(default), removes records with no usable geographic information:decimalLatitudeisNAandcountryCodeisNAor empty. Records with either a coordinate or a country code are kept. When enabled, afilter_countryCodelogical column is added toocc_marked; see Per-rule verdict columns.- filter_coordinateUncertainty
Non-negative numeric scalar. Removes records whose
coordinateUncertaintyInMetersis strictly greater than the threshold. Defaults to10000. Records withNAor emptycoordinateUncertaintyInMetersare always kept - they do not participate in this rule. PassNULL,NA, or''to disable the rule. The value is validated when the function is called, before any work is done. When the rule is enabled, afilter_coordinateUncertaintylogical column is added toocc_marked.- filter_date
Logical scalar. If
TRUE, removes records for which all four date components (eventDate,month,year,day) are missing or empty. Defaults toFALSE.- filter_identifiedBy
Logical scalar. If
TRUE, removes records whoseidentifiedByis missing or contains no named identifier (see Collector junk detection). Defaults toFALSE.- filter_recordedBy
Logical scalar. If
TRUE, removes records whoserecordedByis missing or contains no named collector. Defaults toFALSE.- filter_gbif_issues_max
Non-negative numeric scalar. Removes records flagged with more GBIF issues than the threshold (
gbif_issues > filter_gbif_issues_max). Defaults to5. PassNULL,NA, or''to disable the rule. The value is validated when the function is called, before any work is done.Each of
filter_date,filter_identifiedBy,filter_recordedBy, andfilter_gbif_issues_maxadds a logical column named after the argument toocc_markedwhen it is enabled; see Per-rule verdict columns.
Value
An object of class "customFiltered", implemented as a named list
with three elements:
occ_filtered: the records that passed every applied test, as adata.tablewith the occurrence and joined columns only - no verdict columns.summary: adata.tablewith columnsrule,dropped,remaining,failed, andonly_failed_here, giving for each applied step (thetaxon_resolvedjoin plus each enabled rule) how many records it removed, how many remained, and how many it rejects under the independent and exclusive readings described in Per-rule verdict columns.occ_marked: the records excluded by at least one applied test, reduced togbifIDand one verdict column per applied test (see Per-rule verdict columns). Every such record fails at least one of them, so its verdict columns contain at least oneFALSE. Together withocc_filtered, this table accounts for every record inocc_import.
A print() method for class "customFiltered" displays how many records
were kept and excluded, and the per-rule summary.
Details
Joining the inputs
The two inputs are joined by gbifID. For memory efficiency the joins are
performed in place on a single defensive copy of occ_import:
copy() is made once and each join adds columns via :=, instead of
materialising a fresh full-width table per join. The caller's occ_import
is never modified. Rows are not deleted during the join or the rules; every
criterion accumulates into a logical mask and a single row subset is
applied at the end. Peak memory is therefore close to the input plus one
working copy, regardless of how many rules are enabled.
check_taxon() already removes records that fail the accuracy threshold,
whose Taxonomic_status is neither "Accepted" nor "Synonym", or that
resolve to a genus-level or unranked accepted name, so occ_taxa_checked
contains only fully resolved records. Rows absent from occ_taxa_checked
are dropped via the keep mask instead of carrying NA taxonomy through the
rest of the pipeline. The number removed is recorded in summary under the
rule name taxon_resolved.
extract_issues() is called internally on occ_import and returns exactly
one row per imported record, so the issue join is one-to-one and cannot
change the row count. The function verifies this and stops if any record
lacks an issue count. The raw issue column is removed from occ_import
and replaced by gbif_issues, the per-record issue count.
Filter rules
Every applied test inspects the full table on its own terms rather than only
the records left by the preceding tests, and its verdict is stored as a
logical column named after the test. TRUE means the record passed that
test. By default countryCode, coordinateUncertainty, and
gbif_issues_max are enabled; date, identifiedBy, and recordedBy are
disabled. Numeric thresholds are validated before any work is done, so an
invalid value fails immediately.
The always-on taxon_resolved test is the first one applied. It is not a
selectable rule: check_taxon() only retains records whose name resolved, so
records absent from taxa_checked$occ_taxa_checked receive NA Accepted_name
at the join and fail this test. It always contributes a
filter_taxon_resolved column.
countryCode: drop records withNAlatitude andNA/emptycountryCode.coordinateUncertainty: drop records withcoordinateUncertaintyInMeters > threshold; records withNAor empty uncertainty are kept.date: drop records witheventDate,month,year, anddayall missing.identifiedBy/recordedBy: drop records whose value is junk (see below).gbif_issues_max: drop records withgbif_issues > threshold. The one-to-one issue join guarantees every record carries an issue count, so no record is exempt from this rule.
Per-rule verdict columns
Each applied test contributes a logical column named after it:
filter_taxon_resolved (always) plus filter_countryCode,
filter_coordinateUncertainty, filter_date, filter_identifiedBy,
filter_recordedBy, and filter_gbif_issues_max for the selected rules.
TRUE marks a record that passed that test. A disabled rule contributes no
column, so the output width depends on which rules were selected. The columns
are carried by occ_marked, where they identify which tests rejected each
excluded record; occ_filtered holds only occurrence and joined columns.
Because each rule is evaluated independently, one record can fail several
tests at once and is then counted by each of them. The summary table makes
this explicit:
failedis the number of records that test rejects on its own terms; these counts overlap and sum to more than the number of records removed.only_failed_hereis the number of records that test rejects and no other test does; these counts are disjoint but incomplete, since a record failing several tests is credited to none of them.droppedandremainingremain the sequential attribution: each removed record is credited to the first enabled test that rejected it, sodroppedsums exactly to the number of records removed.
The split of the output reflects the same distinction. occ_filtered holds
the records that passed every applied test, with their occurrence and joined
columns and no verdict columns; occ_marked holds the rest, reduced to
gbifID and the verdict columns. Because every applied test has a column,
each marked record carries at least one FALSE and shows exactly which tests
rejected it - a record excluded only because its taxon name did not resolve
has FALSE in filter_taxon_resolved alone.
Collector junk detection
The identifiedBy and recordedBy rules are identical in strictness. A
value is treated as junk - and the record removed - when it is missing or
empty, or when it matches a curated set of "no named person" patterns while
containing no name separator.
The keyword list covers English (unknown, anonymous, unnamed,
unidentified, unrecorded, incognito), other languages
(desconocido, desconhecido, anonimo and its accented Spanish and
Portuguese variants,
sin nombre, sem nome, inconnu, anonyme, unbekannt, anonym,
and four Chinese terms meaning "unknown", "unnamed", "anonymous" and
"no details"), and whole-value patterns such as s.n., n/a, et al.,
and no collector.
Name separators (,, ;, &, +, and, plus the full-width comma,
full-width semicolon, and ideographic enumeration comma used in CJK text)
protect values that mix a keyword with a real name, e.g.
"Unknown; Jongmans WJ" or "Collector(s): Eric Sundell, unknown" are
kept because a named person is present.
Word-boundary matching means the Chinese keywords only match standalone values; a longer phrase that merely begins with one of them (for instance the Chinese for "unknown collector") is not removed. This is deliberately conservative: without boundaries, a real name that happens to contain a keyword could be wrongly dropped.
See also
import_records()andcheck_taxon()for the two inputs.extract_issues()for the issue count behind thegbif_issues_maxrule.
Examples
if (FALSE) { # interactive()
gbif_file <- system.file(
"extdata",
"0003386-260721160103020.zip",
package = "VasGBIF"
)
occ <- import_records(path = gbif_file)
taxa_checked <- check_taxon(occ_import = occ, accuracy = 0.85)
filtered <- customized_filter(
occ_import = occ,
taxa_checked = taxa_checked,
filter_date = TRUE,
filter_identifiedBy = TRUE,
filter_recordedBy = TRUE
)
filtered
filtered$summary
# The kept records carry the occurrence and joined columns only:
names(filtered$occ_filtered)
# The excluded records carry `gbifID` and one verdict column per applied
# test, so each shows exactly which tests rejected it:
names(filtered$occ_marked)
head(filtered$occ_marked)
# Disable the coordinate-uncertainty rule (NULL / NA / '' all work). The
# rule contributes no verdict column:
filtered_loose <- customized_filter(
occ_import = occ,
taxa_checked = taxa_checked,
filter_coordinateUncertainty = NULL
)
"filter_coordinateUncertainty" %in% names(filtered_loose$occ_marked)
}