Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid `all` function returning `TRUE` when comparing to `NULL` or an empty object

Tags:

r

I often use all function and, whenever I get TRUE, I find myself checking that none of the elements of the comparison is NULL or empty because this could give a false TRUE result.

Example:

y<-1:10
z<-5:15

# I make a comparison which is really true
all(y[y>5 & y<10]==z[z>5 & z<10]) 
[1] TRUE

# Now I make a typo because I often do, but I don't notice:
all(y[y>5 & y<0]==z[z>5 & z<10])
[1] TRUE
# the result is also true but only because y[y>5 & y<0] is empty:
y[y>5 & y<0]
#integer(0)

So, in the second case, if I don't check each element of all, I will go one with my code, thinking everything went well and, of course, the final result will be incorrect.

Instead of checking the element I put in my all call, I could add a length call: (all(y[y>5 & y<0]==z[z>5 & z<10]) & length(y[y>5 & y<0])>0 & length(z[z>5 & z<10]>0) but that seems rather tedious...

Is there a way to make all return NA or FALSE when either element is of length 0 (all help is not very helpful on that subject) or is there an alternative function that would do that ?

EDIT

Thanks to @Metrics, there is an alternative with function identical:

identical(y[y>5 & y<0],z[z>5 & z<10])
[1] FALSE

Although identicaldoesn't return TRUE in this case, it still doesn't warn me that something is going wrong...

The ideal solution would return a warning telling, for example, that one element is empty.

like image 864
Cath Avatar asked Feb 13 '15 14:02

Cath


Video Answer


2 Answers

The documentation for all clearly says:

That all(logical(0)) is true is a useful convention: it ensures that

all(all(x), all(y)) == all(x, y) even if x has length zero.

so there is no way to obtain your desired result with all.

As noted in the comments, identical and all.equal are closer matches to your request. However, identical wouldn't warn you if the objects under comparison are of different length. The drawback of all.equal is that it wouldn't return you a logical value in the case of different lengths:

all.equal(y[y>5 & y<0],z[z>5 & z<10])
# [1] "Numeric: lengths (0, 4) differ"

and I believe that the official documentation suggests not to use all.equal directly in if expressions:

Do not use all.equal directly in if expressions—either use isTRUE(all.equal(....)) or identical if appropriate.

However, isTRUE(all.equal(y[y>5 & y<0],z[z>5 & z<10])) wouldn't tell you about different lengths.

[Solution]

You can simply write your own function for this purpose and add some syntactic sugar for convenience:

'%=%' <- function(a,b) {
   if (length(a)!=length(b)) warning('Objects are of different length')
   identical(a,b)
 }

It will return TRUE if the objects are identical

y[y>5 & y<10]  %=%  z[z>5 & z<10]
# [1] TRUE

and FALSE if the objects are different (+warning if they are of different length):

y[y>5 & y<0]  %=%  z[z>5 & z<10]
#  [1] FALSE
#  Warning message:
#    In y[y > 5 & y < 0] %=% z[z > 5 & z < 10] :
#    Objects are of different length
like image 161
Marat Talipov Avatar answered Nov 15 '22 07:11

Marat Talipov


I don't think you should use either identical or all.equal for this purpose. As already outlined, the latter doesn't return always a logical vector, while the first is much more stringent and should be used only to check if two objects are really the same. Consider this example:

y<-setNames(1:10,letters[1:10])
z<-5:15
identical(y[y>5 & y<10],z[z>5 & z<10])

it gives FALSE because y has names. The all function is the way to go. If you are really bothered about the zero length issue, try:

myAll <- function(x,na.rm=FALSE) {
   if (length(x)==0) {
      warning("zero length argument")
      return(TRUE)
   }
   all(x,na.rm=na.rm)
}

Of course you can change the behaviour when x has zero length or you can define a binary operator as already mentioned.

like image 42
nicola Avatar answered Nov 15 '22 08:11

nicola