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 commandPrinting 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
#> DoneHelp 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 commandCommand history and quitting R
history() # show recently entered commands
q() # quit R, in RStudio this ends the sessionVersion and session
R.version.string # installed R version
sessionInfo() # version, operating system and loaded packagesWorking 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 directoryFiles 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 fileRead 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 packageAn 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 packageShow 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 packageLoad 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 5Show 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 variablesCompare 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] TRUEData 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
| Structure | Content | Create with |
|---|---|---|
| Vector | values of the same type in a row | c() |
| List | values of any type, including other lists | list() |
| Matrix | values of the same type in rows and columns | matrix() |
| Array | values of the same type in any number of dimensions | array() |
| Factor | categories with fixed values | factor() |
| Data frame | table, each column has its own type | data.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] NAMissing 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] 0Operators
Arithmetic operators
| Operator | Meaning | Example | Result |
|---|---|---|---|
+ - * / | basic arithmetic | 7 / 2 | 3.5 |
^ | power | 2 ^ 3 | 8 |
%% | remainder of a division (modulo) | 7 %% 2 | 1 |
%/% | integer division | 7 %/% 2 | 3 |
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 16Comparison 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 FALSELogical 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] FALSEAssignment operators
| Operator | Meaning |
|---|---|
<- 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] 2Other 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] 6Vectors
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 13Sort 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 46Combine 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 3Order 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 TRUECreate 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] TRUELists
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 4Name 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 6Matrices
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 6Name 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 4Access 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] 8Calculate 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.5Append 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 9Arrays
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 12Overwrite 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 108Data 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 FShow 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 MIn 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 myDataFrameRead 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 FRead 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 49Add 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 CologneSort
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 BonnGroup 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 1Join 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 586608Conditions
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] 3Loops
for
for (i in seq(from = 0, to = 20, by = 5)) {
print(i)
}
#> [1] 0
#> [1] 5
#> [1] 10
#> [1] 15
#> [1] 20seq_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 Samwhile 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] 15next
for (i in 1:6) {
if (i %% 2 == 0) next # skip even numbers
print(i)
}
#> [1] 1
#> [1] 3
#> [1] 5Many 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 20Functions
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] 6Write 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] 42Any number of parameters
total <- function(...) sum(...)
total(1, 2, 3, 4)
#> [1] 10Apply 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 9Error 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 metMathematics 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] 1Displayed 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.00Relationship 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.344472Random 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.3Format 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 timed <- 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.44iris |>
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.3Databases
The package DBI is the common interface to databases in R. Each database has its own driver:
| Database | Package |
|---|---|
| SQLite | RSQLite |
| MySQL and MariaDB | RMariaDB (replaces the older RMySQL) |
| PostgreSQL | RPostgres |
| any database with an ODBC driver | odbc (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.2658956The 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.
