You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardexpand all lines: vignettes/ggplot2-in-packages.Rmd
+22-24
Original file line number
Diff line number
Diff line change
@@ -31,7 +31,7 @@ mpg_drv_summary <- function() {
31
31
mpg_drv_summary()
32
32
```
33
33
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`).
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!
51
51
52
52
## Using `aes()` and `vars()` in a package function
53
53
54
54
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:
@@ -75,12 +74,11 @@ There are three situations in which you will encounter this problem:
75
74
- You have the column name as a character vector.
76
75
- 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()`.
77
76
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()`).
If you have the column name as a character vector (e.g., `col = "drv"`), use `.data[[col]]`:
90
88
91
89
```{r}
92
-
#' @importFrom rlang .data
93
-
col_summary <- function(data, col) {
94
-
ggplot(mpg) +
90
+
col_summary <- function(df, col) {
91
+
ggplot(df) +
95
92
geom_bar(aes(x = .data[[col]])) +
96
93
coord_flip()
97
94
}
98
95
99
96
col_summary(mpg, "drv")
100
97
```
101
98
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()`.
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 }})`.
117
116
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.
119
118
120
119
## Best practices for common tasks
121
120
122
121
### Using ggplot2 to visualize an object
123
122
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:
125
124
126
125
```{r}
127
126
mpg_drv_dist <- structure(
@@ -134,7 +133,7 @@ mpg_drv_dist <- structure(
134
133
)
135
134
```
136
135
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:
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).
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!
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>`.
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.
0 commit comments