```{r}
x <- rnorm(100)
mean(x)
```[1] -0.01310165
Learning objectives
By the end of this chapter, you should be able to:
- Describe how R reads, evaluates, and prints an expression.
- Distinguish an object from the name bound to that object.
- Identify the major data types and data structures in R.
- Predict how vectorization, coercion, recycling, and missing values affect a computation.
- Create, inspect, index, and modify vectors, matrices, arrays, lists, and data frames.
- Write and use simple R functions.
- Choose appropriately among vectorization, loops, and the
apply()family.- Use the tidyverse pipe and core
dplyrverbs to express a data-analysis pipeline.- Write computations that are reproducible, readable, and numerically responsible.
Modern statistical analyses routinely involve hundreds, thousands, or millions of observations. The calculations are too numerous, and often too complex, to perform reliably by hand. We therefore need software that can manage data, implement statistical methods, automate repeated computations, and document the complete analysis.
R is both a high-level programming language and an environment for statistical computing and graphics. It is especially useful for statistics because it is:
R is not a menu-driven statistics program. Learning R means learning to express an analysis as a sequence of precise and reproducible computations.
R performs the computation. RStudio is an integrated development environment that helps us write and run code. Quarto combines text, code, results, figures, equations, and references into reproducible HTML, PDF, Word, or presentation output.
There are different integrated development environment to use R, some of them are Rstudio, VS Code and Positron.
Rstudio
RStudio is an integrated development environment (IDE) designed specifically for working with the R programming language. It provides a user-friendly interface that includes a source editor, console, environment pane, and tools for plotting, debugging, version control, and package management. RStudio supports both R and Python and is widely used for data analysis, statistical modeling, and reproducible research. It also integrates seamlessly with tools like R Markdown, Shiny, and Quarto, making it popular among data scientists, statisticians, and educators.
Visual Studio Code (VS Code)
VS Code is a versatile code editor that supports multiple programming languages, including R. With the R extension for VS Code, users can write and execute R code, access R’s console, and utilize features like syntax highlighting, code completion, and debugging. While not as specialized as RStudio for R development, VS Code offers a lightweight alternative with extensive customization options and support for various programming tasks.
Positron
Positron IDE is the next-generation integrated development environment developed by Posit, the company behind RStudio. Designed to be a modern, extensible, and language-agnostic IDE, Positron builds on the strengths of RStudio while supporting a broader range of languages and workflows, including R, Python, and Quarto.
RStudio is an integrated development environment (IDE) for R, Python, and reproducible publishing. Its default layout contains four panes:
| Pane | Main purpose |
|---|---|
| Source | Write and edit scripts and Quarto documents. |
| Console | Send expressions directly to R and view immediate results. |
| Environment/History | Inspect objects and review previous commands. |
| Files/Plots/Packages/Help/Viewer | Navigate files, inspect graphics, manage packages, read documentation, and preview output. |
Use the console for short experiments. Any computation that matters should be placed in an R script or Quarto document so that it can be reproduced.
An R script is a plain-text file with the extension .R. It contains R expressions that can be executed one line, one selection, or one file at a time.
A Quarto document has the extension .qmd. It combines:
In Figure 1.3, the red box is the yaml, the yellow box is the executable code chunk and the green box is a figure.
For example, an executable R chunk is written as
Rendering the document executes the enabled chunks in order and places their results into the final output.
Create one RStudio Project for the course or for each substantial analysis. Use project-relative paths such as data/survey.csv rather than hard-coded paths tied to one computer.
A simple project might contain:
stat8670-project/
├── data/ # original and processed data
├── R/ # reusable functions
├── figures/ # saved figures
├── notes/ # Quarto lecture or analysis files
└── stat8670-project.Rproj
Avoid using setwd() inside a reproducible document. When a project is opened, its root provides a stable reference point for relative paths.
At the console, R follows a read, evaluate, print loop (REPL):
R respects operator precedence, so multiplication is performed before addition.
Assignment usually returns its value invisibly:
Most R code can be understood as functions operating on objects. Even operators such as + are functions.
Two fundamental kinds of objects that we will work with are:
Data objects include numbers, vectors, matrices, arrays, lists, data frames, and fitted models.
A function is a set of instructions that receives input, performs a computation, and returns an output. Functions can be built into R, provided by a package, or written by the user.
Here, c(1, 2, 3, 4) is a data object and mean() is a function.
R computes with objects. A name is a symbol bound to an object.
The assignment operator <- creates or changes a binding.
Names are case-sensitive:
A useful naming convention is to separate words using _:
Use ls() to list names in the current environment and rm() to remove a binding.
The fundamental data structure in R is the vector. An atomic vector contains elements of one common type.
Common atomic types include:
| Type | Example | Typical use |
|---|---|---|
| logical | TRUE, FALSE |
conditions and indicators |
| integer | 2L |
counts and indices |
| double | 2, 3.14 |
numerical computation |
| character | "R" |
text and labels |
| complex | 1 + 2i |
complex arithmetic |
| raw | charToRaw("R") |
bytes |
[1] "logical"
[1] "integer"
[1] "double"
[1] "character"
Data types can be explicitly converted, but conversion may result in information loss.
Here, converting \(\pi\) to an integer removes its decimal component.
Common conversion functions include:
as.integer()as.numeric()as.character()as.logical()as.Date()as.factor()as.list()as.matrix()as.data.frame()as.vector()as.complex()Always inspect the result after converting an object from one type to another.
A unary operator operates on one object:
A binary operator operates on two objects:
Comparison operators
Comparison operators return logical values:
Logical operators
Logical operators combine logical values:
The operators & and | operate element by element.
[1] TRUE TRUE FALSE
[1] TRUE FALSE FALSE
By contrast, && and || are intended for a single logical condition and are commonly used inside if statements.
Many R functions and operators act on entire vectors.
[1] 1 4 9 16
[1] 1.000000 1.414214 1.732051 2.000000
[1] 10
[1] 2.5
Vectorized code often expresses statistical calculations more directly than element-by-element code.
Arithmetic between vectors is usually element-wise.
If one vector is shorter, R may recycle its values.
When the shorter length does not divide the longer length exactly, R produces a warning.
An atomic vector must have one type. Combining different types may cause R to convert values to a common type.
[1] 1.0 2.0 3.5
[1] "double"
[1] "TRUE" "2" "3.5" "four"
[1] "character"
A simplified coercion hierarchy is:
logical → integer → double → complex → character
Inspect unfamiliar objects using
R uses NA to represent a missing value.
day weather
1 Monday Raining
2 Tuesday Sunny
3 Wednesday <NA>
4 Thursday Windy
5 Friday Snowing
Missing values usually propagate through calculations:
[1] NA
[1] 87.66667
[1] FALSE TRUE FALSE FALSE
Do not use
to detect missing values. Use
instead.
Other special numerical values include
which produce Inf, -Inf, and NaN.
Indexing allows us to access or modify parts of an object.
Unlike languages such as Python and C++, R uses 1-based indexing. The first element is indexed by 1, not 0.
For a vector:
first
10
first third
10 30
first third fourth
10 30 40
third fourth
30 40
second fourth
20 40
Positive integers select positions, negative integers exclude positions, logical values filter positions, and character values select named elements.
Prefer seq_along(x) to 1:length(x) when iterating over the positions of an object.
Names can be attached to vector elements:
Mon Tues Wed Thurs Fri
20 30 27 31 45
Wed
27
A vector does not have rows, so this is inappropriate:
A matrix can have row and column names:
An array is a multidimensional data structure.
[1] 1 2 3 4 5 6 7 8 9 10
[,1] [,2] [,3]
[1,] 1 5 9
[2,] 2 6 10
[3,] 3 7 11
[4,] 4 8 12
, , 1
[,1] [,2] [,3]
[1,] 1 5 9
[2,] 2 6 10
[3,] 3 7 11
[4,] 4 8 12
, , 2
[,1] [,2] [,3]
[1,] 13 17 21
[2,] 14 18 22
[3,] 15 19 23
[4,] 16 20 24
A matrix is a two-dimensional array:
[,1] [,2] [,3]
[1,] 1 5 9
[2,] 2 6 10
[3,] 3 7 11
[4,] 4 8 12
[1] TRUE
[1] TRUE
[1] TRUE
[1] TRUE
A matrix is an atomic vector with a dim attribute, so all entries must share one common type.
Matrix multiplication uses %*%:
while * performs element-wise multiplication.
A list can contain objects of different types and lengths.
List of 3
$ method : chr "least squares"
$ coefficients: Named num [1:2] 1.2 0.8
..- attr(*, "names")= chr [1:2] "intercept" "slope"
$ converged : logi TRUE
Named list elements naturally form key-value pairs.
$Tues
[1] 32
$Wed
[1] 28
Values can then be obtained from their keys:
For lists, note the important distinction:
$coefficients
intercept slope
1.2 0.8
intercept slope
1.2 0.8
[ returns a sublist, whereas [[ extracts the element itself.
A factor represents categorical data using integer codes and a set of levels.
[1] control treatment control
Levels: control treatment
[1] 1 2 1
attr(,"levels")
[1] "control" "treatment"
[1] "control" "treatment"
The order of factor levels matters in statistical models, so define it deliberately when necessary.
A data frame is a two-dimensional rectangular data structure.
Conceptually, a data frame is a named list of equal-length vectors:
id name program score completed
1 1 Alice MS 90 TRUE
2 2 Bob PhD 85 TRUE
3 3 Chen MS NA FALSE
4 4 Divya PhD 94 TRUE
5 5 Elena MS 88 TRUE
6 6 Farah PhD 91 TRUE
Another familiar built-in data frame is:
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1 5.1 3.5 1.4 0.2 setosa
2 4.9 3.0 1.4 0.2 setosa
3 4.7 3.2 1.3 0.2 setosa
4 4.6 3.1 1.5 0.2 setosa
5 5.0 3.6 1.4 0.2 setosa
6 5.4 3.9 1.7 0.4 setosa
Always inspect newly imported data before analyzing them.
[1] "data.frame"
[1] 6 5
[1] 6
[1] 5
[1] "id" "name" "program" "score" "completed"
'data.frame': 6 obs. of 5 variables:
$ id : int 1 2 3 4 5 6
$ name : chr "Alice" "Bob" "Chen" "Divya" ...
$ program : chr "MS" "PhD" "MS" "PhD" ...
$ score : num 90 85 NA 94 88 91
$ completed: logi TRUE TRUE FALSE TRUE TRUE TRUE
id name program score completed
Min. :1.00 Length :6 Length :6 Min. :85.0 Mode :logical
1st Qu.:2.25 N.unique :6 N.unique :2 1st Qu.:88.0 FALSE:1
Median :3.50 N.blank :0 N.blank :0 Median :90.0 TRUE :5
Mean :3.50 Min.nchar:3 Min.nchar:2 Mean :89.6
3rd Qu.:4.75 Max.nchar:5 Max.nchar:3 3rd Qu.:91.0
Max. :6.00 Max. :94.0
NAs :1
id name program score completed
1 1 Alice MS 90 TRUE
2 2 Bob PhD 85 TRUE
3 3 Chen MS NA FALSE
str() is especially useful because it gives a compact summary of the object’s structure and types.
Use
for two-dimensional indexing:
id name program score completed
1 1 Alice MS 90 TRUE
name score
1 Alice 90
2 Bob 85
3 Chen NA
4 Divya 94
5 Elena 88
6 Farah 91
id name program score completed
2 2 Bob PhD 85 TRUE
4 4 Divya PhD 94 TRUE
6 6 Farah PhD 91 TRUE
name score
1 Alice 90
2 Bob 85
3 Chen NA
[1] 85
Three commonly used ways to extract a column are:
score
1 90
2 85
3 NA
4 94
5 88
6 91
[1] 90 85 NA 94 88 91
[1] 90 85 NA 94 88 91
The first returns a one-column data frame. The latter two return the underlying vector.
id name program score completed passed centered_score
1 1 Alice MS 90 TRUE TRUE 0.4
2 2 Bob PhD 85 TRUE TRUE -4.6
3 3 Chen MS NA FALSE NA NA
4 4 Divya PhD 94 TRUE TRUE 4.4
5 5 Elena MS 88 TRUE TRUE -1.6
6 6 Farah PhD 91 TRUE TRUE 1.4
Missingness propagates naturally. If a score is unknown, whether the student passed is also unknown.
After importing, check:
Import settings such as delimiters, missing-value codes, decimal marks, encodings, and inferred variable types can change the data that R sees and therefore affect the final analysis.
apply() first converts a data frame to a matrix. A mixed-type data frame can therefore be converted entirely to character values. Use lapply(), vapply(), or dplyr::across() for column-wise operations when types differ.
A function packages a computation so that it can be reused.
[1] -1.1618950 -0.3872983 0.3872983 1.1618950
A function has three main components:
$x
$remove_na
[1] FALSE
{
center <- mean(x, na.rm = remove_na)
spread <- sd(x, na.rm = remove_na)
(x - center)/spread
}
<environment: R_GlobalEnv>
When a function is called, R creates a temporary environment, matches the arguments, evaluates the body, and returns the value of the final expression unless return() is called earlier.
Arguments can be matched by name or position.
Named arguments are often clearer in code intended to be read by others.
R evaluates function arguments only when they are needed.
[1] 10
This behavior is called lazy evaluation.
An environment stores name-object bindings and has a parent environment.
R uses lexical scoping: functions look for nonlocal names in the environment where they were defined.
[1] 25
[1] 125
A function together with its defining environment is called a closure.
Local assignment affects the current function environment:
R behaves as if objects are copied when modified.
Changing y does not change x.
Repeatedly expanding an object can require many allocations:
Instead, preallocate the required memory:
An iterative computation can usually be expressed in several ways.
[1] 4 16 36 64
[1] 4 16 36 64
[1] 4 16 36 64
Use vectorization when an existing vectorized function directly expresses the computation. Use a loop when the calculation has evolving state or complex indexing. Use an apply-family function when the same operation is independently applied to several pieces of an object.
apply() and lapply() still perform iteration internally. They are not automatically faster than a well-written preallocated for loop. Their main advantage is often clarity and conciseness.
apply()apply(X, MARGIN, FUN, ...) applies a function over dimensions of a matrix or array.
MARGIN = 1: rowsMARGIN = 2: columns x1 x2 x3 x4
[1,] 1 2 3 4
[2,] 5 6 7 8
[3,] 9 10 11 12
[1] 2.5 6.5 10.5
x1 x2 x3 x4
4 4 4 4
Additional arguments are passed through ...:
For common operations, specialized functions are preferable:
apply() can be dangerous for data frames id x y
[1,] "A" "10" "2"
[2,] "B" "20" "4"
[3,] "C" "30" "6"
[1] "character"
Because one column is character, the entire matrix becomes character.
A safer approach is:
lapply()lapply() applies a function to each element and always returns a list.
$id
[1] "integer"
$name
[1] "character"
$program
[1] "character"
$score
[1] "numeric"
$completed
[1] "logical"
$passed
[1] "logical"
$centered_score
[1] "numeric"
$score
[1] 89.6
$centered_score
[1] 5.861978e-15
sapply() and vapply()sapply() attempts to simplify the output.
id name program score completed
"integer" "character" "character" "numeric" "logical"
passed centered_score
"logical" "numeric"
vapply() requires the expected output type:
id name program score completed
"integer" "character" "character" "double" "logical"
passed centered_score
"logical" "double"
This makes vapply() safer for reusable code.
tapply()tapply() splits a vector into groups and applies a function within each group.
Map() and mapply()These functions apply a function to corresponding elements of multiple inputs.
| Goal | Recommended starting point |
|---|---|
| Element-wise arithmetic | Vectorized function or operator |
| Standard matrix row/column summaries | rowSums(), colSums(), rowMeans(), colMeans() |
| General matrix row/column operation | apply() |
| Operation on list or data-frame columns | lapply() |
| Known scalar output type | vapply() |
| Grouped operation on one vector | tapply() |
| Data-frame columns in tidyverse | dplyr::across() |
| Complex state-dependent computation | Preallocated for loop |
Packages extend R with functions, data, documentation, and statistical methods. The historical package count can be found https://www.datasciencemeta.com/rpackages.
Load an installed package:
or call one function explicitly:
Useful help tools include:
The tidyverse is a coordinated collection of R packages for data manipulation, visualization, importing, tidying, and programming.
Important packages include:
Base R and tidyverse tools are complementary rather than competing systems.
A tibble is a modern data frame.
# A tibble: 6 × 7
id name program score completed passed centered_score
<int> <chr> <chr> <dbl> <lgl> <lgl> <dbl>
1 1 Alice MS 90 TRUE TRUE 0.400
2 2 Bob PhD 85 TRUE TRUE -4.60
3 3 Chen MS NA FALSE NA NA
4 4 Divya PhD 94 TRUE TRUE 4.40
5 5 Elena MS 88 TRUE TRUE -1.60
6 6 Farah PhD 91 TRUE TRUE 1.40
[1] "tbl_df" "tbl" "data.frame"
Some important differences are:
| Operation | Base data frame | Tibble |
|---|---|---|
| Printing | May print many rows | Compact preview |
x[, "score"] |
May simplify | Remains a tibble |
| Partial matching | May occur in some contexts | More strict |
| Row names | Supported | Discouraged |
Both are still data frames:
The native R pipe is
and the commonly used magrittr/tidyverse pipe is
The pipe passes the result of one operation to the next function.
[1] 0.37
[1] 0.37
The pipe makes the sequence of operations explicit.
A useful formatting rule is that |> should have spaces around it and normally appear at the end of a line.
dplyr verbs| Verb | Purpose |
|---|---|
select() |
Choose or reorder columns |
filter() |
Retain rows satisfying conditions |
arrange() |
Sort observations |
mutate() |
Create or modify variables |
summarise() |
Compute summaries |
group_by() |
Define groups |
Example:
# A tibble: 5 × 3
name program score
<chr> <chr> <dbl>
1 Divya PhD 94
2 Farah PhD 91
3 Alice MS 90
4 Elena MS 88
5 Bob PhD 85
Create variables and summarize by group:
# A tibble: 2 × 5
program n n_observed mean_score sd_score
<chr> <int> <int> <dbl> <dbl>
1 MS 3 2 89 1.41
2 PhD 3 3 90 4.58
Inside mutate() or summarise(), across() applies functions to selected columns.
# A tibble: 1 × 6
id_mean id_sd score_mean score_sd centered_score_mean centered_score_sd
<dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 3.5 1.87 89.6 3.36 5.86e-15 3.36
This provides a tidyverse alternative to column-wise lapply() or vapply().
For example, the same grouped mean can be obtained using either style.
program score
1 MS 89
2 PhD 90
# A tibble: 2 × 2
program mean_score
<chr> <dbl>
1 MS 89
2 PhD 90
The important questions are the same regardless of syntax:
Most real numbers cannot be represented exactly using binary floating-point arithmetic.
For computed floating-point quantities, tolerance-based comparisons such as all.equal() are usually more appropriate than exact equality.
Numerical algorithms must also consider overflow, underflow, cancellation, conditioning, and convergence.
[1] -Inf
[1] -1618.143
The second computation is numerically safer.
Pseudo-random numbers are generated deterministically from an internal state.
[1] -0.4916281 1.1467524 1.5963118 -0.6655463 0.5516107
[1] -0.4916281 1.1467524 1.5963118 -0.6655463 0.5516107
The two results are identical.
Set the seed at a meaningful boundary, usually once near the beginning of a simulation, rather than repeatedly inside the simulation loop.
Record the information necessary to reproduce the analysis:
R version 4.6.0 (2026-04-24)
Platform: aarch64-apple-darwin23
Running under: macOS Tahoe 26.6.2
Matrix products: default
BLAS: /Library/Frameworks/R.framework/Versions/4.6/Resources/lib/libRblas.0.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/4.6/Resources/lib/libRlapack.dylib; LAPACK version 3.12.1
locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
time zone: America/New_York
tzcode source: internal
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] lubridate_1.9.5 forcats_1.0.1 stringr_1.6.0 dplyr_1.2.1
[5] purrr_1.2.2 readr_2.2.0 tidyr_1.3.2 tibble_3.3.1
[9] ggplot2_4.0.3 tidyverse_2.0.0
loaded via a namespace (and not attached):
[1] gtable_0.3.6 jsonlite_2.0.0 compiler_4.6.0 tidyselect_1.2.1
[5] scales_1.4.0 yaml_2.3.12 fastmap_1.2.0 R6_2.6.1
[9] generics_0.1.4 knitr_1.51 htmlwidgets_1.6.4 pillar_1.11.1
[13] RColorBrewer_1.1-3 tzdb_0.5.0 rlang_1.3.0 utf8_1.2.6
[17] stringi_1.8.9 xfun_0.60 S7_0.2.2 otel_0.2.0
[21] timechange_0.4.0 cli_3.6.6 withr_3.0.3 magrittr_2.0.5
[25] digest_0.6.39 grid_4.6.0 rstudioapi_0.19.0 hms_1.1.4
[29] lifecycle_1.0.5 vctrs_0.7.3 evaluate_1.0.5 glue_1.8.1
[33] farver_2.1.2 codetools_0.2-20 rmarkdown_2.31 tools_4.6.0
[37] pkgconfig_2.0.3 htmltools_0.5.9
When an error occurs:
str(), typeof(), class(), and dim().traceback() after an error when necessary.stopifnot().A reliable statistical computation separates:
For example:
(Intercept) x
1.020794 1.991843
This script records where the data came from, fixes the random seed, creates an explicit data object, and passes that object to the statistical model.
Predict the value and type of each expression before running the code.
Using
write expressions that return:
Without running the code, determine the result of f(5) and identify where each occurrence of a is found.
Write a function that takes a positive integer n and returns a vector whose \(i\)th element is
\[ \frac{(-1)^{i+1}}{i}. \]
Preallocate the result and use seq_len(n).
Using students, complete each task once with base R and once with dplyr:
name, program, and score;score - 90;Before running the code, predict whether each result is a vector, data frame, tibble, or grouped tibble.
For each task, choose among a vectorized function, apply(), lapply(), vapply(), tapply(), dplyr::across(), or a loop. Justify your choice and then write the code.
students.apply() is designed primarily for matrices and arrays and can cause unwanted coercion when applied to mixed-type data frames.%>% or |>) can make a sequence of data transformations easier to read.Explain why these two functions may have very different memory and timing behavior even though they return the same values.
Design a timing experiment for increasing values of n.
Some of the materials are adapted from CMU Stat36-350.
A comprehensive reference for the tidyverse tools is R for Data Science.
A comprehensive reference for ggplot2 is ggplot2: Elegant Graphics for Data Analysis.
Additional introductory material is adapted from Why R?.