Мы работаем с function, который может нарисовать сюжет или нет.
Я ищу решение, чтобы проверить, есть ли у функции побочный эффект рисования.
Я надеюсь, что есть какое-то dev.* решение, чтобы проверить это.inherits можно использовать только для решений, которые возвращают повторно используемые объекты, такие как ggplot2. С другой стороны, boxplot возвращает list и plot класс NULL.
Я ожидаю точно проверить dev.
Предоставляется обширный список различной графики и неграфики.
input_plots <- list(
function() print(ggplot2::qplot(1)),
function() lattice::densityplot(1),
function() grid::grid.draw(ggplotify::as.grob(lattice::densityplot(1))),
function() plot(1),
function() boxplot(2),
function() hist(1)
)
input_noplots <- list(
function() list(),
function() NULL,
function() 2,
function() NA
)
# We are working with a function which could draw a plot or not
all(vapply(input_plots, is.function, FUN.VALUE = logical(1)))
#> [1] TRUE
all(vapply(input_noplots, is.function, FUN.VALUE = logical(1)))
#> [1] TRUE
# all input_plots should be TRUE for is_draw
# all input_noplots should be FALSE for is_draw
is_draw <- function(fun){
# inherits works only for functions returning proper instances
# you can call a function fun()
...
# return logical if the fun draw a plot
}
# all(vapply(input_plots, is_draw, FUN.VALUE = logical(1)))
# TRUE
# all(vapply(input_noplots, Negate(is_draw), FUN.VALUE = logical(1)))
# TRUE
Created on 2022-11-29 with reprex v2.0.2
ПРОВЕРИТЬ РЕШЕНИЕ:
# all input_plots should be TRUE for is_draw
# all input_noplots should be FALSE for is_draw
# this function will clear your device
is_draw <- function(f) {
try(dev.off(), silent = TRUE)
# graphics.off() # close any current graphics devices
cdev <- dev.cur()
f()
if (cdev != dev.cur()) {
on.exit(dev.off())
return(TRUE)
}
return(FALSE)
}
all(vapply(input_plots, is_draw, FUN.VALUE = logical(1)))
#> Warning: `qplot()` was deprecated in ggplot2 3.4.0.
#> `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
#> [1] TRUE
# TRUE
all(vapply(input_noplots, Negate(is_draw), FUN.VALUE = logical(1)))
#> [1] TRUE
# TRUE
plot(1)
all(vapply(input_plots, is_draw, FUN.VALUE = logical(1)))
#> `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
#> [1] TRUE
# TRUE
all(vapply(input_noplots, Negate(is_draw), FUN.VALUE = logical(1)))
#> [1] TRUE
# TRUE
Created on 2022-11-29 with reprex v2.0.2
Мы можем вызывать каждую функцию внутри. Да, с ggplot мы должны его распечатать, я обновил вопрос. Я также добавил grid::grid.draw в grob.
Важным замечанием является то, что это НЕ будет работать для блестящих reactive для базовых графиков (не подлежащих повторному использованию) и их второго и следующего вызовов. Только первый реактивный вызов базовых графиков будет отображать что-либо.





Пока у вас в настоящее время нет открытого графического устройства, один из способов — проверить, изменилось ли текущее устройство.
is_draw <- function(f) {
graphics.off() # close any current graphics devices
cdev <- dev.cur()
f()
if (cdev != dev.cur()) {
on.exit(dev.off())
return(TRUE)
}
return(FALSE)
}
Обратите внимание, что это зависит от того, сможете ли вы оценить функцию. Это возвращает FALSE для вашего примера lattice, потому что, как и ggplot, эти графики отображаются только при вызове метода print().
Этот метод также закрывает графическое устройство, поэтому сюжет теряется, но вам нужно закрыть его, чтобы увидеть, откроется ли новое.
Загвоздка в том, что этот метод работает только до тех пор, пока у вас не открыто графическое устройство в начале. В противном случае функция построения графика не создаст новое графическое устройство, а заменит последнее.
Я сделал небольшое обновление этой функции try(dev.off(), silent = TRUE) в начале. Пожалуйста, подумайте о том, чтобы добавить его и сюда. Большое спасибо.
@polkas Я изменил его на graphics.off(), который закроет все устройства, если открыто более одного.
Еще одно спасибо за ваше время и отличное решение.
Одна вещь, на которую следует обратить внимание: любые вызовы par() откроют устройство, даже если функция в конечном итоге ничего не отрисует.
Кажется, это работает на Rgui в Windows. Вы можете проверить, работает ли он в вашей среде. Запустите все графические устройства с помощью dev.off, а затем после запуска кода проверьте длину dev.list() .
for(d in dev.list()) dev.off()
x <- 3 # does not plot
length(dev.list())
## [1] 0
plot(0) # plots
length(dev.list())
## [1] 1
Спасибо за ваше время, но, на мой взгляд, второе решение кажется более подходящим.
Альтернативный подход с использованием recordPlot():
is_draw <- function(f) {
plot(rnorm(10))
pre <- recordPlot()
f()
post <- recordPlot()
!identical(pre, post)
}
Спасибо, интересное решение. Я не думаю, что вы намеренно оставляете это plot(rnorm(10)) или это альтернатива dev.off. Кажется, что функция работает так же без этого вызова сюжета. К сожалению, решение не работает для lattice::densityplot(1).
Это нужно для того, чтобы убедиться, что мы начинаем со случайного состояния. В противном случае последовательный вызов одного и того же f() должен давать TRUE только для первого вызова и FALSE для остальных (на практике это происходит не всегда, но я не проверял, почему). Кроме того, без какого-либо предыдущего графика первый вызов recordPlot() может привести к ошибке.
И он не должен работать с lattice::densityplot(1), или ggplot2::qplot(1) и т. д., потому что эти вызовы ничего не строят. Они будут отображаться только при печати, поэтому вам нужно print(lattice::densityplot(1)) и т. д. Я думаю, что другие указывали на это ранее. То же самое с hist(plot=TRUE) и hist(plot=FALSE).
На самом деле нет способа надежно сделать это, не вызывая функцию или что-то в этом роде. Я имею в виду, что даже
hist()можно назвать с помощьюhist(plot=TRUE)илиhist(plot=FALSE). А функцииggplotна самом деле ничего не рисуют. Это методprint()для объектаggplot, который взаимодействует с графическим устройством. Так что ваш первыйinput_plotsдействительно должен быть ЛОЖНЫМ. Вы согласны с запуском функции для наблюдения за возможными побочными эффектами? Или вы пытаетесь это выяснить, не запуская функцию?