Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
8e2f6f9
Decouple example values from description
katrinleinweber Feb 20, 2019
941673d
Argue for reproducibility due to multiple data files
katrinleinweber Mar 24, 2019
1e086eb
Turn 1st roxygen challenge into faded example
katrinleinweber Mar 24, 2019
c3bf956
Turn 2nd roxygen challange into faded example
katrinleinweber Mar 26, 2019
b34919c
Reverse stance on commenting
katrinleinweber Mar 23, 2019
6a2949b
Add CSVLint & goodtables challange
katrinleinweber Apr 4, 2019
8c281f5
Teach use_description() after RStudio's import
katrinleinweber Apr 4, 2019
505a1cd
Fix typo
katrinleinweber Apr 4, 2019
381ba43
Rephrase & expand commenting advice to be a mini-ToC of this lesson
katrinleinweber Apr 5, 2019
b664b42
Merge branch 'content-changes' into INTEGRATE
katrinleinweber Apr 5, 2019
c23e91a
Merge branch 'faded-examples' into INTEGRATE
katrinleinweber Apr 5, 2019
560961f
Merge branch 'katrinleinweber-patch-1' into INTEGRATE
katrinleinweber Apr 5, 2019
a8ed64d
Rephrase test-example dichotomy to include R CMD check
katrinleinweber Apr 4, 2019
1cb2360
Move basic auto-testing into function files
katrinleinweber Apr 4, 2019
c169f66
Refer to testthat::auto_test_package() docu
katrinleinweber Apr 4, 2019
ec4a740
Explain how to run packaged tests
katrinleinweber Apr 4, 2019
1662ef3
Move function test content to own file
katrinleinweber Apr 4, 2019
6d2fd39
Highlight the intermittent step back from v2 functions
katrinleinweber Apr 4, 2019
933349d
Add library call to before all unpackaged tests
katrinleinweber Apr 5, 2019
171139a
Fix typos
Apr 8, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions _episodes_rmd/01-intro-rstudio.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ on if needed. You can copy-paste into the R console, but the RStudio script
editor allows you to 'send' the current line or the currently selected text to
the R console using the <kbd>Ctrl</kbd>+<kbd>Return</kbd> shortcut.

At some point in your analysis you may want to check the content of variable or
At some point in your analysis you may want to check the content of a variable or
the structure of an object, without necessarily keep a record of it in your
script. You can type these commands directly in the console. RStudio provides
the <kbd>Ctrl</kbd>+<kbd>1</kbd> and <kbd>Ctrl</kbd>+<kbd>2</kbd> shortcuts allow you to jump between the script and the
Expand All @@ -61,8 +61,16 @@ window and press <kdb>Esc</kdb>; this should help you out of trouble.

### Commenting

