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)
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)
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:
Limit-offset: mediante el índice del primer elemento a
obtener (offset, default 0) y el número de elmentos a
obtener (limit).
Cursor-based: mediante el ID único (cursor)
del elemento a obtener primero, y un número de elmentos a
obtener.
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