#r #error-handling #try-catch
#r #обработка ошибок #try-catch
Вопрос:
У меня есть оператор функции для обработки ошибок построения:
library(ggplot2)
library(dplyr)
handle_plot_error <- function(f) {
tryCatch(
f,
error = function(e) plot_error(e)
)
}
plot_error <- function(error) {
ggplot(
tibble(text = str_wrap(error, 80)),
aes(x = 1, y = 1, label = text)
)
geom_text(color = "red")
theme_void()
}
У меня также есть функция для создания фасетов графика:
plot_facet <- function(df) {
df %>%
ggplot(aes(cyl, mpg))
geom_line()
facet_grid(~am)
}
Может случиться plot_facet
так, что для ввода будет получен пустой фрейм данных:
handle_plot_error(plot_facet(tibble()))
Эта ошибка не обрабатывается моей функцией, поэтому я хотел бы улучшить handle_plot_error
, чтобы включить что-то вроде:
if(nrow(df) == 0) {
stop("No data available")
}
а затем передает это сообщение об ошибке в plot_error
функцию.
Я знаю, что мог бы включить stop
регистр в plot_facet
, но я предпочитаю, чтобы он был в handle_plot_error
, поскольку я использую его для многих функций построения графиков.
Возможно, это может быть полезно: https://adv-r.hadley.nz/function-operators.html
Комментарии:
1. Не уверен, понял ли я ваш вопрос.
handle_plot_error
работает как оператор функции для построения функций и улавливает возможные ошибки построения и отображает их на графиках (я использую его на Shiny).2. Мне только что пришло в голову, что основная цель вашей
handle_plot_error
функции — отображать значимые сообщения об ошибках в блестящем приложении. В противном случае вам, вероятно, не нужно было бы печатать ошибку внутри графика ggplot2. Если ваш вариант использования — блестящее приложение, тогда вам лучше использоватьvalidate(need())
. Особенно, если выdata.frame
вводите данные пользователем, вы можете легко проверить, отображается лиnrow(df)== 0
ошибка на ваших графиках (и вся реактивность вниз по потоку будет остановлена).
Ответ №1:
Ваш код не улавливает ошибку, потому что она возникает только при построении графика для печати. Если вы не хотите вызывать print
явно, как в handle_plot_error(print(plot_facet(tibble())))
, вам нужно построить график внутри вашего tryCatch
.
handle_plot_error <- function(f) {
tryCatch({
ggplot_build(f)
f
},
error = function(e) plot_error(e)
)
}
Это немного неэффективно для больших и сложных графиков.
Редактировать:
Вы можете легко получить доступ к данным объекта ggplot и протестировать их.
handle_plot_error <- function(f) {
tryCatch({
stopifnot("Data.frame is empty!" = nrow(f$data) > 0)
ggplot_build(f)
f
},
error = function(e) plot_error(e)
)
}
Комментарии:
1. Спасибо! Это работает, но я хотел бы знать, возможно ли проверить (
if(nrow(df)) == 0
) первый аргумент функцииf
, который передаетсяhandle_plot_error
, чтобы я мог напечатать более интуитивное сообщение об ошибке?
Ответ №2:
Вы могли бы определить nrow(df) == 0
функцию проверки, а затем включить ее в tryCatch
.
Ваша исходная handle_plot_error
функция еще не является оператором функции, поскольку операторы функций принимают функцию в качестве входных данных и возвращают функцию в качестве выходных данных. Вот почему я изменил handle_plot_error
, обернув function(…) {}
вокруг tryCatch
, чтобы сделать его похожим на оператор функции purrr::possibly
. Затем вы либо оборачиваете функцию, которую хотите обработать, а затем вызываете аргументы, подобные in handle_plot_error(plot_facet)(mtcars)
, либо определяете обработанную версию своей функции try_plot_facet <- handle_plot_error(plot_facet)
, а затем можете вызывать ее обычным способом try_plot_facet(mtcars)
.
library(ggplot2)
library(dplyr)
check_df <- function(df) {
if(nrow(df) == 0) {
rlang::abort("No data available") # `abort` has nicer error messages
}
}
handle_plot_error <- function(f) {
function(...){
tryCatch({
check_df(...)
ggplot_build(f(...))
},
error = function(e) plot_error(e)
)
}
}
plot_error <- function(error) {
plot <- ggplot(
tibble(text = stringr::str_wrap(error, 80)),
aes(x = 1, y = 1, label = text)
)
geom_text(color = "red")
theme_void()
list(plot = plot)
}
plot_facet <- function(df) {
df %>%
ggplot(aes(cyl, mpg))
geom_line()
facet_grid(~am)
}
handle_plot_error(plot_facet)(tibble())
handle_plot_error(plot_facet)(mtcars)
Если у вас более одного аргумента, plot_facet
мы можем явно указать их в нашей функции обработки ошибок:
handle_plot_error <- function(f) {
function(df, ...){
tryCatch({
check_df(df)
ggplot_build(f(df, ...))
},
error = function(e) plot_error(e)
)
}
}
plot_facet <- function(df, var) {
df %>%
ggplot(aes({{var}}, mpg))
geom_line()
facet_grid(~am)
}
handle_plot_error(plot_facet)(tibble())
handle_plot_error(plot_facet)(mtcars, cyl)
Комментарии:
1. Я думаю, вам тогда не нужно
check_df()
внутриplot_facet
? Работает ли он для нескольких аргументовplot_facet
?2. @mihagazvoda: да
check_df()
, insideplot_facet
не нужен. Я просто забыл удалить его и теперь исправил ответ.3. Извините, я заметил, что в моем втором вопросе есть опечатка. : D Что делать в случае
plot_facet
, если имеет несколько аргументов?4. @mihagazvoda: это зависит от типа аргумента, это строка или простое имя переменной? Может быть, вы можете опубликовать пример. Мой ответ здесь был скорее для того, чтобы указать, как выглядит реальный оператор функции, определенный в «Advanced R». Отказ от использования оператора функции может быть проще, как предложено в ответе Roland.
5. @mihagazvoda: Смотрите мой обновленный ответ с двумя аргументами.
Ответ №3:
Как говорит @Roland, ошибка возникает при сборке. Его решение, вероятно, лучшее, потому что оно будет обнаруживать другие виды ошибок, помимо просто нулевых строк. Но если вы действительно хотите выдать специальное сообщение об ошибке для них, вы можете посмотреть на результат выражения plot и посмотреть, имеет ли включенный data
компонент нулевые строки. Например,
handle_plot_error <- function(f) {
tryCatch({
if (!inherits(f, "ggplot"))
stop("You need to construct a plot.",
call. = FALSE)
if (nrow(f$data) == 0)
stop("Dataset must have at least one row",
call. = FALSE)
f
},
error = function(e) plot_error(e)
)
}
Вы могли бы выполнить оба подхода, вызвав ggplot_build
после проверок.