This collection shows the most important commands of the R programming language, each with a short example. Lines starting with #> show the output of R. All examples run on R 4.1 or later.

Basics

Comments

R ignores everything after a # up to the end of the line.

# I am a comment
x <- 5  # A comment can also follow a command

Printing to the console

print() shows an object the way R displays it, with the position number [1] in front. cat() prints the plain text. sprintf() inserts values into a text template.

print("Hello World")
#> [1] "Hello World"
cat("Hello", "World", "\n")
#> Hello World
cat(sprintf("Pi is about %.2f\n", pi))
#> Pi is about 3.14
message("Done")   # print a note, RStudio shows it in red
#> Done

Help on a command

?which              # help page for the command which
help(which)         # the same
??"regression"      # full text search in all installed help pages
apropos("which")    # all commands whose name contains "which"
example(which)      # run the examples from the help page
args(paste)         # show the parameters of a command

Command history and quitting R

history()           # show recently entered commands
q()                 # quit R, in RStudio this ends the session

Version and session

R.version.string    # installed R version
sessionInfo()       # version, operating system and loaded packages

Working directory and files

Show and change the working directory

R reads and writes files without a path in the working directory. On Windows, too, folders are separated with / or with a double \\. If you work with projects in RStudio, you rarely need setwd(), because RStudio sets the working directory to the project folder.

getwd()                          # current working directory
setwd("C:/Users/aaron/Projects") # set a new working directory

Files and folders

list.files()                         # files in the working directory
list.files("data", pattern = "\\.csv$")  # only CSV files in the folder data
file.exists("data/customers.csv")    # TRUE if the file exists
dir.create("results")                # create a new folder
file.path("data", "customers.csv")   # build a path: "data/customers.csv"

Save and load objects

saveRDS() saves a single object. When loading it with readRDS(), you choose the name yourself. save() saves several objects together with their names, load() restores them under the same names.

saveRDS(myTable, file = "myTable.rds")
myTable <- readRDS("myTable.rds")

save(myTable, myList, file = "objects.RData")
load("objects.RData")

savehistory("commands.Rhistory")     # write the command history to a file

Read and write CSV files

read.csv() and write.csv() are meant for files with a comma as separator and a point as decimal mark. Many European CSV files separate columns with a semicolon and write decimals with a comma. For those, use read.csv2() and write.csv2().

customers <- read.csv("C:/data/customers.csv")
customers <- read.csv2("C:/data/customers.csv", encoding = "UTF-8")
customers <- read.table("C:/data/customers.csv", header = TRUE, sep = ";", dec = ",")

write.csv(customers, "customers_new.csv", row.names = FALSE)

# Faster for large files: package readr
library(readr)
customers <- read_csv("C:/data/customers.csv")

Packages

Install and update packages

install.packages("ggplot2")
install.packages(c("dplyr", "readr", "tidyr"))
update.packages(ask = FALSE)    # update all installed packages
remove.packages("ggplot2")      # remove a package

An overview of all packages in the official CRAN repository is available at cran.r-project.org/web/packages.

Load a package

library(ggplot2)              # error if the package is missing
require(ggplot2)              # returns FALSE if the package is missing
dplyr::filter(iris, Sepal.Length > 7)  # call a command without loading the package

Show installed and loaded packages

.libPaths()                   # folders where packages are installed
Sys.getenv("R_LIBS_USER")     # personal package folder
installed.packages()[, "Version"]  # all installed packages with version
library()                     # installed packages in a separate window
(.packages())                 # currently loaded packages
getOption("defaultPackages")  # packages R loads at startup
packageVersion("ggplot2")     # version of a package

Load packages automatically at startup

R runs the commands in the file .Rprofile in your home folder at every start. You can open the file directly from R:

file.edit("~/.Rprofile")

Content of the file, for example:

library(ggplot2)
library(dplyr)

A script that relies on automatically loaded packages does not run on another computer. That is why library() still belongs at the top of every script.

The file Rprofile.site in the etc folder of the R installation applies to all users and needs administrator rights to edit. Every new R version comes with its own Rprofile.site, so the changes have to be entered again after each update. The personal .Rprofile is therefore the better choice.

Variables

Assign a value

a <- 1          # usual notation
b = 2           # works as well
3 -> c          # assignment to the right, rarely used
assign("d", 4)  # pass the name as text
`<-`(e, 5)      # assignment as a function call
c(a, b, c, d, e)
#> [1] 1 2 3 4 5

Show and remove variables