Use `#` signs to comment. Comment liberally in your R scripts. Anything to the
right of a `#` is ignored by R.
Use `#` symbols to start a comment. Anything to the
right of a `#` is ignored by R. However, keep in mind that code should be
[like a joke: good without explanation](https://twitter.com/search?q=code%20joke%20explain%20good).
This means that adding comments should be of lower priority than:

- naming functions, arguments and variables in a self-explanatory
manner,
- cleaning up or rewriting hard-to-understand code
- adding formal documentation for your functions
- adding automated tests for your code

### Assignment Operator

Expand Down
87 changes: 44 additions & 43 deletions _episodes_rmd/03-func-R.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ knitr_fig_path("03-func-R-")
```

If we only have one data set to analyze, it would probably be faster to load the file into a spreadsheet and use that to plot some simple statistics.
However, we often have multiple data files or can expect more in the future.
That's why it's usually a good idea to prepare for this case
and write code in a reproducible manner right away.
In this lesson, we'll learn how to write a function so that we can repeat several operations with a single command.

### Defining functions
Expand Down Expand Up @@ -197,8 +200,8 @@ center <- function(data, desired) {
```

We could test this on our actual data, but since we don't know what the values ought to be, it will be hard to tell if the result was correct.
Instead, let's create a vector of 0s and then center that around 3.
This will make it simple to see if our function is working as expected:
Instead, let's create a small vector of integers and center it around a small integer,
so that we can comprehend, retrace and therefore check the calculation in our head:

```{r, }
(z <- c(1, 2, 3))
Expand Down Expand Up @@ -310,20 +313,23 @@ Each `@param` documents an input parameter, while `@return` explains the functio
output. The more comprehensible a description of its in- and output data (types), the
easier a function can be integrated into larger analysis pipelines.

```{r challenge-more-advanced-function-analyze, eval=FALSE, include=FALSE}
analyze <- function(filename) {
dat <- read.csv(file = filename, header = FALSE)
avg_day_inflammation <- colMeans(dat)
plot(avg_day_inflammation)
}
```

> ## Functions to Create Graphs
>
> Write a function called `analyze` that takes a filename as an argument
> and displays the graph produced in the [previous lesson][start-ep] (average inflammation over time).
> `analyze("inflammation.csv")` should produce the graph already shown,
> Be sure to document your function with roxygen comments.
> To automate the plotting of the graphs from the
> [previous lesson][start-ep] (average inflammation over time),
> we wrote the following function:
> ~~~
> analyze <- function(filename) {
> # Input a character string that correspondes to a filename to
> # to get the average inflammation of each day plotted.
> dat <- read.csv(file = filename, header = FALSE)
> avg_day_inflammation <- colMeans(dat)
> plot(avg_day_inflammation)
> }
> ~~~
> {: .r}
>
> Formalise the above comments into roxygen-style function documentation.
>
> > ## Solution
> > ~~~
Expand All @@ -335,11 +341,7 @@ analyze <- function(filename) {
> > #'
> > #' @examples
> > #' analyze("inflammation.csv")
> > analyze <- function(filename) {
> > dat <- read.csv(file = filename, header = FALSE)
> > avg_day_inflammation <- colMeans(dat)
> > plot(avg_day_inflammation)
> > }
> > analyze <- function(filename) { … }
> > ~~~
> > {: .r}
> {: .solution}
Expand All @@ -352,34 +354,34 @@ and a roxygen comment skeleton will be inserted. Note that we are not using
`@export` here, because it will only become relevant for [packaging]({{ page.root }}/reference/#packages).
You can safely ignore it for now, or delete it.

```{r challenge-more-advanced-function-rescale, include=FALSE}
#' Rescaling vectors to lie in the range 0 to 1
#'
#' @param v A numeric vector
#'
#' @return The rescaled numeric vector
#'
#' @examples
#' rescale(c(1, 2, 3)) # should return [1] 0.0 0.5 1.0
#' rescale(c(1, 2, 3, 4, 5)) # should return [1] 0.00 0.25 0.50 0.75 1.00
## Rescaling

Another example of an informally documented function:

```{r challenge-more-advanced-function-rescale}
rescale <- function(v) {
# takes a vector as input
# returns a corresponding vector of values scaled to the range 0 to 1
# e.g.: rescale(c(1, 2, 3)) => 0.0 0.5 1.0
L <- min(v)
H <- max(v)
result <- (v - L) / (H - L)
return(result)
}
```

> ## Rescaling
>
> Write a function `rescale` that takes a vector as input and returns a corresponding vector of values scaled to lie in the range 0 to 1.
> Please create a new file for this and save it as `rescale.R`.
> (If `L` and `H` are the lowest and highest values in the original vector, then the replacement for a value `v` should be `(v-L) / (H-L)`.)
> Be sure to document your function with roxygen comments.
Please create a new file for this and save it as `rescale.R`, and test that
it is mathematically correct by using `min`, `max`, and `plot`.

> ## Rescaling documentation
>
> Test that your `rescale` function is working properly using `min`, `max`, and `plot`.
> Convert `rescale`'s comments into roxygen function docu! Which keyboard shortcut
> gets you started in RStudio?
>
> > ## Solution
> >
> > Find the keyboard shortcut above, and one possible roxygen documentation below.
> >
> > ~~~
> > #' Rescaling vectors to lie in the range 0 to 1
> > #'
Expand All @@ -390,13 +392,7 @@ rescale <- function(v) {
> > #' @examples
> > #' rescale(c(1, 2, 3)) # should return [1] 0.0 0.5 1.0
> > #' rescale(c(1, 2, 3, 4, 5)) # should return [1] 0.00 0.25 0.50 0.75 1.00
> >
> > rescale <- function(v) {
> > L <- min(v)
> > H <- max(v)
> > result <- (v - L) / (H - L)
> > return(result)
> > }
> > rescale <- function(v) { ... }
> > ~~~
> > {: .r}
> {: .solution}
Expand Down Expand Up @@ -546,3 +542,8 @@ saved by typing less. Also, a good IDE (integrated development environment) will
reduce your typing with auto-completion. Thus, it is generally better to only omit
an argument name if its value clarifies it. For example, `read.csv("path/to/file.xyz")`
is comprehensible even without `...(file = "...` in the middle.

> For the next episodes, we'll initially ignore both `2`-versions of our
> functions. Keep them in separate files, close those and concentrate on going
> back to the roots: `center()` and `rescale()`.
{: .callout}
107 changes: 107 additions & 0 deletions _episodes_rmd/04-testthat.Rmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
---
title: "Testing R Functions Automatically"
teaching: 30
exercises: 10
questions:
- "What is the benefit of auto-testing my functions?"
- "How do I create and run test-cases in R?"
- "Why would I change my code after I got it to run?"
objectives:
- "Formalise documented examples as tests."
- "Use testthat functions to create and run tests."
keypoints:
- "Tests help you ensure correct code."
source: Rmd
---

```{r, include = FALSE}
source("../bin/chunk-options.R")
knitr_fig_path("04-testthat-")
```

### Auto-testing functions with the `testthat` package

Computer code evolves. As you just saw, functions need to be updated
when new use-cases appear, or new goals need to be achieved, or
sped up when more data needs to be crunched, or cleaned up to make their code
more readable for collaborators, reviewers, etc. You probably know the saying
"Never change a running system!" We surely invested a lot of time already to make
sure our functions work fine now. It is natural to be cautious about changing
computer code and it rightly shouldn't be done on a whim.

However, as you have probably learned in the Git lesson, branching is one way to
try out code changes in a way that allows you to recover from a mistake, and only
merge successful changes. Automatic tests are another way to "span a safety net",
and in R, the [`testthat` package][testthat] is commonly used. Install it through
RStudio's `Packages` panel or execute `install.packages("testthat")` in the console.

> ## Loading packages in RStudio or the console
>
> Checking and unticking a box in RStudio's `Packages` panel is the same as
> executing `library("…")` and `detach("…")` in the console. Note how your mouse
> interaction with the panel sends commands, or how your console commands affect
> the panel. The same is true for `install.packages()` and `remove.packages()`.
{: .callout}

A `testthat` case is composed of the following elements:

```{r center-test}
library("testthat")
test_that("centering works", { # function call with a descriptive string
expect_equal( # expectation, which compares…
object = center(data = c(1, 2, 3), desired = 0), # … the function's actual output…
expected = c(-1, 0, 1) # … with our expected result
)
})
```

Don't worry that there is no output when you execute one or more `expect_equal()`
statement(s), or the whole `test_that()` block. In good UNIX
tradition, `testthat` will only bother you in case some expectations are _not_
met, i.e. one or more unit tests fail.

> ## Adding more test-cases
>
> When we tested our `rescale()` function interactively, we used several different
> numeric examples. Search your script and/or your console history in RStudio to
> find these examples and convert them into more `expect_equal(…)` test-cases.


> ## Testing our rescaling functions
>
> Apply the above example of converting the examples into unit tests in the
> `rescale.R` file as well.
>
> > ## Solution
> > ~~~
> > library("testthat")
> > test_that("rescaling works", {
> > expect_equal(rescale(c(1, 2, 3)), c(0.0, 0.5, 1.0))
> > expect_equal(rescale(c(1, 2, 3, 4, 5)), c(0.0, 0.25, 0.5, 0.75, 1.0))
> > })
> > ~~~
> > {: .r}
> {: .solution}
>
> Think about the [DRY principle]({{ page.root }}/03-func-R/#composing-functions).
> Is it necessary to keep the `@examples` in the documentation when you are using
> them in the tests, or vice versa? Which considerations would help you decide?
>
> > ## Hint
> >
> > Examples are a good starting point, but: Tests help you ensure semantically,
> > mathematically and scientifically correct code. This may require covering
> > edge-cases, errors, etc. with dedicated tests. These may not be helpful,
> > instructive examples for your users. The examples should teach how your
> > functions can be applied to solve your users' problems.
> {: .solution}
{: .challenge}

In order to get very quick feedback on your code, consider activating RStudio's
`Source on Save` function and getting used to pressing <kbd>Ctrl</kbd>+<kbd>S</kbd>
whenever you think your code is ready to be saved. If there are any errors, read
the error message, investigate and correct either your code or the test and
<kbd>Ctrl</kbd>+<kbd>S</kbd> again.

Once all your tests are green, a good moment has come to also commit your tests
for `center.R` and `rescale.R`. Pick a self-explanatory commit message ;-)
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ source: Rmd

```{r, include = FALSE}
source("../bin/chunk-options.R")
knitr_fig_path("04-making-packages-R-")
knitr_fig_path("05-making-packages-R-")
```

Why should you make your own R packages?
Expand Down Expand Up @@ -94,16 +94,28 @@ Suggestion: organize in a logical manner so that you know which file holds which

### `DESCRIPTION` file

We used RStudio's import process to include the `.R` files we already had. However, FAIRer
(because richer in metadata) `DESCRIPTION` files can be generated with a
[helper package called `usethis`][usethis]. Again, install it with RStudio or
the console, then load it and execute

```{r use_desc, eval=FALSE}
use_description()
```

~~~
Package: PackageName
Type: Package
Title: What the Package Does (Title Case)
Version: 0.1.0
Author: Who wrote it
Maintainer: The package maintainer <yourself@somewhere.net>
Description: More about what it does (maybe more than one line)
Title: What the Package Does (One Line, Title Case)
Authors@R:
person(given = "First",
family = "Last",
role = c("aut", "cre"),
email = "first.last@example.com")
Description: What the package does (one paragraph).
Use four spaces when indenting paragraphs within the Description.
License: What license is it under?
License: What license it uses
Encoding: UTF-8
LazyData: true
~~~
Expand Down Expand Up @@ -175,7 +187,7 @@ After `Install and Restart`-ing again, looking up the documentation should work.
What exactly does `roxygen2` do? It reads lines that begin with `#'` as the function documentation for your package.
Descriptive tags are preceded with the `@` symbol. For example, `@param` has information about the input parameters for the function.

### Exporting "user-level" function
### Exporting "user-level" functions

We haven't talked about the `NAMESPACE` file yet! It belongs to the package skeleton,
and was set up by RStudio with an `exportPattern`. Any file name in `R/` that
Expand All @@ -193,7 +205,7 @@ Learn more about this from the ["R packages" book][r-pkgs-name].

### Finishing up

Please take a look at RStudio's `Files` panes now. The `/man` directory should
Please take a look at RStudio's `Files` panes now. The `man/` directory should
now contain one LaTeX-like formatted `.Rd` file for each function.

In case you learned about Git already, also view the `.Rd` files in RStudio's
Expand Down Expand Up @@ -231,4 +243,3 @@ your personal package. A good resource to find more guidance on packaging R code
is [ROpenSci's onboarding guide][ROSPG].

[ROSPG]: https://onboarding.ropensci.org/packaging_guide.html
[ep-func]: {{ page.root }}/03-func-R/
Loading