testthat different interactive vs terminal - r

Hit a weird quirk of R and testthat, wondering whether anyone can explain it?
I have found that testthat seems to give different results depending on whether it is run interactively or on the terminal?
An example is create a file example_funcs.R
function_main <- function(df, avg_gear = NA) {
function_main2(df, avg_gear)
}
function_main2 <- function(data_df, avg_gear) {
data_df$car_name = row.names(data_df)
get_avg_gear(data_df)
}
# This function is missing an argument
get_avg_gear <- function(data_df) {
data_df %>%
filter(gear == avg_gear)
}
Then create a second file called example.R
library(dplyr)
source("example_funcs.R")
data_df = mtcars
avg_gear = median(data_df$gear)
test_that("example", {
output = function_main(data_df, avg_gear = avg_gear)
expect_true(
all(output$gear == 4)
)
})
If you run
> testthat::test_file("example.R")
══ Testing example.R ═══════════════════════════════════════════════════════════════════════════════════════
[ FAIL 0 | WARN 0 | SKIP 0 | PASS 1 ] Done!
You see it passes. However in a terminal with the identical command (using GitBash terminal in RStudio)
$ Rscript -e ' testthat::test_file("example.R")'
══ Testing example.R ═══════════════════════════════════════════════════════════════════════════════════════════════════════════[ FAIL 1 | WARN 0 | SKIP 0 | PASS 0 ]
── Error (example.R:11:3): example ─────────────────────────────────────────────
Error in `filter(., gear == avg_gear)`: Problem while computing `..1 = gear == avg_gear`.
Caused by error:
! object 'avg_gear' not found
Backtrace:
1. global function_main(data_df, avg_gear = avg_gear)
at example.R:11:2
6. dplyr:::filter.data.frame(., gear == avg_gear)
7. dplyr:::filter_rows(.data, ..., caller_env = caller_env())
8. dplyr:::filter_eval(dots, mask = mask, error_call = error_call)
10. mask$eval_all_filter(dots, env_filter)
[ FAIL 1 | WARN 0 | SKIP 0 | PASS 0 ]
It now fails?
I can't think why that would be? The error is caused by a missing function argument, but R lexical scoping rules should cover it I think
First, R looks inside the current function. Then, it looks where that function was defined (and so on, all the way up to the global environment). Finally, it looks in other loaded packages. Advanced R

Related

R package development - Calling a function that renders a Rmarkdown file that uses `dplyr`? Mask errors

I am currently developing an R package and all the functions are working nicely except one. This problematic function was meant to render a Rmarkdown file for generating a final overview.
This Rmarkdown calls other functions from my package, but then for some reason, they do not work as expected when rendering the Rmarkdown.
Here's the function that renders my Rmarkdown:
#' #importFrom rmarkdown render
#' #export
quality_report <- function(data_folder = "test/test_dataset/sanger_sequences", outputfile = "QC_report.html", output_dir = "test/", processors = 1) {
input <- system.file("rmd", "HC_report.Rmd", package = "RepertoiR")
render(input,
output_dir = output_dir,
params = list(
data_folder = data_folder,
output_dir = output_dir,
processors = processors
),
output_file = outputfile
)
}
Here's the beginning of the Rmarkdown chunk where I get the error:
library(mypackage)
# Function to summarise result that works fine outside the Rmarkdown file
sf <- summarise_quality(folder_sequences = data_folder,
secondary.peak.ratio = 0.33,
trim.cutoff = 0.01,
processors = processors)
# If-conditional to try to stop knitting if the first function does not generate a data.frame as expected
if (is.null(dim(sf[["summaries"]]))) {
print("No files were processed, knitting was stopped early.")
knitr::knit_exit()
}
sf_summaries <- sf[["summaries"]]
# Use dplyr to just filter a random variable that should be present in the df
sf_filtered <- sf_summaries %>%
filter(raw.length >= 400)
ERROR:
Quitting from lines 61-104 (HC_report.Rmd)
Error in `filter()`:
! Problem while computing `..1 = raw.length >= 400`.
Caused by error in `mask$eval_all_filter()`:
! object 'raw.length' not found
Backtrace:
1. sf_summaries %>% filter(raw.length >= 400)
3. dplyr:::filter.data.frame(., raw.length >= 400)
4. dplyr:::filter_rows(.data, ..., caller_env = caller_env())
5. dplyr:::filter_eval(dots, mask = mask, error_call = error_call)
7. mask$eval_all_filter(dots, env_filter)
I thought it was some error regarding the masking so I have tried .data$raw.length to
see if it would work, but without success:
Error in `filter()`:
! Problem while computing `..1 = .data$raw.length >= 400`.
Caused by error in `.data$raw.length`:
! Column `raw.length` not found in `.data`.
Backtrace:
1. sf_summaries %>% filter(.data$raw.length >= 400)
11. rlang:::abort_data_pronoun(x, call = y)
I am almost thinking that dplyr does not work for development inside Rmarkdown files, I have functions that use dplyr and they work fine, I could fix errors by using .data for masking. Do you have any recommendations of what might be the issue or advice regarding functions to render Rmarkdown files?