var1 <- 10; var2 <- 20; name <- "R"; .hidden <- 30
ls()                        # all variables
#> [1] "name" "var1" "var2"
ls(pattern = "var")         # variables whose name contains "var"
#> [1] "var1" "var2"
ls(all.names = TRUE)        # also variables starting with a dot
#> [1] ".hidden" "name"    "var1"    "var2"
rm(var1)                    # remove one variable
exists("var1")
#> [1] FALSE
rm(list = ls())             # remove all variables

Compare values

Computers calculate decimal numbers with rounding errors. That is why you compare them with all.equal() instead of ==.

a <- 1; b <- 2
a == b
#> [1] FALSE
0.1 + 0.2 == 0.3
#> [1] FALSE
isTRUE(all.equal(0.1 + 0.2, 0.3))
#> [1] TRUE
identical(c(1, 2), c(1, 2))
#> [1] TRUE

Data types

Basic types

class(TRUE)          # logical: TRUE or FALSE
#> [1] "logical"
class(42L)           # integer: whole number, marked by the L
#> [1] "integer"
class(42)            # numeric: number with decimals
#> [1] "numeric"
class(1 + 2i)        # complex: complex number
#> [1] "complex"
class("Hello")       # character: text
#> [1] "character"
class(as.raw(255))   # raw: single bytes, shown in hexadecimal
#> [1] "raw"
as.raw(255)
#> [1] ff
typeof(42)           # internal storage type
#> [1] "double"

Data structures

StructureContentCreate with
Vectorvalues of the same type in a rowc()
Listvalues of any type, including other listslist()
Matrixvalues of the same type in rows and columnsmatrix()
Arrayvalues of the same type in any number of dimensionsarray()
Factorcategories with fixed valuesfactor()
Data frametable, each column has its own typedata.frame()

Check and convert types

is.numeric(42)
#> [1] TRUE
is.character("42")
#> [1] TRUE
as.numeric("42") + 1
#> [1] 43
as.integer(3.9)       # cuts off the decimals
#> [1] 3
as.character(42)
#> [1] "42"
as.logical(c("TRUE", "no", "T"))
#> [1] TRUE   NA TRUE
as.numeric("abc")     # cannot be converted: NA with a warning
#> Warning: NAs introduced by coercion
#> [1] NA

Missing values

NA stands for a missing value, NULL for an empty object, NaN for an invalid calculation result and Inf for infinity.

x <- c(4, NA, 7)
is.na(x)
#> [1] FALSE  TRUE FALSE
sum(x)                 # NA as soon as one value is missing
#> [1] NA
sum(x, na.rm = TRUE)   # skip missing values
#> [1] 11
0 / 0
#> [1] NaN
1 / 0
#> [1] Inf
length(NULL)
#> [1] 0

Operators

Arithmetic operators

OperatorMeaningExampleResult
+ - * /basic arithmetic7 / 23.5
^power2 ^ 38
%%remainder of a division (modulo)7 %% 21
%/%integer division7 %/% 23

Arithmetic operators work on every element of a vector:

a <- c(4, 8, 12)
b <- c(12, 8, 4)
a %% b      # remainder of a / b
#> [1] 4 0 0
a %/% b     # how often b fits completely into a
#> [1] 0 1 3
c(2, 3, 4) ^ c(3, 2, 2)   # 2*2*2, 3*3, 4*4
#> [1]  8  9 16

Comparison operators

== equal, != not equal, < less, > greater, <= less or equal, >= greater or equal.

a <- c(4, 8, 12)
b <- c(12, 8, 4)
a > b
#> [1] FALSE FALSE  TRUE
a == b
#> [1] FALSE  TRUE FALSE

Logical operators

The operators & (and), | (or) and ! (not) check vectors element by element. && and || check exactly one value and belong in conditions such as if. The number 0 counts as FALSE, every other number as TRUE.

a <- c(3, 0, TRUE, FALSE, 0)
b <- c(4, 0, FALSE, FALSE, 0.1)
a & b
#> [1]  TRUE FALSE FALSE FALSE FALSE
a | b
#> [1]  TRUE FALSE  TRUE FALSE  TRUE
!c(TRUE, FALSE)
#> [1] FALSE  TRUE
xor(TRUE, FALSE)    # exactly one of the two is TRUE
#> [1] TRUE
any(a > 2)          # at least one value meets the condition
#> [1] TRUE
all(a > 2)          # all values meet the condition
#> [1] FALSE

Assignment operators

OperatorMeaning
<- and =assign a value to the variable on the left
->assign a value to the variable on the right
<<- and ->>assign a value to a variable outside the current function
counter <- 0
increase <- function() counter <<- counter + 1
increase(); increase()
counter
#> [1] 2

Other operators

