A Developer’s Introduction to Plumber API-How to expose your R Models in a simple way.
By: Bansi Maddali
Tags: Deployment, Predictive Analytics, Api Development, R Programming, Machine Learning

Owing to the rising demand of AI/ML, the popularity of programming languages majorly used for Data Science is still growing day by day.

Majority of the Data Scientists/Analysts/Developers use Python and R with a visualization tool to build their data solutions.

Being an ML Developer my job is to take the raw code(code in jupyter) given by the analyst, optimize it, write some pipelines to reduce code overhead, save graphs, models programmatically and expose it as an REST API. In this process, majority of the people use Tensorflow 2.0/Pytorch to implement their solution, where as some people build solution entirely in R using the open source packages. Let’s compare R vs Python on very high level.

Python is an general purpose language, where as R is also a programming language designed only for statistics and visualization.(Excellent Visualizations!) Python has light-weight API interface, Flask, whereas R has a package called Plumber. In terms of API Functionality, both the frameworks are vey good, but remember R is single threaded. R has vast amount of algorithms and visualizations installed in a minute, whereas python has no built-in, but depends purely on frameworks, which needs a separate installation. Why does it matter? It matters in deploying the solutions, which I will try to cover in a separate blog.

The Plumber API — How to use it?

It is installed using simple command :

Sys.setenv(https_proxy="https://ip:port")

Sys.setenv(http_proxy="http://ip:port")

install.packages("plumber")

install.packages("logger")#It's a good idea to use explicit logger

install.packages("jsonlite")#simple json encoder/decoder

Sys.setenv() sets the system proxy for R process to download the packages. This applies only if you are using corporate machines, which obviously doesn’t allow downloading packages until proxy is explicitly specified.

Let’s start by predicting with a simple Regression model predict.R :

predictRegressionModel = function(dataset_path,model_path)

{ #Create an Object to send response to UI

responseToUi = NULL tryCatch(

{

log_info("Trying to predict using Linear Regression") #Here, db connections objects can passed

dataset = readr::read_csv(dataset_path) #All R Objects are saved in .rds format

model = read.rds(model_path)predicted_data = (model,dataset) responseToUi <<-(predictions=predicted_data,algorithmStatus = 'success')

log_success("Prediction Successfull") },

error = function(err)

{

log_error(err$message)

responseToUi <<- (predictions=NULL, algorithmStatus = 'failed')

}

return(responseToUi)

}

That is just a simple snippet, which can be extended to meet the requirements such as data validation, predictor validation etc.,.

mainFile.R

CORS Filtering needs to be enabled , for Angular UI to communicate, else plumber will deny requests by default from cross-origin.

# Enable CORS Filtering #' @filter cors

cors <- function(req,res)

{

res$setHeader("Access-Control-Allow-Origin","<ur-angular-ip(s)">)

if(req$REQUEST_METHOD == "OPTIONS")

{

res$setHeader("Access-Control-Allow-Origin","POST")

res$setHeader("Access-Control-Allow-Headers",req$HTTP_ACCESS_CONTROL_REQUEST_HEADERS)

res$status <- 200

return (list())

}

else

{

plumber::forward()

}

}

Prediction Part :

#Specify the serializer it is json,pdf or xls

#' @serializer contentType list(type="application/json") #Speficify whether to parse JSON

#* parse JSON #Pass req object as parameter using @param notation

#* @param req #Specify the type and endpoint of request

#* @post /predict

funtion(req,res)

{ #Load the Module

source(predict.R) #Parse the JSON

requestBody = jsonlite::fromJSON(req$postBody) #Perform prediction

responseToUi = predictRegressionModel(requestBody$dataset_path,requestBody$model_path) #Construct and return response object

res = jsonlite::toJSON(responseToUi,pretty = TRUE , auto_unbox = TRUE)

}

How to start ?

>plumber::plumb("mainFile.R")$run(host="<your-ip",port="<some-port",swagger=TRUE)

The command is executed in R Console. To open R console, open your cmd/shell,navigate to directory where both files are present and just enter R and run the above command.

Sample Input would be :

POST <your-ip>:<some-port>/predict

{

"dataset_path" : "/some file location/",

"model_path":"/some model path/model_name.rds"

}

Sample Output would be :

{

"algorithmStatus":"success",

"predictions":"<ARRAY>

}

Points to Note :

I always invoked functions by package call at that point, rather than loading all at once in the beginning, because when you have around 50 + packages there will be a chance of ambiguity of same function names in different packages. The plumb can be invoked using a simple R say say init.R , where you can also specify the libpaths to use etc.

If R is single threaded , how can multiple users use it or how to deploy in production ?

The answer is pretty simple , using containers, let each user have a dedicated container spin up, when he wants to use R/ when a user session is created. Plumber can be easily converted into a docker image.