Jyotirmoy Bhattacharya
Jyotirmoy Bhattacharya

Reputation: 9587

How to count TRUE values in a logical vector

In R, what is the most efficient/idiomatic way to count the number of TRUE values in a logical vector? I can think of two ways:

z <- sample(c(TRUE, FALSE), 1000, rep = TRUE)
sum(z)
# [1] 498

table(z)["TRUE"]
# TRUE 
#  498 

Which do you prefer? Is there anything even better?

Upvotes: 203

Views: 378653

Answers (9)

Ben Nikkel
Ben Nikkel

Reputation: 11

Generate a summary table with counts and proportions per class using:

janitor::tabyl(z)

Upvotes: 0

Marek
Marek

Reputation: 50783

The safest way is to use sum with na.rm = TRUE:

sum(z, na.rm = TRUE) # best way to count TRUE values

which gives 1.

There are some problems with other solutions when logical vector contains NA values.

See for example:

z <- c(TRUE, FALSE, NA)

sum(z) # gives you NA
table(z)["TRUE"] # gives you 1
length(z[z == TRUE]) # f3lix answer, gives you 2 (because NA indexing returns values)

Additionally table solution is less efficient (look at the code of table function).

Also, you should be careful with the "table" solution, in case there are no TRUE values in the logical vector. See for example:

z <- c(FALSE, FALSE)
table(z)["TRUE"] # gives you `NA`

or

z <- c(NA, FALSE)
table(z)["TRUE"] # gives you `NA`

Upvotes: 225

Jeff Bezos
Jeff Bezos

Reputation: 2283

There's also a package called bit that is specifically designed for fast boolean operations. It's especially useful if you have large vectors or need to do many boolean operations.

z <- sample(c(TRUE, FALSE), 1e8, rep = TRUE)

system.time({
  sum(z) # 0.170s
})

system.time({
  bit::sum.bit(z) # 0.021s, ~10x improvement in speed
})

Upvotes: 1

ramrad
ramrad

Reputation: 91

Another option is to use summary function. It gives a summary of the Ts, Fs and NAs.

> summary(hival)
   Mode   FALSE    TRUE    NA's 
logical    4367      53    2076 
> 

Upvotes: 9

A_Skelton73
A_Skelton73

Reputation: 1200

I've just had a particular problem where I had to count the number of true statements from a logical vector and this worked best for me...

length(grep(TRUE, (gene.rep.matrix[i,1:6] > 1))) > 5

So This takes a subset of the gene.rep.matrix object, and applies a logical test, returning a logical vector. This vector is put as an argument to grep, which returns the locations of any TRUE entries. Length then calculates how many entries grep finds, thus giving the number of TRUE entries.

Upvotes: 0

aL3xa
aL3xa

Reputation: 36130

I've been doing something similar a few weeks ago. Here's a possible solution, it's written from scratch, so it's kind of beta-release or something like that. I'll try to improve it by removing loops from code...

The main idea is to write a function that will take 2 (or 3) arguments. First one is a data.frame which holds the data gathered from questionnaire, and the second one is a numeric vector with correct answers (this is only applicable for single choice questionnaire). Alternatively, you can add third argument that will return numeric vector with final score, or data.frame with embedded score.

fscore <- function(x, sol, output = 'numeric') {
    if (ncol(x) != length(sol)) {
        stop('Number of items differs from length of correct answers!')
    } else {
        inc <- matrix(ncol=ncol(x), nrow=nrow(x))
        for (i in 1:ncol(x)) {
            inc[,i] <- x[,i] == sol[i]
        }
        if (output == 'numeric') {
            res <- rowSums(inc)
        } else if (output == 'data.frame') {
            res <- data.frame(x, result = rowSums(inc))
        } else {
            stop('Type not supported!')
        }
    }
    return(res)
}

I'll try to do this in a more elegant manner with some *ply function. Notice that I didn't put na.rm argument... Will do that

# create dummy data frame - values from 1 to 5
set.seed(100)
d <- as.data.frame(matrix(round(runif(200,1,5)), 10))
# create solution vector
sol <- round(runif(20, 1, 5))

Now apply a function:

> fscore(d, sol)
 [1] 6 4 2 4 4 3 3 6 2 6

If you pass data.frame argument, it will return modified data.frame. I'll try to fix this one... Hope it helps!

Upvotes: 0

Shane
Shane

Reputation: 100204

Another option which hasn't been mentioned is to use which:

length(which(z))

Just to actually provide some context on the "which is faster question", it's always easiest just to test yourself. I made the vector much larger for comparison:

z <- sample(c(TRUE,FALSE),1000000,rep=TRUE)
system.time(sum(z))
   user  system elapsed 
   0.03    0.00    0.03
system.time(length(z[z==TRUE]))
   user  system elapsed 
   0.75    0.07    0.83 
system.time(length(which(z)))
   user  system elapsed 
   1.34    0.28    1.64 
system.time(table(z)["TRUE"])
   user  system elapsed 
  10.62    0.52   11.19 

So clearly using sum is the best approach in this case. You may also want to check for NA values as Marek suggested.

Just to add a note regarding NA values and the which function:

> which(c(T, F, NA, NULL, T, F))
[1] 1 4
> which(!c(T, F, NA, NULL, T, F))
[1] 2 5

Note that which only checks for logical TRUE, so it essentially ignores non-logical values.

Upvotes: 94

aL3xa
aL3xa

Reputation: 36130

which is good alternative, especially when you operate on matrices (check ?which and notice the arr.ind argument). But I suggest that you stick with sum, because of na.rm argument that can handle NA's in logical vector. For instance:

# create dummy variable
set.seed(100)
x <- round(runif(100, 0, 1))
x <- x == 1
# create NA's
x[seq(1, length(x), 7)] <- NA

If you type in sum(x) you'll get NA as a result, but if you pass na.rm = TRUE in sum function, you'll get the result that you want.

> sum(x)
[1] NA
> sum(x, na.rm=TRUE)
[1] 43

Is your question strictly theoretical, or you have some practical problem concerning logical vectors?

Upvotes: 6

f3lix
f3lix

Reputation: 29885

Another way is

> length(z[z==TRUE])
[1] 498

While sum(z) is nice and short, for me length(z[z==TRUE]) is more self explaining. Though, I think with a simple task like this it does not really make a difference...

If it is a large vector, you probably should go with the fastest solution, which is sum(z). length(z[z==TRUE]) is about 10x slower and table(z)[TRUE] is about 200x slower than sum(z).

Summing up, sum(z) is the fastest to type and to execute.

Upvotes: 15

Related Questions