1:5                   # sequence of numbers
#> [1] 1 2 3 4 5
3 %in% c(1, 2, 3)     # is the value contained in the vector?
#> [1] TRUE
matrix(1:4, 2) %*% matrix(1:4, 2)   # matrix multiplication
#>      [,1] [,2]
#> [1,]    7   15
#> [2,]   10   22
c(1, 4, 9) |> sqrt() |> sum()       # pipe: pass the result to the next command
#> [1] 6

Vectors

Create a vector

c(1, 2, c(3, 4), 5)          # nested vectors become flat
#> [1] 1 2 3 4 5
1:10
#>  [1]  1  2  3  4  5  6  7  8  9 10
seq(0, 100, by = 25)
#> [1]   0  25  50  75 100
seq(1, 2, length.out = 5)
#> [1] 1.00 1.25 1.50 1.75 2.00
rep(0, 5)
#> [1] 0 0 0 0 0
rep(c("a", "b"), times = 2)
#> [1] "a" "b" "a" "b"
rep(c("a", "b"), each = 2)
#> [1] "a" "a" "b" "b"
c("hello", 0, TRUE)          # one text turns all values into text
#> [1] "hello" "0"     "TRUE"

Access elements

Counting starts at 1.

days <- c("mo", "tu", "we", "th", "fr", "sa", "su")
days[3]                 # third element
#> [1] "we"
days[1:3]               # first to third element
#> [1] "mo" "tu" "we"
days[c(1, 7)]           # first and seventh element
#> [1] "mo" "su"
days[-c(2, 3)]          # everything except the second and third element
#> [1] "mo" "th" "fr" "sa" "su"
days[c(TRUE, FALSE)]    # every second element
#> [1] "mo" "we" "fr" "su"
days[length(days)]      # last element
#> [1] "su"
head(days, 2)
#> [1] "mo" "tu"
tail(days, 2)
#> [1] "sa" "su"

Name and select elements

prices <- c(bread = 3.5, milk = 1.2, cheese = 4.9)
prices["milk"]
#> milk 
#>  1.2
prices[prices > 2]      # only values that meet the condition
#>  bread cheese 
#>    3.5    4.9
which(prices > 2)       # positions of these values
#>  bread cheese 
#>      1      3
names(prices)
#> [1] "bread"  "milk"   "cheese"

Calculate with vectors

If the lengths differ, R repeats the shorter vector. The example calculates 1*1, 2*2, 3*3, 1*4, 2*5, 3*6.

a <- c(1, 2, 3)
b <- c(1, 2, 3, 4, 5, 6)
a * b
#> [1]  1  4  9  4 10 18
a + 10
#> [1] 11 12 13

Sort and analyse

x <- c(4, 1, 9, 4, 10, 18)
length(x)
#> [1] 6
sort(x)
#> [1]  1  4  4  9 10 18
sort(x, decreasing = TRUE)
#> [1] 18 10  9  4  4  1
order(x)            # positions in sorted order
#> [1] 2 1 4 3 5 6
rev(x)              # reverse the order
#> [1] 18 10  4  9  1  4
unique(x)
#> [1]  4  1  9 10 18
duplicated(x)
#> [1] FALSE FALSE FALSE  TRUE FALSE FALSE
table(x)            # frequency of each value
#> x
#>  1  4  9 10 18 
#>  1  2  1  1  1
cumsum(x)           # running total
#> [1]  4  5 14 18 28 46

Combine vectors

x <- c("a", "b")
y <- c("A", "B")
c(x, y)             # into one longer vector
#> [1] "a" "b" "A" "B"
paste(x, y)         # element by element into text
#> [1] "a A" "b B"
rbind(x, y)         # as rows of a matrix
#>   [,1] [,2]
#> x "a"  "b" 
#> y "A"  "B"
cbind(x, y)         # as columns of a matrix
#>      x   y  
#> [1,] "a" "A"
#> [2,] "b" "B"

Strings

Length and parts

a <- "abcdefg"
nchar(a)
#> [1] 7
substr(a, 3, 5)
#> [1] "cde"
toupper("Hello")
#> [1] "HELLO"
tolower("Hello")
#> [1] "hello"
trimws("   spaces   ")
#> [1] "spaces"

Single and double quotes are equivalent. R always displays text with double quotes.

'hello'
#> [1] "hello"

Join text

a <- "Hello"
b <- "How"
c <- "are you?"
paste(a, b, c)
#> [1] "Hello How are you?"
paste(a, b, c, sep = "@")
#> [1] "Hello@How@are you?"
paste0(a, b, c)                       # without separator
#> [1] "HelloHoware you?"
paste(c("a", "b", "c"), collapse = "-")  # vector into one text
#> [1] "a-b-c"
sprintf("%s has %d points", "Linda", 42L)
#> [1] "Linda has 42 points"

Format text