My testthat tests pass the devtools::test() check but fail the devtools::check() step when developing an R package

I have 3 rds files I use in my testthat tests. the files are in /tests/testthat/testdata/. When I run the tests with devtools::test(), the files can be found. When I use devtools::check(), the files won't be found.
I have seen this issue discussed here and here and many other places but all the suggested solutions won't work for me.
I tried adding the Sys.setenv(R_TESTS="") line at the top of my "testthat.R" file; I tried using here::here() from the package "here" instead of the , system.file() function, etc.
What can I do?
Here is my test:
test_that("the test_arguments() returns errors as it should", {
x <- readRDS(system.file("tests", "testthat", "testdata", "objectA.rds", package = "package"))
y <- readRDS(system.file("tests", "testthat", "testdata", "objectB.rds", package = "package"))
z <- readRDS(here::here("tests", "testthat", "testdata", "objectC.rds", package = "package"))
expect_error(test_arguments(z, y), "X has to be an object of class SpatialPolygonsDataFrame")
expect_error(test_arguments(x, z), "y has to be an object of class RasterLayer")
})
And here is the result of the devtools::check():
-- Error (test_user_input.R:3:3): the arguments are of the right class ---------------------
Error: cannot open the connection
Backtrace:
x
1. \-base::readRDS(here::here("tests", "testthat", "testdata", "objectA.rds")) test_user_input.R:3:2
2. \-base::gzfile(file, "rb")
[ FAIL 1 | WARN 1 | SKIP 0 | PASS 0 ]
Error: Test failures
Execution halted
1 error x | 1 warning x | 1 note x

Report extra information from a test_that block when failing

