Skip to content

Commit 3db630d

Browse files
committed
added verbosity and error trapping to fit via a control object
1 parent e03bf4d commit 3db630d

File tree

6 files changed

+107
-32
lines changed

6 files changed

+107
-32
lines changed

NAMESPACE

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,6 @@ importFrom(rlang,quos)
3434
importFrom(stats,as.formula)
3535
importFrom(stats,model.frame)
3636
importFrom(stats,model.response)
37+
importFrom(stats,terms)
38+
importFrom(utils,capture.output)
3739
importFrom(utils,installed.packages)

R/fit.R

Lines changed: 78 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,17 @@
1818
#' @param engine A character string for the software that should
1919
#' be used to fit the model. This is highly dependent on the type
2020
#' of model (e.g. linear regression, random forest, etc.).
21+
#' @param .control A named list with elements `verbosity` and
22+
#' `catch`. `verbosity` should be an integer where a value of zero
23+
#' indicates that no messages or output should be shown when
24+
#' packages are loaded or when the model is fit. A value of 1 means
25+
#' that package loading is quiet but model fits can produce output
26+
#' to the screen (depending on if they contain their own
27+
#' `verbose`-type argument). A value of 2 or more indicates that
28+
#' any output should be seen. `catch` is a logical where a value of
29+
#' `TRUE` will evaluate the model inside of `try(, silent = TRUE)`.
30+
#' If the model fails, an object is still returned (without an
31+
#' error) that inherits the class "try-error".
2132
#' @param ... Other options required to fit the model. If `x` is a
2233
#' formula or recipe, then the `data` argument should be passed
2334
#' here. For the "x/y" interface, the outcome data should be passed
@@ -48,22 +59,25 @@ fit <- function (object, ...)
4859
#' @return An object for the fitted model.
4960
#' @export
5061
#' @rdname fit
51-
fit.model_spec <- function(object, x, engine = object$engine, ...) {
62+
fit.model_spec <- function(object, x, engine = object$engine,
63+
.control = list(verbosity = 1, catch = TRUE),
64+
...) {
5265
object$engine <- engine
5366
object <- check_engine(object)
67+
.control <- check_control(.control)
5468

5569
# sub in arguments to actual syntax for corresponding engine
5670
object <- finalize(object, engine = object$engine)
5771
check_installs(object)
5872

5973
if (inherits(x, "formula")) {
60-
res <- fit_formula(object, formula = x, ...)
74+
res <- fit_formula(object, formula = x, .control = .control, ...)
6175
} else {
6276
if (inherits(x, c("matrix", "data.frame"))) {
63-
res <- fit_xy(object, x = x, ...)
77+
res <- fit_xy(object, x = x, .control = .control, ...)
6478
} else {
6579
if (inherits(x, "recipe")) {
66-
res <- fit_recipe(object, recipe = x, ...)
80+
res <- fit_recipe(object, recipe = x, .control = .control, ...)
6781
} else {
6882
stop("`x` should be a formula, data frame, matrix, or recipe")
6983
}
@@ -76,26 +90,25 @@ fit.model_spec <- function(object, x, engine = object$engine, ...) {
7690

7791
#' @importFrom rlang eval_tidy quos
7892
#' @importFrom stats as.formula
79-
fit_formula <- function(object, formula, engine = engine, ...) {
93+
fit_formula <- function(object, formula, engine = engine, .control, ...) {
8094
opts <- quos(...)
8195

8296
if(!any(names(opts) == "data"))
8397
stop("Please pass a data frame with the `data` argument.",
8498
call. = FALSE)
8599

86100
# TODO Should probably just load the namespace
87-
for(pkg in object$method$library)
88-
suppressPackageStartupMessages(library(pkg, character.only = TRUE))
101+
load_libs(object, .control$verbosity < 2)
89102

90103
# Look up the model's interface (e.g. formula, recipes, etc)
91104
# and delagate to the connector functions (`formula_to_recipe` etc)
92105
if(object$method$interface == "formula") {
93106
fit_expr <- sub_arg_values(object$method$fit, opts["data"])
94107
fit_expr$formula <- as.formula(eval(formula))
95-
res <- eval_tidy(fit_expr)
108+
res <- eval_mod(fit_expr, capture = .control$verbosity == 0, catch = .control$catch)
96109
} else {
97110
if(object$method$interface %in% c("data.frame", "matrix")) {
98-
res <- formula_to_xy(object = object, formula = formula, data = opts["data"])
111+
res <- formula_to_xy(object = object, formula = formula, data = opts["data"], .control)
99112
} else {
100113
stop("I don't know about the ",
101114
object$method$interface, " interface.",
@@ -105,25 +118,24 @@ fit_formula <- function(object, formula, engine = engine, ...) {
105118
res
106119
}
107120

108-
fit_xy <- function(object, x, ...) {
121+
fit_xy <- function(object, x, .control, ...) {
109122
opts <- quos(...)
110123

111124
if(!any(names(opts) == "y"))
112125
stop("Please pass a data frame with the `y` argument.",
113126
call. = FALSE)
114127

115128
# TODO Should probably just load the namespace
116-
for(pkg in object$method$library)
117-
suppressPackageStartupMessages(library(pkg, character.only = TRUE))
129+
load_libs(object, .control$verbosity < 2)
118130

119131
# Look up the model's interface (e.g. formula, recipes, etc)
120132
# and delegate to the connector functions (`xy_to_formula` etc)
121133
if(object$method$interface == "formula") {
122-
res <- xy_to_formula(object = object, x = x, y = opts["y"])
134+
res <- xy_to_formula(object = object, x = x, y = opts["y"], .control)
123135
} else {
124136
if(object$method$interface %in% c("data.frame", "matrix")) {
125137
fit_expr <- sub_arg_values(object$method$fit, opts["y"])
126-
res <- eval_tidy(fit_expr)
138+
res <- eval_mod(fit_expr, capture = .control$verbosity == 0, catch = .control$catch)
127139
} else {
128140
stop("I don't know about the ",
129141
object$method$interface, " interface.",
@@ -133,24 +145,23 @@ fit_xy <- function(object, x, ...) {
133145
res
134146
}
135147

136-
fit_recipe <- function(object, recipe, ...) {
148+
fit_recipe <- function(object, recipe, .control, ...) {
137149
opts <- quos(...)
138150

139151
if(!any(names(opts) == "data"))
140152
stop("Please pass a data frame with the `data` argument.",
141153
call. = FALSE)
142154

143155
# TODO Should probably just load the namespace
144-
for(pkg in object$method$library)
145-
suppressPackageStartupMessages(library(pkg, character.only = TRUE))
156+
load_libs(object, .control$verbosity < 2)
146157

147158
# Look up the model's interface (e.g. formula, recipes, etc)
148159
# and delegate to the connector functions (`recipe_to_formula` etc)
149160
if(object$method$interface == "formula") {
150-
res <- recipe_to_formula(object = object, recipe = recipe, data = opts["data"])
161+
res <- recipe_to_formula(object = object, recipe = recipe, data = opts["data"], .control)
151162
} else {
152163
if(object$method$interface %in% c("data.frame", "matrix")) {
153-
res <- recipe_to_xy(object = object, recipe = recipe, data = opts["data"])
164+
res <- recipe_to_xy(object = object, recipe = recipe, data = opts["data"], .control)
154165
} else {
155166
stop("I don't know about the ",
156167
object$method$interface, " interface.",
@@ -163,15 +174,15 @@ fit_recipe <- function(object, recipe, ...) {
163174

164175
###################################################################
165176

166-
formula_to_recipe <- function(object, formula, data) {
177+
formula_to_recipe <- function(object, formula, data, .control) {
167178
# execute the formula
168179
# extract terms _and roles_
169180
# put into recipe
170181

171182
}
172183

173-
#' @importFrom stats model.frame model.response
174-
formula_to_xy <- function(object, formula, data) {
184+
#' @importFrom stats model.frame model.response terms
185+
formula_to_xy <- function(object, formula, data, .control) {
175186
# TODO how do we fill in the other standard things here (subset, contrasts etc)?
176187
# TODO add a "matrix" option here and invoke model.matrix
177188

@@ -182,13 +193,13 @@ formula_to_xy <- function(object, formula, data) {
182193
if (!isTRUE(all.equal(outcome_cols, 0))) {
183194
x <- x[,-outcome_cols, drop = FALSE]
184195
}
185-
eval_tidy(object$method$fit)
196+
eval_mod(object$method$fit, capture = .control$verbosity == 0, catch = .control$catch)
186197
}
187198

188199
###################################################################
189200

190201
#' @importFrom recipes prep juice all_predictors all_outcomes
191-
recipe_to_formula <- function(object, recipe, data) {
202+
recipe_to_formula <- function(object, recipe, data, .control) {
192203
# TODO case weights
193204
recipe <-
194205
prep(recipe, training = eval_tidy(data[["data"]]), retain = TRUE)
@@ -204,10 +215,10 @@ recipe_to_formula <- function(object, recipe, data) {
204215
fit_expr <- object$method$fit
205216
fit_expr$formula <- as.formula(paste0(y_names, "~."))
206217
fit_expr$data <- quote(dat)
207-
eval_tidy(fit_expr)
218+
eval_mod(fit_expr, capture = .control$verbosity == 0, catch = .control$catch)
208219
}
209220

210-
recipe_to_xy <- function(object, recipe, data) {
221+
recipe_to_xy <- function(object, recipe, data, .control) {
211222
# TODO case weights
212223
recipe <-
213224
prep(recipe, training = eval_tidy(data[["data"]]), retain = TRUE)
@@ -221,12 +232,12 @@ recipe_to_xy <- function(object, recipe, data) {
221232
y <- y[[1]]
222233

223234
fit_expr <- object$method$fit
224-
eval_tidy(fit_expr)
235+
eval_mod(fit_expr, capture = .control$verbosity == 0, catch = .control$catch)
225236
}
226237

227238
###################################################################
228239

229-
xy_to_formula <- function(object, x, y) {
240+
xy_to_formula <- function(object, x, y, .control) {
230241
if(!is.data.frame(x))
231242
x <- as.data.frame(x)
232243
x$.y <- eval_tidy(y[["y"]])
@@ -236,6 +247,44 @@ xy_to_formula <- function(object, x, y) {
236247
eval_tidy(fit_expr)
237248
}
238249

239-
xy_to_recipe <- function(object, x, y) {
250+
xy_to_recipe <- function(object, x, y, .control) {
240251

241252
}
253+
254+
###################################################################
255+
256+
#' @importFrom utils capture.output
257+
eval_mod <- function(e, capture = FALSE, catch = FALSE) {
258+
if (capture) {
259+
if (catch) {
260+
junk <- capture.output(res <- try(eval_tidy(e), silent = TRUE))
261+
} else {
262+
junk <- capture.output(res <- eval_tidy(e))
263+
}
264+
} else {
265+
if (catch) {
266+
res <- try(eval_tidy(e), silent = TRUE)
267+
} else {
268+
res <- eval_tidy(e)
269+
}
270+
}
271+
res
272+
}
273+
274+
###################################################################
275+
276+
277+
check_control <- function(x) {
278+
if (!is.list(x))
279+
stop(".control should be a named list.", call. = FALSE)
280+
if (!isTRUE(all.equal(sort(names(x)), c("catch", "verbosity"))))
281+
stop(".control should be a named list with elements 'verbosity' and 'catch'.",
282+
call. = FALSE)
283+
# based on ?is.integer
284+
int_check <- function(x, tol = .Machine$double.eps^0.5) abs(x - round(x)) < tol
285+
if (!int_check(x$verbosity))
286+
stop("verbosity should be an integer.", call. = FALSE)
287+
if (!is.logical(x$catch))
288+
stop("catch should be a logical.", call. = FALSE)
289+
x
290+
}

R/logistic_reg.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
#' logistic_reg()
3737
#'
3838
#' # Parameters can be represented by a placeholder:
39-
#' logistic_reg(link = "probit", regularization = varying())
39+
#' logistic_reg(regularization = varying())
4040

4141
logistic_reg <- function (mode, ...)
4242
UseMethod("logistic_reg")

R/misc.R

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,15 @@ model_printer <- function(x, ...) {
6363
}
6464
}
6565
cat("\n")
66+
}
67+
68+
load_libs <- function(x, quiet) {
69+
if (quiet) {
70+
for (pkg in x$method$library)
71+
suppressPackageStartupMessages(library(pkg, character.only = TRUE))
72+
} else {
73+
for (pkg in x$method$library)
74+
library(pkg, character.only = TRUE)
75+
}
76+
invisible(x)
6677
}

man/fit.Rd

Lines changed: 14 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/logistic_reg.Rd

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)