---
title: "Cambios entre versiones de la base de plantas endemicas del Peru"
author: "ppendemic"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Cambios entre versiones de la base de plantas endemicas del Peru}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  fig.width = 7,
  fig.height = 4.5
)

library(dplyr)
library(tibble)
library(tidyr)

data("ppendemic_tab14", package = "ppendemic")
data("ppendemic_tab15", package = "ppendemic")
data("ppendemic_tab16", package = "ppendemic")
```

## Objetivo

Las tablas `ppendemic_tab14`, `ppendemic_tab15` y `ppendemic_tab16`
representan extracciones sucesivas de WCVP. Compararlas permite identificar:

* nombres que permanecen entre versiones;
* posibles inclusiones y exclusiones;
* correcciones ortograficas o de terminacion;
* cambios de rango infraespecifico; y
* posibles transferencias entre generos.

Una inclusion o exclusion observada no implica necesariamente que una especie
haya sido descubierta o dejado de ser endemica. Tambien puede reflejar cambios
taxonomicos, nomenclaturales o de los criterios de distribucion usados por
WCVP. Por eso, esta viñeta distingue los cambios observados de su posible
interpretacion.

## Funciones para comparar versiones

La unidad de comparacion es `taxon_name`. Los nombres presentes solo en la
version nueva son inclusiones observadas y los presentes solo en la version
anterior son exclusiones observadas.

Para detectar posibles reemplazos se calcula una puntuacion conservadora entre
cada nombre retirado y cada nombre añadido. La puntuacion combina similitud
textual y coincidencias en epitetos, autoria, familia y año de publicacion.
Estos vinculos son candidatos para revision, no sinonimias confirmadas.

```{r comparison-functions}
candidate_replacements <- function(removed, added) {
  if (nrow(removed) == 0L || nrow(added) == 0L) {
    return(tibble::tibble())
  }

  same_val <- function(x, y) {
    !is.na(x) & !is.na(y) & x != "" & y != "" & x == y
  }

  name_sim <- function(x, y) {
    distance <- diag(adist(tolower(x), tolower(y)))
    lengths <- pmax(nchar(x), nchar(y))
    1 - distance / lengths
  }

  pairs <- dplyr::cross_join(
    removed %>% dplyr::rename_with(~ paste0(.x, "_old")),
    added %>% dplyr::rename_with(~ paste0(.x, "_new"))
  )

  candidates <- pairs %>%
    dplyr::mutate(
      similarity = name_sim(taxon_name_old, taxon_name_new),
      same_species = same_val(Species_old, Species_new),
      same_infraspecies = same_val(infraspecies_old, infraspecies_new),
      same_author = same_val(taxon_authors_old, taxon_authors_new),
      same_family = same_val(family_old, family_new),
      same_year = same_val(year_actual_old, year_actual_new),
      
      score = 0.40 * similarity +
        0.25 * same_species +
        0.10 * same_infraspecies +
        0.15 * same_author +
        0.05 * same_family +
        0.05 * same_year
    ) %>%
    dplyr::filter(score >= 0.50)

  if (nrow(candidates) == 0L) {
    return(tibble::tibble())
  }

  candidates %>%
    dplyr::mutate(
      interpretacion = dplyr::case_when(
        same_species & same_infraspecies & coalesce(infraspecific_rank_old != infraspecific_rank_new, FALSE) ~ "Posible cambio de rango",
        same_species & coalesce(Genus_old != Genus_new, FALSE) ~ "Posible transferencia de genero",
        similarity >= 0.85 & (same_author | same_year) ~ "Posible correccion ortografica",
        TRUE ~ "Posible reemplazo taxonomico"
      ),
      puntuacion = round(score, 3)
    ) %>%
    dplyr::select(
      exclusion_observada = taxon_name_old,
      inclusion_observada = taxon_name_new,
      familia = family_new,
      interpretacion,
      puntuacion
    ) %>%
    dplyr::arrange(dplyr::desc(puntuacion))
}

compare_versions <- function(old, new) {
  removed <- dplyr::anti_join(old, new, by = "taxon_name")
  added <- dplyr::anti_join(new, old, by = "taxon_name")
  candidates <- candidate_replacements(removed, added)

  linked_removed <- unique(candidates$exclusion_observada)
  linked_added <- unique(candidates$inclusion_observada)

  list(
    summary = tibble::tibble(
      version_anterior = unique(old$version),
      version_nueva = unique(new$version),
      registros_anteriores = nrow(old),
      registros_nuevos = nrow(new),
      inclusiones_observadas = nrow(added),
      exclusiones_observadas = nrow(removed),
      cambio_neto = registros_nuevos - registros_anteriores,
      posibles_reemplazos = nrow(candidates)
    ),
    candidates = candidates,
    probable_inclusions = added %>%
      dplyr::filter(!taxon_name %in% linked_added) %>%
      dplyr::select(taxon_name, family, year_actual),
    probable_exclusions = removed %>%
      dplyr::filter(!taxon_name %in% linked_removed) %>%
      dplyr::select(taxon_name, family, year_actual)
  )
}

comparison_14_15 <- compare_versions(ppendemic_tab14, ppendemic_tab15)
comparison_15_16 <- compare_versions(ppendemic_tab15, ppendemic_tab16)
```

## Magnitud de los cambios

```{r summary-table}
change_summary <- dplyr::bind_rows(
  comparison_14_15$summary,
  comparison_15_16$summary
)

