---
title: "Inspecting a name list with inspect()"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Inspecting a name list with inspect()}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  eval = FALSE
)
```

This vignette shows how to screen a species list for the names that need
attention before analysis. A field list arrives as a column of strings, and
some of those strings are wrong: a genus is misspelled, an animal sits in a list
of plants, a synonym slipped in from an old data sheet, the same species appears
under two spellings. `taxify()` resolves what it can and returns a row for every
name, matched or not, so the problems are spread through a wide table.
`inspect()` returns one row per anomalous name, ordered most-notable first, each
labelled with what stands out and, where known, the name to use instead. Clean
names are dropped, so a short report means a clean list.

1. **Screen** the raw names against the genus register and the rest of the list
   with `inspect()`.
2. **Match** them to pick up typos, synonyms and ambiguity, with
   `backbones = TRUE` or by piping in a `taxify()` result.
3. **Place** the matched species against a declared `region`, `coords` or
   `range`.
4. **Filter** the report to the rows that need action with `min_tier`.
5. **Apply** the confident `suggestion`s and re-run `taxify()` on the corrected
   list.

## Example

```{r, eval = TRUE, message = FALSE, warning = FALSE}
library(taxify)
```

### A first look, without matching

By default `inspect()` runs only the checks that need no backbone; it matches
nothing unless asked. On a plain character vector it asks the genus register
whether each genus is a real one and compares each name against the rest of the
batch. Both are fast and offline.

```{r}
names <- c(
  "Quercus robur",
  "Panthera leo",         # an animal among plants
  "Bogusia fakensis",     # not a real genus
  "Festuca rubra",
  "Festuca rubra",
  "Festuca rubraa",       # one stray letter
  "Pinus sylvestris",
  "Pinus abies"           # a synonym of Picea abies
)

inspect(names)
```

```
#> ── taxify inspection ──────────────────────────────────────
#>   8 names inspected  |  3 with anomalies
#>   backbones: none (register + list checks only)
#>   unresolved: 1   review: 2
#>   ────────────────────────────────────────────────────────────
#>   [unresolved] Bogusia fakensis  ->  ?               genus 'Bogusia' is not in the taxonomic register
#>   [review    ] Festuca rubraa    ->  Festuca rubra   near-duplicate of more frequent 'Festuca rubra'
#>   [review    ] Panthera leo      ->  ?               animalia outlier (list is mostly plantae)
```

Three names surface. *Bogusia fakensis* uses a genus no backbone recognises.
*Festuca rubraa* is one letter off *Festuca rubra*, which appears twice in the
same list, so it looks like a slip of that name. *Panthera leo* is the lone
animal among plants, and a cross-kingdom homonym typo often shows up exactly
this way.

*Pinus abies* slips past this first pass. It is a well-formed binomial, and its
synonymy with *Picea abies* surfaces only when a backbone is matched. That
check is opt-in.

### Turning on matching

To pick up typos, synonyms, and ambiguity, let `inspect()` match. The
simplest route is `backbones = TRUE`, which runs the names through every
installed backbone and records which ones it used in the report header.

```{r}
inspect(names, backbones = TRUE)
```

```
#> ── taxify inspection ──────────────────────────────────────
#>   8 names inspected  |  4 with anomalies
#>   backbones: COL, GBIF, ITIS
#>   unresolved: 1   review: 2   note: 1
#>   ────────────────────────────────────────────────────────────
#>   [unresolved] Bogusia fakensis  ->  ?              genus 'Bogusia' is not in the taxonomic register
#>   [review    ] Festuca rubraa    ->  Festuca rubra  likely misspelling; near-duplicate of more frequent 'Festuca rubra'
#>   [review    ] Panthera leo      ->  Panthera leo   animalia outlier (list is mostly plantae)
#>   [note      ] Pinus abies       ->  Picea abies    outdated synonym
```

Now *Pinus abies* is recognised as a synonym and resolved to *Picea abies*,
and *Festuca rubraa* carries both its list-context label and the fuzzy `typo`
label that confirms it. *Panthera leo* now matches in GBIF, so the
candidate-typo label drops. It stays a kingdom-group outlier in a plant list.

If the list is already matched, inspect the result instead of asking
`inspect()` to match again. This reuses the exact backbone, region, and options
of the original call.

```{r}
taxify(names, backbone = c("col", "gbif")) |>
  inspect()
```

The two routes return the same kind of report. Pass `backbones = TRUE` for a
quick standalone check; pipe a result in when matching is already part of the
workflow.

### Geographic checks

When a list is regionally coherent, a species whose range sits elsewhere is
worth a second look. With a declared `region`, `inspect()` flags matched
species that WCVP does not record there.

```{r}
alpine <- taxify(c("Gentiana lutea", "Primula veris", "Banksia serrata")) |>
  inspect(region = "Europe")