format(18, width = 5)
#> [1] "   18"
format("Hello", width = 9, justify = "centre")   # "left", "right", "centre"
#> [1] "  Hello  "
formatC(7, width = 3, flag = "0")                # leading zeros
#> [1] "007"

Search, replace and split

fruits <- c("Apple", "Banana", "Apricot")
grepl("^A", fruits)             # does the text start with A?
#> [1]  TRUE FALSE  TRUE
startsWith(fruits, "A")         # the same without a regular expression
#> [1]  TRUE FALSE  TRUE
sub("a", "4", "Banana")         # replace the first match
#> [1] "B4nana"
gsub("a", "4", "Banana")        # replace all matches
#> [1] "B4n4n4"
strsplit("a,b,c", ",")[[1]]     # split text at a character
#> [1] "a" "b" "c"
text <- "Order 4711 from Monday"
regmatches(text, regexpr("[0-9]+", text))   # first number in the text
#> [1] "4711"

Factors

A factor stores categories. The possible values are called levels.

v <- c("thomas", "thomas", "thomas", "linda")
class(v)
#> [1] "character"
levels(v)
#> NULL

v <- factor(v)
class(v)
#> [1] "factor"
typeof(v)        # internally R stores the levels as integers
#> [1] "integer"
levels(v)
#> [1] "linda"  "thomas"
nlevels(v)
#> [1] 2
table(v)
#> v
#>  linda thomas 
#>      1      3

Order of the levels

Without further information, R sorts the levels alphabetically. With levels you set the order yourself, for example for the axes of a chart.

a <- factor(c("Thomas", "Linda", "Felix", "Sam", "Linda"))
levels(a)
#> [1] "Felix"  "Linda"  "Sam"    "Thomas"
a <- factor(a, levels = c("Linda", "Sam", "Thomas", "Felix"))
levels(a)
#> [1] "Linda"  "Sam"    "Thomas" "Felix"

Ordered factors

size <- factor(c("M", "S", "L", "M"), levels = c("S", "M", "L"), ordered = TRUE)
size
#> [1] M S L M
#> Levels: S < M < L
size > "S"
#> [1]  TRUE FALSE  TRUE  TRUE

Create and clean up factors

gl(n, k) creates n levels, each repeated k times in a row.

gl(2, 4, labels = c("Linda", "Sam"))
#> [1] Linda Linda Linda Linda Sam   Sam   Sam   Sam  
#> Levels: Linda Sam
f <- factor(c("a", "b", "c"))[1:2]
droplevels(f)        # remove levels that are no longer used
#> [1] a b
#> Levels: a b
as.character(f)      # back to text
#> [1] "a" "b"

Since R 4.0, data.frame() no longer converts text to factors automatically. If you need the old behaviour, set stringsAsFactors = TRUE.

df <- data.frame(name = c("Thomas", "Linda"))
is.factor(df$name)
#> [1] FALSE
df <- data.frame(name = c("Thomas", "Linda"), stringsAsFactors = TRUE)
is.factor(df$name)
#> [1] TRUE

Lists

A list can hold values of different types, including vectors and other lists.

myList <- list(1, "Text", c(3, 4))
myList
#> [[1]]
#> [1] 1
#> 
#> [[2]]
#> [1] "Text"
#> 
#> [[3]]
#> [1] 3 4

Name and read elements

Single square brackets [ ] return a list again. Double square brackets [[ ]] and $ return the element itself.

person <- list(name = "Linda", age = 34, hobbies = c("Dancing", "Reading"))
names(person)
#> [1] "name"    "age"     "hobbies"
person$name
#> [1] "Linda"
person[["hobbies"]]
#> [1] "Dancing" "Reading"
person["age"]
#> $age
#> [1] 34
str(person)
#> List of 3
#>  $ name   : chr "Linda"
#>  $ age    : num 34
#>  $ hobbies: chr [1:2] "Dancing" "Reading"

Change, add and remove elements

person$age <- 35            # overwrite an element
person$city <- "Cologne"    # add an element
person$hobbies <- NULL      # remove an element
names(person)
#> [1] "name" "age"  "city"

Combine and convert lists

a <- list(1, 2, 3)
b <- list(4, 5, 6)
myNewList <- c(a, b)
unlist(myNewList)           # convert a list into a vector
#> [1] 1 2 3 4 5 6

Matrices

A matrix always has two dimensions and values of a single type. Without byrow = TRUE, R fills the matrix column by column.

matrix(1:6, nrow = 2, ncol = 3)
#>      [,1] [,2] [,3]
#> [1,]    1    3    5
#> [2,]    2    4    6
matrix(1:6, nrow = 2, ncol = 3, byrow = TRUE)
#>      [,1] [,2] [,3]
#> [1,]    1    2    3
#> [2,]    4    5    6

