From 45efe99479e70cbf58a50403447dadde19ef5d7a Mon Sep 17 00:00:00 2001 From: Julia Silge Date: Wed, 22 Feb 2023 15:26:38 -0700 Subject: [PATCH 1/6] Add methods for recipes --- R/recipe.R | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 R/recipe.R diff --git a/R/recipe.R b/R/recipe.R new file mode 100644 index 00000000..90d291bb --- /dev/null +++ b/R/recipe.R @@ -0,0 +1,52 @@ +#' @rdname vetiver_create_description +#' @export +vetiver_create_description.recipe <- function(model) { + num_steps <- length(model$steps) + cli::pluralize("A feature engineering recipe with {num_steps} step{?s}") +} + +#' @rdname vetiver_create_meta +#' @export +vetiver_create_meta.recipe <- function(model, metadata) { + reqs <- required_pkgs(model) + reqs <- sort(unique(c(reqs, "recipes"))) + vetiver_meta(metadata, required_pkgs = reqs) +} + +#' @rdname vetiver_create_ptype +#' @export +vetiver_ptype.recipe <- function(model, ...) { + rlang::check_dots_used() + dots <- list(...) + check_ptype_data(dots) + ptype <- vctrs::vec_ptype(dots$prototype_data) + tibble::as_tibble(ptype) +} + +#' @rdname vetiver_create_description +#' @export +vetiver_prepare_model.recipe <- function(model) { + if (!recipes::fully_trained(model)) { + rlang::abort("Your `model` object is not a trained recipe.") + } + ret <- butcher::butcher(model) + ret <- bundle::bundle(ret) + ret +} + +#' @rdname handler_startup +#' @export +handler_startup.recipe <- function(vetiver_model) { + attach_pkgs(vetiver_model$metadata$required_pkgs) +} + +#' @rdname handler_startup +#' @export +handler_predict.recipe <- function(vetiver_model, ...) { + + function(req) { + new_data <- req$body + new_data <- vetiver_type_convert(new_data, vetiver_model$prototype) + bake(vetiver_model$model, new_data = new_data, ...) + } +} From c527545dc4775cced55ff446215c16741219155d Mon Sep 17 00:00:00 2001 From: Julia Silge Date: Wed, 22 Feb 2023 15:26:54 -0700 Subject: [PATCH 2/6] Update tests --- tests/testthat/_snaps/recipe.md | 33 ++++++++++++++++ tests/testthat/test-recipe.R | 70 +++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 tests/testthat/_snaps/recipe.md create mode 100644 tests/testthat/test-recipe.R diff --git a/tests/testthat/_snaps/recipe.md b/tests/testthat/_snaps/recipe.md new file mode 100644 index 00000000..d908d8dd --- /dev/null +++ b/tests/testthat/_snaps/recipe.md @@ -0,0 +1,33 @@ +# can print recipe + + Code + v + Output + + -- car-splines - model for deployment + A feature engineering recipe with 1 step using 2 features + +# create plumber.R for recipe + + Code + cat(readr::read_lines(tmp), sep = "\n") + Output + # Generated by the vetiver package; edit with care + + library(pins) + library(plumber) + library(rapidoc) + library(vetiver) + + # Packages needed to generate model predictions + if (FALSE) { + library(recipes) + } + b <- board_folder(path = "") + v <- vetiver_pin_read(b, "car-splines") + + #* @plumber + function(pr) { + pr %>% vetiver_api(v) + } + diff --git a/tests/testthat/test-recipe.R b/tests/testthat/test-recipe.R new file mode 100644 index 00000000..7707ae7f --- /dev/null +++ b/tests/testthat/test-recipe.R @@ -0,0 +1,70 @@ +skip_if_not_installed("recipes") +skip_if_not_installed("plumber") + +library(plumber) +library(recipes) + +trained_rec <- + recipe(mpg ~ disp + wt, mtcars) %>% + step_ns(wt) %>% + prep(retain = FALSE) + +v <- vetiver_model(trained_rec, "car-splines", prototype_data = mtcars[c("disp", "wt")]) + +test_that("can print recipe", { + expect_snapshot(v) +}) + +test_that("can pin a recipe", { + b <- board_temp() + vetiver_pin_write(b, v) + pinned <- pin_read(b, "car-splines") + expect_equal( + pinned, + list( + model = bundle::bundle(butcher::butcher(trained_rec)), + prototype = vctrs::vec_slice(tibble::as_tibble(mtcars[c("disp", "wt")]), 0) + ) + ) + expect_equal( + pin_meta(b, "car-splines")$user$required_pkgs, + c("recipes") + ) +}) + +test_that("default endpoint for recipe", { + p <- pr() %>% vetiver_api(v) + p_routes <- p$routes[-1] + expect_equal(names(p_routes), c("ping", "predict")) + expect_equal(map_chr(p_routes, "verbs"), + c(ping = "GET", predict = "POST")) +}) + +test_that("default OpenAPI spec", { + v$metadata <- list(url = "potatoes") + p <- pr() %>% vetiver_api(v) + car_spec <- p$getApiSpec() + expect_equal(car_spec$info$description, + "A feature engineering recipe with 1 step") + post_spec <- car_spec$paths$`/predict`$post + expect_equal(names(post_spec), c("summary", "requestBody", "responses")) + expect_equal(as.character(post_spec$summary), + "Return predictions from model using 2 features") + get_spec <- car_spec$paths$`/pin-url`$get + expect_equal(as.character(get_spec$summary), + "Get URL of pinned vetiver model") + +}) + +test_that("create plumber.R for recipe", { + skip_on_cran() + b <- board_folder(path = tmp_dir) + vetiver_pin_write(b, v) + tmp <- tempfile() + vetiver_write_plumber(b, "car-splines", file = tmp) + expect_snapshot( + cat(readr::read_lines(tmp), sep = "\n"), + transform = redact_vetiver + ) +}) + From ff5e70369defa7f4f5837a7741f9f2b7b37247f0 Mon Sep 17 00:00:00 2001 From: Julia Silge Date: Wed, 22 Feb 2023 15:27:14 -0700 Subject: [PATCH 3/6] Redocument --- NAMESPACE | 6 ++++++ man/handler_startup.Rd | 8 +++++++- man/vetiver_create_description.Rd | 10 ++++++++-- man/vetiver_create_meta.Rd | 6 +++++- man/vetiver_create_ptype.Rd | 7 +++++-- 5 files changed, 31 insertions(+), 6 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 8ecc22f5..7b7082e3 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -16,6 +16,7 @@ S3method(handler_predict,kproto) S3method(handler_predict,lm) S3method(handler_predict,model_stack) S3method(handler_predict,ranger) +S3method(handler_predict,recipe) S3method(handler_predict,train) S3method(handler_predict,workflow) S3method(handler_predict,xgb.Booster) @@ -25,6 +26,7 @@ S3method(handler_startup,gam) S3method(handler_startup,keras.engine.training.Model) S3method(handler_startup,model_stack) S3method(handler_startup,ranger) +S3method(handler_startup,recipe) S3method(handler_startup,train) S3method(handler_startup,workflow) S3method(handler_startup,xgb.Booster) @@ -44,6 +46,7 @@ S3method(vetiver_create_description,kproto) S3method(vetiver_create_description,lm) S3method(vetiver_create_description,model_stack) S3method(vetiver_create_description,ranger) +S3method(vetiver_create_description,recipe) S3method(vetiver_create_description,train) S3method(vetiver_create_description,workflow) S3method(vetiver_create_description,xgb.Booster) @@ -54,6 +57,7 @@ S3method(vetiver_create_meta,keras.engine.training.Model) S3method(vetiver_create_meta,kproto) S3method(vetiver_create_meta,model_stack) S3method(vetiver_create_meta,ranger) +S3method(vetiver_create_meta,recipe) S3method(vetiver_create_meta,train) S3method(vetiver_create_meta,workflow) S3method(vetiver_create_meta,xgb.Booster) @@ -66,6 +70,7 @@ S3method(vetiver_prepare_model,kproto) S3method(vetiver_prepare_model,lm) S3method(vetiver_prepare_model,model_stack) S3method(vetiver_prepare_model,ranger) +S3method(vetiver_prepare_model,recipe) S3method(vetiver_prepare_model,train) S3method(vetiver_prepare_model,workflow) S3method(vetiver_prepare_model,xgb.Booster) @@ -78,6 +83,7 @@ S3method(vetiver_ptype,kproto) S3method(vetiver_ptype,lm) S3method(vetiver_ptype,model_stack) S3method(vetiver_ptype,ranger) +S3method(vetiver_ptype,recipe) S3method(vetiver_ptype,train) S3method(vetiver_ptype,workflow) S3method(vetiver_ptype,xgb.Booster) diff --git a/man/handler_startup.Rd b/man/handler_startup.Rd index b40dee4b..44a40ff2 100644 --- a/man/handler_startup.Rd +++ b/man/handler_startup.Rd @@ -1,6 +1,6 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/caret.R, R/gam.R, R/glm.R, R/handlers.R, -% R/keras.R, R/kproto.R, R/lm.R, R/mlr3.R, R/ranger.R, R/stacks.R, +% R/keras.R, R/kproto.R, R/lm.R, R/mlr3.R, R/ranger.R, R/recipe.R, R/stacks.R, % R/tidymodels.R, R/xgboost.R \name{handler_startup.train} \alias{handler_startup.train} @@ -20,6 +20,8 @@ \alias{handler_predict.Learner} \alias{handler_startup.ranger} \alias{handler_predict.ranger} +\alias{handler_startup.recipe} +\alias{handler_predict.recipe} \alias{handler_startup.model_stack} \alias{handler_predict.model_stack} \alias{handler_startup.workflow} @@ -62,6 +64,10 @@ handler_predict(vetiver_model, ...) \method{handler_predict}{ranger}(vetiver_model, ...) +\method{handler_startup}{recipe}(vetiver_model) + +\method{handler_predict}{recipe}(vetiver_model, ...) + \method{handler_startup}{model_stack}(vetiver_model) \method{handler_predict}{model_stack}(vetiver_model, ...) diff --git a/man/vetiver_create_description.Rd b/man/vetiver_create_description.Rd index 9e0a2657..37d798ae 100644 --- a/man/vetiver_create_description.Rd +++ b/man/vetiver_create_description.Rd @@ -1,7 +1,7 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/caret.R, R/gam.R, R/glm.R, R/keras.R, -% R/kproto.R, R/lm.R, R/mlr3.R, R/prepare.R, R/ranger.R, R/stacks.R, -% R/tidymodels.R, R/xgboost.R +% R/kproto.R, R/lm.R, R/mlr3.R, R/prepare.R, R/ranger.R, R/recipe.R, +% R/stacks.R, R/tidymodels.R, R/xgboost.R \name{vetiver_create_description.train} \alias{vetiver_create_description.train} \alias{vetiver_prepare_model.train} @@ -23,6 +23,8 @@ \alias{vetiver_prepare_model.default} \alias{vetiver_create_description.ranger} \alias{vetiver_prepare_model.ranger} +\alias{vetiver_create_description.recipe} +\alias{vetiver_prepare_model.recipe} \alias{vetiver_create_description.model_stack} \alias{vetiver_prepare_model.model_stack} \alias{vetiver_create_description.workflow} @@ -71,6 +73,10 @@ vetiver_prepare_model(model) \method{vetiver_prepare_model}{ranger}(model) +\method{vetiver_create_description}{recipe}(model) + +\method{vetiver_prepare_model}{recipe}(model) + \method{vetiver_create_description}{model_stack}(model) \method{vetiver_prepare_model}{model_stack}(model) diff --git a/man/vetiver_create_meta.Rd b/man/vetiver_create_meta.Rd index c9a1a75b..eb9a8feb 100644 --- a/man/vetiver_create_meta.Rd +++ b/man/vetiver_create_meta.Rd @@ -1,6 +1,7 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/caret.R, R/gam.R, R/keras.R, R/kproto.R, -% R/meta.R, R/mlr3.R, R/ranger.R, R/stacks.R, R/tidymodels.R, R/xgboost.R +% R/meta.R, R/mlr3.R, R/ranger.R, R/recipe.R, R/stacks.R, R/tidymodels.R, +% R/xgboost.R \name{vetiver_create_meta.train} \alias{vetiver_create_meta.train} \alias{vetiver_create_meta.gam} @@ -11,6 +12,7 @@ \alias{vetiver_create_meta.default} \alias{vetiver_create_meta.Learner} \alias{vetiver_create_meta.ranger} +\alias{vetiver_create_meta.recipe} \alias{vetiver_create_meta.model_stack} \alias{vetiver_create_meta.workflow} \alias{vetiver_create_meta.xgb.Booster} @@ -34,6 +36,8 @@ vetiver_create_meta(model, metadata) \method{vetiver_create_meta}{ranger}(model, metadata) +\method{vetiver_create_meta}{recipe}(model, metadata) + \method{vetiver_create_meta}{model_stack}(model, metadata) \method{vetiver_create_meta}{workflow}(model, metadata) diff --git a/man/vetiver_create_ptype.Rd b/man/vetiver_create_ptype.Rd index 95deb366..f84c808b 100644 --- a/man/vetiver_create_ptype.Rd +++ b/man/vetiver_create_ptype.Rd @@ -1,7 +1,7 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/caret.R, R/gam.R, R/glm.R, R/keras.R, -% R/kproto.R, R/lm.R, R/mlr3.R, R/prototype.R, R/ranger.R, R/stacks.R, -% R/tidymodels.R, R/xgboost.R +% R/kproto.R, R/lm.R, R/mlr3.R, R/prototype.R, R/ranger.R, R/recipe.R, +% R/stacks.R, R/tidymodels.R, R/xgboost.R \name{vetiver_ptype.train} \alias{vetiver_ptype.train} \alias{vetiver_ptype.gam} @@ -14,6 +14,7 @@ \alias{vetiver_ptype.default} \alias{vetiver_create_ptype} \alias{vetiver_ptype.ranger} +\alias{vetiver_ptype.recipe} \alias{vetiver_ptype.model_stack} \alias{vetiver_ptype.workflow} \alias{vetiver_ptype.xgb.Booster} @@ -41,6 +42,8 @@ vetiver_create_ptype(model, save_prototype, ...) \method{vetiver_ptype}{ranger}(model, ...) +\method{vetiver_ptype}{recipe}(model, ...) + \method{vetiver_ptype}{model_stack}(model, ...) \method{vetiver_ptype}{workflow}(model, ...) From 37ac83e207b99ce9a5e4a4cf75fcd1f208247b23 Mon Sep 17 00:00:00 2001 From: Julia Silge Date: Wed, 22 Feb 2023 15:43:49 -0700 Subject: [PATCH 4/6] Namespace for `recipes::bake()` --- R/recipe.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/recipe.R b/R/recipe.R index 90d291bb..c9b0b9d7 100644 --- a/R/recipe.R +++ b/R/recipe.R @@ -47,6 +47,6 @@ handler_predict.recipe <- function(vetiver_model, ...) { function(req) { new_data <- req$body new_data <- vetiver_type_convert(new_data, vetiver_model$prototype) - bake(vetiver_model$model, new_data = new_data, ...) + recipes::bake(vetiver_model$model, new_data = new_data, ...) } } From f93e62d25bb202b7be723250b0aa80d332985395 Mon Sep 17 00:00:00 2001 From: Julia Silge Date: Thu, 23 Feb 2023 10:37:29 -0700 Subject: [PATCH 5/6] Update R/recipe.R Co-authored-by: Emil Hvitfeldt --- R/recipe.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/recipe.R b/R/recipe.R index c9b0b9d7..8e72fb2b 100644 --- a/R/recipe.R +++ b/R/recipe.R @@ -46,7 +46,7 @@ handler_predict.recipe <- function(vetiver_model, ...) { function(req) { new_data <- req$body - new_data <- vetiver_type_convert(new_data, vetiver_model$prototype) + new_data <- vetiver_type_convert(new_data, vetiver_model$prototype) recipes::bake(vetiver_model$model, new_data = new_data, ...) } } From c25088bd3f89543b03dd1769bf62a6af94fb7971 Mon Sep 17 00:00:00 2001 From: Julia Silge Date: Thu, 2 Mar 2023 13:18:22 -0700 Subject: [PATCH 6/6] Update NEWS --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 11904870..d9bad57e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,6 @@ # vetiver (development version) -* Added support for keras (#164). +* Added support for keras (#164) and recipes (#179). * Moved where `required_pkgs` metadata is stored remotely, from the binary blob to plain text YAML (#176).