Librerías necesarias para correr este tutorial:

library(httr)
library(jsonlite)
library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(stringr)

Descargar un conjunto de datos de < 1000 registros

Primero hay que definir una query the GraphQL para bajar los datos deseados.

La documentación de GraphQL y la Introducción a GraphQL y consultas a la API tienen los detalles, pero en resumen una query está compuesta por tipos y campos dentro de esos tipos. Puedes pensar en los tipos como los nombres de las tablas y los campos como las columnas de dichas tablas. Los registros serán las filas de datos de dichas tablas. GraphQL puede obtener campos asociados a un registro en diferentes tipos.

En la query de abajo los tipos son registros, proyecto, sitio, taxon, y para cada uno pedimos nos brinde solo ciertos campos de todos los posibles.

Los tipos pueden contener agrumentos, que se especifican dentro de (). Algunos de estos pueden ser obligatorios, como pagination, que sirve para decir cuántos elementos quieres obtener. El máximo que pueden hacerse por consulta son 1000. El ejemplo de abajo solo baja los 100 primeros resultados. En la última sección de este tutorial explicamos cómo definir pagination para bajar un número determinado, o todos, los elementos de un conjunto de datos.

Para explorar qué argumentos tiene cada tipo, puedes ir a la API que deseas consultar y buscar el nombre del tipo en la documentanción (menú Docs en la esquina superior derecha). Los argumentos obligatorios terminan en !.

Antes de intentar bajar desde R, recomendamos escribir la query en en la API graphiQL y asegurarse que funciona. Es decir, que te regresa los datos deseados como en el panel de la derecha en la imagen de arriba.

Una vez que tengas la query funcionando, deberás guardarla en R como un vector de caracteres:

(el ejemplo de abajo solo baja los 100 primeros resultados, en la última sección de este tutorial explicamos cómo definir pagination para bajar un número determinado, o todos, los elementos de un conjunto de datos).

my_query<- "{
  registros(pagination: {limit: 100, offset: 0}) {
    taxon(search: {field: taxon_id}) {
      genero
      especie
      raza
    }
    sitio(search: {field: id}) {
      latitud
      longitud
      altitud
      estado
    }
    
    proyecto
    procedencia
    fecha_colecta_observacion
    colector_observador
    coleccion
    numero_catalogo
    fecha_determinacion
    determinador
    licencia_uso
    referencias
    forma_citar
  }
}
"

También debemos definir la url del servidor GraphiQL al que debe hacerse el query, que es la misma url donde escribiste la query para ver que funcionara:

url<-"https://maices-siagro.conabio.gob.mx/api/graphql"

Para bajar los datos desde el servidor GraphQL y transformarlos a una tabla bonita dentro de R, utilizamos la función get_from_graphQL. Para poderla cargar a R, necesitas hacer source() al archivo “get_from_graphQL.R”, que en este ejemplo se asume se encuentra en el working directory de estas notas:

# load function
source("get_from_graphQL.R")

# check what it does
get_from_graphQL
## function (query, url) 
## {
##     result <- POST(url, body = list(query = query), encode = c("json"))
##     satus_code <- result$status_code
##     if (satus_code != 200) {
##         print(paste0("Oh, oh: status code ", satus_code, ". Check your query and that the server is working"))
##     }
##     else {
##         jsonResult <- content(result, as = "text")
##         errors <- grepl("errors*{10}", jsonResult)
##         if (errors == TRUE) {
##             print("Sorry :(, your data downloaded with errors, check your query and API server for details")
##         }
##         else {
##             readableResult <- fromJSON(jsonResult, flatten = T)
##             data <- as.data.frame(readableResult$data[1])
##             x <- str_match(colnames(data), "\\w*$")[, 1]
##             colnames(data) <- x
##             return(data)
##         }
##     }
## }

Ahora podemos correr la función (utilizando la query que habíamos definido anteriormente):

# get data
data<-get_from_graphQL(query=my_query, url= url)
head(data)

Descargar todos los datos (> 1,000)

Si el dataset es muy grande, probablemente no podamos descargarlo de una sola vez, por lo que será necesario utilizar paginación (pagination), es decir, descargarlo por partes. Este es un parámetro dentro de la query de graphQL. La paginación puede hacerse con:

Dentro de una query de Zendro la paginación tiene la sintaxis:

pagination:{limit:[integer], offset:[integer]}

Ver documentación de graphQL y este tutoral de paginación en GraphQL para más detalles.

En los ejemplos anteriores descargamos solo 100 elementos (pagination:{limit:100})) del conjunto de datos de las Colectas RG, pero en realidad la base completa es mucho mayor.

Para saber el número de elementos de un tipo, podemos hacer una query con la función count, si está disponible para el tipo del que deseamos saber cuántos elementos tiene. Podemos saber si esta función está disponible en la documentación (Docs, esquina superior derecha de la API).

En este tutorial nos interesa saber el número de registros, por lo tanto:

# query API with count function
no_records<-get_from_graphQL(query="{countRegistros}", url="https://maices-siagro.conabio.gob.mx/api/graphql")

# change to vector, we don't need a df
no_records<-no_records[1,1]
no_records
## [1] 26578

El límite de elementos que podemos consultar en una sola query a la API es de 1,000 y en este caso tenemos 26578. Por lo que descargaremos la base de 1,000 en 1,000 hasta cubrir el total de elementos.