Name rows and columns

myMatrix <- matrix(1:12, nrow = 3, ncol = 4, byrow = TRUE,
                   dimnames = list(c("row1", "row2", "row3"),
                                   c("col1", "col2", "col3", "col4")))
myMatrix
#>      col1 col2 col3 col4
#> row1    1    2    3    4
#> row2    5    6    7    8
#> row3    9   10   11   12
dim(myMatrix)
#> [1] 3 4

Access rows, columns and values

myMatrix[1, ]           # first row
#> col1 col2 col3 col4 
#>    1    2    3    4
myMatrix[, 1]           # first column
#> row1 row2 row3 
#>    1    5    9
myMatrix[1, 3]          # value in row 1, column 3
#> [1] 3
myMatrix["row2", "col4"]
#> [1] 8

Calculate with matrices

+, -, * and / calculate element by element. %*% is the matrix multiplication.

m1 <- matrix(1:6, nrow = 2, byrow = TRUE)
m2 <- matrix(1:6, nrow = 2, byrow = TRUE)
m1 + m2
#>      [,1] [,2] [,3]
#> [1,]    2    4    6
#> [2,]    8   10   12
t(m1)                   # swap rows and columns
#>      [,1] [,2]
#> [1,]    1    4
#> [2,]    2    5
#> [3,]    3    6
m1 %*% t(m2)
#>      [,1] [,2]
#> [1,]   14   32
#> [2,]   32   77
rowSums(m1)
#> [1]  6 15
colMeans(m1)
#> [1] 2.5 3.5 4.5

Append rows and columns

m <- matrix(1:4, nrow = 2)
rbind(m, c(9, 9))       # append a row
#>      [,1] [,2]
#> [1,]    1    3
#> [2,]    2    4
#> [3,]    9    9
cbind(m, c(9, 9))       # append a column
#>      [,1] [,2] [,3]
#> [1,]    1    3    9
#> [2,]    2    4    9

Arrays

An array can have any number of dimensions. The following array has 3 rows, 4 columns and 2 layers.

array("a", dim = c(3, 4, 2))
#> , , 1
#> 
#>      [,1] [,2] [,3] [,4]
#> [1,] "a"  "a"  "a"  "a" 
#> [2,] "a"  "a"  "a"  "a" 
#> [3,] "a"  "a"  "a"  "a" 
#> 
#> , , 2
#> 
#>      [,1] [,2] [,3] [,4]
#> [1,] "a"  "a"  "a"  "a" 
#> [2,] "a"  "a"  "a"  "a" 
#> [3,] "a"  "a"  "a"  "a"

Array with names

If there are not enough values for all cells, R repeats them. Here the numbers 1 to 12 fill both layers.

myArray <- array(1:12, dim = c(3, 4, 2),
                 dimnames = list(c("row1", "row2", "row3"),
                                 c("col1", "col2", "col3", "col4")))
myArray[1, 3, 2]        # row 1, column 3, layer 2
#> [1] 7
myArray[, , 2]          # the whole second layer
#>      col1 col2 col3 col4
#> row1    1    4    7   10
#> row2    2    5    8   11
#> row3    3    6    9   12

Overwrite and analyse a layer

apply() applies a function to rows (1), columns (2) or both (c(1, 2)). The example calculates the sum of each row across all columns and both layers.

myArray[, , 2] <- 13:24
apply(myArray, 1, sum)
#> row1 row2 row3 
#>   92  100  108

Data frames

A data frame is a table. All values of a column have the same type, all columns have the same length.

myDataFrame <- data.frame(
  name   = c("Thomas", "Johan", "Linda"),
  age    = c(34, 21, 49),
  gender = c("M", "M", "F")
)
myDataFrame
#>     name age gender
#> 1 Thomas  34      M
#> 2  Johan  21      M
#> 3  Linda  49      F

Show the structure

str(myDataFrame)
#> 'data.frame':	3 obs. of  3 variables:
#>  $ name  : chr  "Thomas" "Johan" "Linda"
#>  $ age   : num  34 21 49
#>  $ gender: chr  "M" "M" "F"
summary(myDataFrame)
#>      name                age           gender         
#>  Length:3           Min.   :21.00   Length:3          
#>  Class :character   1st Qu.:27.50   Class :character  
#>  Mode  :character   Median :34.00   Mode  :character  
#>                     Mean   :34.67                     
#>                     3rd Qu.:41.50                     
#>                     Max.   :49.00
dim(myDataFrame)          # rows and columns
#> [1] 3 3
nrow(myDataFrame)
#> [1] 3
colnames(myDataFrame)
#> [1] "name"   "age"    "gender"
head(myDataFrame, 2)      # the first rows
#>     name age gender
#> 1 Thomas  34      M
#> 2  Johan  21      M

