Skip to Content
Course content

212: Model Deployment as a Plumber API

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I once worked with a lead data scientist who had built a masterpiece of a churn prediction model. It was a high-performing random forest that could tell you exactly which customers were about to leave with startling accuracy. The problem? The model lived in a series of fragmented .Rmd files on his local machine. When the product team wanted to integrate those predictions into the customer dashboard, he tried to explain that they could just "load the .rds file in an R session." The frontend developers, who were building in React, looked at him like he was speaking a dead language. They didn't want an R session; they wanted a URL they could send a JSON payload to and get a probability back in milliseconds.

This is the "last mile" problem of data science. You can have the most accurate model in the world, but if it isn't accessible as a service, it's essentially a fancy spreadsheet. In the R ecosystem, the plumber package is the bridge. It allows you to take a standard R function and expose it as a REST API endpoint without requiring you to become a full-stack web developer.

Turning R Functions into Endpoints

The magic of plumber lies in its use of special comments, often called decorators. Instead of writing complex routing logic, you write your R functions as usual and add specific comments above them. These comments tell plumber how to map an incoming HTTP request to your function.

For a model deployment, you'll almost always use #* @post. While #* @get is great for fetching a static value, @post allows the client to send a body of data—like a customer's usage metrics—which your model then uses to make a prediction. Here is how a basic prediction endpoint looks:

#* @post /predict
#* @param tenure The length of time the customer has been with us
#* @param monthly_charges The monthly bill amount
function(tenure, monthly_charges) {
  # Assume 'churn_model' is already loaded in the environment
  new_data <- data.frame(tenure = as.numeric(tenure), 
                         monthly_charges = as.numeric(monthly_charges))
  
  prediction <- predict(churn_model, new_data, type = "response")
  return(list(churn_probability = prediction))
}

I should mention a common mistake I see: forgetting to cast inputs. Plumber receives arguments as strings by default. If your model expects a numeric value and you pass it a string, your API will crash with a cryptic error. Always wrap your inputs in as.numeric() or as.factor() to be safe.

Optimizing Model Loading and the Router

If you load your model .rds file inside the function, your API will be painfully slow because it will read the file from the disk every single time someone hits the endpoint. That's a recipe for a server crash under any real load. Instead, you want to load the model into the environment before the API starts running.

The standard workflow is to create a separate file (e.g., plumber.R) where you load your dependencies and your model at the top of the script. When you call plumb("plumber.R") in your main R session, the model is loaded once into memory, and every subsequent request to the /predict endpoint uses that pre-loaded object.

# plumber.R
library(randomForest)
churn_model <- readRDS("models/churn_rf_v1.rds")

#* @post /predict
function(req) {
  # req$body contains the parsed JSON input
  input_data <- as.data.frame(req$body)
  predict(churn_model, input_data)
}

Testing via the Swagger UI

One of the best features of plumber is that it automatically generates a Swagger UI. Once you run your API using pr_run(pr), you don't need to struggle with curl commands or external tools like Postman just to see if it works. You can navigate to the API URL in your browser, and you'll see a fully interactive documentation page.

This UI allows you to input parameters directly into the browser and hit "Execute" to see the JSON response. It's an incredible tool for collaborating with frontend engineers; you can simply send them the Swagger link, and they can test the API's inputs and outputs themselves without ever touching a line of R code.




📋 Practical Task

Deploying a Wine Quality Prediction API

You have been provided with a pre-trained model file named wine_model.rds (a model that predicts wine quality based on acidity, sugar, and alcohol content). Your task is to create a Plumber API that allows a user to submit these chemical properties and receive a quality score.

Requirements:

  • Create a file named wine_api.R.
  • Load the wine_model.rds file at the top of the script so it is only loaded once.
  • Create a POST endpoint at /score.
  • The endpoint must accept three parameters: acidity, sugar, and alcohol.
  • Ensure the parameters are converted to numeric values before being passed to the predict() function.
  • The API should return a list containing the final predicted score.

Verification: Once the API is running, use the Swagger UI to send a request with acidity = 0.7, sugar = 1.2, and alcohol = 11.5 and verify that you receive a numeric prediction in the JSON response.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.