forked from chrismainey/PackageBuildSeminar
-
Notifications
You must be signed in to change notification settings - Fork 0
/
package_building_presentation.html
501 lines (364 loc) · 18.8 KB
/
package_building_presentation.html
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
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
<!DOCTYPE html>
<html lang="" xml:lang="">
<head>
<title>Package Building</title>
<meta charset="utf-8" />
<meta name="author" content="Chris Mainey" />
<script src="libs/header-attrs/header-attrs.js"></script>
<link rel="stylesheet" href="xaringan-themer.css" type="text/css" />
</head>
<body>
<textarea id="source">
class: title-slide right
<br><br><br>
# Why and how to build an R package
#### Chris Mainey - Healthcare Evaluation Data (HED)
#### University Hospitals Birmingham NHS FT
<br>
[[email protected]](mailto:[email protected]) <svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" style="height:1em;fill:currentColor;position:relative;display:inline-block;top:.1em;"> [ comment ] <path d="M464 64H48C21.49 64 0 85.49 0 112v288c0 26.51 21.49 48 48 48h416c26.51 0 48-21.49 48-48V112c0-26.51-21.49-48-48-48zm0 48v40.805c-22.422 18.259-58.168 46.651-134.587 106.49-16.841 13.247-50.201 45.072-73.413 44.701-23.208.375-56.579-31.459-73.413-44.701C106.18 199.465 70.425 171.067 48 152.805V112h416zM48 400V214.398c22.914 18.251 55.409 43.862 104.938 82.646 21.857 17.205 60.134 55.186 103.062 54.955 42.717.231 80.509-37.199 103.053-54.947 49.528-38.783 82.032-64.401 104.947-82.653V400H48z"></path></svg>
[HED.nhs.uk](https://www.hed.nhs.uk) <svg viewBox="0 0 496 512" xmlns="http://www.w3.org/2000/svg" style="height:1em;fill:currentColor;position:relative;display:inline-block;top:.1em;"> [ comment ] <path d="M336.5 160C322 70.7 287.8 8 248 8s-74 62.7-88.5 152h177zM152 256c0 22.2 1.2 43.5 3.3 64h185.3c2.1-20.5 3.3-41.8 3.3-64s-1.2-43.5-3.3-64H155.3c-2.1 20.5-3.3 41.8-3.3 64zm324.7-96c-28.6-67.9-86.5-120.4-158-141.6 24.4 33.8 41.2 84.7 50 141.6h108zM177.2 18.4C105.8 39.6 47.8 92.1 19.3 160h108c8.7-56.9 25.5-107.8 49.9-141.6zM487.4 192H372.7c2.1 21 3.3 42.5 3.3 64s-1.2 43-3.3 64h114.6c5.5-20.5 8.6-41.8 8.6-64s-3.1-43.5-8.5-64zM120 256c0-21.5 1.2-43 3.3-64H8.6C3.2 212.5 0 233.8 0 256s3.2 43.5 8.6 64h114.6c-2-21-3.2-42.5-3.2-64zm39.5 96c14.5 89.3 48.7 152 88.5 152s74-62.7 88.5-152h-177zm159.3 141.6c71.4-21.2 129.4-73.7 158-141.6h-108c-8.8 56.9-25.6 107.8-50 141.6zM19.3 352c28.6 67.9 86.5 120.4 158 141.6-24.4-33.8-41.2-84.7-50-141.6h-108z"></path></svg>
[mainard.co.uk](https://www.mainard.co.uk) <svg viewBox="0 0 496 512" xmlns="http://www.w3.org/2000/svg" style="height:1em;fill:currentColor;position:relative;display:inline-block;top:.1em;"> [ comment ] <path d="M336.5 160C322 70.7 287.8 8 248 8s-74 62.7-88.5 152h177zM152 256c0 22.2 1.2 43.5 3.3 64h185.3c2.1-20.5 3.3-41.8 3.3-64s-1.2-43.5-3.3-64H155.3c-2.1 20.5-3.3 41.8-3.3 64zm324.7-96c-28.6-67.9-86.5-120.4-158-141.6 24.4 33.8 41.2 84.7 50 141.6h108zM177.2 18.4C105.8 39.6 47.8 92.1 19.3 160h108c8.7-56.9 25.5-107.8 49.9-141.6zM487.4 192H372.7c2.1 21 3.3 42.5 3.3 64s-1.2 43-3.3 64h114.6c5.5-20.5 8.6-41.8 8.6-64s-3.1-43.5-8.5-64zM120 256c0-21.5 1.2-43 3.3-64H8.6C3.2 212.5 0 233.8 0 256s3.2 43.5 8.6 64h114.6c-2-21-3.2-42.5-3.2-64zm39.5 96c14.5 89.3 48.7 152 88.5 152s74-62.7 88.5-152h-177zm159.3 141.6c71.4-21.2 129.4-73.7 158-141.6h-108c-8.8 56.9-25.6 107.8-50 141.6zM19.3 352c28.6 67.9 86.5 120.4 158 141.6-24.4-33.8-41.2-84.7-50-141.6h-108z"></path></svg>
[github.com/chrismainey](https://github.com/chrismainey) <svg viewBox="0 0 496 512" xmlns="http://www.w3.org/2000/svg" style="height:1em;fill:currentColor;position:relative;display:inline-block;top:.1em;"> [ comment ] <path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"></path></svg>
[twitter.com/chrismainey](https://twitter.com/chrismainey) <svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" style="height:1em;fill:currentColor;position:relative;display:inline-block;top:.1em;"> [ comment ] <path d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"></path></svg>
.footnote[
Background image by Leone Venter: https://unsplash.com/@fempreneurstyledstock
]
---
# Overview
+ What is an R package and why might you want to build one
+ Functions
+ Package basic structures and concepts
+ `Roxygen2` documentation and vignettes
+ Dependencies
+ Build and check
--
+ Extras:
+ Source control (Git and GitHub)
+ Unit tests and code coverage
+ Releasing to CRAN
__We'll look at this practically by building an R package__
__References:__
+ [Hadley Wickham, Jenny Bryan's: Building R packages](https://r-pkgs.org/)
+ [`R` official Documentation](https://cran.r-project.org/doc/manuals/R-exts.html#Creating-R-packages)
---
# R packages
+ Use packages all the time (base, stats, utils). You probably know `tidyverse`, `dplyr`, `ggplot2`.
+ `R` package repository CRAN has 16181 packages at time of writing.
+ Other places like Bioconductor, source packages on GitHub etc.
--
<br><br>
+ __What are they?__
+ Collections of functions related to certain workflows and tasks
+ Up to the author what goes in
+ Can be depend on other packages or be written from scratch
--
+ __Why use them?__
+ Don't reinvent the wheel!
+ Do you know how to do _[Insert thing]_ better than a topic expert?
--
+ __Why build a package?__
+ You might be the topic expert!
+ Share your code with colleagues, or others, and build collaboratively.
---
# But I can't build one...
--
.pull-left[
__...yes you can!__ You don't need perfect code to share it! _As you'll see from mine..._
<br><br>
+ You can get much of the background today and by looking online.
+ You will get it wrong a lot. That's part of the process.
<br>
Our first step is to convert our code to a format that can be run by others
+ We need to convert our code to a `function`.
]
.pull-right[
<img src="https://raw.githubusercontent.com/allisonhorst/stats-illustrations/master/rstats-artwork/code_hero.jpg" width="90%">
.footnote[
Artwork by @allison_horst: https://github.com/allisonhorst/stats-illustrations
]
]
---
# Functions
Functions have a structure that considers generic inputs and outputs, and performs something on them in between.
_(bad explanation, but stay with me...)_
```r
mydataframe <- data.frame(id=seq(3), old_col = c(97.5, 100, 147.5))
```
--
.pull-left[
#### You have code like this:
```r
mydataframe$new_col <-
(mydataframe$old_col + 2.5) / 50
mydataframe
```
```
## id old_col new_col
## 1 1 97.5 2.00
## 2 2 100.0 2.05
## 3 3 147.5 3.00
```
]
--
.pull-right[
#### Turn it into a function:
```r
my_function <- function(col){
rtn <- (col + 2.5) / 50
return(rtn)
}
mydataframe$new_col2 <- my_function(mydataframe$old_col)
mydataframe
```
```
## id old_col new_col new_col2
## 1 1 97.5 2.00 2.00
## 2 2 100.0 2.05 2.05
## 3 3 147.5 3.00 3.00
```
]
---
# Functions
.pull-left[
+ __Function has:__
+ A name (`my_function`)
+ An input (`cols`)
+ A return value (by default, returns the last line, but can be explicit with `return()`)
]
.pull-right[
```r
my_function <- function(col){
new_col <- col + 2.5 / 50
return(new_col)
}
```
]
---
# Functions
.pull-left[
+ __Function has:__
+ A name (`my_function`)
+ An input (`cols`)
+ A return value (by default, returns the last line, but can be explicit with `return()`)
]
.pull-right[
```r
my_function <- function(col){
new_col <- col + 2.5 / 50
return(new_col)
}
```
]
<br><br>
It's these functions that we use, and often chain together to build packages.
We need to:
+ Document them, including help files for users.
+ Tell `R` whether they depend on any other files/packages.
+ Better packages will consider error handling and how best to return values.
---
## Structure of a package
Can contain one or many functions. The parts it requires are:
+ __DESCRIPTION__ - A file with meta data about package, authors etc. Includes a list dependencies
+ __NAMESPACE__ - A short-hand file so `R` can understand what functions and dependencies to import
+ __Function(s)__ - Files with `R` functions, saved in a directory called `R`
+ __Help files__ - Text describing functions. These are written in syntax similar to latex, but it's common to generate them using `roxygen2` - a package documentation tool.
Optional:
+ __Vignette__ - Worked examples of using your functions. I think all good packages should have at least one. Again, written in same format as help files, or Rmarkdown.
+ __Unit tests__ - Automated tests help to detect errors in your code when working on a package.
---
## Dependencies
If you package requires other packages, you need to convey that so they are installed.
+ Description file can specify:
+ `Imports` - list packages that are ___required___ to use your code (added to NAMESPACE).
+ `Suggests` - A courtesy to your users, packages that are not required but help (e.g. used in vignette). Don't need to use this with a local file
--
You no longer need `Depends` since NAMESPACE files are used, and `LinkingTo` can be used to link to C++ or other files. `Enhanced` can be used to indicate your functions enhance another package.
--
Refer to Jim Hester's post: https://www.tidyverse.org/blog/2019/05/itdepends/
__Beware of tidyverse dependencies!__ Hadley's Wickham's advice:
>Because the tidyverse is a set of packages designed for interactive data analysis, this is, in short, a bad idea. The tidyverse package includes a substantial number of direct and indirect dependencies (79 packages, as of this writing), many of which are likely unnecessary for the purposes of your package. Furthermore, the CRAN maintainers frown upon depending on it, which can cause hassle for you down the line.
---
# Extras (1)
### Source control:
+ Source control is important in software development, allowing you to track changes and 'rollback'.
+ Git is common, but not only option. With Git you have a local repository, and can sync to a 'remote' like GitHub.
+ You or other users can make 'branches' to build functions before merging them together.
+ Happy Git with `R` https://happygitwithr.com/
+ GitHub automatically renders README.md when you land on it.
--
### Unit tests:
+ Use `testthat` package, use a control script called 'testthat', in a folder called 'tests'.
+ Sub-folder called 'test_that' containing test scripts.
+ See `testthat` vignettes for more details.
+ Can be combined with 'Code coverage' e.g. `covr` package.
---
# Extras (2)
### Continuous integration (CI)
+ When building larger packages collaboratively, CI builds packages after each push.
+ Recommend GitHub actions, but Travis-CI is also common.
### `pkgdown`
+ A extension builds a package website from your helpfiles, README, help files, and metadata.
+ Can then be hosted anywhere, but GitHub pages is common.
+ Can be built from CI systems
--
## Trust the `usethis` package!
---
# Lets build a package!
+ Use Rstudio to set up a new project with an 'R package template'
+ Write our functions.
+ Insert a 'Roxygen Skeleton' ('Code' menu), and fill in
+ Build our package, use CRAN checks
+ Write a brief unit test
+ Commit this all to GitHub.
+ We will use Rstudio, `devtools`, `usethis` and `roxygen2` in many places, as they set up the elements for you.
+ You need [Rtools](https://cran.r-project.org/bin/windows/Rtools/) on Windows!
___Here's one I prepared earlier:___
https://github.com/chrismainey/brilliant2
---
# Releasing to CRAN
+ CRAN is the most popular repository for packages. It has clear rules, and moderated by wonderful volunteers.
--
+ Rules around release, and package must pass CRAN checks and build on Windows, macOS and various Linux distros.
--
+ CI can help with builds, but also use Win-builder and R-Hub, through `devtools`.
--
+ Should include a 'NEWS' file and 'cran_comments' describing package, changes, and any errors in builds.
--
+ Follow all the steps in `devtools::release()`
---
class: middle
# Look at an existing package:
https://github.com/chrismainey/FunnelPlotR
---
# Summary
+ Packages are ways to make functions portable
+ Help you share code, collaborate and make code available to non-experts
+ Rstudio gives you package templates, and integrated with build, check and other tools
+ Metadata is saved in DESCRIPTION file
+ NAMESPACE contains import/dependency details, generated in build in `RStudio`/`devtools`/`roxygen2`
+ `R` functions saved in R folder, document using `roxygen2`
+ Unit test check code is working properly (`testthat` package)
+ Build (using RTools in Windows). If releasing to CRAN, use checks with `--as-cran` option.
+ Can build to binary and install from zip
+ GitHub, CI, codecov and `pkgdown` can all help with process
</textarea>
<style data-target="print-only">@media screen {.remark-slide-container{display:block;}.remark-slide-scaler{box-shadow:none;}}</style>
<script src="https://remarkjs.com/downloads/remark-latest.min.js"></script>
<script>var slideshow = remark.create({
"ratio": "16:9",
"highlightStyle": "github",
"highlightLines": true,
"countIncrementalSlides": false
});
if (window.HTMLWidgets) slideshow.on('afterShowSlide', function (slide) {
window.dispatchEvent(new Event('resize'));
});
(function(d) {
var s = d.createElement("style"), r = d.querySelector(".remark-slide-scaler");
if (!r) return;
s.type = "text/css"; s.innerHTML = "@page {size: " + r.style.width + " " + r.style.height +"; }";
d.head.appendChild(s);
})(document);
(function(d) {
var el = d.getElementsByClassName("remark-slides-area");
if (!el) return;
var slide, slides = slideshow.getSlides(), els = el[0].children;
for (var i = 1; i < slides.length; i++) {
slide = slides[i];
if (slide.properties.continued === "true" || slide.properties.count === "false") {
els[i - 1].className += ' has-continuation';
}
}
var s = d.createElement("style");
s.type = "text/css"; s.innerHTML = "@media print { .has-continuation { display: none; } }";
d.head.appendChild(s);
})(document);
// delete the temporary CSS (for displaying all slides initially) when the user
// starts to view slides
(function() {
var deleted = false;
slideshow.on('beforeShowSlide', function(slide) {
if (deleted) return;
var sheets = document.styleSheets, node;
for (var i = 0; i < sheets.length; i++) {
node = sheets[i].ownerNode;
if (node.dataset["target"] !== "print-only") continue;
node.parentNode.removeChild(node);
}
deleted = true;
});
})();
(function() {
"use strict"
// Replace <script> tags in slides area to make them executable
var scripts = document.querySelectorAll(
'.remark-slides-area .remark-slide-container script'
);
if (!scripts.length) return;
for (var i = 0; i < scripts.length; i++) {
var s = document.createElement('script');
var code = document.createTextNode(scripts[i].textContent);
s.appendChild(code);
var scriptAttrs = scripts[i].attributes;
for (var j = 0; j < scriptAttrs.length; j++) {
s.setAttribute(scriptAttrs[j].name, scriptAttrs[j].value);
}
scripts[i].parentElement.replaceChild(s, scripts[i]);
}
})();
(function() {
var links = document.getElementsByTagName('a');
for (var i = 0; i < links.length; i++) {
if (/^(https?:)?\/\//.test(links[i].getAttribute('href'))) {
links[i].target = '_blank';
}
}
})();
// adds .remark-code-has-line-highlighted class to <pre> parent elements
// of code chunks containing highlighted lines with class .remark-code-line-highlighted
(function(d) {
const hlines = d.querySelectorAll('.remark-code-line-highlighted');
const preParents = [];
const findPreParent = function(line, p = 0) {
if (p > 1) return null; // traverse up no further than grandparent
const el = line.parentElement;
return el.tagName === "PRE" ? el : findPreParent(el, ++p);
};
for (let line of hlines) {
let pre = findPreParent(line);
if (pre && !preParents.includes(pre)) preParents.push(pre);
}
preParents.forEach(p => p.classList.add("remark-code-has-line-highlighted"));
})(document);</script>
<script>
slideshow._releaseMath = function(el) {
var i, text, code, codes = el.getElementsByTagName('code');
for (i = 0; i < codes.length;) {
code = codes[i];
if (code.parentNode.tagName !== 'PRE' && code.childElementCount === 0) {
text = code.textContent;
if (/^\\\((.|\s)+\\\)$/.test(text) || /^\\\[(.|\s)+\\\]$/.test(text) ||
/^\$\$(.|\s)+\$\$$/.test(text) ||
/^\\begin\{([^}]+)\}(.|\s)+\\end\{[^}]+\}$/.test(text)) {
code.outerHTML = code.innerHTML; // remove <code></code>
continue;
}
}
i++;
}
};
slideshow._releaseMath(document);
</script>
<!-- dynamically load mathjax for compatibility with self-contained -->
<script>
(function () {
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'https://mathjax.rstudio.com/latest/MathJax.js?config=TeX-MML-AM_CHTML';
if (location.protocol !== 'file:' && /^https?:/.test(script.src))
script.src = script.src.replace(/^https?:/, '');
document.getElementsByTagName('head')[0].appendChild(script);
})();
</script>
</body>
</html>