```

```
#> ── taxify inspection ──────────────────────────────────────
#>   3 names inspected  |  1 with anomalies
#>   backbones: COL
#>   review: 1
#>   ────────────────────────────────────────────────────────────
#>   [review] Banksia serrata  ->  Banksia serrata  outside region per WCVP
```

*Banksia serrata* is a real, well-matched name, so nothing else flags it. It
is the geographic context that makes it stand out: an Australian shrub in a
European list. The same check accepts `coords` instead of a region name, and a
`range` argument to count only native or only introduced records. The
[geographic constraints vignette](https://gillescolling.com/taxify/articles/regions.html)
covers those inputs in full. The geographic arguments act on matched names, so
on a character vector inspected without matching they have no effect.

Without a declared region, the `out_of_range` check does the comparison from
the list itself: it finds the continents that hold the bulk of the matched
species and flags any species occurring on none of them. A globally spread
list needs too many continents to reach that bulk, fails the coherence test,
and flags nothing, so the check stays quiet unless the list is regionally
tight. Both geographic checks use WCVP, which covers vascular plants only.

### Reporting fewer rows

On a long list even the `note` rows add up. `min_tier` raises the floor so the
report keeps only what needs action.

```{r}
# only names that need a decision or a second look
inspect(names, backbones = TRUE, min_tier = "review")
```

```
#> ── taxify inspection ──────────────────────────────────────
#>   8 names inspected  |  3 with anomalies
#>   backbones: COL, GBIF, ITIS
#>   unresolved: 1   review: 2
#>   ────────────────────────────────────────────────────────────
#>   [unresolved] Bogusia fakensis  ->  ?              genus 'Bogusia' is not in the taxonomic register
#>   [review    ] Festuca rubraa    ->  Festuca rubra  likely misspelling; near-duplicate of more frequent 'Festuca rubra'
#>   [review    ] Panthera leo      ->  Panthera leo   animalia outlier (list is mostly plantae)
```

`min_tier = "review"` drops the `note`-tier synonym; `min_tier = "unresolved"`
would leave only the unknown name.

### Using the report in a cleaning script

The printed view is a convenience. The object underneath is an ordinary
data.frame with columns `input_name`, `suggestion`, `anomalies`, `tier`,
`reason`, `fuzzy_dist`, and `backbone`.

```{r}
report <- inspect(names, backbones = TRUE)

# the names that came back with a confident replacement
fixes <- report[!is.na(report$suggestion), c("input_name", "suggestion")]
fixes
```

```
#>      input_name    suggestion
#> 1 Festuca rubraa Festuca rubra
#> 2    Pinus abies   Picea abies
```

`tier` is an ordered factor (`note` < `review` < `unresolved`), so
`report[report$tier >= "review", ]` keeps the rows worth a person's time. A
typical loop is to run `inspect()`, apply the confident `suggestion`s, decide
the handful of `unresolved` names by hand, then re-run `taxify()` on the
corrected list.

## The labels

Each flagged name carries one or more labels in its `anomalies` column. The
list-only checks need no matching:

| Label | Meaning |
|---|---|
| `unknown` | The genus is not in the register, the union of every backbone's genera. No backbone recognises it. |
| `near_duplicate` | A near-identical spelling of a more frequent name in the same list, so probably a misspelling of it. Caught from the list alone, even for names no backbone holds. |
| `outlier_group` | The name's kingdom group is a tiny minority of an otherwise coherent list, typically a cross-kingdom homonym typo. |

The remaining labels read from a `taxify()` result and only appear once
matching has run:

| Label | Meaning |
|---|---|
| `typo` | Resolved only after fuzzy correction. The input most likely contains a spelling error; `suggestion` holds the corrected name. |
| `synonym` | The input is an outdated synonym, or an unplaced name whose basionym the backbone places; `suggestion` holds the current accepted name. |
| `case` | Resolved only after ignoring case. |
| `rank_fallback` | An infraspecific name the backbone does not carry, resolved to its species. |
| `ambiguous` | A homonym resolving to more than one accepted taxon. |
| `geographic` | The matched species is real but has no record in a declared region (vascular plants, via WCVP). |
| `out_of_range` | No region declared, yet the species' range falls outside the list's main continents. |

## Tiers

Every flagged row also gets a `tier`. A tier names the action a row needs
before analysis, from a required decision down to optional cleanup:

- `unresolved`: no usable name came back, so the row needs a decision before
  analysis. `unknown` lands here.
- `review`: a name is there, but its identity is uncertain. The identity
  checks (`typo`, `near_duplicate`, `rank_fallback`, `ambiguous`,
  `geographic`, `out_of_range`, `outlier_group`) land here.
- `note`: the name is correct, the change is optional cleanup. `case` and
  `synonym` land here.

An anomaly can be intended. A list may genuinely include one animal among
plants, or deliberately keep a synonym. The tier is a triage hint, so treat it
as a starting point.

## What needs a batch, and what needs the register

The list-context labels (`near_duplicate`, `outlier_group`, `out_of_range`)
weigh a name against the rest of the batch, so they cannot apply to a single
name. `inspect()` on one name warns and reports only the per-name labels.

```{r}
inspect("Quercus robber", backbones = TRUE)
#> Warning: list-context anomaly checks need a batch of names; with a single
#> name only the per-name checks run.
```

The register checks (`unknown`, and the register-derived `outlier_group`) need
the genus register installed, and the range checks need the WCVP range data.
A check that could not run is named on a `not checked` line in the report
header, so an empty report is never mistaken for a clean one, and the rest of
the checks still run.

## Where to go next

- [Geographic constraints](https://gillescolling.com/taxify/articles/regions.html)
  for the `region`, `coords`, and `range` arguments the geographic checks share
  with `taxify()`.

- [Fuzzy matching](https://gillescolling.com/taxify/articles/fuzzy-matching.html)
  for how the `typo` label is produced and tuned.

- [Getting started](https://gillescolling.com/taxify/articles/quickstart.html)
  for the matching pipeline `inspect()` sits on top of.
