--- title: "05 - Logical Functions" output: litedown::html_format: options: toc: true number_sections: true vignette: > %\VignetteIndexEntry{05 - Logical Functions} %\VignetteEngine{litedown::vignette} %\VignetteEncoding{UTF-8} editor: markdown: wrap: sentence bibliography: ["references.bib"] --- ## Notes * These examples were adapted from @vaughan24. * 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: ```cpp [[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: ```r 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: ```r # 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) ) ``` ```r 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: ```cpp [[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: ```r 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: ```r 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]) ) ``` ```r 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: ```cpp [[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: ```cpp [[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: ```r 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) ``` ```r microbenchmark( all(x > 2), all_cpp_1(x > 2), all_cpp_2(x > 2), all_cpp_3(x > 2), all_cpp_4(x > 2) ) ``` ```r 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