Cheatsheet R
Linguagem para estatística, análise de dados e visualização
R
Basic
Variables and Assignment
# Assignment (preferred form) x <- 10 name <- "Anna" # Other forms y = 20 # also works 30 -> z # to the right (rare) # Built-in constants pi # 3.141593 LETTERS # "A"..."Z" letters # "a"..."z" # Remove objects from memory rm(x) rm(list = ls()) # clear everything
In R, assignment is mostly done with the <- operator. The = sign also works, but by convention it is reserved for function arguments. rm() removes objects from memory.
Vectorized Operations
v <- c(10, 20, 30, 40, 50) # Arithmetic (element-wise) v * 2 # 20,40,60,80,100 v + c(1, 2, 3, 4, 5) v ^ 2 # Statistics sum(v) # 150 mean(v) # 30 median(v) # 30 sd(v) # standard deviation min(v); max(v) range(v) # minimum and maximum # Logical any(v > 40) # TRUE all(v > 0) # TRUE which(v > 25) # positions: 3 4 5
Operations are vectorized: they apply to all elements at once, without needing loops. Functions like sum(), mean() and sd() summarize the vector.
Special Values
# NA: missing value x <- c(1, NA, 3) is.na(x) # FALSE TRUE FALSE mean(x) # NA mean(x, na.rm = TRUE) # 2 # NULL: absence of value (empty) y <- NULL is.null(y) # TRUE length(y) # 0 # NaN and Inf 0 / 0 # NaN (not a number) 1 / 0 # Inf (infinity) is.nan(0 / 0) # TRUE is.finite(Inf) # FALSE
NA represents a missing value and propagates through calculations (use na.rm = TRUE to ignore it). NULL is the absence of value. NaN and Inf come from invalid mathematical operations.
Data Types
# Atomic types
42L # integer
3.14 # double (numeric)
"text" # character
TRUE # logical
1 + 2i # complex
# Check the type
class(x) # class
typeof(x) # internal type
is.numeric(x) # TRUE/FALSE
is.character(x)
# Convert
as.numeric("42") # 42
as.character(42) # "42"
as.integer(3.7) # 3
as.logical(1) # TRUEThe six atomic types are integer, double, character, logical, complex and raw. Use class() and typeof() to inspect them and the.*() to convert.
Strings
s <- "Hello World"
# Join text
paste("Hello", "World") # "Hello World"
paste0("a", "b") # "ab" (no space)
paste(1:3, collapse = ", ") # "1, 2, 3"
# Format
sprintf("%.2f", pi) # "3.14"
sprintf("%s is %d", "Anna", 30)
# Manipulate
nchar(s) # 11
toupper(s) # "HELLO WORLD"
substr(s, 1, 5) # "Hello"
gsub("o", "0", s) # replace
strsplit(s, " ") # list of wordsR treats text the character vectors. paste() joins pieces, sprintf() formats and functions like nchar(), toupper() and gsub() manipulate the content.
Creating Vectors
# Combine values v <- c(1, 2, 3, 4, 5) # Sequences v <- 1:10 # 1 to 10 v <- seq(1, 10, by = 2) # 1,3,5,7,9 v <- seq(0, 1, length.out = 5) # Repeat v <- rep(0, times = 5) # 0,0,0,0,0 v <- rep(c(1, 2), each = 3) # 1,1,1,2,2,2 # Empty vector of a type v <- numeric(5) v <- character(3)
A vector is the most basic structure in R and stores elements of the same type. c() combines values, : and seq() create sequences and rep() repeats patterns.
Factors
# Categorical variable
sex <- factor(c("M", "F", "F", "M"))
# With defined order
grade <- factor(
c("B", "A", "C", "A"),
levels = c("C", "B", "A"),
ordered = TRUE
)
# With labels
group <- factor(
c(1, 2, 1, 2),
labels = c("Control", "Treatment")
)
# Operations
levels(sex) # "F" "M"
nlevels(sex) # 2
table(sex) # count per levelA factor stores categorical variables with a fixed set of levels. It is essential in statistical models and plots. ordered = TRUE creates ordered levels.
Accessing Vectors
v <- c(10, 20, 30, 40, 50)
# By position (starts at 1!)
v[1] # first
v[-1] # all except the 1st
v[2:4] # positions 2 to 4
v[c(1, 3, 5)] # specific positions
# By logical condition
v[v > 25] # 30, 40, 50
# By name
names(v) <- c("a", "b", "c", "d", "e")
v["a"] # 10Indexing in R starts at 1, not 0. Negative indices exclude elements and a logical vector selects those that are TRUE. With names() you can access by name.
Operators
# Arithmetic 10 %% 3 # remainder: 1 10 %/% 3 # integer division: 3 2 ^ 10 # power: 1024 # Comparison == != > < >= <= # Logical & | ! # vectorized && || # scalar (for if) # Membership 3 %in% c(1, 2, 3) # TRUE # Pipes (chain operations) x |> sum() |> sqrt() # native (R 4.1+) x %>% sum() %>% sqrt() # magrittr
R has operators like %% (remainder) and %/% (integer division). %in% tests membership and the pipes |> (native) and %>% (magrittr) chain functions from left to right.
Flow Control
If / Else
x <- 5
if (x > 0) {
print("Positive")
} else if (x < 0) {
print("Negative")
} else {
print("Zero")
}
# switch by value
day <- switch("2",
"1" = "Monday",
"2" = "Tuesday",
"Other"
)if evaluates a logical condition and runs a block. You can chain else if and else. switch() is a clean alternative to compare one value against several options.
The apply Family
mat <- matrix(1:12, nrow = 3) # apply: by row (1) or column (2) apply(mat, 1, sum) # sum by row apply(mat, 2, mean) # mean by column # lapply: list -> list lapply(1:5, function(x) x ^ 2) # sapply: list -> vector (simplified) sapply(1:5, function(x) x ^ 2) # vapply: guaranteed return type vapply(1:5, sqrt, numeric(1)) # mapply: multiple arguments mapply(function(x, y) x + y, 1:3, 4:6) # tapply: by group tapply(values, groups, mean)
The apply family applies a function to elements without writing loops. lapply() returns a list, sapply() simplifies to a vector and vapply() guarantees the return type, being safer.
Vectorized ifelse
grades <- c(12, 8, 15, 6) # Applies to each element result <- ifelse(grades >= 10, "Passed", "Failed") # "Passed" "Failed" "Passed" "Failed" # Nested category <- ifelse(grades >= 14, "High", ifelse(grades >= 10, "Medium", "Low")) # case_when (dplyr, more readable) library(dplyr) df |> mutate(level = case_when( grades >= 14 ~ "High", grades >= 10 ~ "Medium", TRUE ~ "Low" ))
Unlike if, the ifelse() function is vectorized: it evaluates a whole vector at once. For multiple conditions, case_when() from dplyr is more readable.
purrr and map
library(purrr) # map: returns a list map(1:5, ~ .x ^ 2) # Specific types map_dbl(1:5, sqrt) # double map_chr(1:5, as.character) # text map_lgl(1:5, ~ .x > 3) # logical # Two arguments map2(first_names, ages, ~ paste(.x, .y)) # Filter keep(1:10, ~ .x %% 2 == 0) # evens discard(1:10, ~ .x > 5) # Reduce to one value reduce(list(df1, df2, df3), merge)
The purrr package brings modern functional programming. map() applies a function and the variants map_dbl() and map_chr() guarantee the type. The formula ~ .x is a shorthand anonymous function.
For Loops
# Iterate over a sequence
for (i in 1:5) {
print(i)
}
# Loop over elements
for (x in c("a", "b", "c")) {
cat(x, "\n")
}
# With safe index
v <- c(10, 20, 30)
for (i in seq_along(v)) {
cat(i, ":", v[i], "\n")
}
# break and next
for (i in 1:10) {
if (i == 3) next # skips 3
if (i == 7) break # stops at 7
print(i)
}The for loop goes through each element of a vector. seq_along() generates safe indices. next skips an iteration and break ends the loop. In R, prefer vectorized operations over loops.
Vectorized Conditions
v <- c(5, 2, 8, 1, 9)
# Positions that meet the condition
which(v > 4) # 1 3 5
which.max(v) # 5 (position of the maximum)
which.min(v) # 4
# Global checks
any(v > 8) # TRUE (any?)
all(v > 0) # TRUE (all?)
# cut: split into intervals
groups <- cut(ages,
breaks = c(0, 18, 65, Inf),
labels = c("Young", "Adult", "Senior")
)which() returns the positions that satisfy a condition; which.max() and which.min() give the position of the extreme. any() and all() test the whole vector and cut() groups values into intervals.
While and Repeat
# while: repeats while true
x <- 1
while (x <= 5) {
print(x)
x <- x + 1
}
# repeat: infinite loop (needs break)
x <- 1
repeat {
if (x > 5) break
print(x)
x <- x + 1
}
# Numerical convergence
value <- 1
repeat {
new_val <- (value + 2 / value) / 2
if (abs(new_val - value) < 1e-6) break
value <- new_val
}
value # square root of 2while repeats while the condition is true. repeat creates an infinite loop that only stops with break — useful for convergence somethingrithms.
Error Handling
result <- tryCatch({
log("abc") # raises an error
}, error = function(e) {
message("Error: ", e$message)
NA # fallback value
}, warning = function(w) {
message("Warning: ", w$message)
NULL
}, finally = {
message("Done") # always runs
})
# Signal conditions
stop("Fatal error") # stops with an error
warning("Watch out") # warning
message("Information") # message
# Validate inputs
stopifnot(is.numeric(x), x > 0)tryCatch() catches errors, warnings and runs final code with finally. stop() throws an error, warning() a warning and stopifnot() validates input conditions compactly.
Functions
Defining Functions
# Basic function
add <- function(a, b) {
a + b
}
# With default values
greet <- function(name, greeting = "Hello") {
paste(greeting, name)
}
greet("Anna") # "Hello Anna"
greet("Anna", "Good morning") # "Good morning Anna"
# Arguments by name
greet(greeting = "Hi", name = "Bob")
# Implicit return (last expression)
twice <- function(x) x * 2A function is created with function(). Default values are set with = in the arguments. R automatically returns the last evaluated expression, without needing return().
Functions the Objects
# Assign to a variable
f <- sum
f(1, 2, 3) # 6
# Pass the an argument
apply_fn <- function(data, fn) {
fn(data)
}
apply_fn(1:10, mean) # 5.5
apply_fn(1:10, sum) # 55
# Return a function
power <- function(n) {
function(x) x ^ n
}
square <- power(2)
square(5) # 25
# Composition
compose <- function(f, g) function(x) f(g(x))
sqrt_log <- compose(sqrt, log)
# Negate: invert a logical function
not_na <- Negate(is.na)In R, functions are first-class objects: you can assign them to variables, pass them the arguments and return them. Negate() creates a function that inverts the logical result of another.
Anonymous Functions
# Classic form
sapply(1:5, function(x) x ^ 2)
# Shorthand (R 4.1+)
sapply(1:5, \(x) x ^ 2)
# Multiple arguments
mapply(\(x, y) x + y, 1:3, 4:6)
# Multi-line body
sapply(1:5, \(x) {
y <- x ^ 2
y + 1
})
# Pass to higher-order functions
Filter(\(x) x > 3, 1:10)
Map(\(a, b) a * b, 1:3, 4:6)An anonymous function has no name and is used directly the an argument. Since R 4.1 you can write \(x) instead of function(x). They are ideal for the apply families and Map.
S3 Methods
# Create an object with a class
create_person <- function(name, age) {
structure(
list(name = name, age = age),
class = "Person"
)
}
# Method for the print generic
print.Person <- function(x, ...) {
cat(x$name, "is", x$age, "years old\n")
}
# New generic with UseMethod
greet <- function(x) UseMethod("greet")
greet.Person <- function(x) paste("Hello,", x$name)
greet.default <- function(x) "Hello!"
# Use
p <- create_person("Anna", 30)
class(p) # "Person"
inherits(p, "Person") # TRUEThe S3 system is the most common in R. An object is a list with a class attribute. Methods like print.Person are dispatched by UseMethod() according to the class of the object.
Lexical Scope and Closures
x <- 10
my_func <- function() {
x <- 20 # local variable
x
}
my_func() # 20
x # 10 (global intact)
# Closure: function that keeps its environment
make_mult <- function(multiplier) {
function(x) x * multiplier
}
triple <- make_mult(3)
triple(5) # 15
# Superassignment (avoid)
counter <- 0
increment <- function() {
counter <<- counter + 1
}R uses lexical scope: a function looks for variables first in its own environment and then in the environment where it was created. A closure keeps that environment, allowing persistent state. <<- modifies variables outside the function (use with caution).
Recursion
# Factorial
fact <- function(n) {
if (n <= 1) return(1)
n * fact(n - 1)
}
# Fibonacci
fib <- function(n) {
if (n <= 1) return(n)
fib(n - 1) + fib(n - 2)
}
# Recall: safe self-reference
f <- function(n) {
if (n == 0) return(1)
n * Recall(n - 1)
}
# With memoization (store results)
memo <- new.env()
fib_memo <- function(n) {
if (n <= 1) return(n)
key <- as.character(n)
if (is.null(memo[[key]]))
memo[[key]] <- fib_memo(n-1) + fib_memo(n-2)
memo[[key]]
}A recursive function calls itself until it reaches a base case. Recall() refers to the function itself safely. Memoization stores results in an environment to avoid recomputation.
Advanced Arguments
# ... (dots): variable arguments
my_plot <- function(x, ...) {
plot(x, ...)
}
my_plot(1:10, col = "red", pch = 19)
# missing(): was the argument given?
f <- function(a, b) {
if (missing(b)) b <- a
a + b
}
# match.arg: validate options
f <- function(color = c("red", "blue", "green")) {
color <- match.arg(color)
color
}
# on.exit: guaranteed cleanup
read_file <- function(path) {
con <- file(path)
on.exit(close(con))
readLines(con)
}
# do.call: call with a list of args
do.call(paste, list("a", "b", "c"))The ... (dots) accept variable arguments and pass them on to other functions. missing() checks whether an argument was given, match.arg() validates options and on.exit() guarantees cleanup even on error.
Debugging
# browser(): interactive pause
my_function <- function(x) {
browser() # debugging REPL
x * 2
}
# debug(): debug the whole function
debug(my_function)
my_function(5)
undebug(my_function)
# traceback(): call stack
traceback()
# options(error): recover from errors
options(error = recover)
# Validate and measure time
stopifnot(is.numeric(x), x > 0)
system.time(result <- computation())browser() pauses execution to inspect variables; debug() enables debugging of a function. traceback() shows the call stack after an error and system.time() measures the duration.
Data Structures
Data Frames
# Create
df <- data.frame(
name = c("Anna", "Ray", "Eve"),
age = c(30, 25, 35),
grade = c(14, 16, 12)
)
# Access
df$name # column by name
df[["name"]] # same
df[1, 2] # row 1, column 2
df[1:2, ] # rows 1 and 2
df[, "age"] # age column
# Info
nrow(df); ncol(df)
dim(df) # 3 3
str(df) # structure
head(df) # first rows
summary(df) # statistical summaryA data.frame is a table with columns of different types, the central structure for data in R. Columns are accessed with $ or [[]]. str() shows the structure and summary() a summary.
Modifying Data Frames
df <- data.frame(x = 1:3, y = c("a", "b", "c"))
# Add column
df$z <- df$x * 2
df <- transform(df, total = x + z)
# Remove column
df$z <- NULL
# Rename
names(df)[1] <- "value"
# Filter rows
df[df$x > 1, ]
subset(df, x > 1 & y != "a")
# Sort
df[order(df$x), ] # ascending
df[order(-df$x), ] # descending
# Join tables
merge(df1, df2, by = "id")You can create columns with $ or transform() and remove them by assigning NULL. order() returns the sorting order and merge() joins two data.frame by a common column.
Lists
# Heterogeneous list lst <- list( name = "Anna", age = 30, grades = c(14, 16, 12), active = TRUE ) # Access lst$name # "Anna" lst[[1]] # 1st element lst[1] # sub-list lst[["name"]] # "Anna" # Modify lst$email <- "a@b.c" # add lst$age <- NULL # remove # Flatten unlist(lst) # simple vector # length(lst) # number of elements
A list stores elements of different types, including other lists. [[]] extracts the element itself, while [] returns a sub-list. unlist() flattens the list into a vector.
Tibbles
library(tibble)
# Create a tibble
tb <- tibble(
name = c("Anna", "Ray"),
age = c(30, 25)
)
# By rows
tribble(
~name, ~age,
"Anna", 30,
"Ray", 25
)
# List-columns
tb2 <- tibble(
id = 1:3,
data = list(c(1, 2), c(3, 4, 5), c(6))
)
# Convert
as_tibble(df)
as.data.frame(tb)A tibble is a modern data.frame from the tidyverse: it does not convert text to factors, preserves column names and prints more clearly. tribble() creates it by rows and it accepts list-columns.
Matrices
# Create (filled by column) m <- matrix(1:12, nrow = 3, ncol = 4) # By row m <- matrix(1:12, nrow = 3, byrow = TRUE) # Access m[1, 2] # row 1, column 2 m[1, ] # row 1 m[, 2] # column 2 m[1:2, 1:2] # sub-matrix # Linear algebra t(m) # transpose m %*% n # matrix product solve(m) # inverse det(m) # determinant eigen(m) # eigenvalues and eigenvectors
A matrix is a two-dimensional vector with elements of the same type. By default it is filled by columns (byrow = TRUE changes that). %*% does matrix multiplication and solve() computes the inverse.
Dates and Times
library(lubridate)
# Create dates (component order)
ymd("2024-03-15")
dmy("15/03/2024")
ymd_hms("2024-03-15 14:30:00")
# Extract components
year(d); month(d); day(d)
wday(d, label = TRUE) # day of the week
# Arithmetic
d + days(7)
d - months(1)
interval(d1, d2) / days(1) # no. of days
# Base R
Sys.Date()
as.Date("2024-01-01")
format(Sys.Date(), "%d/%m/%Y")
difftime(d2, d1, units = "days")The lubridate package simplifies dates: ymd() and dmy() interpret the component order. Add periods with days() and months(). In base R, use as.Date() and difftime().
Arrays and Combining
# Array with n dimensions
arr <- array(1:24, dim = c(2, 3, 4))
arr[1, 2, 3]
# Combine by rows
rbind(c(1, 2), c(3, 4))
# Combine by columns
cbind(c(1, 2), c(3, 4))
# Join data frames
rbind(df1, df2) # stack rows
cbind(df1, df2) # side by side
# Name the dimensions
dimnames(m) <- list(
c("r1", "r2", "r3"),
c("c1", "c2", "c3", "c4")
)An array generalizes the matrix to more dimensions. rbind() joins by rows and cbind() by columns, both for vectors and for data.frame. dimnames() names the dimensions.
Inspection and Conversion
x <- c(1, 2, 3)
# Inspect
class(x) # "numeric"
typeof(x) # "double"
str(x) # compact structure
length(x) # 3
attributes(x) # attributes
# Convert between types
as.integer(c(1.5, 2.9)) # 1 2
as.character(1:3) # "1" "2" "3"
as.numeric(c("1", "2")) # 1 2
as.logical(c(0, 1, 2)) # FALSE TRUE TRUE
# Restructure
unlist(list(1, 2, 3))
as.vector(matrix(1:6))class() and typeof() reveal the type; str() shows the structure compactly. The the.*() functions convert between types — note that as.integer() truncates decimals.
Data Manipulation
dplyr — Basic Verbs
library(dplyr) # filter: select rows df |> filter(age > 25, active == TRUE) # select: choose columns df |> select(name, age) df |> select(-grade) # remove # mutate: create/modify columns df |> mutate( doubled = age * 2, senior = ifelse(age > 30, "S", "J") ) # arrange: sort df |> arrange(desc(age), name) # slice: by position df |> slice(1:5) df |> slice_max(grade, n = 3) # rename df |> rename(id = code)
dplyr manipulates data with clear verbs linked by the pipe |>. filter() selects rows, select() columns, mutate() creates columns, arrange() sorts and rename() renames.
across — Multiple Columns
library(dplyr)
# Apply to several columns
df |> mutate(across(c(x, y, z), ~ .x * 2))
# By type
df |> mutate(across(where(is.numeric), round, 2))
# Several functions at once
df |> summarise(
across(
where(is.numeric),
list(avg = mean, sd = sd),
na.rm = TRUE
)
)
# Conditions on columns
df |> filter(if_any(ends_with("_p"), ~ .x > 0.5))
df |> filter(if_all(starts_with("q"), ~ .x > 0))
# Bulk rename
df |> rename_with(toupper)across() applies the same function to several columns at once, combined with where() or selectors. if_any() and if_all() filter rows based on conditions across several columns.
stringr — Text
library(stringr)
# Detect and count
str_detect(s, "pattern")
str_count(s, "a")
# Extract and replace
str_extract(s, "\d+")
str_replace(s, "old", "new")
str_replace_all(s, "\d", "#")
# Manipulate
str_pad("42", 5, pad = "0") # "00042"
str_trim(" text ")
str_to_title("hello world")
# Split and join
str_split(s, ",")
str_c("a", "b", "c", sep = "-")
str_flatten(c("a", "b"), collapse = ", ")The stringr package offers text functions with consistent names (all start with str_). str_detect() searches for patterns, str_replace() replaces and str_pad() pads.
dplyr — Group and Summarise
library(dplyr)
df |>
group_by(department) |>
summarise(
avg = mean(salary),
total = n(),
max_sal = max(salary),
.groups = "drop"
)
# Multiple groups
df |>
group_by(year, month) |>
summarise(sales = sum(value))
# mutate by group
df |>
group_by(team) |>
mutate(rank = rank(-points))
# Quick count
df |> count(category, sort = TRUE)group_by() splits the data into groups and summarise() computes summaries per group, such the mean() and n() (count). count() is a shortcut to count by category.
tidyr — Reshape
library(tidyr)
# Wide -> long
df |> pivot_longer(
cols = c(year1, year2, year3),
names_to = "year",
values_to = "value"
)
# Long -> wide
df |> pivot_wider(
names_from = category,
values_from = value
)
# Separate / unite columns
df |> separate(name, c("first", "last"), " ")
df |> unite("full", first, last, sep = " ")tidyr changes the shape of the data. pivot_longer() turns columns into rows (long format) and pivot_wider() does the opposite. separate() splits a column and unite() joins several.
dplyr — Joins
library(dplyr)
# Keep only matches
inner_join(orders, customers, by = "id")
# All from the left
left_join(orders, customers, by = "id")
# All from the right
right_join(orders, customers, by = "id")
# All from both
full_join(df1, df2, by = "id")
# Filter: only those that exist
semi_join(orders, customers, by = "id")
# Exclusion: those that do not exist
anti_join(orders, customers, by = "id")
# Keys with different names
left_join(a, b, by = c("id" = "code"))The join functions combine tables by a key. left_join() keeps all rows from the left; inner_join() only the matches. semi_join() and anti_join() filter without adding columns.
tidyr — NAs and Nesting
library(tidyr) # Fill NAs downward df |> fill(column, .direction = "down") # Remove rows with NA df |> drop_na() df |> drop_na(age, name) # only these cols # Replace NAs df |> replace_na(list(age = 0)) # Nest data by group df |> nest(data = -group) # Unnest df |> unnest(data)
fill() fills missing values with the last known value. drop_na() removes rows with NA and replace_na() replaces them. nest() stores sub-tables in a list-column.
Column Selection (tidyselect)
library(dplyr)
# By name pattern
df |> select(starts_with("pre"))
df |> select(ends_with("_id"))
df |> select(contains("date"))
df |> select(matches("^col\d+$"))
# By type
df |> select(where(is.numeric))
df |> select(where(is.character))
# Variable with names
cols <- c("a", "b", "c")
df |> select(all_of(cols)) # error if missing
df |> select(any_of(cols)) # tolerant
# Reorder
df |> select(id, everything())tidyselect chooses columns by patterns: starts_with(), ends_with(), contains() and matches() (regex). where() selects by type and everything() catches the rest.
Missing Data (base R)
v <- c(1, NA, 3, NA, 5) # Detect is.na(v) # logical vector sum(is.na(v)) # total NAs colSums(is.na(df)) # per column # Remove na.omit(df) # rows with NA df[complete.cases(df), ] # Replace v[is.na(v)] <- 0 ifelse(is.na(v), 0, v) # Ignore in calculations mean(v, na.rm = TRUE) sum(v, na.rm = TRUE)
is.na() detects missing values. na.omit() and complete.cases() remove them; you can also replace them by index. The na.rm = TRUE option ignores NA in statistical functions.
Advanced
Reading and Writing Data
# CSV (base R)
df <- read.csv("data.csv")
write.csv(df, "out.csv", row.names = FALSE)
# readr (faster, tibble)
library(readr)
df <- read_csv("data.csv")
write_csv(df, "out.csv")
# Excel
library(readxl)
df <- read_excel("data.xlsx", sheet = 1)
# RDS (a single R object)
saveRDS(obj, "data.rds")
obj <- readRDS("data.rds")
# Text lines
lines <- readLines("file.txt")
writeLines(lines, "out.txt")read.csv() reads CSV files in base R; read_csv() from readr is faster and returns a tibble. saveRDS() and readRDS() store R objects in native format.
Parallel Programming
library(parallel)
# No. of available colors
detectCores()
# Create cluster
cl <- makeCluster(4)
# Parallel lapply
results <- parLapply(cl, items, fn)
# Export variables/functions
clusterExport(cl, c("items", "fn"))
# Stop (important!)
stopCluster(cl)
# Simple alternative (Unix only)
mclapply(items, fn, mc.colors = 4)The parallel package distributes work across several colors. makeCluster() creates the workers, parLapply() applies in parallel and stopCluster() frees the resources. On Unix, mclapply() is simpler.
Packages
# Install from CRAN
install.packages("dplyr")
install.packages(c("ggplot2", "tidyr"))
# Load
library(dplyr)
require(ggplot2) # returns TRUE/FALSE
# Use without loading
dplyr::filter(df, x > 1)
# Info
installed.packages()
packageVersion("dplyr")
sessionInfo()
# Update
update.packages()install.packages() installs from CRAN and library() loads the package into the session. The :: operator uses a function without loading the package, avoiding name conflicts. sessionInfo() lists the active packages.
R6 Classes
library(R6)
# Define class
Person <- R6Class("Person",
public = list(
name = NULL,
age = NULL,
initialize = function(name, age) {
self$name <- name
self$age <- age
},
greet = function() {
paste("Hello,", self$name)
}
),
private = list(
secret = "private"
)
)
# Use
p <- Person$new("Anna", 30)
p$greet()
p$name <- "Ray"R6 brings reference-based object-oriented programming (unlike S3). public and private define members, initialize() is the constructor and self refers to the object. Instances are created with $new().
data.table
library(data.table)
# Read very fast
dt <- fread("big.csv")
# Syntax: dt[i, j, by]
# Filter (i), compute (j), group (by)
dt[age > 25, .(avg = mean(salary)), by = dep]
# Add/modify columns by reference
dt[, new_col := salary * 2]
# Several operations
dt[, .(total = .N, avg = mean(value)), by = group]
# Sort
setorder(dt, -age)
# Convert
setDT(df) # data.frame -> data.table
as.data.table(df)data.table is ideal for large data. The dt[i, j, by] syntax filters, computes and groups in a single expression. := modifies columns by reference (without copying) and fread() reads files very fast.
Environments
# Create environment
env <- new.env()
# Assign and access
env$x <- 10
env[["y"]] <- 20
env$x # 10
# List and check
ls(env)
exists("x", envir = env)
# Remove
rm("x", envir = env)
# Global and function environments
globalenv()
environment()
# Used in closures and memoization
cache <- new.env()An environment maps names to values, like a list, but unordered and by reference. It is the basis of scoping in R and of the object system. It is widely used for caches and mutable state, the in memoization.
Performance
# Vectorize (avoid loops) # Bad: r <- numeric(length(x)) for (i in seq_along(x)) r[i] <- x[i] * 2 # Good: r <- x * 2 # Pre-allocate results result <- numeric(n) # apply instead of for results <- lapply(items, process) # Measure time system.time(computation()) # Compare methods microbenchmark::microbenchmark( method1(), method2(), times = 100 ) # Profiling Rprof(); my_code(); Rprof(NULL) summaryRprof()
The golden rule is to vectorize: operations on whole vectors are much faster than loops. Pre-allocate results with numeric(), use lapply() and measure with system.time() or microbenchmark().
Projects and Reproducibility
# renv: pin package versions
renv::init() # create renv.lock
renv::snapshot() # save current state
renv::restore() # restore from lockfile
# devtools: create packages
devtools::create("myPackage")
devtools::document() # generate documentation
devtools::install()
devtools::check()
# usethis: set up projects
usethis::create_project("analysis")
usethis::use_git()
usethis::use_testthat()
# Best practices
# - One project per folder (.Rproj)
# - Relative paths
# - renv.lock under version controlrenv makes analyses reproducible by pinning package versions in a renv.lock. devtools and usethis help create packages and projects. Always use relative paths and one project per folder.
Preview
ggplot2 — Basics
library(ggplot2)
# Structure: data + aesthetics + geometry
ggplot(df, aes(x = age, y = salary)) +
geom_point()
# Add a trend line
ggplot(df, aes(x = age, y = salary)) +
geom_point() +
geom_smooth(method = "lm")
# Title and axes
ggplot(df, aes(x = age, y = salary)) +
geom_point() +
labs(
title = "Salary by age",
x = "Age",
y = "Salary (€)"
)ggplot2 builds charts in layers: ggplot() defines the data, aes() the aesthetics (axes, colors) and the geom_*() the chart type. Layers are added with +.
Facets
library(ggplot2) # One panel per level of a variable ggplot(df, aes(x = age, y = salary)) + geom_point() + facet_wrap(~group) # Grid with two variables ggplot(df, aes(x = age, y = salary)) + geom_point() + facet_grid(sex ~ bracket) # Free scales per panel ggplot(df, aes(x = year, y = sales)) + geom_line() + facet_wrap(~product, scales = "free_y")
facets split the chart into several panels. facet_wrap() creates panels by one variable and facet_grid() a grid with two. scales = "free_y" lets each panel have its own scale.
Essential Geoms
library(ggplot2) # Histogram ggplot(df, aes(x = age)) + geom_histogram(bins = 20, fill = "steelblue") # Boxplot ggplot(df, aes(x = group, y = value)) + geom_boxplot() # Bars (count) ggplot(df, aes(x = category, fill = type)) + geom_bar(position = "dodge") # Lines ggplot(df, aes(x = year, y = sales)) + geom_line() # Density ggplot(df, aes(x = value, fill = group)) + geom_density(alpha = 0.4)
Each geom draws a chart type: geom_histogram() for distributions, geom_boxplot() for summaries, geom_bar() for counts and geom_line() for series. alpha controls transparency.
Multiple Layers
library(ggplot2)
ggplot(df, aes(x = year, y = value)) +
# Line per series (grouped)
geom_line(aes(group = id, color = id), alpha = 0.4) +
# Points on top
geom_point(aes(color = id)) +
# Vertical reference line
geom_vline(xintercept = 2020, linetype = "dashed") +
# Annotated text
annotate("text", x = 2021, y = 100, label = "Event") +
theme_minimal()You can overlay several layers: geom_line() with geom_point() on top, reference lines with geom_vline() and annotations with annotate(). The layer order defines what stays on top.
Colors and Scales
library(ggplot2)
p <- ggplot(df, aes(x, y, color = group)) +
geom_point()
# Manual colors
p + scale_color_manual(values = c("red", "blue"))
# ColorBrewer palettes
p + scale_color_brewer(palette = "Set2")
# Axes
p + scale_x_continuous(limits = c(0, 100))
p + scale_y_log10()
# Fill
ggplot(df, aes(x, fill = type)) +
geom_bar() +
scale_fill_brewer(palette = "Pastel1")The scale_*() functions control colors and axes. scale_color_manual() sets fixed colors and scale_color_brewer() uses ColorBrewer palettes. Use color for lines/points and fill for fills.
Base R Charts
# Scatter plot(df$age, df$salary, main = "Title", xlab = "Age", ylab = "Salary") # Histogram hist(df$age, breaks = 20, col = "lightblue") # Boxplot boxplot(value ~ group, data = df) # Bars barplot(table(df$category), col = "gray") # Lines plot(1:10, type = "l", col = "red") lines(1:10, 10:1, col = "blue") # Graphical parameters par(mfrow = c(1, 2)) # 2 charts side by side
Base R includes charts without extra packages: plot() for scatter plots, hist() for histograms, boxplot() and barplot(). par(mfrow) lays out several charts in the same window.
Themes and Labels
library(ggplot2) p <- ggplot(df, aes(x, y)) + geom_point() # Ready-made themes p + theme_minimal() p + theme_classic() p + theme_dark() # Customize elements p + theme( plot.title = element_text(size = 16, face = "bold"), legend.position = "bottom", panel.grid = element_blank() ) # Full labels p + labs( title = "Title", subtitle = "Subtitle", caption = "Source: data", color = "Group" )
Ready-made themes like theme_minimal() change the overall look. theme() adjusts individual elements with element_text() and element_blank(). labs() sets titles and legends.
Saving Charts
library(ggplot2)
p <- ggplot(df, aes(x, y)) + geom_point()
# ggsave (recommended)
ggsave("chart.png", p, width = 10, height = 6, dpi = 300)
# Format by suffix
ggsave("chart.pdf", p)
ggsave("chart.svg", p)
# Base R devices
png("chart.png", width = 800, height = 600)
plot(1:10)
dev.off() # close and save
pdf("chart.pdf")
hist(df$age)
dev.off()ggsave() saves the last chart; the format is detected by the suffix (.png, .pdf, .svg). In base R, open a device like png(), draw and close with dev.off().
Statistics and Models
Descriptive Statistics
v <- c(12, 15, 18, 22, 25, 30) # Measures of central tendency mean(v) # mean median(v) # median # Spread sd(v) # standard deviation var(v) # variance IQR(v) # interquartile range range(v) # minimum and maximum # Quantiles quantile(v) quantile(v, probs = c(0.1, 0.9)) # Full summary summary(v) summary(df) # of all columns
R computes statistics directly: mean() and median() for the center, sd() and var() for the spread. quantile() gives the quartiles and summary() a quick five-number summary.
Linear Regression
# Fit the model model <- lm(salary ~ age + experience, data = df) # Summary (coefficients, R², p-values) summary(model) # Coefficients coef(model) confint(model) # confidence intervals # Predict new values predict(model, newdata = new_data) # Formulas lm(y ~ x1 * x2, data = df) # with interaction lm(y ~ x1 + I(x1^2), data = df) # quadratic term lm(y ~ ., data = df) # all columns
lm() fits a linear regression with the formula y ~ x. summary() shows the coefficients, the R² and the p-values. * adds interactions and I() allows terms like x^2.
Correlation
# Pearson coefficient
color(x, y)
# Other methods
color(x, y, method = "spearman")
color(x, y, method = "kendall")
# With hypothesis test
color.test(x, y) # value + p-value
# Correlation matrix
color(df[, c("age", "salary", "grade")])
# Ignore NAs
color(x, y, use = "complete.obs")color() computes the correlation between two variables (Pearson by default). color.test() adds the p-value and the confidence interval. With several columns, it returns a correlation matrix.
GLM and Logistic Regression
# Logistic regression (binary outcome) model <- glm(passed ~ grade + hours, data = df, family = binomial) summary(model) # Predicted probabilities predict(model, newdata = new_data, type = "response") # Poisson regression (counts) glm(count ~ x, data = df, family = poisson) # Other families glm(y ~ x, family = Gamma) glm(y ~ x, family = gaussian) # Odds ratios exp(coef(model))
glm() generalizes linear regression through the family argument. binomial does logistic regression and poisson models counts. type = "response" returns probabilities instead of log-odds.
Distributions
# Four prefixes: d, p, q, r # r: generate random values rnorm(100, mean = 0, sd = 1) # normal runif(100, min = 0, max = 1) # uniform rbinom(100, size = 10, prob = 0.5) # d: density / probability dnorm(0) # density at 0 dbinom(5, 10, 0.5) # p: cumulative probability pnorm(1.96) # about 0.975 # q: quantile (inverse of p) qnorm(0.975) # about 1.96 qt(0.975, df = 10)
Each distribution has four functions: r* generates random values, d* gives the density, p* the cumulative probability and q* the quantile. Examples: rnorm(), pnorm(), qnorm().
Diagnostics and Comparison
model <- lm(y ~ x1 + x2, data = df) # Diagnostic plots (4) plot(model) # Confidence intervals confint(model) # ANOVA table anova(model) # Compare models m1 <- lm(y ~ x1, data = df) m2 <- lm(y ~ x1 + x2, data = df) AIC(m1, m2) # lower is better anova(m1, m2) # F test # Residuals resid(model) fitted(model)
plot(model) generates four diagnostic plots of the residuals. AIC() compares models (lower is better) and anova() tests whether adding variables improves the fit. resid() and fitted() extract residuals and fitted values.
Hypothesis Tests
# t-test (one sample) t.test(v, mu = 0) # t-test (two samples) t.test(group1, group2) t.test(value ~ group, data = df) # Chi-squared chisq.test(tbl) # Normality shapiro.test(v) # Mann-Whitney (non-parametric) wilcox.test(group1, group2) # ANOVA model <- aov(value ~ group, data = df) summary(model) TukeyHSD(model) # multiple comparisons
t.test() compares means, chisq.test() tests associations in tables and shapiro.test() checks normality. aov() performs analysis of variance and TukeyHSD() compares groups with each other.
broom — Tidy Models
library(broom) model <- lm(salary ~ age + experience, data = df) # Coefficients the a data frame tidy(model) # Model summary (R², AIC...) glance(model) # Predictions with residuals augment(model) # Works with many models tidy(glm(passed ~ grade, data = df, family = binomial)) tidy(t.test(group1, group2)) glance(aov(value ~ group, data = df))
The broom package converts model results into clean data.frames. tidy() gives the coefficients, glance() a one-row summary and augment() the data with predictions and residuals.