Skip to content

Commit a9d34f4

Browse files
committed
implement suggestions from code review: make links refer to pkgdown websites if they exist, clarify usage of .data; improve overall consistency
1 parent a119ebd commit a9d34f4

File tree

1 file changed

+22
-24
lines changed

1 file changed

+22
-24
lines changed

vignettes/ggplot2-in-packages.Rmd

+22-24
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ mpg_drv_summary <- function() {
3131
mpg_drv_summary()
3232
```
3333

34-
If you use ggplot2 functions frequently, you may wish to import one or more functions from ggplot2 into your `NAMESPACE`. If you use [roxygen2](https://cran.r-project.org/package=roxygen2), you can include `#' @importFrom ggplot2 <one or more function names>` in any roxygen comment block (this will not work for datasets like `mpg`).
34+
If you use ggplot2 functions frequently, you may wish to import one or more functions from ggplot2 into your `NAMESPACE`. If you use [roxygen2](https://cran.r-project.org/package=roxygen2), you can include `#' @importFrom ggplot2 <one or more object names>` in any roxygen comment block (this will not work for datasets like `mpg`).
3535

3636
```{r}
3737
#' @importFrom ggplot2 ggplot aes geom_bar coord_flip
@@ -47,14 +47,13 @@ mpg_drv_summary <- function() {
4747
mpg_drv_summary()
4848
```
4949

50-
Even if you use many ggplot2 functions in your package, it is unwise to use ggplot2 in `Depends` or import the entire package into your `NAMESPACE`. Using ggplot2 in `Depends` will attach ggplot2 when your package is attached, which includes when your package is tested. This makes it difficult to ensure that others can use the functions in your package without attaching it (i.e., using `::`). Similarly, importing all 450 of ggplot2's exported objects into your namespace makes it difficult to separate the responsibility of your package and the responsibility of ggplot2, in addition to making it difficult for readers of your code to figure out where functions are coming from!
50+
Even if you use many ggplot2 functions in your package, it is unwise to use ggplot2 in `Depends` or import the entire package into your `NAMESPACE` (e.g. with `#' @import ggplot2`). Using ggplot2 in `Depends` will attach ggplot2 when your package is attached, which includes when your package is tested. This makes it difficult to ensure that others can use the functions in your package without attaching it (i.e., using `::`). Similarly, importing all 450 of ggplot2's exported objects into your namespace makes it difficult to separate the responsibility of your package and the responsibility of ggplot2, in addition to making it difficult for readers of your code to figure out where functions are coming from!
5151

5252
## Using `aes()` and `vars()` in a package function
5353

5454
To create any graphic using ggplot2 you will probably need to use `aes()` at least once. If your graphic uses facets, you might be using `vars()` to refer to columns in the plot/layer data. Both of these functions use non-standard evaluation, so if you try to use them in a function within a package they will result in a CMD check note:
5555

5656
```{r}
57-
#' @importFrom ggplot2 ggplot aes geom_bar coord_flip
5857
mpg_drv_summary <- function() {
5958
ggplot(ggplot2::mpg) +
6059
geom_bar(aes(x = drv)) +
@@ -75,12 +74,11 @@ There are three situations in which you will encounter this problem:
7574
- You have the column name as a character vector.
7675
- The user specifies the column name or expression, and you want your function to use the same kind of non-standard evaluation used by `aes()` and `vars()`.
7776

78-
If you already know the mapping in advance (like the above example) you should use the `.data` pronoun from [rlang](https://cran.r-project.org/package=rlang) to make it explicit that you are referring to the `drv` in the data and not some other variable named `drv` (which may or may not exist elsewhere).
77+
If you already know the mapping in advance (like the above example) you should use the `.data` pronoun from [rlang](https://rlang.r-lib.org/) to make it explicit that you are referring to the `drv` in the layer data and not some other variable named `drv` (which may or may not exist elsewhere). To avoid a similar note from the CMD check about `.data`, use `#' @importFrom rlang .data` in any roxygen code block (typically this should be in the package documentation as generated by `usethis::use_package_doc()`).
7978

8079
```{r}
81-
#' @importFrom rlang .data
8280
mpg_drv_summary <- function() {
83-
ggplot(mpg) +
81+
ggplot(ggplot2::mpg) +
8482
geom_bar(aes(x = .data$drv)) +
8583
coord_flip()
8684
}
@@ -89,39 +87,40 @@ mpg_drv_summary <- function() {
8987
If you have the column name as a character vector (e.g., `col = "drv"`), use `.data[[col]]`:
9088

9189
```{r}
92-
#' @importFrom rlang .data
93-
col_summary <- function(data, col) {
94-
ggplot(mpg) +
90+
col_summary <- function(df, col) {
91+
ggplot(df) +
9592
geom_bar(aes(x = .data[[col]])) +
9693
coord_flip()
9794
}
9895
9996
col_summary(mpg, "drv")
10097
```
10198

102-
To use the same kind of non-standard evaluation used by `aes()` and `vars()`, use `{{ col }}` to pass the unevaluated expression the user typed in `col` to `aes()`.
103-
104-
<!-- this uses development rlang, which is not yet released -->
99+
If the column name or expression is supplied by the user, you can also pass it to `aes()` or `vars()` using `{{ col }}`. This tidy eval operator captures the expression supplied by the user and forwards it to another tidy eval-enabled function such as `aes()` or `vars()`.
105100

106-
```{r, eval=FALSE}
107-
col_summary <- function(data, col) {
108-
ggplot(mpg) +
101+
```{r, eval = (packageVersion("rlang") >= "0.3.4.9003")}
102+
col_summary <- function(df, col) {
103+
ggplot(df) +
109104
geom_bar(aes(x = {{ col }})) +
110105
coord_flip()
111106
}
112107
113108
col_summary(mpg, drv)
114109
```
115110

116-
To summarise, if you know the mapping or facet specification is `col` in advance, use `aes(.data$col)` or `vars(.data$col)`. If `col` is a variable that contains the column name as a character scalar, use `aes(.data[[col]]` or `vars(.data[[col]])`. If you would like the behaviour of `col` to look and feel like `aes()` and `vars()`, use `aes({{ col }})` or `vars({{ col }})`.
111+
To summarise:
112+
113+
- If you know the mapping or facet specification is `col` in advance, use `aes(.data$col)` or `vars(.data$col)`.
114+
- If `col` is a variable that contains the column name as a character vector, use `aes(.data[[col]]` or `vars(.data[[col]])`.
115+
- If you would like the behaviour of `col` to look and feel like it would within `aes()` and `vars()`, use `aes({{ col }})` or `vars({{ col }})`.
117116

118-
You will see a lot of other ways to do this in the wild, but the syntax we use here is the only one we can guarantee will work in the future! In particular, don't use `aes_()` or `aes_string()`, as they are deprecated and may be removed in a future version. Finally, don't skip the step of creating a data frame and a mapping to pass in to `ggplot()` or its layers! You will see other ways of doing this in the wild, but these may rely on undocumented behaviour and can fail in unexpected ways.
117+
You will see a lot of other ways to do this in the wild, but the syntax we use here is the only one we can guarantee will work in the future! In particular, don't use `aes_()` or `aes_string()`, as they are deprecated and may be removed in a future version. Finally, don't skip the step of creating a data frame and a mapping to pass in to `ggplot()` or its layers! You will see other ways of doing this, but these may rely on undocumented behaviour and can fail in unexpected ways.
119118

120119
## Best practices for common tasks
121120

122121
### Using ggplot2 to visualize an object
123122

124-
ggplot2 is commonly used in packages to visualize objects (e.g., in a `plot()`-style function). For example, a package might define an S3 object that represents the probability of various discrete values:
123+
ggplot2 is commonly used in packages to visualize objects (e.g., in a `plot()`-style function). For example, a package might define an S3 class that represents the probability of various discrete values:
125124

126125
```{r}
127126
mpg_drv_dist <- structure(
@@ -134,7 +133,7 @@ mpg_drv_dist <- structure(
134133
)
135134
```
136135

137-
Many S3 objects in R implement a `plot()` method, but it is unrealistic to expect that a single `plot()` method can provide the visualization every one of your users is looking for. It is useful, however, to provide a `plot()` method as a visual summary that users can call to understand the essence of an object. To satisfy all your users, we suggest writing a function that transforms the object into a data frame (or a `list()` of data frames if your object is more complicated). A good example of this approach is [ggdendro](https://cran.r-project.org/package=ggdendro), which creates dendrograms using ggplot2 but also computes the data necessary for users to make their own. For the above example, the function might look like this:
136+
Many S3 classes in R have a `plot()` method, but it is unrealistic to expect that a single `plot()` method can provide the visualization every one of your users is looking for. It is useful, however, to provide a `plot()` method as a visual summary that users can call to understand the essence of an object. To satisfy all your users, we suggest writing a function that transforms the object into a data frame (or a `list()` of data frames if your object is more complicated). A good example of this approach is [ggdendro](https://cran.r-project.org/package=ggdendro), which creates dendrograms using ggplot2 but also computes the data necessary for users to make their own. For the above example, the function might look like this:
138137

139138
```{r}
140139
discrete_distr_data <- function(x) {
@@ -147,11 +146,10 @@ discrete_distr_data <- function(x) {
147146
discrete_distr_data(mpg_drv_dist)
148147
```
149148

150-
In general, users of `plot()` call it for its side-effects: it results in a graphic being displayed. This is different than the behaviour of a `ggplot()`, which is not rendered unless it is explicitly `print()`ed. Because of this, ggplot2 defines its own generic `autoplot()`, a call to which is expected to return a `ggplot()` (with no side effects).
149+
In general, users of `plot()` call it for its side-effects: it results in a graphic being displayed. This is different than the behaviour of a `ggplot()`, which is not displayed unless it is explicitly `print()`ed. Because of this, ggplot2 defines its own generic `autoplot()`, a call to which is expected to return a `ggplot()` (with no side effects).
151150

152151
```{r}
153152
#' @importFrom ggplot2 autoplot
154-
#' @importFrom rlang .data
155153
autoplot.discrete_distr <- function(object, ...) {
156154
plot_data <- discrete_distr_data(object)
157155
ggplot(plot_data, aes(.data$value, .data$probability)) +
@@ -170,7 +168,7 @@ plot.discrete_distr <- function(x, ...) {
170168
}
171169
```
172170

173-
It is considered bad practice to implement an S3 generic like `plot()`, or `autoplot()` if you don't own the S3 object, as it makes it hard for the package developer who does have control over the S3 to implement the method themselves. This shouldn't stop you from creating your own functions to visualize these objects!
171+
It is considered bad practice to implement an S3 generic like `plot()`, or `autoplot()` if you don't own the S3 class, as it makes it hard for the package developer who does have control over the S3 to implement the method themselves. This shouldn't stop you from creating your own functions to visualize these objects!
174172

175173
### Creating a new theme
176174

@@ -204,7 +202,7 @@ mpg_drv_summary2 <- function() {
204202

205203
### Testing ggplot2 output
206204

207-
We suggest testing the output of ggplot2 in using the [vdiffr](https://cran.r-project.org/package=vdiffr) package, which is a tool to manage visual test cases (this is one of the ways we test ggplot2). If changes in ggplot2 or your code introduce a change in the visual output of a ggplot, tests will fail when you run them locally or on Travis. To use vdiffr, make sure you are using [testthat](https://cran.r-project.org/package=testthat) (you can use `usethis::use_testthat()` to get started) and add vdiffr to `Suggests` in your `DESCRIPTION`. Then, use `expect_doppleganger(<name of plot>, <ggplot object>)` to make a test that fails if there are visual changes in `<ggplot object>`.
205+
We suggest testing the output of ggplot2 in using the [vdiffr](https://cran.r-project.org/package=vdiffr) package, which is a tool to manage visual test cases (this is one of the ways we test ggplot2). If changes in ggplot2 or your code introduce a change in the visual output of a ggplot, tests will fail when you run them locally or on Travis. To use vdiffr, make sure you are using [testthat](https://testthat.r-lib.org/) (you can use `usethis::use_testthat()` to get started) and add vdiffr to `Suggests` in your `DESCRIPTION`. Then, use `vdiffr::expect_doppleganger(<name of plot>, <ggplot object>)` to make a test that fails if there are visual changes in `<ggplot object>`.
208206

209207
```r
210208
test_that("output of ggplot() is stable", {
@@ -230,7 +228,7 @@ theme_custom <- function(...) {
230228
mpg_drv_summary() + theme_custom()
231229
```
232230

233-
Generally, if you add a method for a ggplot2 generic like `autoplot()`, ggplot2 should be in `Imports`. If for some reason you would like to keep ggplot2 in `Suggests`, it is possible to register your generics only if ggplot2 is installed using `vctrs::s3_register()`. If you do this, you should copy and paste the source of `vctrs::s3_register()` into your own package to avoid adding a [vctrs](https://cran.r-project.org/package=vctrs) dependency.
231+
Generally, if you add a method for a ggplot2 generic like `autoplot()`, ggplot2 should be in `Imports`. If for some reason you would like to keep ggplot2 in `Suggests`, it is possible to register your generics only if ggplot2 is installed using `vctrs::s3_register()`. If you do this, you should copy and paste the source of `vctrs::s3_register()` into your own package to avoid adding a [vctrs](https://vctrs.r-lib.org/) dependency.
234232

235233
```{r, eval=FALSE}
236234
.onLoad <- function(...) {

0 commit comments

Comments
 (0)