R Advanced Topics — OOP, Functional Programming, and Best Practices

R AdvancedAdvanced TopicsFree Lesson

Advertisement

R Advanced Topics — OOP, Functional Programming, and Best Practices

Learning Objectives

By the end of this tutorial, you will be able to:

  • Implement object-oriented programming with S3, S4, and R6
  • Apply functional programming concepts (closures, purrr)
  • Optimize R code for performance
  • Write production-quality R code
  • Use modern R development tools

Object-Oriented Programming

S3 Classes (Simple)

# Create S3 class
person <- function(name, age) {
  structure(
    list(name = name, age = age),
    class = "person"
  )
}

# Method
greet <- function(x, ...) {
  UseMethod("greet")
}

greet.person <- function(x, ...) {
  paste("Hello, my name is", x$name, "and I am", x$age, "years old.")
}

# Create object
alice <- person("Alice", 30)
greet(alice)
# [1] "Hello, my name is Alice and I am 30 years old."

# Check class
class(alice)  # [1] "person"
inherits(alice, "person")  # [1] TRUE

S4 Classes (Formal)

# Define S4 class
setClass("Person",
  representation(
    name = "character",
    age = "numeric"
  )
)

# Constructor
setMethod("initialize", "Person", function(.Object, name, age) {
  .Object@name <- name
  .Object@age <- age
  .Object
})

# Method
setGeneric("greet", function(object) standardGeneric("greet"))
setMethod("greet", "Person", function(object) {
  paste("Hello, I am", object@name)
})

# Create object
alice <- new("Person", name = "Alice", age = 30)
greet(alice)

R6 Classes (Reference Classes)

library(R6)

Person <- R6Class("Person",
  public = list(
    name = NULL,
    age = NULL,

    initialize = function(name, age) {
      self$name <- name
      self$age <- age
    },

    greet = function() {
      paste("Hello, I am", self$name)
    },

    birthday = function() {
      self$age <- self$age + 1
      invisible(self)
    }
  )
)

# Create object
alice <- Person$new("Alice", 30)
alice$greet()     # [1] "Hello, I am Alice"
alice$birthday()
alice$age         # [1] 31

S3 vs S4 vs R6

FeatureS3S4R6
ComplexitySimpleFormalReference
EncapsulationNoPartialYes
Method dispatchGenericGenericMethod
InheritanceManualBuilt-inBuilt-in
PerformanceFastestSlowerFast
Use caseQuick classesBioconductorProduction

Functional Programming

First-Class Functions

# Functions are objects
square <- function(x) x^2
class(square)  # [1] "function"

# Assign to variable
f <- square
f(5)  # [1] 25

# Pass as argument
apply_function <- function(f, x) f(x)
apply_function(square, 5)  # [1] 25

# Return function
make_adder <- function(n) {
  function(x) x + n
}
add5 <- make_adder(5)
add5(10)  # [1] 15

Closures

# Functions that capture their environment
counter <- function() {
  count <- 0
  list(
    increment = function() {
      count <<- count + 1
      count
    },
    get = function() count
  )
}

c <- counter()
c$increment()  # [1] 1
c$increment()  # [1] 2
c$get()        # [1] 2

Purrr (Functional Programming)

library(purrr)

# Map variants
map(1:5, \(x) x^2)        # Returns list
map_dbl(1:5, \(x) x^2)    # Returns double vector
map_chr(1:5, \(x) paste(x))  # Returns character vector
map_lgl(1:5, \(x) x > 3)  # Returns logical vector

# Map2 — two inputs
map2(c(1, 2, 3), c(10, 20, 30), \(x, y) x + y)

# Pmap — multiple inputs
pmap(list(1:3, 4:6, 7:9), \(x, y, z) x + y + z)

# Keep and discard
keep(1:10, \(x) x > 5)   # [1] 6 7 8 9 10
discard(1:10, \(x) x > 5) # [1] 1 2 3 4 5

# Reduce
reduce(1:5, `+`)  # [1] 15

# Accumulate
accumulate(1:5, `+`)
# [1]  1  3  6 10 15

# Compose
compose <- function(f, g) function(...) f(g(...))
add1_square <- compose(\(x) x^2, \(x) x + 1)
add1_square(5)  # [1] 36

Performance Optimization

Vectorization

# Slow: loop
x <- 1:1000000
result <- numeric(length(x))
for (i in seq_along(x)) {
  result[i] <- x[i]^2
}

# Fast: vectorized
result <- x^2

# Fast: apply
result <- sapply(x, \(x) x^2)

Data.table

library(data.table)

# Convert to data.table
dt <- as.data.table(mtcars)

# Fast operations
dt[, .(mean_mpg = mean(mpg), sd_mpg = sd(mpg)), by = cyl]

# Fast reading
dt <- fread("large_file.csv")

# Fast writing
fwrite(dt, "output.csv")

Parallel Processing

library(parallel)

# Detect cores
n_cores <- detectCores()

# Make cluster
cl <- makeCluster(n_cores - 1)

# Parallel apply
result <- parLapply(cl, 1:1000, function(x) x^2)