I want to cat() some information to the console in the case a test fails (I'm getting confident this won't happen but I can't prove it wont) so I can investigate the issue.
Now I have code that is approximately like this:
testthat::test_that('Maybe fails', {
seed <- as.integer(Sys.time())
set.seed(seed)
testthat::expect_true(maybe_fails(runif(100L)))
testthat::expect_equal(long_vector(runif(100L)), target, tol = 1e-8)
if (failed()) {
cat('seed: ', seed, '\n')
}
})
Unfortunately, failed() doesn't exist.
Return values of expect_*() don't seem useful, they just return the actual argument.
I'm considering to just check again using all.equal() but that is a pretty ugly duplication.
Instead of using cat, you could use the info argument managed by testthat and its reporters for all expect functions (argument kept for compatibility reasons):
library(testthat)
testthat::test_that("Some tests",{
testthat::expect_equal(1,2,info=paste('Test 1 failed at',Sys.time()))
testthat::expect_equal(1,1,info=paste('Test 2 failed at',sys.time()))
})
#> -- Failure (<text>:5:3): Some tests --------------------------------------------
#> 1 not equal to 2.
#> 1/1 mismatches
#> [1] 1 - 2 == -1
#> Test 1 failed at 2021-03-03 17:25:37

Testthat does not see external csv data

I'm writing test for my package and I want to use macro economical data rather than artificially randomized. The problem is that when I'm using read.csv('my_file.csv') and then running test_that, all tests using my data are ignored. For example
library('tseries')
library('testthat')
data<-read.csv('my_file.csv')
test_that('ADF test',{
vec<-data[,2]
expect_is(adf.test(vec),'htest')
})
After running 'testpackage' I get no information about failure or passing of my test. where is the problem ?
testthat only returns an error in console if the test didn't succeed:
library(testthat)
data<-iris
test_that('test1',{
expect_is(data$Petal.Length,'numeric')
})
test_that('test2',{
expect_is(data$Species,'numeric')
})
#> Error: Test failed: 'test2'
#> * <text>:8: data$Species inherits from `factor` not `numeric`.
Created on 2020-09-21 by the reprex package (v0.3.0)
You can use test_file or test_dir to get the results:
res <- test_file('mytest.R',
reporter = "list",
env = test_env(),
start_end_reporter = TRUE,
load_helpers = TRUE,
wrap = TRUE)
√ | OK F W S | Context
x | 1 1 | mytest
--------------------------------------------------------------------------------
mytest.R:8: failure: test2
data$Species inherits from `factor` not `numeric`.
--------------------------------------------------------------------------------
== Results =====================================================================
OK: 1
Failed: 1
Warnings: 0
Skipped: 0
Warning message:
`encoding` is deprecated; all files now assumed to be UTF-8

Rscript: How to inject options for an R script [duplicate]

I've got a R script for which I'd like to be able to supply several command-line parameters (rather than hardcode parameter values in the code itself). The script runs on Windows.
I can't find info on how to read parameters supplied on the command-line into my R script. I'd be surprised if it can't be done, so maybe I'm just not using the best keywords in my Google search...
Any pointers or recommendations?
Dirk's answer here is everything you need. Here's a minimal reproducible example.
I made two files: exmpl.bat and exmpl.R.
exmpl.bat:
set R_Script="C:\Program Files\R-3.0.2\bin\RScript.exe"
%R_Script% exmpl.R 2010-01-28 example 100 > exmpl.batch 2>&1
Alternatively, using Rterm.exe:
set R_TERM="C:\Program Files\R-3.0.2\bin\i386\Rterm.exe"
%R_TERM% --no-restore --no-save --args 2010-01-28 example 100 < exmpl.R > exmpl.batch 2>&1
exmpl.R:
options(echo=TRUE) # if you want see commands in output file
args <- commandArgs(trailingOnly = TRUE)
print(args)
# trailingOnly=TRUE means that only your arguments are returned, check:
# print(commandArgs(trailingOnly=FALSE))
start_date <- as.Date(args[1])
name <- args[2]
n <- as.integer(args[3])
rm(args)
# Some computations:
x <- rnorm(n)
png(paste(name,".png",sep=""))
plot(start_date+(1L:n), x)
dev.off()
summary(x)
Save both files in the same directory and start exmpl.bat. In the result you'll get:
example.png with some plot
exmpl.batch with all that was done
You could also add an environment variable %R_Script%:
"C:\Program Files\R-3.0.2\bin\RScript.exe"
and use it in your batch scripts as %R_Script% <filename.r> <arguments>
Differences between RScript and Rterm:
Rscript has simpler syntax
Rscript automatically chooses architecture on x64 (see R Installation and Administration, 2.6 Sub-architectures for details)
Rscript needs options(echo=TRUE) in the .R file if you want to write the commands to the output file
A few points:
Command-line parameters are
accessible via commandArgs(), so
see help(commandArgs) for an
overview.
You can use Rscript.exe on all platforms, including Windows. It will support commandArgs(). littler could be ported to Windows but lives right now only on OS X and Linux.
There are two add-on packages on CRAN -- getopt and optparse -- which were both written for command-line parsing.
Edit in Nov 2015: New alternatives have appeared and I wholeheartedly recommend docopt.
Add this to the top of your script:
args<-commandArgs(TRUE)
Then you can refer to the arguments passed as args[1], args[2] etc.
Then run
Rscript myscript.R arg1 arg2 arg3
If your args are strings with spaces in them, enclose within double quotes.
Try library(getopt) ... if you want things to be nicer. For example:
spec <- matrix(c(
'in' , 'i', 1, "character", "file from fastq-stats -x (required)",
'gc' , 'g', 1, "character", "input gc content file (optional)",
'out' , 'o', 1, "character", "output filename (optional)",
'help' , 'h', 0, "logical", "this help"
),ncol=5,byrow=T)
opt = getopt(spec);
if (!is.null(opt$help) || is.null(opt$in)) {
cat(paste(getopt(spec, usage=T),"\n"));
q();
}
Since optparse has been mentioned a couple of times in the answers, and it provides a comprehensive kit for command line processing, here's a short simplified example of how you can use it, assuming the input file exists:
script.R:
library(optparse)
option_list <- list(
make_option(c("-n", "--count_lines"), action="store_true", default=FALSE,
help="Count the line numbers [default]"),
make_option(c("-f", "--factor"), type="integer", default=3,
help="Multiply output by this number [default %default]")
)
parser <- OptionParser(usage="%prog [options] file", option_list=option_list)
args <- parse_args(parser, positional_arguments = 1)
opt <- args$options
file <- args$args
if(opt$count_lines) {
print(paste(length(readLines(file)) * opt$factor))
}
Given an arbitrary file blah.txt with 23 lines.
On the command line:
Rscript script.R -h outputs
Usage: script.R [options] file
Options:
-n, --count_lines
Count the line numbers [default]
-f FACTOR, --factor=FACTOR
Multiply output by this number [default 3]
-h, --help
Show this help message and exit
Rscript script.R -n blah.txt outputs [1] "69"
Rscript script.R -n -f 5 blah.txt outputs [1] "115"
you need littler (pronounced 'little r')
Dirk will be by in about 15 minutes to elaborate ;)
In bash, you can construct a command line like the following:
$ z=10
$ echo $z
10
$ Rscript -e "args<-commandArgs(TRUE);x=args[1]:args[2];x;mean(x);sd(x)" 1 $z
[1] 1 2 3 4 5 6 7 8 9 10
[1] 5.5
[1] 3.027650
$
You can see that the variable $z is substituted by bash shell with "10" and this value is picked up by commandArgs and fed into args[2], and the range command x=1:10 executed by R successfully, etc etc.
FYI: there is a function args(), which retrieves the arguments of R functions, not to be confused with a vector of arguments named args
If you need to specify options with flags, (like -h, --help, --number=42, etc) you can use the R package optparse (inspired from Python):
http://cran.r-project.org/web/packages/optparse/vignettes/optparse.pdf.
At least this how I understand your question, because I found this post when looking for an equivalent of the bash getopt, or perl Getopt, or python argparse and optparse.
I just put together a nice data structure and chain of processing to generate this switching behaviour, no libraries needed. I'm sure it will have been implemented numerous times over, and came across this thread looking for examples - thought I'd chip in.
I didn't even particularly need flags (the only flag here is a debug mode, creating a variable which I check for as a condition of starting a downstream function if (!exists(debug.mode)) {...} else {print(variables)}). The flag checking lapply statements below produce the same as:
if ("--debug" %in% args) debug.mode <- T
if ("-h" %in% args || "--help" %in% args)
where args is the variable read in from command line arguments (a character vector, equivalent to c('--debug','--help') when you supply these on for instance)
It's reusable for any other flag and you avoid all the repetition, and no libraries so no dependencies:
args <- commandArgs(TRUE)
flag.details <- list(
"debug" = list(
def = "Print variables rather than executing function XYZ...",
flag = "--debug",
output = "debug.mode <- T"),
"help" = list(
def = "Display flag definitions",
flag = c("-h","--help"),
output = "cat(help.prompt)") )
flag.conditions <- lapply(flag.details, function(x) {
paste0(paste0('"',x$flag,'"'), sep = " %in% args", collapse = " || ")
})
flag.truth.table <- unlist(lapply(flag.conditions, function(x) {
if (eval(parse(text = x))) {
return(T)
} else return(F)
}))
help.prompts <- lapply(names(flag.truth.table), function(x){
# joins 2-space-separatated flags with a tab-space to the flag description
paste0(c(paste0(flag.details[x][[1]][['flag']], collapse=" "),
flag.details[x][[1]][['def']]), collapse="\t")
} )
help.prompt <- paste(c(unlist(help.prompts),''),collapse="\n\n")
# The following lines handle the flags, running the corresponding 'output' entry in flag.details for any supplied
flag.output <- unlist(lapply(names(flag.truth.table), function(x){
if (flag.truth.table[x]) return(flag.details[x][[1]][['output']])
}))
eval(parse(text = flag.output))
Note that in flag.details here the commands are stored as strings, then evaluated with eval(parse(text = '...')). Optparse is obviously desirable for any serious script, but minimal-functionality code is good too sometimes.
Sample output:
$ Rscript check_mail.Rscript --help
--debug Print variables rather than executing function XYZ...
-h --help Display flag definitions

Resources