In RStudio, View(myDataFrame) opens the table in a separate window. edit() and fix() open a simple editor where you can change values directly.

View(myDataFrame)
myDataFrame <- edit(myDataFrame)
fix(myDataFrame)          # saves the change directly in myDataFrame

Read columns

myDataFrame$name                 # column as a vector
#> [1] "Thomas" "Johan"  "Linda"
myDataFrame[["age"]]             # the same with the name as text
#> [1] 34 21 49
myDataFrame[, c("name", "age")]  # several columns as a data frame
#>     name age
#> 1 Thomas  34
#> 2  Johan  21
#> 3  Linda  49
myDataFrame[c(1, 3)]             # columns 1 and 3
#>     name gender
#> 1 Thomas      M
#> 2  Johan      M
#> 3  Linda      F

Read and filter rows

myDataFrame[1, ]                          # first row
#>     name age gender
#> 1 Thomas  34      M
myDataFrame[myDataFrame$age > 30, ]       # rows with a condition
#>     name age gender
#> 1 Thomas  34      M
#> 3  Linda  49      F
subset(myDataFrame, age > 30, select = c(name, age))
#>     name age
#> 1 Thomas  34
#> 3  Linda  49

Add columns and rows

myDataFrame$city <- c("Cologne", "Bonn", "Essen")   # new column

newRows <- data.frame(
  name   = c("Igor", "Andreas"),
  age    = c(19, 25),
  gender = c("M", "M"),
  city   = c("Bonn", "Cologne")
)
myDataFrame <- rbind(myDataFrame, newRows)          # new rows
myDataFrame
#>      name age gender    city
#> 1  Thomas  34      M Cologne
#> 2   Johan  21      M    Bonn
#> 3   Linda  49      F   Essen
#> 4    Igor  19      M    Bonn
#> 5 Andreas  25      M Cologne

Sort

myDataFrame[order(myDataFrame$age), ]
#>      name age gender    city
#> 4    Igor  19      M    Bonn
#> 2   Johan  21      M    Bonn
#> 5 Andreas  25      M Cologne
#> 1  Thomas  34      M Cologne
#> 3   Linda  49      F   Essen
myDataFrame[order(-myDataFrame$age), ]     # descending
#>      name age gender    city
#> 3   Linda  49      F   Essen
#> 1  Thomas  34      M Cologne
#> 5 Andreas  25      M Cologne
#> 2   Johan  21      M    Bonn
#> 4    Igor  19      M    Bonn

Group and summarise

aggregate(age ~ gender, data = myDataFrame, FUN = mean)
#>   gender   age
#> 1      F 49.00
#> 2      M 24.75
table(myDataFrame$city)
#> 
#>    Bonn Cologne   Essen 
#>       2       2       1

Join tables

cities <- data.frame(city = c("Cologne", "Bonn", "Essen"),
                     population = c(1084831, 336465, 586608))
merge(myDataFrame, cities, by = "city")
#>      city    name age gender population
#> 1    Bonn   Johan  21      M     336465
#> 2    Bonn    Igor  19      M     336465
#> 3 Cologne  Thomas  34      M    1084831
#> 4 Cologne Andreas  25      M    1084831
#> 5   Essen   Linda  49      F     586608

Conditions

if and else

x <- 7
if (x > 10) {
  "large"
} else if (x > 5) {
  "medium"
} else {
  "small"
}
#> [1] "medium"

ifelse for whole vectors

if checks exactly one value. ifelse() checks every value of a vector.

numbers <- c(3, 8, 12)
ifelse(numbers > 5, "large", "small")
#> [1] "small" "large" "large"

switch

day <- "sa"
switch(day,
       sa = ,
       su = "weekend",
       "weekday")
#> [1] "weekend"

Several commands on one line are separated by a semicolon:

"hello"; 1 + 1; 1 * 3
#> [1] "hello"
#> [1] 2
#> [1] 3

Loops

for

for (i in seq(from = 0, to = 20, by = 5)) {
  print(i)
}
#> [1] 0
#> [1] 5
#> [1] 10
#> [1] 15
#> [1] 20

seq_along() returns the positions of a vector and also works with empty vectors:

people <- c("Linda", "Sam")
for (i in seq_along(people)) {
  cat(i, people[i], "\n")
}
#> 1 Linda 
#> 2 Sam

while and repeat

i <- 5
while (i <= 15) {
  print(i)
  i <- i + 5
}
#> [1] 5
#> [1] 10
#> [1] 15

i <- 5
repeat {
  if (i > 15) break   # leave the loop
  print(i)
  i <- i + 5
}
#> [1] 5
#> [1] 10
#> [1] 15

