forked from COMBINE-Australia/r-pkg-dev
-
Notifications
You must be signed in to change notification settings - Fork 0
/
07-dependencies.Rmd
333 lines (284 loc) · 10.8 KB
/
07-dependencies.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
# Dependencies
Our `make_shades()` function produces shades of a colour but it would be good
to see what those look like. Below is a new function called `plot_colours()`
that can visualise them for us using **ggplot2** (if you don't have **ggplot2**
installed do that now). Add this function to `colours.R`.
```{r}
#' Plot colours
#'
#' Plot a vector of colours to see what they look like
#'
#' @param colours Vector of colour to plot
#'
#' @return A ggplot2 object
#' @export
#'
#' @examples
#' shades <- make_shades("goldenrod", 5)
#' plot_colours(shades)
plot_colours <- function(colours) {
plot_data <- data.frame(Colour = colours)
ggplot(plot_data,
aes(x = .data$Colour, y = 1, fill = .data$Colour,
label = .data$Colour)) +
geom_tile() +
geom_text(angle = "90") +
scale_fill_identity() +
theme_void()
}
```
Now that we have added something new we should run our checks again
(`devtools::document()` is automatically run as part of `devtools::check()` so
we can skip that step).
```{r}
devtools::check()
```
```{}
-- R CMD check results ------------------------------------------- mypkg 0.0.0.9000 ----
Duration: 15.4s
> checking examples ... ERROR
Running examples in 'mypkg-Ex.R' failed
The error most likely occurred in:
> base::assign(".ptime", proc.time(), pos = "CheckExEnv")
> ### Name: plot_colours
> ### Title: Plot colours
> ### Aliases: plot_colours
>
> ### ** Examples
>
> shades <- make_shades("goldenrod", 5)
> plot_colours(shades)
Error in ggplot(plot_data, aes(x = .data$Colour, y = 1, fill = .data$Colour, :
could not find function "ggplot"
Calls: plot_colours
Execution halted
> checking R code for possible problems ... NOTE
plot_colours: no visible global function definition for 'ggplot'
plot_colours: no visible global function definition for 'aes'
plot_colours: no visible binding for global variable '.data'
plot_colours: no visible global function definition for 'geom_tile'
plot_colours: no visible global function definition for 'geom_text'
plot_colours: no visible global function definition for
'scale_fill_identity'
plot_colours: no visible global function definition for 'theme_void'
Undefined global functions or variables:
.data aes geom_text geom_tile ggplot scale_fill_identity theme_void
1 error x | 0 warnings √ | 1 note x
```
The checks have returned one error and one note. The error is more serious so
let's have a look at that first. It says `could not find function "ggplot"`.
Hmmmm...the `ggplot()` function is in the **ggplot2** package. When we used
`col2rgb()` in the `make_shades()` function we had to prefix it with
`grDevices::`, maybe we should do the same here.
```{r}
#' Plot colours
#'
#' Plot a vector of colours to see what they look like
#'
#' @param colours Vector of colour to plot
#'
#' @return A ggplot2 object
#' @export
#'
#' @examples
#' shades <- make_shades("goldenrod", 5)
#' plot_colours(shades)
plot_colours <- function(colours) {
plot_data <- data.frame(Colour = colours)
ggplot2::ggplot(plot_data,
ggplot2::aes(x = .data$Colour, y = 1, fill = .data$Colour,
label = .data$Colour)) +
ggplot2::geom_tile() +
ggplot2::geom_text(angle = "90") +
ggplot2::scale_fill_identity() +
ggplot2::theme_void()
}
```
Now what do our checks say?
```{r}
devtools::check()
```
```{}
-- R CMD check results ------------------------------------------ mypkg 0.0.0.9000 ----
Duration: 15s
> checking examples ... ERROR
Running examples in 'mypkg-Ex.R' failed
The error most likely occurred in:
> base::assign(".ptime", proc.time(), pos = "CheckExEnv")
> ### Name: plot_colours
> ### Title: Plot colours
> ### Aliases: plot_colours
>
> ### ** Examples
>
> shades <- make_shades("goldenrod", 5)
> plot_colours(shades)
Error in loadNamespace(name) : there is no package called 'ggplot2'
Calls: plot_colours ... loadNamespace -> withRestarts -> withOneRestart -> doWithOneRestart
Execution halted
> checking dependencies in R code ... WARNING
'::' or ':::' import not declared from: 'ggplot2'
> checking R code for possible problems ... NOTE
plot_colours: no visible binding for global variable '.data'
Undefined global functions or variables:
.data
1 error x | 1 warning x | 1 note x
```
There is now one error, one warning and one note. That seems like we are going
in the wrong direction but the error is from running the example and the
warning gives us a clue to what the problem is. It says "'::' or ':::' import
not declared from: 'ggplot2'". The important word here is "import". Just like
when we export a function in our package we need to make it clear when we are
using functions in another package. To do this we can use
`usethis::use_package()`.
```{r}
usethis::use_package("ggplot2")
```
```{}
✔ Setting active project to 'C:/Users/Luke/Desktop/mypkg'
✔ Adding 'ggplot2' to Imports field in DESCRIPTION
● Refer to functions with `ggplot2::fun()`
```
The output tells us to refer to functions using "::" like we did above so we
were on the right track. It also mentions that it has modified the `DESCRIPTION`
file. Let's have a look at it now.
```{}
Package: mypkg
Title: My Personal Package
Version: 0.0.0.9000
Authors@R: c(
person(given = "Package",
family = "Creator",
role = c("aut", "cre"),
email = "package.creator@mypkg.com"),
person(given = "Package",
family = "Contributor",
role = c("ctb"),
email = "package.contributor@mypkg.com")
)
Description: This is my personal package. It contains some handy functions that
I find useful for my projects.
License: MIT + file LICENSE
Encoding: UTF-8
LazyData: true
RoxygenNote: 6.1.1
Suggests:
testthat (>= 2.1.0)
Imports:
ggplot2
```
The two lines at the bottom tell us that our package uses functions in
**ggplot2**. There are three main types of dependencies^[There is a fourth kind
(Enhances) but that is almost never used.]. Imports is the most common. This
means that we use functions from these packages and they must be installed when
our package is installed. The next most common is Suggests. These are packages
that we use in developing our package (such as **testthat** which is already
listed here) or packages that provide some additional, optional functionality.
Suggested packages aren't usually installed so we need to do a check before we
use them. The output of `usethis::use_package()` will give you an example if
you add a suggested package. The third type of dependency is Depends. If you
depend on a package it will be loaded whenever your package is loaded. Depends
shouldn't usually be used unless your package closely complements another
package. An example of when Depends is necessary is if you package mainly
operates on an object defined by another package.
> **Should you use a dependency?**
>
> Deciding which packages (and how many) to depend on is a difficult and
> philosophical choice. Using functions from other packages can save you time
> and effort in development but it might make it more difficult to maintain
> your package. Some things you might want to consider before depending on a
> package are:
>
> * How much of the functionality of the package do you want to use?
> * Could you easily reproduce that functionality?
> * How well maintained is the package?
> * How often is it updated? Packages that change a lot are more likely to
> break your code.
> * How many dependencies of it's own does that package have?
> * Are you users likely to have the package installed already?
>
> Packages like **ggplot2** are good choices for dependencies because they are
> well maintained, don't change too often, are commonly used and perform a
> single task so you are likely to use many of the functions.
Hopefully now that we have imported **ggplot2** we should pass the checks.
```{r}
devtools::check()
```
```{}
-- R CMD check results ------------------------------------------ mypkg 0.0.0.9000 ----
Duration: 16.4s
> checking R code for possible problems ... NOTE
plot_colours: no visible binding for global variable '.data'
Undefined global functions or variables:
.data
0 errors √ | 0 warnings √ | 1 note x
```
Success! Now all that's left is that pesky note. Visualisation functions are
probably some of the most common functions in packages but there are some
tricks to programming with **ggplot2**. The details are outside the scope of
this workshop but if you are interested see the "Using ggplot2 in packages"
vignette https://ggplot2.tidyverse.org/dev/articles/ggplot2-in-packages.html.
To solve our problem we need to import the **rlang** package.
```{r}
usethis::use_package("rlang")
```
```{}
✔ Adding 'rlang' to Imports field in DESCRIPTION
● Refer to functions with `rlang::fun()`
```
Writing `rlang::.data` wouldn't be very attractive or readable^[Also for
technical reasons it won't work in this case.]. When we want to use a function
in another package with `::` we need to exlicitly import it. Just like when we
exported our functions we do this using a Roxygen comment.
```{r}
#' Plot colours
#'
#' Plot a vector of colours to see what they look like
#'
#' @param colours Vector of colour to plot
#'
#' @return A ggplot2 object
#' @export
#'
#' @importFrom rlang .data
#'
#' @examples
#' shades <- make_shades("goldenrod", 5)
#' plot_colours(shades)
plot_colours <- function(colours) {
plot_data <- data.frame(Colour = colours)
ggplot2::ggplot(plot_data,
ggplot2::aes(x = .data$Colour, y = 1, fill = .data$Colour,
label = .data$Colour)) +
ggplot2::geom_tile() +
ggplot2::geom_text(angle = "90") +
ggplot2::scale_fill_identity() +
ggplot2::theme_void()
}
```
When we use `devtools::document()` this comment will be read and a note placed
in the `NAMESPACE` file, just like for `@export`.
```{}
# Generated by roxygen2: do not edit by hand
export(make_shades)
export(plot_colours)
importFrom(rlang,.data)
```
Those two steps should fix our note.
```{r}
devtools::check()
```
```{}
-- R CMD check results ------------------------------------------ mypkg 0.0.0.9000 ----
Duration: 16.8s
0 errors √ | 0 warnings √ | 0 notes √
```
If we used `rlang::.data` in multiple functions in our pacakge it might make
sense to only import it once. It doesn't matter where we put the `@importFrom`
line (or how many times) it will still be added to `NAMESPACE`. This means we
can put all import in a central location. The advantage of this is that they
only appear once and are all in one place but it makes it harder to know which
of our functions have which imports and remove them if they are no longer
needed. Which approach you take is up to you.
We should write some tests for this function as well but we will leave that
as an exercise for you to try later.