Using the iNaturalist API to detect novel records

How many never-before recorded species did we have in Uruguay in 2023?

This time, I’m interested in detecting if any of the records uploaded to iNaturalist in Uruguay during the past year, 2023, belong to new species for NaturalistaUY (our national site) or for the iNaturalist global platform. To answer this, I once again explored the iNaturalist API. If you want to know more about my first experiment with the API, see my previous post How many users in NaturalistaUY are Uruguayan?.

So, I need to know if any record is:

  • new for iNaturalist, i.e., a species recorded in Uruguay in 2023 that has no previous records in the platform.
  • new for NaturalistaUY: i.e., a species recorded in Uruguay in 2023 that has been recorded before in the platform, but has no previous records in Uruguay.

The API has a call for taxa which takes a list of species or ids (taxon_id) and returns information about them, such as taxonomic name and, lucky for us, the number of observations the taxa has (observations_count). We will use this to get the numbers for ‘species’ (as taxon rank) we had in Uruguay during 2023.

To do it, we first create the function getiNatTaxonRank which takes a taxon_id as an argument, and returns the taxon_name, taxon_rank (e.g., species, family, subspecies), and observations_count for the taxon.

library(httr)
library(jsonlite)
library(knitr)
library(tidyverse)

getiNatTaxonRank <- function(taxon_id){

  taxaRanks <- tibble(taxon_name = character(),
                      taxon_id = numeric(),
                      taxon_rank = character(),
                      observations_count = numeric())

  num_results = 0 # used to put the API to sleep and print on the console the num

  for (taxon_id_i in taxon_id) {  

    if ((num_results %% 10) + 10 == 10) {
      Sys.sleep(10) # every 10 calls, the code stops for 10
    }

    call_url <- str_glue('https://api.inaturalist.org/v1/taxa/',
                         '{taxon_id_i}')

    get_json_call <- GET(url = call_url) %>%
      content(as = "text") %>% fromJSON(flatten = TRUE)

    results <- as_tibble(get_json_call$results)

    taxaRanks_i <- tibble(taxon_name = results$name,
                          taxon_id = taxon_id_i,
                          taxon_rank = results$rank,
                          observations_count = results$observations_count)

    taxaRanks <- rbind(taxaRanks, taxaRanks_i)
    num_results <- num_results + 1
    cat(num_results, '\n')
  }
  return(taxaRanks)
}

I will download all the records on 2023 from here naturalista.uy/observations/export, using the following URL query: quality_grade=any&identifications=any&place_id=7259&verifiable=true&d1=2023-01-01&d2=2023-12-31. And then read the file.

observations_2023 <-read_csv('datos/observations-2023-UY.csv')

To use the function we will get a unique list of IDs (taxa_list).

taxa_list <- observations_2023 %>%
  filter(!is.na(taxon_id)) %>%
  distinct(taxon_id) %>% pull(taxon_id)

We then run the function.

taxonRank_obs_may_2023 <- getiNatTaxonRank(taxa_list)

Now, let’s reply our questions.

  1. new for iNaturalist, i.e., a species recorded in Uruguay in 2023 that has no previous records in the platform.

To answer this we will assess the field observations_count. If the taxon has only 1 record, then we have a new species!

taxonRank_observations_2023 %>%
  filter(observations_count==1) %>%
  kable()
taxon_nametaxon_idtaxon_rankobservations_count
Helicotropis hookeri1441361species1
Eleocharis contracta1443001species1
Zelurus weyrauchi1443866species1
Myrceugenia mesomischa1405723species1
Charadrius collaris4807species1
Teloschistes cymbalifer864266species1
Prosopis nigra138685species1
Eumusonia livida750671species1
Mikania sulcata1004514species1
Pavonia orientalis1469019species1
Ozotoceros bezoarticus uruguayensis1449698subspecies1
Anodontites patagonica1143075species1

There are 12 species!1 We could also try if we have any second records.

taxonRank_observations_2023 %>%
  filter(observations_count==2) %>%
  kable()
taxon_nametaxon_idtaxon_rankobservations_count
Jacquemontia cataractae1440659species2
Tragia pinnata1353278species2
Phytophthora citrophthora360245species2
Mycotretus tigrinus1246922species2
Cylindera sinuosa1454925species2
Eurata strigiventris415132species2
Cypella charruana1376035species2

There are 7 species that were recorded for the second time in the platform on 2023.

  1. new for NaturalistaUY: i.e., a species recorded in Uruguay in 2023 that has been recorded before in the platform, but has no previous records in Uruguay.

To find out if we have any new records in Uruguay, we cannot use the observations_count because this value is relative to the entire platform. Thus, we will need to get all the records uploaded in Uruguay (yes, all the recods!). This is a huge database (more than 90.000 records) but can be downloaded from here naturalista.uy/observations/export, using the following URL query: quality_grade=any&identifications=any&place_id=7259&verifiable=true

obs_NatUY_before2023 <-read_csv('datos/observations-UY-before2023.csv')

Now, we will check all the taxon_id (to the species level taxon_rank) to see how many records they have in Uruguay before 2023.

speciesList_2023 <- taxonRank_observations_2023 %>%
  filter(taxon_rank=='species')

speciesList_2023 %>%
  filter(!taxon_id %in% obs_NatUY_before2023$taxon_id) %>%
  filter(observations_count>1) %>%
  arrange(observations_count) %>% head(n=25) %>% kable()
taxon_nametaxon_idtaxon_rankobservations_count
Jacquemontia cataractae1440659species2
Phytophthora citrophthora360245species2
Mycotretus tigrinus1246922species2
Cylindera sinuosa1454925species2
Jupunba langsdorffii1455009species3
Agalinis digitalis1444854species3
Epicauta zebra1349935species3
Baccharis megapotamica1197408species3
Serjania herteri1013428species3
Anodontites trapezea1051232species3
Neoconocephalus parvus1468635species3
Pavonia friesii1232616species3
Croton hilarii1448601species3
Scutia arenicola1442712species4
Odontesthes perugiae608799species4
Coccoloba warmingii1468088species5
Pseudomicrothorax agilis1445057species5
Mastophora catarina519650species5
Lippia hieraciifolia1352870species5
Mesembryanthemum theurkauffii483713species6
Nyttum beckeri1274613species7
Ludwigia hookeri1169172species7
Croton campestris1296071species7
Pluchea laxiflora1181589species8
Mikania ternata1004523species8

In total we have 484 species. Here’s a sample of the 25 species with 2 or more observations on iNaturalist (see observations_count).

And, that’s all !

Hope you find this useful too ✨


  1. WARNING: Please bear in mind that this exercise does not handle taxon changes very well, e.g., Prosopis nigra is considered novel when it has actually suffered a taxon name change. ↩︎

Florencia Grattarola
Florencia Grattarola
Postdoc Researcher

Uruguayan biologist doing research in macroecology and biodiversity informatics.