6

Today I found that some of my stopifnot() tests are failing because the passed arguments evaluate to empty logical vectors.

Here is an example:

stopifnot(iris$nosuchcolumn == 2)  # passes without error

This is very unintuitive and seems to contradict a few other behaviours. Consider:

isTRUE(logical())
> FALSE

stopifnot(logical())
# passes

So stopifnot() passes even when this argument is not TRUE.
But furthermore, the behaviour of the above is different with different types of empty vectors.

isTRUE(numeric())
> FALSE

stopifnot(numeric())
# Error: numeric() are not all TRUE

Is there some logic to the above, or should this be considered a bug?

Karolis Koncevičius
  • 9,417
  • 9
  • 56
  • 89
  • 3
    in `stopifnot`, there is a check for is.logical – akrun May 17 '20 at 23:14
  • 3
    This is just a small list among some inconsistencies in R, unfortunately. I don't mean that to be harsh against R so much as admitting that R is far from perfect in behavior-consistency. I agree that this seems counterintuitive. In these examples, one might consider including `length(...) > 0` (or `NROW(iris) > 0`, given the first conditionals work on a frame). – r2evans May 17 '20 at 23:14
  • 3
    [stopifnot with logical(0)](https://stat.ethz.ch/pipermail/r-help/2015-December/434610.html) – Henrik May 17 '20 at 23:23
  • Thanks for the comments, especially @Henrik, I think that thread really answers the question about the logic behind this behaviour. – Karolis Koncevičius May 17 '20 at 23:33

2 Answers2

4

The comments by akrun and r2evans are spot on.

However, to give details on why specifically this happens and why you're confused vs. isTRUE() behavior, note that stopifnot() checks for three things; the check is (where r is the result of the expression you pass):

if (!(is.logical(r) && !anyNA(r) && all(r)))

So, let's take a look:

is.logical(logical())
# [1] TRUE
!anyNA(logical())
# [1] TRUE
all(logical())
# [1] TRUE

is.logical(numeric())
# [1] FALSE
!anyNA(numeric())
# [1] TRUE
all(numeric())
# [1] TRUE

So, the only reason why logical() passes while numeric() fails is because numeric() is not "logical," as suggested by akrun. For this reason, you should avoid checks that may result in logical vectors of length 0, as suggested by r2evans.

duckmayr
  • 16,303
  • 3
  • 35
  • 53
  • 1
    Thanks. I see the line with `is.logical()`, and now get why it behaves this way, but it still seems like a bug. A simple addition of `&& length(r) > 0` would probably fix it... – Karolis Koncevičius May 17 '20 at 23:23
  • 1
    @KarolisKoncevičius In my opinion it would be fair to call it a bug, though I think where the line between "bug" and function not behaving as *you* expect can be fuzzy sometimes, and people draw that line in different places. You may want to check out https://www.r-project.org/bugs.html to see where the R Project stands on what counts as a bug and whether it's worth your time to submit a bug report. – duckmayr May 17 '20 at 23:27
  • 2
    the r-help thread in the comment by Henrik really answered the question about why it behaves that way. Maybe you can include that link in your answer too, and then I would accept it. Just because comments are prone to being deleted... – Karolis Koncevičius May 17 '20 at 23:41
1

Other answers cover the practical reasons why stopifnot behaves the way it does; but I agree with Karolis that the thread linked by Henrik adds the real explanation of why this is the case:

As author stopifnot(), I do agree with [OP]'s "gut feeling" [...] that stopifnot(dim(x) == c(3,4)) [...][should] stop in the case where x is a simple vector instead of a matrix/data.frame/... with dimensions c(3,4) ... but [...] the gut feeling is wrong because of the fundamental lemma of logic: [...]

"All statements about elements of the empty set are true"

Martin Maechler, ETH Zurich

Also, [...], any() is to "|" what sum() is to "+" and what all() is to "&" and prod() is to "*". All the operators have an identity element, namely FALSE, 0, TRUE, and 1 respectively, and the generic convention is that for an empty vector, we return the identity element, for the reason given above.

Peter D.

Fons MA
  • 1,142
  • 1
  • 12
  • 21