05 - Logical Functions

Notes

  • These examples were adapted from Vaughan, Hester, and Francois (2024).
  • These functions ignore NA values for now. Adjustments for handling NA values are covered in a separate vignette.
  • R already provides efficient versions of the functions covered here. This is just to illustrate how to use C++ code.
  • The benchmarks show that some of these C++ implementations are slower than base R.

Is any value in a vector ‘true’?

Base R’s any() function returns TRUE if there is at least one TRUE element in a vector, and FALSE otherwise. Below is one possible C++ implementation:

[[cpp4r::register]]
bool any_cpp(logicals x) {
  int n = x.size();

  for (int i = 0; i < n; ++i) {
    if (x[i]) {
      return true;
    }
  }
  return false;
}

Its R equivalent would be:

any_r <- function(x) {
  n <- length(x)

  for (i in 1:n) {
    if (x[i]) {
      return(TRUE)
    }
  }
  FALSE
}

Add and document the functions, update the package as in the previous vignettes, and then compare the functions speed with:

# install.packages("microbenchmark")
library(microbenchmark)
library(mypkg)

set.seed(123) # for reproducibility
x <- rpois(1e6, lambda = 2) # 1,000,000 elements
y <- ifelse(x > 2, TRUE, FALSE)

any(y)
any_cpp(y)
any_r(y)

microbenchmark(
  any(y),
  any_cpp(y),
  any_r(y)
)
Unit: nanoseconds
       expr min  lq mean median  uq   max neval cld
     any(y) 212 232  294    245 257  4320   100   a
 any_cpp(y) 810 833 1090    859 887 22900   100   a
   any_r(y) 746 822 1510    867 912 63300   100   a

Which elements in a vector are ‘true’?

Base R’s which() function returns the indices of the TRUE elements in a vector. Here is a possible C++ implementation:

[[cpp4r::register]]
integers which_cpp(logicals x) {
  int n = x.size();
  writable::integers res;
  int j = 0;

  for (int i = 0; i < n; ++i) {
    if (x[i]) {
      ++j;
      res.push_back(i + 1);
    }
  }

  if (j == 0) {
    return integers(0);
  } else {
    return res;
  }
}

Its R equivalent would be:

which_r <- function(x) {
  n <- length(x)
  res <- c()
  j <- 0

  for (i in 1:n) {
    if (x[i]) {
      res <- c(res, i)
      j <- j + 1
    }
  }

  if (j == 0) {
    return(0)
  } else {
    return(res)
  }
}

To test the functions, you can run the following benchmark code in the R console:

which(y[1:100])
which_cpp(y[1:100])
which_r(y[1:100])

microbenchmark(
  which(y[1:1000]),
  which_cpp(y[1:1000]),
  which_r(y[1:1000])
)
Unit: microseconds
                 expr    min     lq   mean median     uq   max neval cld
     which(y[1:1000])   6.30   7.08   8.27   7.75   8.86  20.2   100  a
 which_cpp(y[1:1000])   8.52   9.93  11.90  10.90  12.00  72.1   100  a
   which_r(y[1:1000]) 302.00 323.00 348.00 345.00 361.00 439.0   100  b

Are all values in a vector ‘true’?

Base R’s all() function checks if all elements in a vector are TRUE. Here is a possible C++ implementation that loops over the vector:

[[cpp4r::register]]
bool all_cpp_1(logicals x) {
  int n = x.size();
  for (int i = 0; i < n; ++i) {
    if (!x[i]) {
      return false;
    }
  }
  return true;
}

More concise C++ alternatives are:

[[cpp4r::register]]
bool all_cpp_2(logicals x) {
  for (int i = 0; i < x.size(); ++i) {
    if (!x[i]) {
      return false;
    }
  }
  return true;
}

[[cpp4r::register]] bool
all_cpp_3(logicals x) {
  for (bool i : x) {
    if (!i) {
      return false;
    }
  }
  return true;
}

[[cpp4r::register]] bool
all_cpp_4(logicals x) {
  return std::all_of(x.begin(), x.end(), [](bool x) { return x; });
}

To test the functions, you can run the following tests and benchmark code in the R console:

set.seed(123) # for reproducibility
x <- rpois(1e6, lambda = 2) # 1,000,000 elements

all(x > 2)
all_cpp_1(x > 2)
all_cpp_2(x > 2)
all_cpp_3(x > 2)
all_cpp_4(x > 2)

# also test the TRUE-only case
all(x >= 0)
all_cpp_1(x >= 0)
all_cpp_2(x >= 0)
all_cpp_3(x >= 0)
all_cpp_4(x >= 0)
microbenchmark(
  all(x > 2),
  all_cpp_1(x > 2),
  all_cpp_2(x > 2),
  all_cpp_3(x > 2),
  all_cpp_4(x > 2)
)
Unit: milliseconds
             expr  min   lq mean median   uq  max neval cld
       all(x > 2) 2.20 3.08 3.29   3.18 3.33 5.70   100   a
 all_cpp_1(x > 2) 2.24 3.14 3.38   3.23 3.35 5.57   100   a
 all_cpp_2(x > 2) 2.33 3.09 3.28   3.20 3.35 5.79   100   a
 all_cpp_3(x > 2) 2.22 3.13 3.41   3.25 3.39 5.91   100   a
 all_cpp_4(x > 2) 2.33 3.13 3.37   3.23 3.40 5.72   100   a

References

Vaughan, Davis, Jim Hester, and Roman Francois. 2024. “Get Started with Cpp11.” https://cpp11.r-lib.org/articles/cpp11.html#intro.

Loading...