Skip to main content

R processing layer

Published

2026-11-06

This document translates the contents of lab 2 in R. The source Quarto document can be found in _labs/lab02/R/model-vetiver-r.qmd.

fs::dir_tree("_labs/lab02/R/")
## _labs/lab02/R/
## └── model-vetiver-r.qmd

Load packages

library(palmerpenguins)
library(plumber)
library(dplyr)
library(duckdb)
library(fastDummies)
library(vetiver)
library(pins)

Get Data

We load the palmerpenguins data and create a connection with dbConnect(). Then we register the R data frame with duckdb and create the penguins table:

penguins_data <- palmerpenguins::penguins

con <- dbConnect(duckdb::duckdb(), "my-db.duckdb")

duckdb_register(con, "penguins_data", penguins_data)

dbExecute(con, "CREATE OR REPLACE TABLE penguins AS SELECT * FROM penguins_data")

We then drop missing rows and disconnect from the database:

df <- dbGetQuery(con, "SELECT * FROM penguins") |>
  na.omit()

dbDisconnect(con)

View top rows of df:

head(df, 3)

Define Model and Fit

Create dummy variables (one-hot encoding) and fit linear regression model.

X <- df |>
  select(bill_length_mm, species, sex) |>
  dummy_cols(
    select_columns = c("species", "sex"), 
    remove_first_dummy = TRUE) |>
  select(-species, -sex)

y <- df$body_mass_g

model <- lm(y ~ ., data = X)

Extract model fit statistics

We pull the R-squared, intercept, coefficients, and column names from the fitted model:

r_squared <- summary(model)$r.squared
intercept <- coef(model)[1]
coefficients <- coef(model)[-1]
column_names <- names(X)

Then we print each statistic to the console with cat():

cat("R^2:", r_squared, "\n")
cat("Intercept:", intercept, "\n")
cat("Columns:", paste(column_names, collapse = ", "), "\n")
cat("Coefficients:", paste(coefficients, collapse = ", "), "\n")

Turn into Vetiver Model

vetiver_model() wraps the fitted lm() object into a deployable vetiver model named penguin_model:

v <- vetiver_model(model, model_name = "penguin_model")

Save to Board

model_board <- board_folder("./models")
vetiver_pin_write(model_board, v)

Below we can see the model information stored in the models/ folder:

fs::dir_tree("_labs/lab02/R/models/")
## _labs/lab02/R/models/
## └── penguin_model
##     └── 20250923T141916Z-cca3d
##         ├── data.txt
##         └── penguin_model.rds

Turn model into API

Unlike Python, we’ll need to create a plumber router from plumber::pr() to pass to the vetiver_api() (along with the deployable vetiver_model() object).

app <- pr() |>
  vetiver_api(v)

Recap

  1. Getting the data into duckdb from R takes one extra step: duckdb_register() exposes the penguins data frame to the connection before CREATE OR REPLACE TABLE builds the table. na.omit() then drops the missing rows (Python used dropna()).

  2. fastDummies::dummy_cols() with remove_first_dummy = TRUE replaces get_dummies(drop_first = True), and lm() replaces LinearRegression(). The model information comes from summary(model)$r.squared and coef() instead of the scikit-learn attributes.

  3. vetiver_model(), board_folder(), and vetiver_pin_write() mirror the Python functions. The API step differs: R needs a plumber router from plumber::pr() piped into vetiver_api(), while Python’s VetiverAPI() builds the API directly from the model.

Next, Lab 3: Databases and APIs builds Shiny apps in Python and R that send requests to these APIs.