A connection with
description "https://api.crossref.org/works/10.1016/j.gloplacha.2022.103790"
class "url-libcurl"
mode "r"
text "text"
opened "closed"
can read "yes"
can write "no"
response <- httr::GET("http://api.open-notify.org/astros.json")# the content is in binary, so convert the response to an R data objectcontent <- jsonlite::fromJSON(rawToChar(response$content))# check number of peoplecontent$numberresponse <- httr::GET("http://api.open-notify.org/astros.json")# the content is in binary, so convert the response to an R data objectcontent <- jsonlite::fromJSON(rawToChar(response$content))# check number of peoplecontent$numberresponse <- httr::GET("http://api.open-notify.org/astros.json")# the content is in binary, so convert the response to an R data objectcontent <- jsonlite::fromJSON(rawToChar(response$content))# check number of peoplecontent$number
[Guided] How many people are on the International Space Station right now?
response <- httr::GET("http://api.open-notify.org/astros.json")# the content is in binary, so convert the response to an R data objectcontent <- jsonlite::fromJSON(rawToChar(response$content))# check the peoplecontent$people
name craft
1 Jasmin Moghbeli ISS
2 Andreas Mogensen ISS
3 Satoshi Furukawa ISS
4 Konstantin Borisov ISS
5 Oleg Kononenko ISS
6 Nikolai Chub ISS
7 Loral O'Hara ISS
[Guided] How many people are on the International Space Station right now?
Note that we can use the API call with the jsonlite::read_json function; however, we don’t get as much details from the call as we did with our previous approach. Also, this only works when the response returned is in JSON format.
# read contents from API callcontents <- jsonlite::read_json("http://api.open-notify.org/astros.json")# read contents from API callcontents <- jsonlite::read_json("http://api.open-notify.org/astros.json")
[Guided] How many people are on the International Space Station right now?
# read contents from API callcontents <- jsonlite::read_json("http://api.open-notify.org/astros.json")# extract details of the crewcontents$people |> purrr::map(\(x) tibble::tibble(name = x$name, craft = x$craft)) |> purrr::list_rbind()
# A tibble: 7 × 2
name craft
<chr> <chr>
1 Jasmin Moghbeli ISS
2 Andreas Mogensen ISS
3 Satoshi Furukawa ISS
4 Konstantin Borisov ISS
5 Oleg Kononenko ISS
6 Nikolai Chub ISS
7 Loral O'Hara ISS
[DIY] What is the current position of the International Space Station? Can you create a plot?
response <- httr::GET("http://api.open-notify.org/iss-now.json")# the content is in binary, so convert the response to an R data objectcontent <- jsonlite::fromJSON(rawToChar(response$content))# get timestampas.POSIXct(content$timestamp, origin ="1970-01-01")
We can create a helper function to extract multiple records:
iss_position <-function() { response <- httr::GET("http://api.open-notify.org/iss-now.json")# the content is in binary, so convert the response to an R data object content <- jsonlite::fromJSON(rawToChar(response$content))Sys.sleep(1) # pause for 1 second# extract each field and convert to the appropriate data type tibble::tibble(timestamp =as.POSIXct(content$timestamp, origin ="1970-01-01"),longitude =as.numeric(content$iss_position$longitude),latitude =as.numeric(content$iss_position$latitude) )}# map over the helper function X times, here 10iss_position_tbl <-seq_len(10) |># number of positions to extract purrr::map(\(x) iss_position()) |> purrr::list_rbind()iss_position <-function() { response <- httr::GET("http://api.open-notify.org/iss-now.json")# the content is in binary, so convert the response to an R data object content <- jsonlite::fromJSON(rawToChar(response$content))Sys.sleep(1) # pause for 1 second# extract each field and convert to the appropriate data type tibble::tibble(timestamp =as.POSIXct(content$timestamp, origin ="1970-01-01"),longitude =as.numeric(content$iss_position$longitude),latitude =as.numeric(content$iss_position$latitude) )}# map over the helper function X times, here 10iss_position_tbl <-seq_len(10) |># number of positions to extract purrr::map(\(x) iss_position()) |> purrr::list_rbind()iss_position <-function() { response <- httr::GET("http://api.open-notify.org/iss-now.json")# the content is in binary, so convert the response to an R data object content <- jsonlite::fromJSON(rawToChar(response$content))Sys.sleep(1) # pause for 1 second# extract each field and convert to the appropriate data type tibble::tibble(timestamp =as.POSIXct(content$timestamp, origin ="1970-01-01"),longitude =as.numeric(content$iss_position$longitude),latitude =as.numeric(content$iss_position$latitude) )}# map over the helper function X times, here 10iss_position_tbl <-seq_len(10) |># number of positions to extract purrr::map(\(x) iss_position()) |> purrr::list_rbind()iss_position <-function() { response <- httr::GET("http://api.open-notify.org/iss-now.json")# the content is in binary, so convert the response to an R data object content <- jsonlite::fromJSON(rawToChar(response$content))Sys.sleep(1) # pause for 1 second# extract each field and convert to the appropriate data type tibble::tibble(timestamp =as.POSIXct(content$timestamp, origin ="1970-01-01"),longitude =as.numeric(content$iss_position$longitude),latitude =as.numeric(content$iss_position$latitude) )}# map over the helper function X times, here 10iss_position_tbl <-seq_len(10) |># number of positions to extract purrr::map(\(x) iss_position()) |> purrr::list_rbind()iss_position <-function() { response <- httr::GET("http://api.open-notify.org/iss-now.json")# the content is in binary, so convert the response to an R data object content <- jsonlite::fromJSON(rawToChar(response$content))Sys.sleep(1) # pause for 1 second# extract each field and convert to the appropriate data type tibble::tibble(timestamp =as.POSIXct(content$timestamp, origin ="1970-01-01"),longitude =as.numeric(content$iss_position$longitude),latitude =as.numeric(content$iss_position$latitude) )}# map over the helper function X times, here 10iss_position_tbl <-seq_len(10) |># number of positions to extract purrr::map(\(x) iss_position()) |> purrr::list_rbind()iss_position <-function() { response <- httr::GET("http://api.open-notify.org/iss-now.json")# the content is in binary, so convert the response to an R data object content <- jsonlite::fromJSON(rawToChar(response$content))Sys.sleep(1) # pause for 1 second# extract each field and convert to the appropriate data type tibble::tibble(timestamp =as.POSIXct(content$timestamp, origin ="1970-01-01"),longitude =as.numeric(content$iss_position$longitude),latitude =as.numeric(content$iss_position$latitude) )}# map over the helper function X times, here 10iss_position_tbl <-seq_len(10) |># number of positions to extract purrr::map(\(x) iss_position()) |> purrr::list_rbind()iss_position <-function() { response <- httr::GET("http://api.open-notify.org/iss-now.json")# the content is in binary, so convert the response to an R data object content <- jsonlite::fromJSON(rawToChar(response$content))Sys.sleep(1) # pause for 1 second# extract each field and convert to the appropriate data type tibble::tibble(timestamp =as.POSIXct(content$timestamp, origin ="1970-01-01"),longitude =as.numeric(content$iss_position$longitude),latitude =as.numeric(content$iss_position$latitude) )}# map over the helper function X times, here 10iss_position_tbl <-seq_len(10) |># number of positions to extract purrr::map(\(x) iss_position()) |> purrr::list_rbind()iss_position <-function() { response <- httr::GET("http://api.open-notify.org/iss-now.json")# the content is in binary, so convert the response to an R data object content <- jsonlite::fromJSON(rawToChar(response$content))Sys.sleep(1) # pause for 1 second# extract each field and convert to the appropriate data type tibble::tibble(timestamp =as.POSIXct(content$timestamp, origin ="1970-01-01"),longitude =as.numeric(content$iss_position$longitude),latitude =as.numeric(content$iss_position$latitude) )}# map over the helper function X times, here 10iss_position_tbl <-seq_len(10) |># number of positions to extract purrr::map(\(x) iss_position()) |> purrr::list_rbind()
[DIY] What is the current position of the International Space Station? Can you create a plot?
Here is a subset of the position of the ISS, capture over a period of 15 minutes, seq_len(15 * 60):
timestamp
longitude
latitude
2024-02-18 20:17:19
-94.8448
11.1795
2024-02-18 20:17:20
-94.8072
11.1294
2024-02-18 20:17:21
-94.7697
11.0792
2024-02-18 20:17:22
-94.7134
11.0040
2024-02-18 20:17:23
-94.6759
10.9538
2024-02-18 20:17:24
-94.6385
10.9036
2024-02-18 20:17:26
-94.5823
10.8283
2024-02-18 20:17:27
-94.5448
10.7781
2024-02-18 20:17:28
-94.5074
10.7279
2024-02-18 20:17:29
-94.4513
10.6526
2024-02-18 20:34:45
-50.5240
-38.9132
2024-02-18 20:34:46
-50.4619
-38.9522
2024-02-18 20:34:47
-50.3997
-38.9911
2024-02-18 20:34:48
-50.3063
-39.0495
2024-02-18 20:34:49
-50.2439
-39.0883
2024-02-18 20:34:50
-50.1814
-39.1271
2024-02-18 20:34:52
-50.0876
-39.1852
2024-02-18 20:34:53
-50.0250
-39.2239
2024-02-18 20:34:54
-49.9623
-39.2625
2024-02-18 20:34:55
-49.8681
-39.3205
Note
Note that only the top 10 rows and the bottom 10 rows are displayed, there are an additional 880 rows of data ranging from 2024-02-18 20:17:30 to 2024-02-18 20:34:45.
[DIY] What is the current position of the International Space Station? Can you create a plot?
Plot the position of the ISS, here using the {leaflet} package:
# create icon from online imageiss_icon <- leaflet::makeIcon(iconUrl ="https://cdn-icons-png.flaticon.com/512/81/81959.png", iconWidth =15, iconHeight =15)# create plot of the positionsiss_position_tbl |> leaflet::leaflet() |> leaflet::addTiles() |> leaflet::addMarkers(lng =~longitude, lat =~latitude, label =~timestamp,icon = iss_icon )
[DIY] What is the current position of the International Space Station? Can you create a plot?
Plumber allows you to create a web API by merely decorating your existing R source code with roxygen2-like comments. (Schloerke and Allen, 2022)
R comments & decorators
Regular R comments are included with the #.
Roxygen2 comments allow the user to document their functions with the notation #' which is translated into R documentation.
{plumber} uses the notation #*.
{plumber}-ising a function / notation
Given a simple hello world function
# This function returns a messagehello_world <-function() {return("Hello XXIV SIMMAC!")}
{plumber}-ising a function / notation
Given a simple hello world function:
#* This function returns a message#* @get /hello_worldfunction() {return("Hello XXIV SIMMAC!")}#* This function returns a message#* @get /hello_worldfunction() {return("Hello XXIV SIMMAC!")}#* This function returns a message#* @get /hello_worldfunction() {return("Hello XXIV SIMMAC!")}
{plumber}-ised function!
Note
The change from # to #* for the comments.
The addition of @get /<function_name>.
The function name was relocated.
{plumber}-ising a function / notation: with params
Given a function to calculate the square of a number, a:
# This function calculates the square of `a`square <-function(a) {return(as.numeric(a) ^2)}
{plumber}-ising a function / notation: with params
Given a function to calculate the square of a number, a:
#* This function calculates the square of `a`#* @param a Numeric value.#* @get /square function(a) {return(as.numeric(a) ^2)}#* This function calculates the square of `a`#* @param a Numeric value.#* @get /square function(a) {return(as.numeric(a) ^2)}#* This function calculates the square of `a`#* @param a Numeric value.#* @get /square function(a) {return(as.numeric(a) ^2)}#* This function calculates the square of `a`#* @param a Numeric value.#* @get /square function(a) {return(as.numeric(a) ^2)}#* This function calculates the square of `a`#* @param a Numeric value.#* @get /square function(a) {return(as.numeric(a) ^2)}
{plumber}-ised function!
Note
The change from # to #* for the comments.
The addition of @param <param_name> <description. If the function had multiple params, the each of them will have to be documented using this format.
The addition of @get /<function_name>.
The function name was relocated.
Routing & Input
An incoming HTTP request must be “routed” to one or more R functions. Plumber has two distinct families of functions that it handles: endpoints and filters. (Schloerke and Allen, 2022)
Routing & Input: endpoints
An endpoint is an annotated function, like those we already saw:
#* This function returns a message#* @get /hello_worldfunction() {return("Hello XXIV SIMMAC!")}#* This function returns a message#* @get /hello_worldfunction() {return("Hello XXIV SIMMAC!")}
Note
This annotation specifies that this function is responsible for generating the response to any GET request to /hello_world.
Routing & Input: endpoints
The annotations that generate an endpoint include:
In order to send a response from R to an API client, the object must be “serialized” into some format that the client can understand. (Schloerke and Allen, 2022)
Some examples:
CSV: @serializer csv
JPEG: @serializer jpeg
JSON: @serializer json
PDF: @serializer pdf
PNG: @serializer png
Text: @serializer text
Rendering Output: serializers
Serializers can also be customised:
#* Example of custom graphical output#* @serializer png list(width = 400, height = 500)#* @get /function(){plot(1:10)}#* Example of custom graphical output#* @serializer png list(width = 400, height = 500)#* @get /function(){plot(1:10)}#* Example of custom graphical output#* @serializer png list(width = 400, height = 500)#* @get /function(){plot(1:10)}
Error handling:
#* Example of throwing an error#* @get /simplefunction() {stop("I'm an error!")}#* Generate a friendly error#* @get /friendlyfunction(res) { msg <-"Your request did not include a required parameter." res$status <-400# Bad requestlist(error = jsonlite::unbox(msg))}#* Example of throwing an error#* @get /simplefunction() {stop("I'm an error!")}#* Generate a friendly error#* @get /friendlyfunction(res) { msg <-"Your request did not include a required parameter." res$status <-400# Bad requestlist(error = jsonlite::unbox(msg))}#* Example of throwing an error#* @get /simplefunction() {stop("I'm an error!")}#* Generate a friendly error#* @get /friendlyfunction(res) { msg <-"Your request did not include a required parameter." res$status <-400# Bad requestlist(error = jsonlite::unbox(msg))}#* Example of throwing an error#* @get /simplefunction() {stop("I'm an error!")}#* Generate a friendly error#* @get /friendlyfunction(res) { msg <-"Your request did not include a required parameter." res$status <-400# Bad requestlist(error = jsonlite::unbox(msg))}
Building your own API
Building your own API
Warning
Before you attempt the following steps, you must execute the following command:
install.packages("plumber")
Inside RStudio, click on File > New File > Plumber API...:
Building your own API
Next, choose a name for your API (e.g., XXIV_SIMMAC) and click on Create:
Building your own API
A new R script (called plumber.R) should be displayed in your RStudio session.
# This is a Plumber API. You can run the API by clicking# the 'Run API' button above.## Find out more about building APIs with Plumber here:## https://www.rplumber.io/#library(plumber)#* @apiTitle Plumber Example API#* @apiDescription Plumber example description.#* Echo back the input#* @param msg The message to echo#* @get /echofunction(msg ="") {list(msg =paste0("The message is: '", msg, "'"))}
Note that a new button should have appeared at the top of your script options:
Clicking on this button will deploy your API locally.
Hands on session 2
I. Write a plumber function to use the Gapminder dataset to find the population of Costa Rica in 1982.
Note
Gapminder is a dataset of populations of various countries from 1952 - 2007. We will access it with the {gapminder} package. (Bryan, 2023)
Write a plumber function to allow a user to find out the population of any country during any year in the Gapminder dataset.
Write a plumber function to plot the population change of a user defined country.
I. Write a plumber function to use the Gapminder dataset to find the population of Costa Rica in 1982.
Where do we start?
We can start by creating a “simple” R function that gets the data we want!
# This function returns the population of Costa Rica in 1982pop_cr_1982 <-function() { pop_tbl <- gapminder::gapminder |> dplyr::filter(country =="Costa Rica") |> dplyr::filter(year ==1982)return(pop_tbl$pop)}# call our functionpop_cr_1982()
[1] 2424367
I. Write a plumber function to use the Gapminder dataset to find the population of Costa Rica in 1982.
Next, we can add the “{plumber} decorators” to transform this function.
#* This function returns the population of Costa Rica in 1982#* @get /pop_cr_1982 function() { pop_tbl <- gapminder::gapminder |> dplyr::filter(country =="Costa Rica") |> dplyr::filter(year ==1982)return(pop_tbl$pop)}#* This function returns the population of Costa Rica in 1982#* @get /pop_cr_1982 function() { pop_tbl <- gapminder::gapminder |> dplyr::filter(country =="Costa Rica") |> dplyr::filter(year ==1982)return(pop_tbl$pop)}#* This function returns the population of Costa Rica in 1982#* @get /pop_cr_1982 function() { pop_tbl <- gapminder::gapminder |> dplyr::filter(country =="Costa Rica") |> dplyr::filter(year ==1982)return(pop_tbl$pop)}#* This function returns the population of Costa Rica in 1982#* @get /pop_cr_1982 function() { pop_tbl <- gapminder::gapminder |> dplyr::filter(country =="Costa Rica") |> dplyr::filter(year ==1982)return(pop_tbl$pop)}
I. Write a plumber function to use the Gapminder dataset to find the population of Costa Rica in 1982.
After deploying the API, you can test it as you already learnt:
# the content is in binary, so convert the response to an R data objectcontent <- jsonlite::fromJSON(rawToChar(response$content))content
[1] 2424367
II. Write a plumber function to allow a user to find out the population of any country during any year in the Gapminder dataset.
Where do we start?
We can start by adapting our previous solution.
#* This function returns the population of Costa Rica in 1982#* @get /pop_cr_1982 function() { pop_tbl <- gapminder::gapminder |> dplyr::filter(country =="Costa Rica") |> dplyr::filter(year ==1982)return(pop_tbl$pop)}
II. Write a plumber function to allow a user to find out the population of any country during any year in the Gapminder dataset.
Where do we start?
We can start by adapting our previous solution.
#* This function returns the population for a `country` in a particular `year`#* @param country String with a country in the Gapminder dataset: https://doi.org/10.7910/DVN/GJQNEQ.#* @param year A numeric value with a year of data available in Gapminder dataset (1952 - 2007 in steps of 5 years).#* @get /pop_countryfunction(country, year) { pop_tbl <- gapminder::gapminder |> dplyr::filter(country ==!!country) |> dplyr::filter(year ==as.numeric(!!year))return(pop_tbl$pop)}#* This function returns the population for a `country` in a particular `year`#* @param country String with a country in the Gapminder dataset: https://doi.org/10.7910/DVN/GJQNEQ.#* @param year A numeric value with a year of data available in Gapminder dataset (1952 - 2007 in steps of 5 years).#* @get /pop_countryfunction(country, year) { pop_tbl <- gapminder::gapminder |> dplyr::filter(country ==!!country) |> dplyr::filter(year ==as.numeric(!!year))return(pop_tbl$pop)}#* This function returns the population for a `country` in a particular `year`#* @param country String with a country in the Gapminder dataset: https://doi.org/10.7910/DVN/GJQNEQ.#* @param year A numeric value with a year of data available in Gapminder dataset (1952 - 2007 in steps of 5 years).#* @get /pop_countryfunction(country, year) { pop_tbl <- gapminder::gapminder |> dplyr::filter(country ==!!country) |> dplyr::filter(year ==as.numeric(!!year))return(pop_tbl$pop)}#* This function returns the population for a `country` in a particular `year`#* @param country String with a country in the Gapminder dataset: https://doi.org/10.7910/DVN/GJQNEQ.#* @param year A numeric value with a year of data available in Gapminder dataset (1952 - 2007 in steps of 5 years).#* @get /pop_countryfunction(country, year) { pop_tbl <- gapminder::gapminder |> dplyr::filter(country ==!!country) |> dplyr::filter(year ==as.numeric(!!year))return(pop_tbl$pop)}#* This function returns the population for a `country` in a particular `year`#* @param country String with a country in the Gapminder dataset: https://doi.org/10.7910/DVN/GJQNEQ.#* @param year A numeric value with a year of data available in Gapminder dataset (1952 - 2007 in steps of 5 years).#* @get /pop_countryfunction(country, year) { pop_tbl <- gapminder::gapminder |> dplyr::filter(country ==!!country) |> dplyr::filter(year ==as.numeric(!!year))return(pop_tbl$pop)}#* This function returns the population for a `country` in a particular `year`#* @param country String with a country in the Gapminder dataset: https://doi.org/10.7910/DVN/GJQNEQ.#* @param year A numeric value with a year of data available in Gapminder dataset (1952 - 2007 in steps of 5 years).#* @get /pop_countryfunction(country, year) { pop_tbl <- gapminder::gapminder |> dplyr::filter(country ==!!country) |> dplyr::filter(year ==as.numeric(!!year))return(pop_tbl$pop)}
Note
Note the addition of the bang-bang operator, to “unquote” the values received for country and year. (Wickham, 2014)
II. Write a plumber function to allow a user to find out the population of any country during any year in the Gapminder dataset.
After deploying the API, you can test it as you already learnt:
The function URLencode changes special characters (like spaces) into the appropriate hexadecimal representation.
II. Write a plumber function to allow a user to find out the population of any country during any year in the Gapminder dataset.
Further improvements, the solution shown before does not handle (in a user friendly) common errors like:
entering a country that doesn’t exist in the Gapminder data
choosing a year outside those available
choosing a non-numeric value for the year
and probably others.
II. Write a plumber function to allow a user to find out the population of any country during any year in the Gapminder dataset.
Is there anything we can do to validate these fields?
Yes, of course! We can create a filter or perform these checks within the endpoint.
#* This filter checks for valid countries in the Gapminder dataset#* @filter validate_yearfunction(req, res) {# check if the calling endpoint has a year parameterif ("year"%in%names(req$args)) {# check if the given year is in the Gapminder dataset year <-as.numeric(req$args$year) gapminder_years <-unique(gapminder::gapminder$year)# do check status <-any(year %in% gapminder_years)if (!status) { msg <-paste0("The given year, ", year, ", it's not part of the Gapminder dataset. ","Please, try one of the following: ",paste0(gapminder_years, collapse =", ")) res$status <-400# Bad requestreturn(list(error = jsonlite::unbox(msg),valid_years = gapminder_years ) ) } } plumber::forward()}#* This filter checks for valid countries in the Gapminder dataset#* @filter validate_yearfunction(req, res) {# check if the calling endpoint has a year parameterif ("year"%in%names(req$args)) {# check if the given year is in the Gapminder dataset year <-as.numeric(req$args$year) gapminder_years <-unique(gapminder::gapminder$year)# do check status <-any(year %in% gapminder_years)if (!status) { msg <-paste0("The given year, ", year, ", it's not part of the Gapminder dataset. ","Please, try one of the following: ",paste0(gapminder_years, collapse =", ")) res$status <-400# Bad requestreturn(list(error = jsonlite::unbox(msg),valid_years = gapminder_years ) ) } } plumber::forward()}#* This filter checks for valid countries in the Gapminder dataset#* @filter validate_yearfunction(req, res) {# check if the calling endpoint has a year parameterif ("year"%in%names(req$args)) {# check if the given year is in the Gapminder dataset year <-as.numeric(req$args$year) gapminder_years <-unique(gapminder::gapminder$year)# do check status <-any(year %in% gapminder_years)if (!status) { msg <-paste0("The given year, ", year, ", it's not part of the Gapminder dataset. ","Please, try one of the following: ",paste0(gapminder_years, collapse =", ")) res$status <-400# Bad requestreturn(list(error = jsonlite::unbox(msg),valid_years = gapminder_years ) ) } } plumber::forward()}#* This filter checks for valid countries in the Gapminder dataset#* @filter validate_yearfunction(req, res) {# check if the calling endpoint has a year parameterif ("year"%in%names(req$args)) {# check if the given year is in the Gapminder dataset year <-as.numeric(req$args$year) gapminder_years <-unique(gapminder::gapminder$year)# do check status <-any(year %in% gapminder_years)if (!status) { msg <-paste0("The given year, ", year, ", it's not part of the Gapminder dataset. ","Please, try one of the following: ",paste0(gapminder_years, collapse =", ")) res$status <-400# Bad requestreturn(list(error = jsonlite::unbox(msg),valid_years = gapminder_years ) ) } } plumber::forward()}#* This filter checks for valid countries in the Gapminder dataset#* @filter validate_yearfunction(req, res) {# check if the calling endpoint has a year parameterif ("year"%in%names(req$args)) {# check if the given year is in the Gapminder dataset year <-as.numeric(req$args$year) gapminder_years <-unique(gapminder::gapminder$year)# do check status <-any(year %in% gapminder_years)if (!status) { msg <-paste0("The given year, ", year, ", it's not part of the Gapminder dataset. ","Please, try one of the following: ",paste0(gapminder_years, collapse =", ")) res$status <-400# Bad requestreturn(list(error = jsonlite::unbox(msg),valid_years = gapminder_years ) ) } } plumber::forward()}#* This filter checks for valid countries in the Gapminder dataset#* @filter validate_yearfunction(req, res) {# check if the calling endpoint has a year parameterif ("year"%in%names(req$args)) {# check if the given year is in the Gapminder dataset year <-as.numeric(req$args$year) gapminder_years <-unique(gapminder::gapminder$year)# do check status <-any(year %in% gapminder_years)if (!status) { msg <-paste0("The given year, ", year, ", it's not part of the Gapminder dataset. ","Please, try one of the following: ",paste0(gapminder_years, collapse =", ")) res$status <-400# Bad requestreturn(list(error = jsonlite::unbox(msg),valid_years = gapminder_years ) ) } } plumber::forward()}#* This filter checks for valid countries in the Gapminder dataset#* @filter validate_yearfunction(req, res) {# check if the calling endpoint has a year parameterif ("year"%in%names(req$args)) {# check if the given year is in the Gapminder dataset year <-as.numeric(req$args$year) gapminder_years <-unique(gapminder::gapminder$year)# do check status <-any(year %in% gapminder_years)if (!status) { msg <-paste0("The given year, ", year, ", it's not part of the Gapminder dataset. ","Please, try one of the following: ",paste0(gapminder_years, collapse =", ")) res$status <-400# Bad requestreturn(list(error = jsonlite::unbox(msg),valid_years = gapminder_years ) ) } } plumber::forward()}#* This filter checks for valid countries in the Gapminder dataset#* @filter validate_yearfunction(req, res) {# check if the calling endpoint has a year parameterif ("year"%in%names(req$args)) {# check if the given year is in the Gapminder dataset year <-as.numeric(req$args$year) gapminder_years <-unique(gapminder::gapminder$year)# do check status <-any(year %in% gapminder_years)if (!status) { msg <-paste0("The given year, ", year, ", it's not part of the Gapminder dataset. ","Please, try one of the following: ",paste0(gapminder_years, collapse =", ")) res$status <-400# Bad requestreturn(list(error = jsonlite::unbox(msg),valid_years = gapminder_years ) ) } } plumber::forward()}#* This filter checks for valid countries in the Gapminder dataset#* @filter validate_yearfunction(req, res) {# check if the calling endpoint has a year parameterif ("year"%in%names(req$args)) {# check if the given year is in the Gapminder dataset year <-as.numeric(req$args$year) gapminder_years <-unique(gapminder::gapminder$year)# do check status <-any(year %in% gapminder_years)if (!status) { msg <-paste0("The given year, ", year, ", it's not part of the Gapminder dataset. ","Please, try one of the following: ",paste0(gapminder_years, collapse =", ")) res$status <-400# Bad requestreturn(list(error = jsonlite::unbox(msg),valid_years = gapminder_years ) ) } } plumber::forward()}#* This filter checks for valid countries in the Gapminder dataset#* @filter validate_yearfunction(req, res) {# check if the calling endpoint has a year parameterif ("year"%in%names(req$args)) {# check if the given year is in the Gapminder dataset year <-as.numeric(req$args$year) gapminder_years <-unique(gapminder::gapminder$year)# do check status <-any(year %in% gapminder_years)if (!status) { msg <-paste0("The given year, ", year, ", it's not part of the Gapminder dataset. ","Please, try one of the following: ",paste0(gapminder_years, collapse =", ")) res$status <-400# Bad requestreturn(list(error = jsonlite::unbox(msg),valid_years = gapminder_years ) ) } } plumber::forward()}
II. Write a plumber function to allow a user to find out the population of any country during any year in the Gapminder dataset.
Let’s test if the new filter works, by requesting data for a year not in the Gapminder dataset:
# the content is in binary, so convert the response to an R data objectcontent <- jsonlite::fromJSON(rawToChar(response$content))content
$error
[1] "The given year, 2024, it's not part of the Gapminder dataset. Please, try one of the following: 1952, 1957, 1962, 1967, 1972, 1977, 1982, 1987, 1992, 1997, 2002, 2007"
$valid_years
[1] 1952 1957 1962 1967 1972 1977 1982 1987 1992 1997 2002 2007
III. Write a plumber function to plot the population change of a user defined country.
Where do we start?
We can start by adapting our previous solution.
#* This function returns the population for a `country` in a particular `year`#* @param country String with a country in the Gapminder dataset: https://doi.org/10.7910/DVN/GJQNEQ.#* @param year A numeric value with a year of data available in Gapminder dataset (1952 - 2007 in steps of 5 years).#* @get /pop_countryfunction(country, year) { pop_tbl <- gapminder::gapminder |> dplyr::filter(country ==!!country) |> dplyr::filter(year ==as.numeric(!!year))return(pop_tbl$pop)}
III. Write a plumber function to plot the population change of a user defined country.
Where do we start?
We can start by adapting our previous solution.
#* This function returns a plot of the change in population for a `country`, as per the Gapminder dataset#* @param country String with a country in the Gapminder dataset: https://doi.org/10.7910/DVN/GJQNEQ.#* @serializer png#* @get /pop_country_changefunction(country) { pop_tbl <- gapminder::gapminder |> dplyr::filter(country ==!!country) |> dplyr::select(year, pop)options(scipen=999) # Change number format on axesplot(pop_tbl, xlab ="Year", ylab ="Population")}#* This function returns a plot of the change in population for a `country`, as per the Gapminder dataset#* @param country String with a country in the Gapminder dataset: https://doi.org/10.7910/DVN/GJQNEQ.#* @serializer png#* @get /pop_country_changefunction(country) { pop_tbl <- gapminder::gapminder |> dplyr::filter(country ==!!country) |> dplyr::select(year, pop)options(scipen=999) # Change number format on axesplot(pop_tbl, xlab ="Year", ylab ="Population")}#* This function returns a plot of the change in population for a `country`, as per the Gapminder dataset#* @param country String with a country in the Gapminder dataset: https://doi.org/10.7910/DVN/GJQNEQ.#* @serializer png#* @get /pop_country_changefunction(country) { pop_tbl <- gapminder::gapminder |> dplyr::filter(country ==!!country) |> dplyr::select(year, pop)options(scipen=999) # Change number format on axesplot(pop_tbl, xlab ="Year", ylab ="Population")}#* This function returns a plot of the change in population for a `country`, as per the Gapminder dataset#* @param country String with a country in the Gapminder dataset: https://doi.org/10.7910/DVN/GJQNEQ.#* @serializer png#* @get /pop_country_changefunction(country) { pop_tbl <- gapminder::gapminder |> dplyr::filter(country ==!!country) |> dplyr::select(year, pop)options(scipen=999) # Change number format on axesplot(pop_tbl, xlab ="Year", ylab ="Population")}
III. Write a plumber function to plot the population change of a user defined country.
After deploying the API, you can test it as you already learnt:
# the content is in binary, so convert the response to an R data objectcontent <- png::readPNG(response$content)grid::grid.raster(content, height = grid::unit(5, "in"))
III. Write a plumber function to plot the population change of a user defined country.
Alternative, instead of returning a plot, we can return a data frame by updating the endpoint:
#* This function returns a data frame with the change in population for a `country`, as per the Gapminder dataset#* @param country String with a country in the Gapminder dataset: https://doi.org/10.7910/DVN/GJQNEQ.#* @get /pop_country_change_dffunction(country) { pop_tbl <- gapminder::gapminder |> dplyr::filter(country ==!!country) |> dplyr::select(year, pop)return(pop_tbl)}#* This function returns a data frame with the change in population for a `country`, as per the Gapminder dataset#* @param country String with a country in the Gapminder dataset: https://doi.org/10.7910/DVN/GJQNEQ.#* @get /pop_country_change_dffunction(country) { pop_tbl <- gapminder::gapminder |> dplyr::filter(country ==!!country) |> dplyr::select(year, pop)return(pop_tbl)}#* This function returns a data frame with the change in population for a `country`, as per the Gapminder dataset#* @param country String with a country in the Gapminder dataset: https://doi.org/10.7910/DVN/GJQNEQ.#* @get /pop_country_change_dffunction(country) { pop_tbl <- gapminder::gapminder |> dplyr::filter(country ==!!country) |> dplyr::select(year, pop)return(pop_tbl)}#* This function returns a data frame with the change in population for a `country`, as per the Gapminder dataset#* @param country String with a country in the Gapminder dataset: https://doi.org/10.7910/DVN/GJQNEQ.#* @get /pop_country_change_dffunction(country) { pop_tbl <- gapminder::gapminder |> dplyr::filter(country ==!!country) |> dplyr::select(year, pop)return(pop_tbl)}
III. Write a plumber function to plot the population change of a user defined country.
# the content is in binary, so convert the response to an R data objectcontent <- jsonlite::fromJSON(rawToChar(response$content))tibble::as_tibble(content)
III. Write a plumber function to plot the population change of a user defined country.
Alternative plot:
response <- httr::GET(URLencode("http://127.0.0.1:1234/pop_country_change_df?country=Costa Rica"))jsonlite::fromJSON(rawToChar(response$content)) |> tibble::as_tibble() |> ggplot2::ggplot() + ggplot2::geom_point(ggplot2::aes(x = year, y = pop /1E6)) + ggplot2::labs(x ="Year", y ="Population [millions]", title ="Population change of Costa Rica") + ggplot2::theme_bw()