class: center, middle, inverse, title-slide # Best Coding Practices in R ## Advanced R for Bioinformatics. Visby, 2018 ### Marcin Kierczak ### 28 May, 2018 --- class: spaced name: overview ## Topics of This Presentation <br><br> Code: <br> * **Style** -- __howTo_style.yourCode? * **Structure** -- manufacture your own building blocks. * **Debugging** -- my code does not run. * **Profiling** -- now it does run but... out of memory! * **Optimization** -- making things better. --- name: coding-style ## Coding Style * Naming conventions -- assigning names to variables. * Code formatting -- placement of braces, use of whitespace characters etc. .center[ <img src="./assets/coding_style.jpg" class="fancyimage", style="width:49%; height:49%; box-shadow:0px 0px 0px white"><br> .vsmall[From: [Behind The Lines](http://geekandpoke.typepad.com/geekandpoke/2010/09/behind-the-lines.html) 2010-09-23. By Oliver Widder, Webcomics Geek And Poke.] ] --- name: naming-conventions ## Naming Conventions A syntactically valid name: * Consists of: + letters: `abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ` + digits: `0123456789` + period: `.` + underscore: `_` * begins with a letter or the period (`.`) **not** followed by a number * cannot be one of the *reserved words*: `if`, `else`, `repeat`, `while`, `function`, `for`, `in`, `next`, `break`, `TRUE`, `FALSE`, `NULL`, `Inf`, `NaN`, `NA`, `NA_integer_`, `NA_real_`, `NA_complex_`, `NA_character_` * also cannot be: `c`, `q`, `t`, `C`, `D`, `I` --- name: naming-styles ## Naming Style Variable names that are legal are not necesserily a good style and they may be dangerous: ```r F + T F <- T F + T ``` ``` ## [1] 1 ## [1] 2 ``` do not do this! -- unless you are a politician... <br><br><br> .center[.large[Avoid `T` and `F` as variable names.]] --- ## Customary Variable Names Also, there is a number of variable names that are traditionally used to name particular variables: * `usr` -- user, * `pwd` -- password, * `x`, `y`, `z` -- vectors, * `w` -- weights, * `f`, `g` -- functions, * `n` -- number of rows, * `p` -- number of columns, * `i`, `j`, `k` -- indexes, * `df` -- data frame, * `cnt` -- counter, * `M`, `N`, `W` -- matrices, * `tmp` -- temporary variables Sometimes these are domain-specific: * `p`, `q` -- allele frequencies in genetics, * `N`, `k` -- number of trials and number of successes in stats <br><br> .center[.large[Try to avoid use these in this way to avoid possible confusion.]] --- ## Different Notations People use different notation styles hroughout their code: * `snake_notation_looks_like_this`, * `camelNotationLooksLikeThis`, * `period.notation.looks.like.this`, * `LousyNotation_looks.likeThis` Try to be consistent and stick to one of them. Bear in mind `period.notation` is used by S3 classes to create generic functions, e.g. `plot.my.object`. A reason to avoid it? .center[***] It is also important to maintain code readability by having your variable names: * informative, e.g. `genotypes` vs. `fsjht45jkhsdf4`, * consistent across your code - the same naming convention, * not too long, e.g. - `weight` vs. `phenotype.weight.measured`, * in the period notation and the snake notation avoid `my.var.2` or `my_var_2`, use `my.var2` and `my_var2` instead, --- ## Special Variable Names Few more things to consider: * there are built-in variable names: + LETTERS: the 26 upper-case letters of the Roman alphabet + letters: the 26 lower-case letters of the Roman alphabet + month.abb: the three-letter abbreviations for the English month names + month.name: the English names for the months of the year + pi: the ratio of the circumference of a circle to its diameter * variable names beginning with period are **hidden**: `.my_secret_variable` will not be shown but can be accessed. --- name: structuring_your_code ## Structure Your Code Decompose the problem! .center[ <img src="./assets/Philip-ii-of-macedon.jpg" class="fancyimage", style="height:200px; box-shadow:0px 0px 0px white"> <img src="./assets/Julius_Ceasar.jpg" class="fancyimage", style="height:200px; box-shadow:0px 0px 0px white"> <img src="./assets/Napoleon_Bonaparte.jpg" class="fancyimage", style="height:200px; box-shadow:0px 0px 0px white"><br> .vsmall[source: Wikimedia Commons] ] -- * *Divide et impera* / top-down approach -- split your BIG problem into small subproblems recursively and, **at some level**, encapsulate your code in functional blocks (functions). * A function should be performing a small task. Should be a logical program unit. **When should I write a function?** * one screen rule (resolution...), * re-use twice rule. Consider creating an S4 class -- data-type safety! --- name: how_to_write_functions ## How to write functions * Avoid accessing (and modifying) globals! * Use data as the very first argument (pipes). * Set parameters to defaults -- better more params than too few. * Remember that global defaults can be changed by `options`. * If you are re-using someone else's function -- write a wrapper. * Showing progress and messages is good, but let the others turn this off. * If you are calling other functions, consider using `...` .center[ <img src="./assets/goto.png" class="fancyimage", style="height:230px; box-shadow:0px 0px 0px white"><br> .vsmall[source: http://www.xkcd/com/292] ] --- name: debugging ## Debugging Your Code * Sooner or later ALL of us, even the most experienced programmers, introduce errors to their code. * *20 percent of the code has 80 percent of the errors. Find them, fix them!* .right[*-- Lowell Arthur*] * *Beware of bugs in the above code; I have only proved it correct, not tried it.* .right[*-- Donald Knuth*] * The process of debugging is about confirming, one-by-one, that our beliefs about the code are actually true (loosely inspired by Pete Salzman). * Debug in a *top-down* and *modular* manner! provided you have coded in a modular way... <img src="assets/konqui_debugging.png" style="display:block; width:30%; margin-left:auto; margin-right:auto;"> --- name: types_of_bugs ## Types of bugs ### There are different types of bugs we can introduce: * Syntax -- `prin(var1), mean(sum(seq((x + 2) * (y - 9 * b)))` * Arithmetic -- `x/0` (not in R, though!) `Inf/Inf` * Type -- `mean('a')` * Logic -- everything works and produces seemingly valid output that is WRONG! ### To avoid bugs: * Encapsulate your code in smaller units (functions), you can test. * Use classes and type checking. * Test at the boundaries, e.g. loops at min and max value. * Feed your functions with test data that should result with a known output. * Use *antibugging*: `stopifnot(y <= 75)` --- name: arithmetic_bugs ## Arithmetic bugs ```r (vec <- seq(0.1, 0.9, by=0.1)) vec == 0.7 vec == 0.5 (0.5 + 0.1) - 0.6 (0.7 + 0.1) - 0.8 ``` ``` ## [1] 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 ## [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE ## [1] FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE ## [1] 0 ## [1] -1.110223e-16 ``` Beware of floating point arithmetics! .small[ ```r head(unlist(.Machine)) head(unlist(.Platform)) ``` ``` ## double.eps double.neg.eps double.xmin double.xmax double.base ## 2.220446e-16 1.110223e-16 2.225074e-308 1.797693e+308 2.000000e+00 ## double.digits ## 5.300000e+01 ## OS.type file.sep dynlib.ext GUI endian pkgType ## "unix" "/" ".so" "unknown" "little" "source" ``` ] --- name: error_handling ## Handling Errors ```r input <- c(1, 10, -7, -2/5, 0, 'char', 100, pi) for (val in input) { (paste0('Log of ', val, 'is ', log10(val))) } ``` ``` ## Error in log10(val): non-numeric argument to mathematical function ``` One option is to use the `try` block: ```r for (val in input) { val <- as.numeric(val) try(print(paste0('Log of ', val, ' is ', log10(val)))) } ``` ``` ## [1] "Log of 1 is 0" ## [1] "Log of 10 is 1" ## [1] "Log of -7 is NaN" ## [1] "Log of -0.4 is NaN" ## [1] "Log of 0 is -Inf" ## [1] "Log of NA is NA" ## [1] "Log of 100 is 2" ## [1] "Log of 3.14159265358979 is 0.497149872694133" ``` --- name: error_handling_cted ## Handling Errors with `tryCatch` ```r for (val in input) { val <- as.numeric(val) result <- tryCatch(log10(val), warning = function(w) { print('Negative argument supplied. Negating.'); log10(-val) }, error = function(e) { print('Not a number!'); NaN }) print(paste0('Log of ', val, ' is ', result)) } ``` ``` ## [1] "Log of 1 is 0" ## [1] "Log of 10 is 1" ## [1] "Negative argument supplied. Negating." ## [1] "Log of -7 is 0.845098040014257" ## [1] "Negative argument supplied. Negating." ## [1] "Log of -0.4 is -0.397940008672038" ## [1] "Log of 0 is -Inf" ## [1] "Log of NA is NA" ## [1] "Log of 100 is 2" ## [1] "Log of 3.14159265358979 is 0.497149872694133" ``` --- ## Debugging -- Errors and Warnings * An error in your code will result in a call to the `stop()` function that: + breaks the execution of the program (loop, if-statemetnt, etc.), + performs the action defined by the global parameter `error`. * A warning just prints out the warning message (or reports it in another way). * Global parameter `error` defines what R should do when an error occurs. ```r options(error = ) ``` * You can use `simpleError()` and `simpleWarning()` to generate errors and warnings in your code: ```r f <- function(x) { if (x < 0) { x <- abs(x) w <- simpleWarning("Value less than 0. Taking abs(x)") w } } ``` --- ## Debugging -- What are my Options? * Old-school debugging: a lot of `print` statements + print values of your variables at some checkpoints, + sometimes fine but often laborious, + need to remove/comment out manually after debugging. * Dumping frames + on error, R state will be saved to a file, + file can be read into debugger, + values of all variables can be checked, + can debug on another machine, e.g. send dump to your colleague! * Traceback + a list of the recent function calls with values of their params, * Step-by-step debugging + execute code line by line within the debugger --- name: debugging_dump_frames ## Option 1: Dumping Frames ```r options(error = quote(dump.frames("testdump", TRUE))) f <- function(x) { sin(x) } f('test') ``` ``` ## Error in sin(x): non-numeric argument to mathematical function ``` ```r options(error = NULL) load("testdump.rda") # debugger(testdump) ``` .small[<tt>Message: Error in sin(x) : non-numeric argument to mathematical function <br> Available environments had calls: <br> 1: f("test") <br> <br> Enter an environment number, or 0 to exit <br> Selection: 1 <br> Browsing in the environment with call: <br> f("test") <br> Called from: debugger.look(ind) <br> Browse[1]> x <br> [1] "test" <br> Browse[1]> <br> [1] "test" <br> Browse[1]> </tt> ] Last empty line brings you back to the environments menu. --- name: debugging_traceback ## Option 2: Traceback ```r f <- function(x) { log10(x) } g <- function(x) { f(x) } g('test') ``` ``` ## Error in log10(x): non-numeric argument to mathematical function ``` <tt> > traceback()<br> 2: f(x) at #2<br> 1: g("test")<br> </tt> `traceback()` shows what were the function calls and what parameters were passed to them when the error occured. --- ## Option 3: Debug step-by-step Let us define a new function `h(x, y)`: ```r h <- function(x, y) { f(x) f(y) } ``` Now, we can use `debug()` to debug the function in a step-by-step manner: ```r debug(h) h('text', 7) undebug(h) ``` --- ## Option 3: Debug step-by-step cted. `n` -- execute next line, `c` -- execute whole function, `q` -- quit debugger mode. <tt> > debug(h)<br> > h('text', 7)<br> debugging in: h("text", 7)<br> debug at #1: {<br> f(x)<br> f(y)<br> }<br> </tt> -- <tt>Browse[2]> x<br> [1] "text"<br></tt> -- <tt>Browse[2]> y<br> [1] 7<br></tt> -- <tt>Browse[2]> n<br> debug at #2: f(x)<br></tt> -- <tt>Browse[2]> x<br> [1] "text"<br></tt> -- <tt>Browse[2]> n<br> Error in log10(x) : non-numeric argument to mathematical function<br> </tt> --- name: profiling_proc_time ## Profiling -- `proc.time()` Profiling is the process of identifying memory and time bottlenecks in your code. ```r proc.time() ``` ``` ## user system elapsed ## 2.497 0.205 2.931 ``` * `user time` -- CPU time charged for the execution of user instructions of the calling process, * `system time` -- CPU time charged for execution by the system on behalf of the calling process, * `elapsed time` -- total CPU time elapsed for the currently running R process. ```r pt1 <- proc.time() tmp <- runif(n = 10e5) pt2 <- proc.time() pt2 - pt1 ``` ``` ## user system elapsed ## 0.035 0.003 0.038 ``` --- name: profiling_system_time ## Profiling -- `system.time()` ```r system.time(runif(n = 10e6)) system.time(rnorm(n = 10e6)) ``` ``` ## user system elapsed ## 0.388 0.026 0.420 ## user system elapsed ## 0.677 0.010 0.695 ``` --- name: profiling_system_time ## Profiling in Action Let's see profiling in action! We will define four functions that fill a large vector in two different ways: -- ```r fun_fill_loop1 <- function(n = 10e6, f) { result <- NULL for (i in 1:n) { result <- c(result, eval(call(f, 1))) } return(result) } ``` -- ```r fun_fill_loop2 <- function(n = 10e6, f) { result <- vector(length = n) for (i in 1:n) { result[i] <- eval(call(f, 1)) } return(result) } ``` --- ## Profiling in Action cted. It is maybe better to use... -- vectorization! -- ```r fun_fill_vec1 <- function(n = 10e6, f) { result <- NULL result <- eval(call(f, n)) return(result) } ``` -- ```r fun_fill_vec2 <- function(n = 10e6, f) { result <- vector(length = n) result <- eval(call(f, n)) return(result) } ``` --- name: compare_loop_vec_sys_time ## Profiling our functions ```r system.time(fun_fill_loop1(n = 10e4, "runif")) # Loop 1 system.time(fun_fill_loop2(n = 10e4, "runif")) # Loop 2 system.time(fun_fill_vec1(n = 10e4, "runif")) # Vectorized 1 system.time(fun_fill_vec2(n = 10e4, "runif")) # Vectorized 2 ``` ``` ## user system elapsed ## 17.723 10.508 28.850 ## user system elapsed ## 0.308 0.037 0.346 ## user system elapsed ## 0.004 0.000 0.004 ## user system elapsed ## 0.005 0.000 0.005 ``` The `system.time()` function is not the most accurate though. During the lab, we will experiment with package `microbenchmark`. --- name: Rprof ## More advanced profiling We can also do a bit more advanced profiling, including the memory profiling, using, e.g. `Rprof()` function. And let us summarise: .small[ ```r summary <- summaryRprof('profiler_test.out', memory='both') datatable(summary$by.self, options=list(pageLength = 10, searching = F, info = F)) #knitr::kable(summary$by.self) ```
] --- name: profr_package ## Profiling -- `profr` package There are also packages available that enable even more advanced profiling: ```r library(profr) Rprof("profiler_test2.out", interval = 0.01) tmp <- table(sort(rnorm(1e5))) Rprof(NULL) profile_df <- parse_rprof('profiler_test2.out') ``` This returns a table that can be visualised:
--- name: profr_package_cted ## Profiling -- `profr` package cted. We can also use the overloaded `ggplot` function: ```r profr::ggplot.profr(profile_df) ``` <img src="data:image/png;base64,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" style="display: block; margin: auto;" /> --- name: profviz_package ## Profiling with `profvis` Yet another nice way to profile your code is by using Hadley Wickham's `profvis` package: ```r library(profvis) profvis({fun_fill_loop2(1e4, 'runif') fun_fill_vec2(1e4, 'runif') }) ``` --- name: profvis_run ## Profiling with `profvis` cted.
--- name: optimizing_code1 ## Optimizing your code *We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%. A good programmer will not be lulled into complacency by such reasoning, he will be wise to look carefully at the critical code; but only after that code has been identified.* <br><br> *-- Donald Knuth* <div class="pull-left"> <img src="./assets/xkcd_automation.png" style="height:300px;"> <br> .vsmall[source: http://www.xkcd/com/1319] </div> <div class="pull-right"> <img src="./assets/xkcd_is_it_worth_the_time_2x.png" style="height:300px;"> <br> .vsmall[source: http://www.xkcd/com/1205] </div> --- name: optimization_types ## Ways to optimize the code * write it in a more efficient way, e.g. use vectorization or `*apply` family instead of loops etc., * allocating memory to avoid copy-on-modify, * use package `BLAS` for linear algebra, * use `bigmemory` package, * GPU computations, * multicore support, e.g. `multicore`, `snow` * use `data.table` or `tibble` instead of `data.frame` --- name: copy-on-modify ## Copy-on-modify ```r library(pryr) order <- 1024 matrix_A <- matrix(rnorm(order^2), nrow = order) matrix_B <- matrix_A ``` -- Check where the objects are in the memory: -- ```r address(matrix_A) address(matrix_B) ``` ``` ## [1] "0x112e5a000" ## [1] "0x112e5a000" ``` -- What happens if we modify a value in one of the matrices? -- ```r matrix_B[1,1] <- 1 address(matrix_A) address(matrix_B) ``` ``` ## [1] "0x112e5a000" ## [1] "0x11365b000" ``` --- ## Avoid copying by allocating memory ### No memory allocation ```r f1 <- function(to = 3, silent=F) { tmp <- c() for (i in 1:to) { a1 <- address(tmp) tmp <- c(tmp, i) a2 <- address(tmp) if(!silent) { print(paste0(a1, " --> ", a2)) } } } f1() ``` ``` ## [1] "0x7f83af012b78 --> 0x7f83b4d25538" ## [1] "0x7f83b4d25538 --> 0x7f83b4d25898" ## [1] "0x7f83b4d25898 --> 0x7f83b474cf88" ``` --- ## Avoid copying by allocating memory cted. ### With allocation ```r f2 <- function(to = 3, silent = FALSE) { tmp <- vector(length = to, mode='numeric') for (i in 1:to) { a1 <- address(tmp) tmp[i] <- i a2 <- address(tmp) if(!silent) { print(paste0(a1, " --> ", a2)) } } } f2() ``` ``` ## [1] "0x7f83b5805808 --> 0x7f83b5805808" ## [1] "0x7f83b5805808 --> 0x7f83b5805808" ## [1] "0x7f83b5805808 --> 0x7f83b5805808" ``` --- ## Allocating memory -- benchmark. ```r library(microbenchmark) benchmrk <- microbenchmark(f1(to = 1e3, silent = T), f2(to = 1e3, silent = T), times = 100L) autoplot(benchmrk) ``` <img src="data:image/png;base64,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" style="display: block; margin: auto auto auto 0;" /> --- ## GPU ```r library(gpuR) library(microbenchmark) A = matrix(rnorm(1000^2), nrow=1000) # stored: RAM, computed: CPU B = matrix(rnorm(1000^2), nrow=1000) gpuA = gpuMatrix(A, type = "double") # stored: RAM, computed: GPU gpuB = gpuMatrix(B, type = "double") vclA = vclMatrix(A, type = "double") # stored: GPU, computed: GPU vclB = vclMatrix(B, type = "double") bch <- microbenchmark( cpuC = A %*% B, gpuC = gpuA %*% gpuB, vclC = vclA %*% vclB, times = 10L) ``` .small[ More on [Charles Determan's Blog](https://www.r-bloggers.com/r-gpu-programming-for-all-with-gpur/). ] --- ## GPU cted. ```r autoplot(bch) ``` <img src="data:image/png;base64,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" style="display: block; margin: auto;" /> --- ## Parallelization using package `parallel` Easiest to paralllelize is `lapply`: ```r result <- lapply(1:2, function(x) { c(x, x^2, x^3) } ) result ``` ``` ## [[1]] ## [1] 1 1 1 ## ## [[2]] ## [1] 2 4 8 ``` ```r library(parallel) num_cores <- detectCores() - 1 cl <- makeCluster(num_cores) # Init cluster parLapply(cl, 1:2, function(x) { c(x, x^2, x^3)} ) stopCluster(cl) ``` ``` ## [[1]] ## [1] 1 1 1 ## ## [[2]] ## [1] 2 4 8 ``` --- name: end-slide class: end-slide <h2 style="color:#fff"> Thank you</h2>