From d85901ee3504cab3c047b3b808dabef83d464dd8 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Wed, 20 Nov 2024 13:20:56 +0100 Subject: [PATCH 1/6] mark deprecation in docs --- R/fortify-lm.R | 5 +++++ R/fortify-multcomp.R | 6 ++++++ man/fortify-multcomp.Rd | 5 ++++- man/fortify.lm.Rd | 4 ++++ 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/R/fortify-lm.R b/R/fortify-lm.R index ebfb0334b2..0a42ae6e6d 100644 --- a/R/fortify-lm.R +++ b/R/fortify-lm.R @@ -1,5 +1,10 @@ #' Supplement the data fitted to a linear model with model fit statistics. #' +#' @description +#' `r lifecycle::badge("deprecated")` +#' +#' This method is deprecated because using `broom::augment()` is a better +#' solution to supplement data from a linear model. #' If you have missing values in your model data, you may need to refit #' the model with `na.action = na.exclude`. #' diff --git a/R/fortify-multcomp.R b/R/fortify-multcomp.R index 79714b2a68..68c6a9eb3e 100644 --- a/R/fortify-multcomp.R +++ b/R/fortify-multcomp.R @@ -1,5 +1,11 @@ #' Fortify methods for objects produced by \pkg{multcomp} #' +#' @description +#' `r lifecycle::badge("deprecated")` +#' +#' This function is deprecated because using `broom::tidy()` is a better +#' solution to convert model objects. +#' #' @param model an object of class `glht`, `confint.glht`, #' `summary.glht` or [multcomp::cld()] #' @param data,... other arguments to the generic ignored in this method. diff --git a/man/fortify-multcomp.Rd b/man/fortify-multcomp.Rd index a52dec001c..8e0aba55dd 100644 --- a/man/fortify-multcomp.Rd +++ b/man/fortify-multcomp.Rd @@ -23,7 +23,10 @@ \item{data, ...}{other arguments to the generic ignored in this method.} } \description{ -Fortify methods for objects produced by \pkg{multcomp} +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} + +This function is deprecated because using \code{broom::tidy()} is a better +solution to convert model objects. } \examples{ if (require("multcomp")) { diff --git a/man/fortify.lm.Rd b/man/fortify.lm.Rd index 4a994a6c56..2532df6d2b 100644 --- a/man/fortify.lm.Rd +++ b/man/fortify.lm.Rd @@ -24,6 +24,10 @@ corresponding observation is dropped from model} \item{.stdresid}{Standardised residuals} } \description{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} + +This method is deprecated because using \code{broom::augment()} is a better +solution to supplement data from a linear model. If you have missing values in your model data, you may need to refit the model with \code{na.action = na.exclude}. } From 85baeb99a63e11419b995e97dd24eb407c7393f6 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Wed, 20 Nov 2024 13:30:59 +0100 Subject: [PATCH 2/6] add `broom` to suggests --- DESCRIPTION | 1 + 1 file changed, 1 insertion(+) diff --git a/DESCRIPTION b/DESCRIPTION index 585a51285a..62c27524be 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -44,6 +44,7 @@ Imports: vctrs (>= 0.6.0), withr (>= 2.5.0) Suggests: + broom, covr, dplyr, ggplot2movies, From 6e09cc47c163b658775eccd5210874982df676ed Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Wed, 20 Nov 2024 14:13:35 +0100 Subject: [PATCH 3/6] throw deprecation warnings --- R/fortify-lm.R | 3 +++ R/fortify-multcomp.R | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/R/fortify-lm.R b/R/fortify-lm.R index 0a42ae6e6d..debe080538 100644 --- a/R/fortify-lm.R +++ b/R/fortify-lm.R @@ -79,6 +79,9 @@ #' geom_point(aes(size = .cooksd / .hat)) + #' scale_size_area() fortify.lm <- function(model, data = model$model, ...) { + lifecycle::deprecate_warn( + "3.6.0", I("`fortify()`"), I("`broom::augment()`") + ) infl <- stats::influence(model, do.coef = FALSE) data$.hat <- infl$hat data$.sigma <- infl$sigma diff --git a/R/fortify-multcomp.R b/R/fortify-multcomp.R index 68c6a9eb3e..4bb41ba25c 100644 --- a/R/fortify-multcomp.R +++ b/R/fortify-multcomp.R @@ -39,6 +39,9 @@ NULL #' @rdname fortify-multcomp #' @export fortify.glht <- function(model, data, ...) { + lifecycle::deprecate_warn( + "3.6.0", I("`fortify()`"), I("`broom::tidy()`") + ) base::data.frame( lhs = rownames(model$linfct), rhs = model$rhs, @@ -52,6 +55,9 @@ fortify.glht <- function(model, data, ...) { #' @method fortify confint.glht #' @export fortify.confint.glht <- function(model, data, ...) { + lifecycle::deprecate_warn( + "3.6.0", I("`fortify()`"), I("`broom::tidy()`") + ) coef <- model$confint colnames(coef) <- to_lower_ascii(colnames(coef)) @@ -68,6 +74,9 @@ fortify.confint.glht <- function(model, data, ...) { #' @rdname fortify-multcomp #' @export fortify.summary.glht <- function(model, data, ...) { + lifecycle::deprecate_warn( + "3.6.0", I("`fortify()`"), I("`broom::tidy()`") + ) coef <- as.data.frame( model$test[c("coefficients", "sigma", "tstat", "pvalues")]) names(coef) <- c("estimate", "se", "t", "p") @@ -86,6 +95,9 @@ fortify.summary.glht <- function(model, data, ...) { #' @rdname fortify-multcomp #' @export fortify.cld <- function(model, data, ...) { + lifecycle::deprecate_warn( + "3.6.0", I("`fortify()`"), I("`broom::tidy()`") + ) base::data.frame( lhs = names(model$mcletters$Letters), letters = model$mcletters$Letters, From 8905de7d372e989b580f5829fd0e3a0dfb01c63c Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Wed, 20 Nov 2024 14:14:22 +0100 Subject: [PATCH 4/6] highlight {broom} functions in examples --- R/fortify-lm.R | 57 ++++++----------------------------------- R/fortify-multcomp.R | 33 ++++++++++++++---------- man/fortify-multcomp.Rd | 33 +++++++++++++++--------- man/fortify.lm.Rd | 57 +++++++---------------------------------- 4 files changed, 58 insertions(+), 122 deletions(-) diff --git a/R/fortify-lm.R b/R/fortify-lm.R index debe080538..562fa7e7c7 100644 --- a/R/fortify-lm.R +++ b/R/fortify-lm.R @@ -21,63 +21,22 @@ #' @param ... not used by this method #' @keywords internal #' @export -#' @examples +#' @examplesIf require("broom") #' mod <- lm(mpg ~ wt, data = mtcars) -#' head(fortify(mod)) -#' head(fortify(mod, mtcars)) -#' -#' plot(mod, which = 1) #' -#' ggplot(mod, aes(.fitted, .resid)) + -#' geom_point() + -#' geom_hline(yintercept = 0) + -#' geom_smooth(se = FALSE) +#' # Show augmented model +#' head(augment(mod)) +#' head(fortify(mod)) #' -#' ggplot(mod, aes(.fitted, .stdresid)) + +#' # Using augment to convert model to ready-to-plot data +#' ggplot(augment(mod), aes(.fitted, .resid)) + #' geom_point() + #' geom_hline(yintercept = 0) + #' geom_smooth(se = FALSE) #' -#' ggplot(fortify(mod, mtcars), aes(.fitted, .stdresid)) + -#' geom_point(aes(colour = factor(cyl))) -#' -#' ggplot(fortify(mod, mtcars), aes(mpg, .stdresid)) + -#' geom_point(aes(colour = factor(cyl))) -#' -#' plot(mod, which = 2) -#' ggplot(mod) + -#' stat_qq(aes(sample = .stdresid)) + -#' geom_abline() -#' -#' plot(mod, which = 3) -#' ggplot(mod, aes(.fitted, sqrt(abs(.stdresid)))) + -#' geom_point() + -#' geom_smooth(se = FALSE) -#' -#' plot(mod, which = 4) -#' ggplot(mod, aes(seq_along(.cooksd), .cooksd)) + -#' geom_col() -#' -#' plot(mod, which = 5) -#' ggplot(mod, aes(.hat, .stdresid)) + -#' geom_vline(linewidth = 2, colour = "white", xintercept = 0) + -#' geom_hline(linewidth = 2, colour = "white", yintercept = 0) + -#' geom_point() + geom_smooth(se = FALSE) -#' -#' ggplot(mod, aes(.hat, .stdresid)) + -#' geom_point(aes(size = .cooksd)) + -#' geom_smooth(se = FALSE, linewidth = 0.5) -#' -#' plot(mod, which = 6) -#' ggplot(mod, aes(.hat, .cooksd)) + -#' geom_vline(xintercept = 0, colour = NA) + -#' geom_abline(slope = seq(0, 3, by = 0.5), colour = "white") + -#' geom_smooth(se = FALSE) + +#' # Colouring by original data not included in the model +#' ggplot(augment(mod, mtcars), aes(.fitted, .std.resid, colour = factor(cyl))) + #' geom_point() -#' -#' ggplot(mod, aes(.hat, .cooksd)) + -#' geom_point(aes(size = .cooksd / .hat)) + -#' scale_size_area() fortify.lm <- function(model, data = model$model, ...) { lifecycle::deprecate_warn( "3.6.0", I("`fortify()`"), I("`broom::augment()`") diff --git a/R/fortify-multcomp.R b/R/fortify-multcomp.R index 4bb41ba25c..e75d25300b 100644 --- a/R/fortify-multcomp.R +++ b/R/fortify-multcomp.R @@ -11,28 +11,35 @@ #' @param data,... other arguments to the generic ignored in this method. #' @name fortify-multcomp #' @keywords internal -#' @examples -#' if (require("multcomp")) { +#' @examplesIf require("multcomp") && require("broom") #' amod <- aov(breaks ~ wool + tension, data = warpbreaks) -#' wht <- glht(amod, linfct = mcp(tension = "Tukey")) +#' wht <- multcomp::glht(amod, linfct = multcomp::mcp(tension = "Tukey")) #' +#' tidy(wht) # recommended #' fortify(wht) -#' ggplot(wht, aes(lhs, estimate)) + geom_point() #' -#' CI <- confint(wht) -#' fortify(CI) -#' ggplot(CI, aes(lhs, estimate, ymin = lwr, ymax = upr)) + +#' ggplot(tidy(wht), aes(contrast, estimate)) + geom_point() +#' +#' ci <- confint(wht) +#' tidy(ci) # recommended +#' fortify(ci) +#' +#' ggplot(tidy(confint(wht)), +#' aes(contrast, estimate, ymin = conf.low, ymax = conf.high)) + #' geom_pointrange() #' -#' fortify(summary(wht)) -#' ggplot(mapping = aes(lhs, estimate)) + -#' geom_linerange(aes(ymin = lwr, ymax = upr), data = CI) + -#' geom_point(aes(size = p), data = summary(wht)) + +#' smry <- summary(wht) +#' tidy(smry) # recommended +#' fortify(smry) +#' +#' ggplot(mapping = aes(contrast, estimate)) + +#' geom_linerange(aes(ymin = conf.low, ymax = conf.high), data = tidy(ci)) + +#' geom_point(aes(size = adj.p.value), data = tidy(smry)) + #' scale_size(transform = "reverse") #' -#' cld <- cld(wht) +#' cld <- multcomp::cld(wht) +#' tidy(cld) # recommended #' fortify(cld) -#' } NULL #' @method fortify glht diff --git a/man/fortify-multcomp.Rd b/man/fortify-multcomp.Rd index 8e0aba55dd..cd1992967c 100644 --- a/man/fortify-multcomp.Rd +++ b/man/fortify-multcomp.Rd @@ -29,26 +29,35 @@ This function is deprecated because using \code{broom::tidy()} is a better solution to convert model objects. } \examples{ -if (require("multcomp")) { +\dontshow{if (require("multcomp") && require("broom")) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} amod <- aov(breaks ~ wool + tension, data = warpbreaks) -wht <- glht(amod, linfct = mcp(tension = "Tukey")) +wht <- multcomp::glht(amod, linfct = multcomp::mcp(tension = "Tukey")) +tidy(wht) # recommended fortify(wht) -ggplot(wht, aes(lhs, estimate)) + geom_point() -CI <- confint(wht) -fortify(CI) -ggplot(CI, aes(lhs, estimate, ymin = lwr, ymax = upr)) + +ggplot(tidy(wht), aes(contrast, estimate)) + geom_point() + +ci <- confint(wht) +tidy(ci) # recommended +fortify(ci) + +ggplot(tidy(confint(wht)), + aes(contrast, estimate, ymin = conf.low, ymax = conf.high)) + geom_pointrange() -fortify(summary(wht)) -ggplot(mapping = aes(lhs, estimate)) + - geom_linerange(aes(ymin = lwr, ymax = upr), data = CI) + - geom_point(aes(size = p), data = summary(wht)) + +smry <- summary(wht) +tidy(smry) # recommended +fortify(smry) + +ggplot(mapping = aes(contrast, estimate)) + + geom_linerange(aes(ymin = conf.low, ymax = conf.high), data = tidy(ci)) + + geom_point(aes(size = adj.p.value), data = tidy(smry)) + scale_size(transform = "reverse") -cld <- cld(wht) +cld <- multcomp::cld(wht) +tidy(cld) # recommended fortify(cld) -} +\dontshow{\}) # examplesIf} } \keyword{internal} diff --git a/man/fortify.lm.Rd b/man/fortify.lm.Rd index 2532df6d2b..dab576fd71 100644 --- a/man/fortify.lm.Rd +++ b/man/fortify.lm.Rd @@ -32,61 +32,22 @@ If you have missing values in your model data, you may need to refit the model with \code{na.action = na.exclude}. } \examples{ +\dontshow{if (require("broom")) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} mod <- lm(mpg ~ wt, data = mtcars) -head(fortify(mod)) -head(fortify(mod, mtcars)) - -plot(mod, which = 1) -ggplot(mod, aes(.fitted, .resid)) + - geom_point() + - geom_hline(yintercept = 0) + - geom_smooth(se = FALSE) +# Show augmented model +head(augment(mod)) +head(fortify(mod)) -ggplot(mod, aes(.fitted, .stdresid)) + +# Using augment to convert model to ready-to-plot data +ggplot(augment(mod), aes(.fitted, .resid)) + geom_point() + geom_hline(yintercept = 0) + geom_smooth(se = FALSE) -ggplot(fortify(mod, mtcars), aes(.fitted, .stdresid)) + - geom_point(aes(colour = factor(cyl))) - -ggplot(fortify(mod, mtcars), aes(mpg, .stdresid)) + - geom_point(aes(colour = factor(cyl))) - -plot(mod, which = 2) -ggplot(mod) + - stat_qq(aes(sample = .stdresid)) + - geom_abline() - -plot(mod, which = 3) -ggplot(mod, aes(.fitted, sqrt(abs(.stdresid)))) + - geom_point() + - geom_smooth(se = FALSE) - -plot(mod, which = 4) -ggplot(mod, aes(seq_along(.cooksd), .cooksd)) + - geom_col() - -plot(mod, which = 5) -ggplot(mod, aes(.hat, .stdresid)) + - geom_vline(linewidth = 2, colour = "white", xintercept = 0) + - geom_hline(linewidth = 2, colour = "white", yintercept = 0) + - geom_point() + geom_smooth(se = FALSE) - -ggplot(mod, aes(.hat, .stdresid)) + - geom_point(aes(size = .cooksd)) + - geom_smooth(se = FALSE, linewidth = 0.5) - -plot(mod, which = 6) -ggplot(mod, aes(.hat, .cooksd)) + - geom_vline(xintercept = 0, colour = NA) + - geom_abline(slope = seq(0, 3, by = 0.5), colour = "white") + - geom_smooth(se = FALSE) + +# Colouring by original data not included in the model +ggplot(augment(mod, mtcars), aes(.fitted, .std.resid, colour = factor(cyl))) + geom_point() - -ggplot(mod, aes(.hat, .cooksd)) + - geom_point(aes(size = .cooksd / .hat)) + - scale_size_area() +\dontshow{\}) # examplesIf} } \keyword{internal} From efd6a67e147a7cd6f90555749f05656e69cc4f54 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Wed, 20 Nov 2024 14:19:08 +0100 Subject: [PATCH 5/6] collect in single file --- DESCRIPTION | 3 +- R/fortify-lm.R | 54 --------------------- R/{fortify-multcomp.R => fortify-models.R} | 55 ++++++++++++++++++++++ man/fortify-multcomp.Rd | 2 +- man/fortify.lm.Rd | 2 +- 5 files changed, 58 insertions(+), 58 deletions(-) delete mode 100644 R/fortify-lm.R rename R/{fortify-multcomp.R => fortify-models.R} (61%) diff --git a/DESCRIPTION b/DESCRIPTION index 62c27524be..30689a9307 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -127,9 +127,8 @@ Collate: 'facet-grid-.R' 'facet-null.R' 'facet-wrap.R' - 'fortify-lm.R' 'fortify-map.R' - 'fortify-multcomp.R' + 'fortify-models.R' 'fortify-spatial.R' 'fortify.R' 'stat-.R' diff --git a/R/fortify-lm.R b/R/fortify-lm.R deleted file mode 100644 index 562fa7e7c7..0000000000 --- a/R/fortify-lm.R +++ /dev/null @@ -1,54 +0,0 @@ -#' Supplement the data fitted to a linear model with model fit statistics. -#' -#' @description -#' `r lifecycle::badge("deprecated")` -#' -#' This method is deprecated because using `broom::augment()` is a better -#' solution to supplement data from a linear model. -#' If you have missing values in your model data, you may need to refit -#' the model with `na.action = na.exclude`. -#' -#' @return The original data with extra columns: -#' \item{.hat}{Diagonal of the hat matrix} -#' \item{.sigma}{Estimate of residual standard deviation when -#' corresponding observation is dropped from model} -#' \item{.cooksd}{Cooks distance, [cooks.distance()]} -#' \item{.fitted}{Fitted values of model} -#' \item{.resid}{Residuals} -#' \item{.stdresid}{Standardised residuals} -#' @param model linear model -#' @param data data set, defaults to data used to fit model -#' @param ... not used by this method -#' @keywords internal -#' @export -#' @examplesIf require("broom") -#' mod <- lm(mpg ~ wt, data = mtcars) -#' -#' # Show augmented model -#' head(augment(mod)) -#' head(fortify(mod)) -#' -#' # Using augment to convert model to ready-to-plot data -#' ggplot(augment(mod), aes(.fitted, .resid)) + -#' geom_point() + -#' geom_hline(yintercept = 0) + -#' geom_smooth(se = FALSE) -#' -#' # Colouring by original data not included in the model -#' ggplot(augment(mod, mtcars), aes(.fitted, .std.resid, colour = factor(cyl))) + -#' geom_point() -fortify.lm <- function(model, data = model$model, ...) { - lifecycle::deprecate_warn( - "3.6.0", I("`fortify()`"), I("`broom::augment()`") - ) - infl <- stats::influence(model, do.coef = FALSE) - data$.hat <- infl$hat - data$.sigma <- infl$sigma - data$.cooksd <- stats::cooks.distance(model, infl) - - data$.fitted <- stats::predict(model) - data$.resid <- stats::resid(model) - data$.stdresid <- stats::rstandard(model, infl) - - data -} diff --git a/R/fortify-multcomp.R b/R/fortify-models.R similarity index 61% rename from R/fortify-multcomp.R rename to R/fortify-models.R index e75d25300b..5a0e95199a 100644 --- a/R/fortify-multcomp.R +++ b/R/fortify-models.R @@ -1,3 +1,58 @@ +#' Supplement the data fitted to a linear model with model fit statistics. +#' +#' @description +#' `r lifecycle::badge("deprecated")` +#' +#' This method is deprecated because using `broom::augment()` is a better +#' solution to supplement data from a linear model. +#' If you have missing values in your model data, you may need to refit +#' the model with `na.action = na.exclude`. +#' +#' @return The original data with extra columns: +#' \item{.hat}{Diagonal of the hat matrix} +#' \item{.sigma}{Estimate of residual standard deviation when +#' corresponding observation is dropped from model} +#' \item{.cooksd}{Cooks distance, [cooks.distance()]} +#' \item{.fitted}{Fitted values of model} +#' \item{.resid}{Residuals} +#' \item{.stdresid}{Standardised residuals} +#' @param model linear model +#' @param data data set, defaults to data used to fit model +#' @param ... not used by this method +#' @keywords internal +#' @export +#' @examplesIf require("broom") +#' mod <- lm(mpg ~ wt, data = mtcars) +#' +#' # Show augmented model +#' head(augment(mod)) +#' head(fortify(mod)) +#' +#' # Using augment to convert model to ready-to-plot data +#' ggplot(augment(mod), aes(.fitted, .resid)) + +#' geom_point() + +#' geom_hline(yintercept = 0) + +#' geom_smooth(se = FALSE) +#' +#' # Colouring by original data not included in the model +#' ggplot(augment(mod, mtcars), aes(.fitted, .std.resid, colour = factor(cyl))) + +#' geom_point() +fortify.lm <- function(model, data = model$model, ...) { + lifecycle::deprecate_warn( + "3.6.0", I("`fortify()`"), I("`broom::augment()`") + ) + infl <- stats::influence(model, do.coef = FALSE) + data$.hat <- infl$hat + data$.sigma <- infl$sigma + data$.cooksd <- stats::cooks.distance(model, infl) + + data$.fitted <- stats::predict(model) + data$.resid <- stats::resid(model) + data$.stdresid <- stats::rstandard(model, infl) + + data +} + #' Fortify methods for objects produced by \pkg{multcomp} #' #' @description diff --git a/man/fortify-multcomp.Rd b/man/fortify-multcomp.Rd index cd1992967c..654e5bbe9a 100644 --- a/man/fortify-multcomp.Rd +++ b/man/fortify-multcomp.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/fortify-multcomp.R +% Please edit documentation in R/fortify-models.R \name{fortify-multcomp} \alias{fortify-multcomp} \alias{fortify.glht} diff --git a/man/fortify.lm.Rd b/man/fortify.lm.Rd index dab576fd71..d98b28a07f 100644 --- a/man/fortify.lm.Rd +++ b/man/fortify.lm.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/fortify-lm.R +% Please edit documentation in R/fortify-models.R \name{fortify.lm} \alias{fortify.lm} \title{Supplement the data fitted to a linear model with model fit statistics.} From 5528541bbe6780ff97b1c749e52e45f2967500a6 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Tue, 26 Nov 2024 08:18:26 +0100 Subject: [PATCH 6/6] add news bullet --- NEWS.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NEWS.md b/NEWS.md index 0c493a8f58..42b1bfaa98 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,9 @@ # ggplot2 (development version) +* The following methods have been deprecated: `fortify.lm()`, `fortify.glht()`, + `fortify.confint.glht()`, `fortify.summary.glht()` and `fortify.cld()`. It + is recommend to use `broom::augment()` and `broom::tidy()` instead + (@teunbrand, #3816). * Custom and raster annotation now respond to scale transformations, and can use AsIs variables for relative placement (@teunbrand based on @yutannihilation's prior work, #3120)