Trick or Tips 005 {R}

February 12, 2023

Trick or Tips

Ever tumbled on a code chunk that made you say: "I should have known this f_ piece of code long ago!" Chances are you have, frustratingly, just like we have, and on multiple occasions too. In comes Trick or Tips!

Trick or Tips is a series of blog posts that each present 5 -- hopefully helpful -- coding tips for a specific programming language. Posts should be short (i.e. no more than 5 lines of code, max 80 characters per line, except when appropriate) and provide tips of many kind: a function, a way of combining of functions, a single argument, a note about the philosophy of the language and practical consequences, tricks to improve the way you code, good practices, etc.

Note that while some tips might be obvious for careful documentation readers (God bless them for their wisdom), we do our best to present what we find very useful and underestimated. By the way, there are undoubtedly similar initiatives on the web (e.g. "One R Tip a Day" Twitter account). Last, feel free to comment below tip ideas or a post of code tips of your own which we will be happy to incorporate to our next post.

Enjoy and get ready to frustratingly appreciate our tips!

In this post, I comment on a few objects that are included in all R session and not very well known. I will start with what I think are the ones you will find the must useful and finish with objects that you are less likely to use!

.Last.value

Have you already redone a computation because you forgot to assign the result to a variable. If you have, you will be pleased to know that you actually don’t need to! As explained in the documentation of ?.Last.value (?.Last.value)

The value of the internal evaluation of a top-level R expression is always assigned to ‘.Last.value’ (in ‘package:base’) before further processing (e.g., printing).

Say you run the following

 1 2 3  var1 <- 23 var1 + 12 #R> [1] 35 

and you realize afterwards that you forgot to assign the result, instead of redoing the computation, use .Last.vaue

 1 2  .Last.value #R> [1] 35 

and reassign it to your new varibale like so

 1 2 3  var2 <- .Last.value var2 #R> [1] 35 

