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 toplevel 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 variable 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 toplevel R expression (see https://stackoverflow.com/questions/31475226/usinglastvalueinrmarkdownknitr), 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.3.2/lib/R/sitelibrary"
#R> [3] "/opt/R/4.3.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] 172
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 builtin 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> <bytecode: 0x5623b9acdae8>
#R> <environment: 0x5623b9ac6ea8>
.Library
#R> [1] "/opt/R/4.3.2/lib/R/library"
.Library.site
#R> [1] "/opt/R/4.3.2/lib/R/sitelibrary"

From ?.Platform
‘.Platform’ is a list with some details of the platform under
which R was built. This provides means to write OSportable 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 useful 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> <bytecode: 0x5623b9a66cf0>
#R> <environment: namespace:base>

.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] "19720701 GMT" "19730101 GMT" "19740101 GMT" "19750101 GMT" "19760101 GMT"
#R> [6] "19770101 GMT" "19780101 GMT" "19790101 GMT" "19800101 GMT" "19810701 GMT"
#R> [11] "19820701 GMT" "19830701 GMT" "19850701 GMT" "19880101 GMT" "19900101 GMT"
#R> [16] "19910101 GMT" "19920701 GMT" "19930701 GMT" "19940701 GMT" "19960101 GMT"
#R> [21] "19970701 GMT" "19990101 GMT" "20060101 GMT" "20090101 GMT" "20120701 GMT"
#R> [26] "20150701 GMT" "20170101 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/leapseconds.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
87
88
89

# 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.220446e16
#R>
#R> $double.neg.eps
#R> [1] 1.110223e16
#R>
#R> $double.xmin
#R> [1] 2.225074e308
#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> $sizeof.time_t
#R> [1] 8
#R>
#R> $longdouble.eps
#R> [1] 1.084202e19
#R>
#R> $longdouble.neg.eps
#R> [1] 5.421011e20
#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)!
Display information relative to the R session used to render this post.
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

sessionInfo()
#R> R version 4.3.2 (20231031)
#R> Platform: x86_64pclinuxgnu (64bit)
#R> Running under: Ubuntu 22.04.3 LTS
#R>
#R> Matrix products: default
#R> BLAS: /usr/lib/x86_64linuxgnu/openblaspthread/libblas.so.3
#R> LAPACK: /usr/lib/x86_64linuxgnu/openblaspthread/libopenblaspr0.3.20.so; LAPACK version 3.10.0
#R>
#R> locale:
#R> [1] LC_CTYPE=C.UTF8 LC_NUMERIC=C LC_TIME=C.UTF8 LC_COLLATE=C.UTF8
#R> [5] LC_MONETARY=C.UTF8 LC_MESSAGES=C.UTF8 LC_PAPER=C.UTF8 LC_NAME=C
#R> [9] LC_ADDRESS=C LC_TELEPHONE=C LC_MEASUREMENT=C.UTF8 LC_IDENTIFICATION=C
#R>
#R> time zone: UTC
#R> tzcode source: system (glibc)
#R>
#R> attached base packages:
#R> [1] stats graphics grDevices utils datasets methods base
#R>
#R> other attached packages:
#R> [1] inSilecoRef_0.1.1
#R>
#R> loaded via a namespace (and not attached):
#R> [1] sass_0.4.8 utf8_1.2.4 generics_0.1.3 xml2_1.3.6 blogdown_1.19
#R> [6] stringi_1.8.3 httpcode_0.3.0 digest_0.6.34 magrittr_2.0.3 evaluate_0.23
#R> [11] bookdown_0.37 fastmap_1.1.1 plyr_1.8.9 jsonlite_1.8.8 backports_1.4.1
#R> [16] crul_1.4.0 promises_1.2.1 fansi_1.0.6 jquerylib_0.1.4 bibtex_0.5.1
#R> [21] cli_3.6.2 shiny_1.8.0 rlang_1.1.3 ellipsis_0.3.2 cachem_1.0.8
#R> [26] yaml_2.3.8 tools_4.3.2 dplyr_1.1.4 httpuv_1.6.14 DT_0.31
#R> [31] rcrossref_1.2.0 curl_5.2.0 vctrs_0.6.5 R6_2.5.1 mime_0.12
#R> [36] lifecycle_1.0.4 stringr_1.5.1 fs_1.6.3 htmlwidgets_1.6.4 miniUI_0.1.1.1
#R> [41] pkgconfig_2.0.3 bslib_0.6.1 pillar_1.9.0 later_1.3.2 glue_1.7.0
#R> [46] Rcpp_1.0.12 systemfonts_1.0.5 xfun_0.42 tibble_3.2.1 tidyselect_1.2.0
#R> [51] knitr_1.45 xtable_1.84 htmltools_0.5.7 svglite_2.1.3 rmarkdown_2.25
#R> [56] compiler_4.3.2