El siguiente código calcula la paginación necesarios para bajar un determinado número de registros. Luego corre la función get_from_graphQL() dentro de un loop en cada página hasta obtener el total de registros deseados en una sola data frame.

# Define desired number of records and limit. Number of pages and offset will be estimated based on the number of records to download
no_records<- no_records # this was estimated above with a query to count the total number of records, but you can also manually change it to a custom desired number
my_limit<-1000 # max 1000
no_pages<-ceiling(no_records/my_limit)

## Define offseet.
# You can use the following loop:
# to calculate offset automatically basedon 
# on the number of pages needed.
my_offset<-0 # start in 0. Leave like this
for(i in 1:no_pages){ # loop to 
  my_offset<-c(my_offset, my_limit*i)
}

# Or you can define the offset manually 
# uncommenting the following line
# and commenting the loop above:
# offeset<-c(#manually define your vector) 

## create obtjet where to store downloaded data. Leave empity
data<-character()

##
## Loop to download the data from GraphQL using pagination
## 

for(i in c(1:length(my_offset))){

# Define pagination
    pagination <- paste0("limit:", my_limit, ", offset:", my_offset[i])
    
# Tell the user in which page we are processing:
    print(paste("processing pagination with", pagination))

# Define query looping through desired pagination:
my_query<- paste0("{
  registros(pagination: {",pagination, "}) {
    taxon(search: {field: taxon_id}) {
      genero
      especie
      raza
    }
    sitio(search: {field: id}) {
      latitud
      longitud
      altitud
      estado
    }
    proyecto
    procedencia
    fecha_colecta_observacion
    colector_observador
    coleccion
    numero_catalogo
    fecha_determinacion
    determinador
    licencia_uso
    referencias
    forma_citar
  }
}
")

# Get data and add it to the already created df
data<-rbind(data, get_from_graphQL(query=my_query, url="https://maices-siagro.conabio.gob.mx/api/graphql"))

#end of loop
}
## [1] "processing pagination with limit:1000, offset:0"
## [1] "processing pagination with limit:1000, offset:1000"
## [1] "processing pagination with limit:1000, offset:2000"
## [1] "processing pagination with limit:1000, offset:3000"
## [1] "processing pagination with limit:1000, offset:4000"
## [1] "processing pagination with limit:1000, offset:5000"
## [1] "processing pagination with limit:1000, offset:6000"
## [1] "processing pagination with limit:1000, offset:7000"
## [1] "processing pagination with limit:1000, offset:8000"
## [1] "processing pagination with limit:1000, offset:9000"
## [1] "processing pagination with limit:1000, offset:10000"
## [1] "processing pagination with limit:1000, offset:11000"
## [1] "processing pagination with limit:1000, offset:12000"
## [1] "processing pagination with limit:1000, offset:13000"
## [1] "processing pagination with limit:1000, offset:14000"
## [1] "processing pagination with limit:1000, offset:15000"
## [1] "processing pagination with limit:1000, offset:16000"
## [1] "processing pagination with limit:1000, offset:17000"
## [1] "processing pagination with limit:1000, offset:18000"
## [1] "processing pagination with limit:1000, offset:19000"
## [1] "processing pagination with limit:1000, offset:20000"
## [1] "processing pagination with limit:1000, offset:21000"
## [1] "processing pagination with limit:1000, offset:22000"
## [1] "processing pagination with limit:1000, offset:23000"
## [1] "processing pagination with limit:1000, offset:24000"
## [1] "processing pagination with limit:1000, offset:25000"
## [1] "processing pagination with limit:1000, offset:26000"
## [1] "processing pagination with limit:1000, offset:27000"

El resultado son los datos completos de la base:

head(data)
tail(data)
summary(data)
##    proyecto         procedencia        fecha_colecta_observacion
##  Length:26578       Length:26578       Length:26578             
##  Class :character   Class :character   Class :character         
##  Mode  :character   Mode  :character   Mode  :character         
##                                                                 
##                                                                 
##                                                                 
##                                                                 
##  colector_observador  coleccion         numero_catalogo    fecha_determinacion
##  Length:26578        Length:26578       Length:26578       Length:26578       
##  Class :character    Class :character   Class :character   Class :character   
##  Mode  :character    Mode  :character   Mode  :character   Mode  :character   
##                                                                               
##                                                                               
##                                                                               
##                                                                               
##  determinador       licencia_uso       referencias        forma_citar       
##  Length:26578       Length:26578       Length:26578       Length:26578      
##  Class :character   Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character   Mode  :character  
##                                                                             
##                                                                             
##                                                                             
##                                                                             
##     genero            especie              raza              latitud     
##  Length:26578       Length:26578       Length:26578       Min.   :14.69  
##  Class :character   Class :character   Class :character   1st Qu.:17.07  
##  Mode  :character   Mode  :character   Mode  :character   Median :19.25  
##                                                           Mean   :19.59  
##                                                           3rd Qu.:20.63  
##                                                           Max.   :32.08  
##                                                                          
##     longitud          altitud          estado         
##  Min.   :-115.95   Min.   :   2.0   Length:26578      
##  1st Qu.:-101.06   1st Qu.: 526.2   Class :character  
##  Median : -98.26   Median :1541.0   Mode  :character  
##  Mean   : -98.26   Mean   :1357.9                     
##  3rd Qu.: -95.93   3rd Qu.:2106.0                     
##  Max.   : -86.83   Max.   :3310.0                     
##                    NA's   :8