This is the equivalent of ans in other programming languages such as Matllab and Julia. By the way, in R Markdown, this will not work, because the last expression of a code chunk is not a top-level R expression (see https://stackoverflow.com/questions/31475226/using-last-value-in-rmarkdown-knitr), to overcome this in the example above, I simply copypasted the results from my R console 😏.

.libPaths(), .Library and .Library.site

You may have already asked yourself, where are packages installed in your computer? There are various ways to answer this question, one is to use .libPaths().

 1 2 3  .libPaths() #R> [1] "/home/runner/work/_temp/Library" "/opt/R/4.2.2/lib/R/site-library" #R> [3] "/opt/R/4.2.2/lib/R/library" 

As explained in the manual page (?.libPaths):

‘.libPaths’ gets/sets the library trees within which packages are looked for.

Let’s look at the first path and list all the files contained therein:

 1 2 3 4 5 6  lsf <- list.files(.libPaths()[1]) length(lsf) #R> [1] 182 lsf[1:10] #R> [1] "_cache" "animation" "askpass" "backports" "base64enc" "bibtex" "bit" "bit64" #R> [9] "blob" "blogdown" 

Good to know! So now let’s build a function to locate a given package.

 1 2 3 4 5 6  whereIs <- function(pkg) { out <- unlist(lapply(.libPaths(), list.files, full.names = TRUE)) out[grepl(paste0("/", pkg, "$"), out)] } whereIs("rmarkdown") #R> [1] "/home/runner/work/_temp/Library/rmarkdown"  Note that you may have the same package installed in different locations, which probably means that you installed it several times (different ways and/or different settings). In this case you should check whether you are using the right version of the package! Last point, if you look at the definition of .libPaths(), you can see that it uses two built-in vectors: .Library, the default library, and .Library.site that includes all the site libraries.   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17  .libPaths #R> function (new, include.site = TRUE) #R> { #R> if (!missing(new)) { #R> new <- Sys.glob(path.expand(new)) #R> paths <- c(new, if (include.site) .Library.site, .Library) #R> paths <- paths[dir.exists(paths)] #R> .lib.loc <<- unique(normalizePath(paths, "/")) #R> } #R> else .lib.loc #R> } #R> #R> .Library #R> [1] "/opt/R/4.2.2/lib/R/library" .Library.site #R> [1] "/opt/R/4.2.2/lib/R/site-library"  .Platform From ?.Platform ‘.Platform’ is a list with some details of the platform under which R was built. This provides means to write OS-portable R code.   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24  .Platform #R>$OS.type #R> [1] "unix" #R> #R> $file.sep #R> [1] "/" #R> #R>$dynlib.ext #R> [1] ".so" #R> #R> $GUI #R> [1] "X11" #R> #R>$endian #R> [1] "little" #R> #R> $pkgType #R> [1] "source" #R> #R>$path.sep #R> [1] ":" #R> #R> $r_arch #R> [1] ""  This list could be usefuld to help your function portable. The seven fields of the list are explained in the help of the function and here I will simply show you one example involving $file.sep. You may have already created a function (or part of a function) to concatenate files, basically doing something like this

 1 2 3 4 5  my_paste <- function(path, file) { paste(path, file, sep = "/") } my_paste("here", "myfile.txt") #R> [1] "here/myfile.txt" 

This will work in most cases, but with .Platform there is a simple way to make this portable:

 1 2 3 4 5  my_paste <- function(path, file) { paste(path, file, sep = .Platform$file.sep) } my_paste("here", "myfile.txt") #R> [1] "here/myfile.txt"  That being said, I encourage you to use a function that was built, file.path() which actually uses .Platform$sfile.sep as a default.

 1 2 3 4 5  file.path #R> function (..., fsep = .Platform$file.sep) #R> .Internal(file.path(list(...), fsep)) #R> #R>  .leap.seconds Leap years are very well known, leap seconds maybe less so. Just as leap days, leap seconds are additional seconds added (though negative leap seconds are defined, and leap seconds are added at the same time worldwide) to mitigate the difference between International Atomic Time and Coordinated Universal Time or due to the slowing of rotation of the Earth. In R, all the leap seconds that have ever been added are stored in .leap.seconds.   1 2 3 4 5 6 7 8 9 10 11 12 13  .leap.seconds #R> [1] "1972-07-01 GMT" "1973-01-01 GMT" "1974-01-01 GMT" "1975-01-01 GMT" "1976-01-01 GMT" #R> [6] "1977-01-01 GMT" "1978-01-01 GMT" "1979-01-01 GMT" "1980-01-01 GMT" "1981-07-01 GMT" #R> [11] "1982-07-01 GMT" "1983-07-01 GMT" "1985-07-01 GMT" "1988-01-01 GMT" "1990-01-01 GMT" #R> [16] "1991-01-01 GMT" "1992-07-01 GMT" "1993-07-01 GMT" "1994-07-01 GMT" "1996-01-01 GMT" #R> [21] "1997-07-01 GMT" "1999-01-01 GMT" "2006-01-01 GMT" "2009-01-01 GMT" "2012-07-01 GMT" #R> [26] "2015-07-01 GMT" "2017-01-01 GMT" # in seconds since Unix epoch (Thursday 1 January 1970 00:00:00 UT) format(.leap.seconds, "%s") #R> [1] "78796800" "94694400" "126230400" "157766400" "189302400" "220924800" "252460800" #R> [8] "283996800" "315532800" "362793600" "394329600" "425865600" "489024000" "567993600" #R> [15] "631152000" "662688000" "709948800" "741484800" "773020800" "820454400" "867715200" #R> [22] "915148800" "1136073600" "1230768000" "1341100800" "1435708800" "1483228800"  What happened when a leap second is added? A Leap second is basically an extra second between 23h59m59s (either June 30th or December 31st) and 00h00m00s the next day (https://www.ietf.org/timezones/data/leap-seconds.list). In practice for most system 23.59.59 is repeated twice (see https://access.redhat.com/articles/15145). I very doubt you will ever need to use .leap.seconds but it remains a fun fact to know. .Machine Lastly let’s have a look at .Machine. According to its documentation ‘.Machine’ is a variable holding information on the numerical characteristics of the machine R is running on, such as the largest double or integer and the machine’s precision.   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86  # have a look at the sessionInfo() at the end of the post to know what # system was use .Machine #R>$double.eps #R> [1] 2.220446e-16 #R> #R> $double.neg.eps #R> [1] 1.110223e-16 #R> #R>$double.xmin #R> [1] 2.225074e-308 #R> #R> $double.xmax #R> [1] 1.797693e+308 #R> #R>$double.base #R> [1] 2 #R> #R> $double.digits #R> [1] 53 #R> #R>$double.rounding #R> [1] 5 #R> #R> $double.guard #R> [1] 0 #R> #R>$double.ulp.digits #R> [1] -52 #R> #R> $double.neg.ulp.digits #R> [1] -53 #R> #R>$double.exponent #R> [1] 11 #R> #R> $double.min.exp #R> [1] -1022 #R> #R>$double.max.exp #R> [1] 1024 #R> #R> $integer.max #R> [1] 2147483647 #R> #R>$sizeof.long #R> [1] 8 #R> #R> $sizeof.longlong #R> [1] 8 #R> #R>$sizeof.longdouble #R> [1] 16 #R> #R> $sizeof.pointer #R> [1] 8 #R> #R>$longdouble.eps #R> [1] 1.084202e-19 #R> #R> $longdouble.neg.eps #R> [1] 5.421011e-20 #R> #R>$longdouble.digits #R> [1] 64 #R> #R> $longdouble.rounding #R> [1] 5 #R> #R>$longdouble.guard #R> [1] 0 #R> #R> $longdouble.ulp.digits #R> [1] -63 #R> #R>$longdouble.neg.ulp.digits #R> [1] -64 #R> #R> $longdouble.exponent #R> [1] 15 #R> #R>$longdouble.min.exp #R> [1] -16382 #R> #R> $longdouble.max.exp #R> [1] 16384  This vector gives you various details about the precision of your computer. Here I will use $integer.max to trigger an integer overflow.

 1 2 3  as.integer(.Machine$integer.max) + 1L #R> Warning in as.integer(.Machine$integer.max) + 1L: NAs produced by integer overflow #R> [1] NA 

I have used 1L to force 1 to be an integer, otherwise this will be converted to a double and I won’t get the error.

 1 2 3 4 5 6  typeof(1) #R> [1] "double" typeof(1L) #R> [1] "integer" typeof(as.integer(.Machine\$integer.max) + 1) #R> [1] "double" 

The integer overflow here is harmless but believe me, it can be very nasty, for instance when this happens for the primary key of an important database, or even worse when in happens in a plane of a space rocket, as delineated in the BBC’ article The number glitch that can lead to catastrophe)!

