efficient-programming | R Documentation |
A small set of functions to address some common inefficiencies in R, such as the creation of logical vectors to compare quantities, unnecessary copies of objects in elementary mathematical or subsetting operations, obtaining information about objects (esp. data frames), or dealing with missing values.
anyv(x, value) # Faster than any(x == value). See also kit::panyv()
allv(x, value) # Faster than all(x == value). See also kit::pallv()
allNA(x) # Faster than all(is.na(x)). See also kit::pallNA()
whichv(x, value, # Faster than which(x == value)
invert = FALSE) # or which(x != value). See also Note (3)
whichNA(x, invert = FALSE) # Faster than which((!)is.na(x))
x %==% value # Infix for whichv(v, value, FALSE), use e.g. in fsubset()
x %!=% value # Infix for whichv(v, value, TRUE). See also Note (3)
alloc(value, n, # Fast rep_len(value, n) or replicate(n, value).
simplify = TRUE) # simplify only works if length(value) == 1. See Details.
copyv(X, v, R, ..., invert # Fast replace(X, v, R), replace(X, X (!/=)= v, R) or
= FALSE, vind1 = FALSE, # replace(X, (!)v, R[(!)v]). See Details and Note (4).
xlist = FALSE) # For multi-replacement see also kit::vswitch()
setv(X, v, R, ..., invert # Same for X[v] <- r, X[x (!/=)= v] <- r or
= FALSE, vind1 = FALSE, # x[(!)v] <- r[(!)v]. Modifies X by reference, fastest.
xlist = FALSE) # X/R/V can also be lists/DFs. See Details and Examples.
setop(X, op, V, ..., # Faster than X <- X +\-\*\/ V (modifies by reference)
rowwise = FALSE) # optionally can also add v to rows of a matrix or list
X %+=% V # Infix for setop(X, "+", V). See also Note (2)
X %-=% V # Infix for setop(X, "-", V). See also Note (2)
X %*=% V # Infix for setop(X, "*", V). See also Note (2)
X %/=% V # Infix for setop(X, "/", V). See also Note (2)
na_rm(x) # Fast: if(anyNA(x)) x[!is.na(x)] else x, last
na_locf(x, set = FALSE) # obs. carried forward and first obs. carried back.
na_focb(x, set = FALSE) # (by reference). These also support lists (NULL/empty)
na_omit(X, cols = NULL, # Faster na.omit for matrices and data frames,
na.attr = FALSE, # can use selected columns to check, attach indices,
prop = 0, ...) # and remove cases with a proportion of values missing
na_insert(X, prop = 0.1, # Insert missing values at random
value = NA)
missing_cases(X, cols=NULL, # The opposite of complete.cases(), faster for DF's.
prop = 0, count = FALSE) # See also kit::panyNA(), kit::pallNA(), kit::pcountNA()
vlengths(X, use.names=TRUE) # Faster lengths() and nchar() (in C, no method dispatch)
vtypes(X, use.names = TRUE) # Get data storage types (faster vapply(X, typeof, ...))
vgcd(x) # Greatest common divisor of positive integers or doubles
fnlevels(x) # Faster version of nlevels(x) (for factors)
fnrow(X) # Faster nrow for data frames (not faster for matrices)
fncol(X) # Faster ncol for data frames (not faster for matrices)
fdim(X) # Faster dim for data frames (not faster for matrices)
seq_row(X) # Fast integer sequences along rows of X
seq_col(X) # Fast integer sequences along columns of X
vec(X) # Vectorization (stacking) of matrix or data frame/list
cinv(x) # Choleski (fast) inverse of symmetric PD matrix, e.g. X'X
X , V , R |
a vector, matrix or data frame. | ||||||||||||||||||||||||||
x , v |
a (atomic) vector or matrix ( | ||||||||||||||||||||||||||
value |
a single value of any (atomic) vector type. For | ||||||||||||||||||||||||||
invert |
logical. | ||||||||||||||||||||||||||
set |
logical. | ||||||||||||||||||||||||||
simplify |
logical. If | ||||||||||||||||||||||||||
vind1 |
logical. If | ||||||||||||||||||||||||||
xlist |
logical. If | ||||||||||||||||||||||||||
op |
an integer or character string indicating the operation to perform.
| ||||||||||||||||||||||||||
rowwise |
logical. | ||||||||||||||||||||||||||
cols |
select columns to check for missing values using column names, indices, a logical vector or a function (e.g. | ||||||||||||||||||||||||||
n |
integer. The length of the vector to allocate with | ||||||||||||||||||||||||||
na.attr |
logical. | ||||||||||||||||||||||||||
prop |
double. For | ||||||||||||||||||||||||||
count |
logical. | ||||||||||||||||||||||||||
use.names |
logical. Preserve names if | ||||||||||||||||||||||||||
... |
for |
alloc
is a fusion of rep_len
and replicate
that is faster in both cases. If value
is a length one atomic vector (logical, integer, double, string, complex or raw) and simplify = TRUE
, the functionality is as rep_len(value, n)
i.e. the output is a length n
atomic vector with the same attributes as value
(apart from "names"
, "dim"
and "dimnames"
). For all other cases the functionality is as replicate(n, value, simplify = FALSE)
i.e. the output is a length-n
list of the objects. For efficiency reasons the object is not copied i.e. only the pointer to the object is replicated.
copyv
and setv
are designed to optimize operations that require replacing data in objects in the broadest sense. The only difference between them is that copyv
first deep-copies X
before doing replacements whereas setv
modifies X
in place and returns the result invisibly. There are 3 ways these functions can be used:
To replace a single value, setv(X, v, R)
is an efficient alternative to X[X == v] <- R
, and copyv(X, v, R)
is more efficient than replace(X, X == v, R)
. This can be inverted using setv(X, v, R, invert = TRUE)
, equivalent to X[X != v] <- R
.
To do standard replacement with integer or logical indices i.e. X[v] <- R
is more efficient using setv(X, v, R)
, and, if v
is logical, setv(X, v, R, invert = TRUE)
is efficient for X[!v] <- R
. To distinguish this from use case (1) when length(v) == 1
, the argument vind1 = TRUE
can be set to ensure that v
is always interpreted as an index.
To copy values from objects of equal size i.e. setv(X, v, R)
is faster than X[v] <- R[v]
, and setv(X, v, R, invert = TRUE)
is faster than X[!v] <- R[!v]
.
Both X
and R
can be atomic or data frames / lists. If X
is a list, the default behavior is to interpret it like a data frame, and apply setv/copyv
to each element/column of X
. If R
is also a list, this is done using mapply
. Thus setv/copyv
can also be used to replace elements or rows in data frames, or copy rows from equally sized frames. Note that for replacing subsets in data frames set
from data.table
provides a more convenient interface (and there is also copy
if you just want to deep-copy an object without any modifications to it).
If X
should not be interpreted like a data frame, setting xlist = TRUE
will interpret it like a 1D list-vector analogous to atomic vectors, except that use case (1) is not permitted i.e. no value comparisons on list elements.
None of these functions (apart from alloc
) currently support complex vectors.
setop
and the operators %+=%
, %-=%
, %*=%
and %/=%
also work with integer data, but do not perform any integer related checks. R's integers are bounded between +-2,147,483,647 and NA_integer_
is stored as the value -2,147,483,648. Thus computations resulting in values exceeding +-2,147,483,647 will result in integer overflows, and NA_integer_
should not occur on either side of a setop
call. These are programmers functions and meant to provide the most efficient math possible to responsible users.
It is possible to compare factors by the levels (e.g. iris$Species %==% "setosa")
) or using integers (iris$Species %==% 1L
). The latter is slightly more efficient. Nothing special is implemented for other objects apart from basic types, e.g. for dates (which are stored as doubles) you need to generate a date object i.e. wlddev$date %==% as.Date("2019-01-01")
. Using wlddev$date %==% "2019-01-01"
will give integer(0)
.
setv/copyv
only allow positive integer indices being passed to v
, and, for efficiency reasons, they only check the first and the last index. Thus if there are indices in the middle that fall outside of the data range it will terminate R.
Data Transformations, Small (Helper) Functions, Collapse Overview
oldopts <- options(max.print = 70)
## Which value
whichNA(wlddev$PCGDP) # Same as which(is.na(wlddev$PCGDP))
whichNA(wlddev$PCGDP, invert = TRUE) # Same as which(!is.na(wlddev$PCGDP))
whichv(wlddev$country, "Chad") # Same as which(wlddev$county == "Chad")
wlddev$country %==% "Chad" # Same thing
whichv(wlddev$country, "Chad", TRUE) # Same as which(wlddev$county != "Chad")
wlddev$country %!=% "Chad" # Same thing
lvec <- wlddev$country == "Chad" # If we already have a logical vector...
whichv(lvec, FALSE) # is fastver than which(!lvec)
rm(lvec)
# Using the %==% operator can yield tangible performance gains
fsubset(wlddev, iso3c %==% "DEU") # 3x faster than:
fsubset(wlddev, iso3c == "DEU")
# With multiple categories we can use %iin%
fsubset(wlddev, iso3c %iin% c("DEU", "ITA", "FRA"))
## Math by reference: permissible types of operations
x <- alloc(1.0, 1e5) # Vector
x %+=% 1
x %+=% 1:1e5
xm <- matrix(alloc(1.0, 1e5), ncol = 100) # Matrix
xm %+=% 1
xm %+=% 1:1e3
setop(xm, "+", 1:100, rowwise = TRUE)
xm %+=% xm
xm %+=% 1:1e5
xd <- qDF(replicate(100, alloc(1.0, 1e3), simplify = FALSE)) # Data Frame
xd %+=% 1
xd %+=% 1:1e3
setop(xd, "+", 1:100, rowwise = TRUE)
xd %+=% xd
rm(x, xm, xd)
## setv() and copyv()
x <- rnorm(100)
y <- sample.int(10, 100, replace = TRUE)
setv(y, 5, 0) # Faster than y[y == 5] <- 0
setv(y, 4, x) # Faster than y[y == 4] <- x[y == 4]
setv(y, 20:30, y[40:50]) # Faster than y[20:30] <- y[40:50]
setv(y, 20:30, x) # Faster than y[20:30] <- x[20:30]
rm(x, y)
# Working with data frames, here returning copies of the frame
copyv(mtcars, 20:30, ss(mtcars, 10:20))
copyv(mtcars, 20:30, fscale(mtcars))
ftransform(mtcars, new = copyv(cyl, 4, vs))
# Column-wise:
copyv(mtcars, 2:3, fscale(mtcars), xlist = TRUE)
copyv(mtcars, 2:3, mtcars[4:5], xlist = TRUE)
## Missing values
mtc_na <- na_insert(mtcars, 0.15) # Set 15% of values missing at random
fnobs(mtc_na) # See observation count
missing_cases(mtc_na) # Fast equivalent to !complete.cases(mtc_na)
missing_cases(mtc_na, cols = 3:4) # Missing cases on certain columns?
missing_cases(mtc_na, count = TRUE) # Missing case count
missing_cases(mtc_na, prop = 0.8) # Cases with 80% or more missing
missing_cases(mtc_na, cols = 3:4, prop = 1) # Cases mssing columns 3 and 4
missing_cases(mtc_na, cols = 3:4, count = TRUE) # Missing case count on columns 3 and 4
na_omit(mtc_na) # 12x faster than na.omit(mtc_na)
na_omit(mtc_na, prop = 0.8) # Only remove cases missing 80% or more
na_omit(mtc_na, na.attr = TRUE) # Adds attribute with removed cases, like na.omit
na_omit(mtc_na, cols = .c(vs, am)) # Removes only cases missing vs or am
na_omit(qM(mtc_na)) # Also works for matrices
na_omit(mtc_na$vs, na.attr = TRUE) # Also works with vectors
na_rm(mtc_na$vs) # For vectors na_rm is faster ...
rm(mtc_na)
## Efficient vectorization
head(vec(EuStockMarkets)) # Atomic objects: no copy at all
head(vec(mtcars)) # Lists: directly in C
options(oldopts)
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.