R Inferno : Summary
[
The author of “R Inferno”, Patrick Burns, starts off by saying, “If you are using R and you think you’re in hell, this is a map for you”. Well, this fantastic book needs to be read by any R programmer, irrespective of whether he thinks he is in hell or not. The metaphor used in this book is that of journey through concentric circles, each circle representing people (programmers) who are suffering in pain because of “violating the proper programming conduct”. Using this metaphor, the author makes an amazing list of items that one need to keep in mind while programming in R. There is a good discussion on each of the items too. My intent of this post is to merely list down the main points of this book.
Circle – 1: Falling into the Floating Point Trap
- Be careful with floating point representation of numbers. There will always be numerical errors which are very different between logical errors
Circle – 2: Growing Objects
-
Preallocate objects as much as possible
-
Try to get an upper bound of the vector you will need and allocate the vector before you run any loop. Limit the number of times rbind is used in a loop
-
If you do not know how many elements will get added in each loop, populate the data for each iteration in to a list and then collapse the list in to a data frame
-
R does all the computation in RAM. It means quicker computation but it means that if you are not careful it will eat up all your RAM
-
Error: cannot allocate vector of size 79.8 Mb. This should not be interpreted as “Well, I have X GB of memory and why can’t R allocated 80 MB”. The fact is that R has already allocated the memory efficiently and it has reached a point where it cannot allocate more memory
-
To check the memory that is being used up, generously scatter the code
-
cat(‘point 1 mem’, memory.size(), memory.size(max=TRUE), ‘nn’)
-
memory.size() and memory.limit() gives an account of memory used up and memory that can is still left that can be used
Circle – 3 : Failing to Vectorize
-
Write functions / code which inherently handles vectorized input
-
Vectorization does not mean treating collection of arguments as a vector.
-
min, max, range, sum, and prod take the collection of arguments as the vector. Mean does not adhere to this form mean(1,2,3,4) gives 1 as output whereas min(c(1,2,3,4)) gives the right answer as 2.5
-
Vectorize to have clarity in the code construction.
-
Subscripting can be used as a vectorization tool
-
Use ifelse instead of if to help vectorize your code; vector is not a welcome input in if condition.
-
Use apply/tapply/lapply/sapply/mapply/rapply etc have inbuilt vectorized functions instead of writing loops
Circle – 4 : Over Vectorizing
-
apply function has a for loop inside. lapply function has a for loop inside. Hence mindless application of these functions is skirting with danger
-
If you really want to change NAs to 0, you should rethink what you are doing – you are introducing fictional data
Circle – 5: Not Writing Functions
-
The body of a function needs to be a single expression. Curly brackets convert a bunch of expressions in to single expression
-
Functions can be passed as argument to other functions.
-
do.call allows you to provide the arguments as an actual list
-
Don’t use a list when atomic vector will do
-
Don’t use a data frame when matrix will do
-
Don’t try to use an atomic vector when list is needed
-
Don’t use a matrix when data frame is needed
-
Put spaces between operators and indent the code
-
Avoid superfluous semicolons that you would have been carrying from the old programming languages
-
Rprof can be used to explore which functions are taking most of the time
-
Write a help file for each of your persistent functions.
-
Writing a help file is an excellent way of debugging the function.
-
Add examples while writing a help file and try to use data from the inbuilt datasets package
Circle – 6 : Doing Global Assignments
-
Avoid Global assignments ( «- ). The code is extremely inflexible when global assignments are used.
-
R always passes through value. It never passes by reference.
Circle – 7 : Tripping over Object Orientation
-
S3 methods make the “class” attribute. Only if an object has “class” attribute, do S3 methods really come to an effect.
-
If Generic functions take S3 class as an argument, it searches the S3 class with the function which matches the name of the generic function and executes it
-
getS3method(“median”,”default”)
-
Inheritance should be based on similarity of the structure of the objects , not based on similarity of concepts. Dataframe and matrix might look similar conceptually, but they are completely different as far as code reuse is concerned. Hence inheritance is useless between matrix and dataframe
-
There is multiple dispatch in S4 objects
-
UseMethods creates an S3 generic function
-
standardGeneric creates S4 function. More strict guidelines for S4 class object
-
In S3 the decision of what method to use is made in real-time when the function is called. In S4 the decision is made when the code is loaded into the R session. There is a table that charts the relationships of all the classes.
-
Namespaces : If you have two functions with the same name in two different packages, namespace allows you to pick the right function.
-
A namespace exports one or more objects so that they are visible, but may have some objects that are private.
Circle – 8 : Believing it does as intended
In this circle there are ghosts, chimeras and devils that inflict the maximum pain
Ghosts
-
browser(), recover(), trace(), debug() are THE most important functions in R debuggin
-
always use prebuilt nullcheck functions such as is.null ,is.na
-
objects have one of the following as atomic stogarge modes:logical, integer, numeric, complex, character
-
== operator and %in% operator – Their importance and relevance
-
Sum(numeric(0)) is 1 and prod(numeric(0)) is 1
-
There is no median method that can be applied to data frame.
-
match only matches first occurrence
-
cat prints the contents of the vector . while using cat you must always add a newline as by default it doesn’t have one.
-
cat interprets the string whereas print doesn’t
-
All coercion functions strip the attributes from the object
-
Subscripting almost always strips almost all attributes
-
Extremely good practice to use TRUE and FALSE rather than T and
-
sort.list does not work for lists
-
attach and load put R objects on to the search list. Attach creates a new item in the search list while load puts its content in the global environment, the first place in the search list. source is meant to create objects rather than loading actual objects
-
If you have a character string that contains the name of an object and you want the object, then one uses get function
-
If you want the name of the argument of the function, you can use deparse(substitute(arg_name))
-
If a subscript operation is used on an array , it becomes a vector not a matrix. If you use drop=FALSE , the attribute is kind of preserved
-
Failing to use drop=FALSE inside functions is a major source of bugs.
-
The drop function has no effect on a data frame. Always use drop=FALSE in the subscripting function
-
rownames of a data frame are lost through dropping. Coercing to a matrix first will retain the row names.
-
If you use apply with a function that returns a vector, that becomes the first dimension of the result. I came across this umpteen number of times in my code and I just used to have the result transposed.
-
sweep function is a very useful function that is not emphasized much in the general r literature floating around
-
guidelines for list subscripting
-
single brackets always give you back the same type of object
-
double brackets need not give you the same type of object
-
double brackets always give you one item
-
sungle brackets can give you any number of items
-
-
c function works with lists also
-
for(i in 1:10) i does not print anything . The problem is that no real action is involved in the loop. You must use instead print(i)
-
use of seq_along or seq(along=x) is always better
-
iterate is sacrosanct. Never knew about this earlier. This statement means that if you have a for loop with index on i and then you change the value of i in the loop, it does not effect the global counter of the loop
-
R uses dynamic scoping rather than lexical scoping
Chimeras
-
factor : Factors are an implementation of the idea of categorical data, Class attribute is “factor” , “levels” attribute has a character vector that provides the identity of each category
-
factors do not refer to numbers. as.numeric() typically gives numbers that has nothing to do the factors
-
subscripting does not change the levels of the factor . Use drop=TRUE to drop the levels that are not present in the data.
-
Do not subscript with factors
-
There is no c for factors
-
Missing values makes sense in factors and hence there can be level NA for a factor
-
If you want to convert data frame to character, it is better to convert to a matrix and then convert to a character
-
X[condition,] <- 999 Vs X[which(condition),] <- 999. What’s the difference ? The latter treats NA as false while the former doesn’t
-
There is a difference between && , &. Similarly || , |. The latter is used in vector comparisons and former is used for a single element. Use & | in ifelse condition and && || in if condition.
-
An integer vector tests TRUE with is.numeric. However as.numeric() changes the storage mode to double
-
Be careful to know the difference between max and pmax
-
all.equal and is.identical are two different functions altogether.
-
= is not a synonym of <-
-
Sample has helpful feature that is not always helpful. Its first argument can be either the population of items to sample from, or the number of items in the population.
-
apply function coerces a dataframe in to matrix before the application. Its better to use lappy instead of apply to keep the attributes of dataframe intact.
-
If you think something is a data.frame or a matrix, it is better to use x[,”columnname”]
-
names of a dataframe are the column names while names of a matrix are the names of the individual elements
-
cbind with two vectors gives a matrix , meaning, cbind favors matrices over data.frames
-
data.frame is implemented as a list. But not just any list will do – each component must represent the same number of rows.
Devils
-
read.table creates a data.frame
-
colClasses to control the type of input columns that are imported
-
use strip.white to remove extraneous spaces while importing files
-
scan and readLines function to read files with irregular data format
-
Instead of storing data in a file, retrieving the file back to R , it is better to save the object and attach/load the object as and when required
-
Function given to outer must be vectorized
-
match.call can be used to access … in the argument of the function
-
R uses lazy evaluation. Arguments to functions are not evaluated until they are required
-
The default value of an argument to a function is evaluated inside the function, not in the environment calling the function
-
tapply returns one dimensional array which is neither a vector nor a matrix
-
by is a pretty version of tapply
-
When R coerces from a floating point number to an integer, it truncates rather than rounds
-
Reserved words in R are if , else , repeat , while , function , for , in , next , break , TRUE , FALSE ,NULL , Inf, NaN, NA, NA_integer, NA_real, NA_complex, NA_character
-
return is a function and not a reserved word
-
Before running a batch job, it is better to run parse on the code and check for any errors.
Circle – 9 : Unhelpfully seeking help
This circle gives some guidelines in the context of posting queries in various R help forums.
Takeaway:
This is my favorite book on R. Any R programmer at whatever level of expertise he/she is at, journey through these circles, would certainly make them a better programmer, and their present / future pain of debugging their R code less traumatic.