next

for (i in 1:6) {
  if (i %% 2 == 0) next   # skip even numbers
  print(i)
}
#> [1] 1
#> [1] 3
#> [1] 5

Many loops can be written shorter in R without a loop, because commands such as seq(), sum() or + work on whole vectors:

seq(0, 20, by = 5)
#> [1]  0  5 10 15 20

Functions

Call a function

Parameters can be passed by position or by name.

log2(64)
#> [1] 6
log(x = 64, base = 2)
#> [1] 6
log(64, 2)
#> [1] 6

Write your own function

A function returns the result of its last line. With return() you can leave it earlier.

square <- function(x) x^2
square(4)
#> [1] 16

greet <- function(firstName, greeting = "Hello") {
  if (firstName == "") return("No name given")
  paste(greeting, firstName)
}
greet("Linda")                      # default value for greeting
#> [1] "Hello Linda"
greet("Linda", greeting = "Hi")     # overwrite the default value
#> [1] "Hi Linda"
greet("")
#> [1] "No name given"

Short notation

Since R 4.1 you can write \(x) instead of function(x).

twice <- \(x) x * 2
twice(21)
#> [1] 42

Any number of parameters

total <- function(...) sum(...)
total(1, 2, 3, 4)
#> [1] 10

Apply a function to many values

sapply() returns a vector, lapply() a list. vapply() also checks the type of the result. Map() walks through several vectors at the same time.

sapply(1:5, \(x) x^2)
#> [1]  1  4  9 16 25
lapply(1:2, \(x) x * 10)
#> [[1]]
#> [1] 10
#> 
#> [[2]]
#> [1] 20
vapply(c("a", "bb", "ccc"), nchar, integer(1))
#>   a  bb ccc 
#>   1   2   3
Map(\(x, y) x + y, 1:3, 4:6) |> unlist()
#> [1] 5 7 9

Error handling

result <- tryCatch(
  log(-1),
  warning = function(w) "warning caught",
  error   = function(e) "error caught"
)
result
#> [1] "warning caught"

check <- function(x) {
  if (!is.numeric(x)) stop("x must be a number")
  x * 2
}
tryCatch(check("a"), error = function(e) conditionMessage(e))
#> [1] "x must be a number"

stopifnot(is.numeric(5), 5 > 0)   # stops if a condition is not met

Mathematics and statistics

Rounding and basic functions

round(sqrt(99))
#> [1] 10
round(sqrt(99), digits = 2)
#> [1] 9.95
signif(123456, 2)        # to 2 significant digits
#> [1] 120000
ceiling(2.1)             # round up
#> [1] 3
floor(2.9)               # round down
#> [1] 2
abs(-3)
#> [1] 3
exp(1)
#> [1] 2.718282
10 %% 3
#> [1] 1

Displayed digits

By default R displays numbers with 7 significant digits. options(digits = ) allows values from 1 to 22. The setting only changes the display, not the precision of the calculation.

pi
#> [1] 3.141593
options(digits = 15)
pi
#> [1] 3.14159265358979
options(digits = 7)

Location and spread

x <- c(1, 2, 4, 99)
mean(x)                  # average
#> [1] 26.5
median(x)                # median
#> [1] 3
min(x); max(x)
#> [1] 1
#> [1] 99
range(x)
#> [1]  1 99
diff(range(x))           # range as a single number
#> [1] 98
sum(x)
#> [1] 106
var(x)                   # variance
#> [1] 2337.667
sd(x)                    # standard deviation
#> [1] 48.34942
quantile(x)
#>    0%   25%   50%   75%  100% 
#>  1.00  1.75  3.00 27.75 99.00
summary(x)
#>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
#>    1.00    1.75    3.00   26.50   27.75   99.00

Relationship between two values

cor(mtcars$mpg, mtcars$wt)              # correlation
#> [1] -0.8676594
model <- lm(mpg ~ wt, data = mtcars)    # linear regression
coef(model)
#> (Intercept)          wt 
#>   37.285126   -5.344472

Random numbers

With set.seed(), R returns the same random numbers on every run. This makes results reproducible.

set.seed(42)
sample(1:1000, 5)                # 5 numbers without repetition
#> [1] 561 997 321 153  74
sample(c("heads", "tails"), 3, replace = TRUE)
#> [1] "tails" "tails" "tails"
round(runif(3), 2)               # uniformly distributed between 0 and 1
#> [1] 0.66 0.71 0.46
round(rnorm(3, mean = 100, sd = 15), 1)   # normally distributed
#> [1] 108.7  90.1 123.3

Format numbers