knitr::kable(
  change_summary,
  caption = "Cambios observados y posibles reemplazos entre versiones."
)
```

El cambio neto debe interpretarse junto con las inclusiones y exclusiones
brutas. Por ejemplo, una correccion ortografica genera simultaneamente una
salida y una entrada aunque represente al mismo taxon.

```{r change-plot}
plot_values <- rbind(
  inclusiones = change_summary$inclusiones_observadas,
  exclusiones = -change_summary$exclusiones_observadas,
  cambio_neto = change_summary$cambio_neto
)

barplot(
  plot_values,
  beside = TRUE,
  names.arg = paste(
    change_summary$version_anterior,
    change_summary$version_nueva,
    sep = " a "
  ),
  col = c("#2E8B57", "#B22222", "#4169E1"),
  ylab = "Numero de registros",
  legend.text = rownames(plot_values),
  args.legend = list(x = "topleft", bty = "n")
)
abline(h = 0, col = "grey40")
```

## Posibles correcciones y cambios taxonomicos

Los siguientes pares no deben contarse automaticamente como nuevas especies o
perdidas de endemicidad. Comparten suficiente informacion para considerarlos
posibles reemplazos entre versiones.

```{r candidates-14-15}
knitr::kable(
  comparison_14_15$candidates,
  caption = "Posibles reemplazos entre V-14 y V-15."
)
```

```{r candidates-15-16}
knitr::kable(
  comparison_15_16$candidates,
  caption = "Posibles reemplazos entre V-15 y V-16."
)
```

Entre los patrones detectables se encuentran:

* cambios de terminacion, como `Eudema chacasensis` a
  `Eudema chacasense`;
* correcciones ortograficas, como `Senecio danal` a `Senecio danai`;
* cambios de rango, como `Peperomia nivalis var. nivalis` a
  `Peperomia nivalis f. nivalis`; y
* posibles transferencias de genero, como especies de `Lobivia` que aparecen
  posteriormente bajo `Echinopsis`.

Estos casos requieren validacion contra sinonimos, identificadores taxonomicos
estables o la historia nomenclatural de WCVP.

## Posibles inclusiones

Despues de retirar los candidatos a reemplazo, los nombres restantes son las
inclusiones con mayor probabilidad de representar incorporaciones a la lista.
La version V-16 concentra numerosas inclusiones publicadas recientemente.

```{r inclusion-summary}
inclusions_15_16 <- comparison_15_16$probable_inclusions

inclusion_families <- inclusions_15_16 %>%
  dplyr::count(family, name = "posibles_inclusiones") %>%
  dplyr::arrange(dplyr::desc(posibles_inclusiones)) %>%
  dplyr::slice_head(n = 15) %>%
  dplyr::rename(familia = family)

knitr::kable(
  inclusion_families,
  caption = "Familias con mas posibles inclusiones entre V-15 y V-16."
)
```

```{r recent-inclusions}
recent_inclusions <- inclusions_15_16 %>%
  dplyr::filter(!is.na(year_actual), year_actual >= 2024) %>%
  dplyr::arrange(dplyr::desc(year_actual)) %>%
  dplyr::slice_head(n = 25)

knitr::kable(
  recent_inclusions,
  caption = "Ejemplos de posibles inclusiones publicadas desde 2024."
)
```

## Posibles exclusiones

Los nombres retirados que no tienen un reemplazo candidato son posibles
exclusiones. Una exclusion puede deberse a que el taxon dejo de considerarse
aceptado o exclusivamente peruano, pero esa causa no puede determinarse solo
con estas tablas.

```{r exclusion-summary}
exclusions_15_16 <- comparison_15_16$probable_exclusions

exclusion_families <- exclusions_15_16 %>%
  dplyr::count(family, name = "posibles_exclusiones") %>%
  dplyr::arrange(dplyr::desc(posibles_exclusiones)) %>%
  dplyr::rename(familia = family)

knitr::kable(
  exclusion_families,
  caption = "Familias de las posibles exclusiones entre V-15 y V-16."
)
```

```{r exclusion-list}
knitr::kable(
  exclusions_15_16 %>% dplyr::arrange(family),
  caption = "Posibles exclusiones entre V-15 y V-16."
)
```

## Cambios netos por familia

El balance por familia ayuda a identificar grupos que requieren una revision
prioritaria. Este balance incluye tanto cambios taxonomicos como posibles
incorporaciones o exclusiones reales.

```{r family-deltas}
family_change <- dplyr::full_join(
  ppendemic_tab15 %>% dplyr::count(family, name = "v15"),
  ppendemic_tab16 %>% dplyr::count(family, name = "v16"),
  by = "family"
) %>%
  dplyr::mutate(
    v15 = tidyr::replace_na(v15, 0L),
    v16 = tidyr::replace_na(v16, 0L),
    change = v16 - v15
  ) %>%
  dplyr::arrange(dplyr::desc(abs(change)), family)

knitr::kable(
  head(family_change, 20),
  caption = "Mayores cambios absolutos por familia entre V-15 y V-16."
)
```

## Recomendaciones de interpretacion

1. Usar `taxon_name` para describir entradas y salidas observadas.
2. Revisar primero los posibles reemplazos antes de comunicar descubrimientos
   o perdidas.
3. Confirmar las posibles inclusiones y exclusiones con WCVP y sus
   identificadores estables.
4. No interpretar una exclusion como perdida biologica o extincion.
5. Reportar siempre las versiones y fechas comparadas.

El procedimiento presentado es reproducible y sirve como filtro inicial. Una
evaluacion taxonomica definitiva requiere fuentes externas que documenten
sinonimias, cambios de distribucion y decisiones nomenclaturales.