Session info
  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32  sessionInfo() #R> R version 4.2.2 (2022-10-31) #R> Platform: x86_64-pc-linux-gnu (64-bit) #R> Running under: Ubuntu 22.04.2 LTS #R> #R> Matrix products: default #R> BLAS: /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 #R> LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.20.so #R> #R> locale: #R> [1] LC_CTYPE=C.UTF-8 LC_NUMERIC=C LC_TIME=C.UTF-8 LC_COLLATE=C.UTF-8 #R> [5] LC_MONETARY=C.UTF-8 LC_MESSAGES=C.UTF-8 LC_PAPER=C.UTF-8 LC_NAME=C #R> [9] LC_ADDRESS=C LC_TELEPHONE=C LC_MEASUREMENT=C.UTF-8 LC_IDENTIFICATION=C #R> #R> attached base packages: #R> [1] stats graphics grDevices utils datasets methods base #R> #R> other attached packages: #R> [1] inSilecoRef_0.1.0 #R> #R> loaded via a namespace (and not attached): #R> [1] Rcpp_1.0.10 jquerylib_0.1.4 bslib_0.4.2 compiler_4.2.2 pillar_1.8.1 #R> [6] later_1.3.0 plyr_1.8.8 tools_4.2.2 digest_0.6.31 evaluate_0.20 #R> [11] jsonlite_1.8.4 lifecycle_1.0.3 tibble_3.2.0 rcrossref_1.2.0 pkgconfig_2.0.3 #R> [16] rlang_1.0.6 bibtex_0.5.1 shiny_1.7.4 cli_3.6.0 yaml_2.3.7 #R> [21] crul_1.3 curl_5.0.0 blogdown_1.16 xfun_0.37 fastmap_1.1.1 #R> [26] xml2_1.3.3 stringr_1.5.0 dplyr_1.1.0 knitr_1.42 sass_0.4.5 #R> [31] generics_0.1.3 fs_1.6.1 htmlwidgets_1.6.1 vctrs_0.5.2 DT_0.27 #R> [36] tidyselect_1.2.0 glue_1.6.2 httpcode_0.3.0 R6_2.5.1 fansi_1.0.4 #R> [41] bookdown_0.33 rmarkdown_2.20 magrittr_2.0.3 backports_1.4.1 promises_1.2.0.1 #R> [46] ellipsis_0.3.2 htmltools_0.5.4 mime_0.12 xtable_1.8-4 httpuv_1.6.9 #R> [51] utf8_1.2.3 stringi_1.7.12 miniUI_0.1.1.1 cachem_1.0.7