# Stop cluster
stopCluster(cl)

# Using foreach
library(foreach)
library(doParallel)

registerDoParallel(cl)
result <- foreach(i = 1:1000, .combine = c) %dopar% {
  i^2
}
stopCluster(cl)

Memory Optimization

# Check object size
object.size(mtcars)

# Remove objects
rm(x, y, z)

# Clear all
rm(list = ls())

# Use data.table for large datasets
# Use fst for fast serialization

Best Practices

Code Style

# Use snake_case for variables and functions
my_variable <- 10
my_function <- function(x) x^2

# Use UPPER_CASE for constants
MAX_RETRIES <- 3
PI <- 3.14159

# Use informative names
calculate_average <- function(scores) {
  mean(scores, na.rm = TRUE)
}

# Not: calc_avg <- function(s) mean(s, na.rm = TRUE)

# Add spaces around operators
x <- 1 + 2
y <- x * 3

# Not: x<-1+2

Documentation

#' Calculate BMI
#'
#' @param weight_kg Weight in kilograms
#' @param height_cm Height in centimeters
#' @return BMI value (numeric)
#' @examples
#' calculate_bmi(70, 175)
calculate_bmi <- function(weight_kg, height_cm) {
  height_m <- height_cm / 100
  weight_kg / (height_m ^ 2)
}

Error Handling

safe_function <- function(x) {
  if (!is.numeric(x)) {
    stop("x must be numeric", call. = FALSE)
  }
  if (length(x) == 0) {
    stop("x must not be empty", call. = FALSE)
  }
  mean(x, na.rm = TRUE)
}

Modern R Features

Native Pipe (R 4.1+)

# Base pipe
1:10 |>
  mean() |>
  round(2)

# With placeholder (R 4.2+)
"hello world" |>
  toupper() |>
  paste("!", sep = "")

Lambda Functions (R 4.1+)

# Anonymous functions
\(x) x^2

# In apply
sapply(1:5, \(x) x^2)

# In pipes
1:5 |>
  sapply(\(x) x^2) |>
  sum()

Wildcard Bindings (R 4.0+)

# Tilde imports
library(tidyverse)

# Or selective imports
library(dplyr, include.only = c("filter", "select", "mutate"))

Practical Examples

Example 1: Custom Report Generator

generate_report <- function(data, output_file) {
  # Create Rmd content
  rmd_content <- paste0(
    '---
title: "Data Report"
output: html_document
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = FALSE)

Summary

summary(', deparse(substitute(data)), ')

Plot

plot(', deparse(substitute(data)), '$x, ', deparse(substitute(data)), '$y)

' )

writeLines(rmd_content, output_file) rmarkdown::render(output_file) }


### Example 2: Validation Package

```r
# validators.R
check_numeric <- function(x, name = deparse(substitute(x))) {
  if (!is.numeric(x)) stop(name, " must be numeric")
  if (length(x) == 0) stop(name, " must not be empty")
  if (any(is.na(x))) warning(name, " contains NA values")
  invisible(TRUE)
}

check_positive <- function(x, name = deparse(substitute(x))) {
  check_numeric(x, name)
  if (any(x < 0)) stop(name, " must be positive")
  invisible(TRUE)
}

# Usage
x <- c(1, 2, -3, 4)
check_numeric(x)
# Warning: x contains NA values
check_positive(x)
# Error: x must be positive

Practice Exercises

Exercise 1: R6 Class System

Create an R6 class BankAccount with deposit, withdraw, and balance methods. Include error handling for insufficient funds.

Solution

library(R6)

BankAccount <- R6Class("BankAccount",
  public = list(
    balance = 0,

    initialize = function(initial_balance = 0) {
      if (initial_balance < 0) stop("Initial balance must be positive")
      self$balance <- initial_balance
    },

    deposit = function(amount) {
      if (amount <= 0) stop("Deposit must be positive")
      self$balance <- self$balance + amount
      invisible(self)
    },

    withdraw = function(amount) {
      if (amount <= 0) stop("Withdrawal must be positive")
      if (amount > self$balance) stop("Insufficient funds")
      self$balance <- self$balance - amount
      invisible(self)
    },

    get_balance = function() {
      self$balance
    }
  )
)

# Usage
account <- BankAccount$new(1000)
account$deposit(500)$withdraw(200)$get_balance()
# [1] 1300

Key Takeaways

  • S3 is simplest — use for everyday classes
  • S4 is formal — use for Bioconductor packages
  • R6 is reference-based — use for stateful objects
  • Functional programming — closures, map, reduce, compose
  • Vectorize before looping — 10-100x speedups
  • data.table is fast — for large datasets
  • Parallel processingparallel and foreach packages
  • Always document functions — roxygen2 format
  • Handle errors gracefullystop(), warning(), tryCatch()
  • Use modern R features — pipe |>, lambda \(x)

Congratulations!

You have completed the R Programming A-Z course! You now have comprehensive knowledge from basics to advanced topics. Keep practicing, explore packages, and build real projects to solidify your skills.

Advertisement

Need Expert R Programming Help?

Get personalized tutoring, project support, or professional consulting.

Advertisement