format(20, nsmall = 2)                         # at least 2 decimals
#> [1] "20.00"
format(20.123456789, digits = 4)               # 4 significant digits
#> [1] "20.12"
format(20, scientific = TRUE)                  # scientific notation
#> [1] "2e+01"
format(1234567.891, big.mark = ",", nsmall = 2)
#> [1] "1,234,567.89"
format(100)                                    # number as text
#> [1] "100"

Date and time

Sys.Date()       # today's date
Sys.time()       # current time
d <- as.Date("10/18/2016", format = "%m/%d/%Y")
d
#> [1] "2016-10-18"
format(d, "%d %B %Y")
#> [1] "18 October 2016"
format(d, "%Y")                         # year only
#> [1] "2016"
d + 30                                  # 30 days later
#> [1] "2016-11-17"
as.numeric(as.Date("2026-10-29") - d)   # difference in days
#> [1] 3663
seq(d, by = "month", length.out = 3)
#> [1] "2016-10-18" "2016-11-18" "2016-12-18"

Charts

Base R draws charts without additional packages:

plot(mtcars$wt, mtcars$mpg, xlab = "Weight", ylab = "Miles per gallon")
hist(mtcars$mpg, main = "Distribution", col = "steelblue")
barplot(table(mtcars$cyl))
boxplot(mpg ~ cyl, data = mtcars)

The package ggplot2 builds charts from layers:

library(ggplot2)
ggplot(mtcars, aes(x = wt, y = mpg, colour = factor(cyl))) +
  geom_point() +
  labs(x = "Weight", y = "Miles per gallon", colour = "Cylinders")
ggsave("chart.png", width = 6, height = 4)

Other packages for charts are lattice and plotly.

Data manipulation with dplyr

The package dplyr offers simple commands for filtering, selecting, calculating and summarising. The pipe |> passes the result to the next command.

library(dplyr)

iris |>
  filter(Sepal.Length > 5) |>                  # filter rows
  mutate(Length_to_Width = Sepal.Length / Sepal.Width) |>   # calculate a column
  group_by(Species) |>                         # group
  summarise(Count = n(),
            Mean = mean(Length_to_Width)) |>   # summarise per group
  arrange(desc(Mean))                          # sort descending
#> # A tibble: 3 Γ— 3
#>   Species    Count  Mean
#>   <fct>      <int> <dbl>
#> 1 virginica     49  2.24
#> 2 versicolor    47  2.16
#> 3 setosa        22  1.44
iris |>
  select(Species, Petal.Length) |>    # select columns
  slice_head(n = 3)                   # the first rows
#>   Species Petal.Length
#> 1  setosa          1.4
#> 2  setosa          1.4
#> 3  setosa          1.3

Databases

The package DBI is the common interface to databases in R. Each database has its own driver:

DatabasePackage
SQLiteRSQLite
MySQL and MariaDBRMariaDB (replaces the older RMySQL)
PostgreSQLRPostgres
any database with an ODBC driverodbc (the older RODBC still works, but does not use the DBI interface)
install.packages(c("DBI", "RSQLite"))

The following example creates a database in memory, writes a table into it and queries it with SQL:

library(DBI)
con <- dbConnect(RSQLite::SQLite(), ":memory:")
dbWriteTable(con, "cars", mtcars[1:5, c("mpg", "cyl", "hp")])
dbListTables(con)
#> [1] "cars"
dbGetQuery(con, "SELECT cyl, AVG(hp) AS hp FROM cars GROUP BY cyl")
#>   cyl  hp
#> 1   4  93
#> 2   6 110
#> 3   8 175
dbDisconnect(con)

Connection to a MariaDB or MySQL server:

con <- dbConnect(RMariaDB::MariaDB(),
                 host = "localhost", dbname = "shop",
                 user = "aaron", password = Sys.getenv("DB_PASSWORD"))

The password does not belong in the code. Sys.getenv() reads it from an environment variable, which you can define in the file .Renviron, for example.

Entropy and information gain

Entropy measures how mixed the values of a target column are. Information gain shows how much a feature reduces this mix. Both values help decision trees find the feature with the most predictive power.

entropy <- function(y) {
  p <- table(y) / length(y)
  p <- p[p > 0]
  -sum(p * log2(p))
}

information_gain <- function(y, x) {
  conditional <- sum(sapply(split(y, x), \(part) length(part) / length(y) * entropy(part)))
  entropy(y) - conditional
}

entropy(iris$Species)
#> [1] 1.584963
information_gain(iris$Species, cut(iris$Petal.Length, 3))
#> [1] 1.324664
information_gain(iris$Species, cut(iris$Sepal.Width, 3))
#> [1] 0.2658956

The petal length (Petal.Length) separates the species much better than the sepal width (Sepal.Width). The package FSelector offers the function information.gain() for all columns at once, but needs a Java installation.