R Programming Patterns
## Learning Objectives
- Master common R programming patterns
- Understand debugging techniques
- Handle errors gracefully
- Write efficient R code
## Code Organization
### Project Structure
```text
my_project/
R/
functions.R
helpers.R
data/
raw/
processed/
scripts/
analysis.R
output/
my_project.Rproj
```
### Sourcing Scripts
```r
# Source another R file
source("R/functions.R")
# Source with error handling
tryCatch({
source("R/functions.R")
}, error = function(e) {
message("Error sourcing functions: ", e$message)
})
# Package functions
devtools::source_url("https://example.com/functions.R")
```
## Functions Best Practices
### Function Template
```r
# Documented function
#' Calculate summary statistics
#'
#' @param x A numeric vector
#' @param na.rm Remove NA values (default TRUE)
#' @return A list with mean, sd, and n
#' @examples
#' my_summary(c(1, 2, 3, 4, 5))
my_summary <- function(x, na.rm = TRUE) {
if (!is.numeric(x)) {
stop("x must be numeric")
}
result <- list(
mean = mean(x, na.rm = na.rm),
sd = sd(x, na.rm = na.rm),
n = sum(!is.na(x))
)
return(result)
}
```
### Input Validation
```r
validate_input <- function(x) {
if (is.null(x)) {
stop("x cannot be NULL")
}
if (length(x) == 0) {
stop("x cannot be empty")
}
if (!is.numeric(x)) {
stop("x must be numeric")
}
invisible(TRUE) # Silent success
}
```
### Named Arguments
```r
# Use named arguments for clarity
calculate_stats(
data = my_data,
group_by = "category",
na.rm = TRUE
)
# Use ... for flexibility
flexible_function <- function(data, ...) {
# Pass ... to another function
aggregate(data, ...)
}
```
## Error Handling
### tryCatch Pattern
```r
# Basic tryCatch
result <- tryCatch({
# Try this code
risky_operation()
}, warning = function(w) {
# Handle warning
message("Warning: ", w$message)
NA
}, error = function(e) {
# Handle error
message("Error: ", e$message)
NULL
}, finally = {
# Always runs
cleanup()
})
```
### try for Silent Errors
```r
# try catches error but continues
result <- try({
dangerous_function()
}, silent = TRUE)
if (inherits(result, "try-error")) {
message("Operation failed")
} else {
# Use result
}
```
### Custom Error Classes
```r
# Create custom error class
my_error <- function(message) {
err <- structure(list(message = message), class = "my_error")
}
# Handle custom error
tryCatch({
stop(my_error("Custom error message"))
}, my_error = function(e) {
message("Caught my error: ", e$message)
})
```
## Debugging
### browser() Function
```r
debug_function <- function(x) {
result <- x * 2
browser() # Debugger stops here
return(result)
}
```
### debug() and undebug()
```r
# Mark function for debugging
debug(my_function)
# Run your code
my_function()
# Remove debugging
undebug(my_function)
```
### traceback()
```r
# After an error, see the call stack
traceback()
# Or with traceback
options(error = traceback)
```
### recover()
```r
# Set option to enter browser on error
options(error = recover)
# Now errors will show call stack and let you inspect
```
### cat() Debugging
```r
# Print intermediate values
process_data <- function(df) {
cat("Input rows:", nrow(df), "\n")
df <- filter(df, !is.na(value))
cat("After filter:", nrow(df), "\n")
return(df)
}
```
### str() for Inspection
```r
# Inspect any object
str(df)
str(my_list)
str(my_function)
# Detailed structure
str(df, max.level = 2)
```
## Object-Oriented Programming in R
### S3 Classes
```r
# Create S3 object
my_object <- structure(
list(data = c(1, 2, 3), name = "test"),
class = "my_class"
)
# Print method
print.my_class <- function(x) {
cat("My object:", x$name, "\n")
cat("Data:", x$data, "\n")
}
# Generic function
my_generic <- function(x) {
UseMethod("my_generic")
}
# Default method
my_generic.default <- function(x) {
"Default"
}
# Specific method
my_generic.my_class <- function(x) {
"My class method"
}
```
### R6 Classes
```r
library(R6)
# Define R6 class
Counter <- R6Class(
"Counter",
public = list(
count = 0,
add = function(n = 1) {
self$count <- self$count + n
invisible(self)
},
get = function() {
self$count
}
)
)
# Use R6 object
counter <- Counter$new()
counter$add(5)
counter$get() # 5
```
## Iteration Patterns
### lapply with Progress
```r
library(pbapply)
pbapply::pblapply(1:100, slow_function)
```
### Parallel Processing
```r
library(parallel)
# Detect cores
detectCores()
# Create cluster
cl <- makeCluster(2)
# Parallel lapply
parLapply(cl, 1:10, function(x) x^2)
# Stop cluster
stopCluster(cl)
```
### Future Package
```r
library(future)
# Sequential (default)
plan(sequential)
# Multisession
plan(multisession)
# Future lapply
library(future.apply)
future_lapply(1:10, function(x) x^2)
```
## Performance Optimization
### Profiling
```r
# Profile code
Rprof("profile.out")
source("my_script.R")
Rprof(NULL)
# View results
summaryRprof("profile.out")
```
### Memory Usage
```r
# Object size
object.size(df)
format(object.size(df), units = "Mb")
# Memory usage
gc()
# List largest objects
lsos <- function() {
sort(sapply(ls(envir = globalenv()), object.size), decreasing = TRUE)
}
lsos()
```
### Speeding Up Code
```r
# Preallocate instead of growing
# Slow:
result <- c()
for (i in 1:1000) {
result <- c(result, i^2)
}
# Fast:
result <- numeric(1000)
for (i in 1:1000) {
result[i] <- i^2
}
# Or just:
result <- (1:1000)^2
```
### Data Table for Large Data
```r
library(data.table)
# Fast reading
dt <- fread("large_file.csv")
# Fast aggregation
dt[, .(mean = mean(value)), by = group]
# Fast joins
merge(dt1, dt2, on = "key")
```
## Functional Programming
### Closures
```r
# Function that returns function
make_power <- function(exp) {
function(x) x^exp
}
square <- make_power(2)
cube <- make_power(3)
```
### purrr Functions
```r
library(purrr)
# map - apply function to each element
map(c(1, 2, 3), ~ .x^2) # list(1, 4, 9)
# map_dbl - return double vector
map_dbl(c(1, 2, 3), ~ .x^2) # 1 4 9
# safely - handle errors
safe_read_csv <- safely(read_csv)
result <- safe_read_csv("file.csv")
result$result # data or NULL
result$error # error or NULL
# possibly - provide default
map_chr(list(1, 2, 3), possibly(~ as.character(.x), "default"))
```
## Summary
- Source scripts with `source()`
- Validate inputs in functions
- Use `tryCatch()` for error handling
- Debug with `browser()`, `debug()`, `traceback()`
- S3 and R6 for OOP patterns
- Consider parallel processing for large loops
- Profile before optimizing
- Use data.table for large datasets
- purrr for functional programming
Comments
Comments powered by Giscus
To enable comments, add your Giscus embed code here.
Learn more about Giscus →