Statistical notebooks · Mathematics and programming
R Programming Fundamentals
A Statistical and Mathematical Approach
Originally published: September 11, 2021
Revised and expanded edition: November 2, 2026
A program organizes transformations of representations. Understanding it requires connecting the form of the data, the meaning of the operations, and the conditions under which a result can become knowledge.
This chapter integrates the original document’s contents into a self-contained, revised, and expanded exposition: from operators to data structures; from functions to recursion; from a rule on integers to the analysis of economic information. Code examples are provided as copyable text, with mathematical distinctions accompanying the practical work.
Starting point
1. Programming as a Way of Knowing
Programming in R means representing data, defining operations on those data, and organizing their execution so that the result can be inspected and reproduced. A correct instruction must satisfy two requirements: express the intended calculation and preserve the meaning of the information being processed. The first requirement is computational; the second concerns the scientific problem, its concepts, and the way observations were produced.
This chapter connects operators, vectors, lists, matrices, statistical variables, and data frames with functions, conditions, iteration, and recursion. Applications range from student grades to Collatz dynamics and the treatment of gross domestic product growth rates. The discussion addresses both how to write instructions and why those instructions represent particular mathematical operations, together with the limits of their interpretation.
R, RStudio, and working files
R is the language and computing environment. RStudio is an integrated development environment that helps users edit programs, execute instructions, inspect objects, and display plots. R can be used without RStudio; the functions introduced here belong to R or to explicitly identified packages. R supports functional programming and several systems of object-oriented programming. You do not need to master the latter before beginning to calculate and analyze data.
An .R file stores instructions; a data file stores observations. The console evaluates expressions and displays results. The editor allows you to construct a program that can be executed again in the same order. The object pane helps inspect what has been created, but the program must preserve the evidence of how each object was obtained. A useful practice is therefore to begin with a fresh session and execute the code from the beginning.
# Lines starting with # are comments.
v1 <- 10
v3 <- 150 / v1
v3
# [1] 15
getwd() # Current working directory.
R.version.string # R version used to run the program.The <- symbol binds a name to a value in an environment. After the first instruction, v1 retrieves the number 10; after the second, v3 retrieves the result 15. An identifier must be distinguished from a character string: v1 looks up an object, whereas "v1" represents text. R is case-sensitive: Data and data are different names.
From the question to the result
Question, unit of analysis, and variables.
Objects, types, dimensions, and missingness.
Functions, conditions, and algorithms.
Result, assumptions, and limitations.
These stages inform and correct one another. An import anomaly may reveal a formatting problem; an inadmissible operation may expose confusion between categories and quantities; a numerically valid result may answer a different question from the one under investigation. Statistical programming requires keeping the relationship between representation, procedure, and interpretation visible.
Syntax and meaning
2. Operators, Comparisons, and Logic
Assignment, equality, and separators
x <- 12
12 -> y
z = 12
identical(x, y)
# [1] TRUE
x == z # Compares values; does not assign a new value.
# [1] TRUE
x <- 12; x # Two expressions on the same line.
# [1] 12
hello_value <- 5
greeting <- "hello"
greeting
# [1] "hello"Rightward assignment -> exists, although this chapter generally uses <- for a consistent reading direction. The = sign also permits assignment in some contexts; inside a call, it usually matches arguments to names. In mean(x = 1:3), x = identifies the function argument. The == operator compares values. The <= operator means “less than or equal to” and must not be confused with assignment.
A semicolon ; separates expressions on the same line. In the console, evaluating a name may display its value through automatic printing; the semicolon is not a printing function. A comma , separates arguments in calls and index positions in multidimensional structures. In an R numeric constant, a period marks the decimal part: 1.65. A decimal comma in an imported file is specified through reading options and is a separate matter.
| Element | Purpose | Example |
|---|---|---|
<- | Assign a value to a name | x <- 3 |
== / != | Compare equality / inequality | x != 4 |
; | Separate expressions | x <- 3; x |
, | Separate arguments or indices | sum(1, 2); M[2, 3] |
() | Calls and grouping | mean(x); (2 + 3) * 4 |
{} | Group instructions | The body of a function or loop |
[] / [[]] | Select subsets / extract components | x[2]; L[[2]] |
Sequences and arithmetic operations
2:5
# [1] 2 3 4 5
5:2
# [1] 5 4 3 2
a <- 17
b <- 5
c(addition = a + b, subtraction = a - b, product = a * b,
division = a / b, quotient = a %/% b, remainder = a %% b,
power = b^2)
# addition 22; subtraction 12; product 85; division 3.4;
# quotient 3; remainder 2; power 25.
typeof(8)
# [1] "double"
typeof(8L)
# [1] "integer"For numeric arguments, a:b generates a sequence with increments of 1 or −1. 2:5 is a vector of four ordered values. It does not represent the closed real interval [2, 5], which contains infinitely many real numbers. A vector must also be distinguished from a set: it preserves positions and may contain repetitions. If the endpoints do not differ by an integer, the final endpoint may not appear in the sequence.
%/% and %% connect quotient and remainder. For 17 and 5: 17 = 5 × 3 + 2.The L suffix in 8L requests an integer constant; it does not perform an arithmetic operation. Powers are written with ^; R also accepts **. Parentheses make the intended order explicit. For example, -2^2 is interpreted as -(2^2), whereas (-2)^2 returns 4.
Elementwise comparisons
v <- c(2, 5.5, 6, 9)
t <- c(8, 2.5, 14, 9)
v > t
# [1] FALSE TRUE FALSE FALSE
v == t
# [1] FALSE FALSE FALSE TRUE
v != t
# [1] TRUE TRUE TRUE FALSE
c(0.1 + 0.2 == 0.3,
isTRUE(all.equal(0.1 + 0.2, 0.3)))
# [1] FALSE TRUEThe operators <, >, <=, >=, ==, and != return logical values. For compatible vectors, comparison takes place by position. Use identical() for complete object identity; all.equal() may be more suitable for comparing numerical quantities within a tolerance. The decimal example illustrates a limitation of binary floating-point representation, not a contradiction in arithmetic.
Conjunction, disjunction, negation, and membership
| p | q | p & q | p | q | !p |
|---|---|---|---|---|
| FALSE | FALSE | FALSE | FALSE | TRUE |
| FALSE | TRUE | FALSE | TRUE | TRUE |
| TRUE | FALSE | FALSE | TRUE | FALSE |
| TRUE | TRUE | TRUE | TRUE | FALSE |
u <- c(TRUE, FALSE, TRUE, FALSE)
w <- c(TRUE, TRUE, FALSE, FALSE)
u & w
# [1] TRUE FALSE FALSE FALSE
u | w
# [1] TRUE TRUE TRUE FALSE
!u
# [1] FALSE TRUE FALSE TRUE
c(3, 8, 3) %in% c(1, 3, 5)
# [1] TRUE FALSE TRUE
x <- c(2, 5, 8, NA_real_)
x[!is.na(x) & x > 3 & x < 8]
# [1] 5
any(u)
# [1] TRUE
all(u)
# [1] FALSEConjunction & requires both conditions to hold. Disjunction | is inclusive: it is also true when both conditions are true. Negation ! reverses known logical values. Mathematically, it is an involution because applying it twice recovers the initial value; describing it as an isomorphism requires specifying the structure being preserved. %in% checks whether each element of its left argument appears in its right argument.
divide_if_valid <- function(x, y) {
if (length(y) == 1L && is.numeric(y) &&
!is.na(y) && is.finite(y) && y != 0) {
x / y
} else {
stop("The divisor must be a finite, scalar, nonzero number.")
}
}
divide_if_valid(10, 2)
# [1] 5The operators && and || evaluate from left to right and stop once the result is determined. In the example, the function checks the length and type of y before evaluating value-based scalar conditions. These operators require length-one operands in current R versions. To summarize a logical vector, explicitly choose any() or all(), depending on the question.
The elementary structure
3. Vectors and Sequences
A vector stores elements in ordered positions. Atomic vectors have a single basic storage type; lists can combine components of different types and sizes. Atomic types include logical, integer, double, complex, character, and raw. The word “numeric” may be used broadly to include integers and doubles, so inspect the type with typeof() when the distinction matters.
A numeric vector of length n can represent an element of ℝn. This interpretation is useful for algebraic operations, but it does not characterize every R vector: a list or a vector of words requires other interpretations. A simple vector also has no row or column orientation by default; dimensions make that orientation explicit when a matrix is used.
Constructing vectors, allocating space, and assigning positions
v1 <- c(3, 1, 5, 6, 7)
v2 <- c(TRUE, FALSE, TRUE)
v3 <- c("ONE", "Monday", "Team")
integer_vector <- c(2L, 4L, 6L)
complex_vector <- c(1 + 2i, 3 - 1i)
a <- vector(mode = "numeric", length = 4)
a
# [1] 0 0 0 0
a[1:4] <- 1
a
# [1] 1 1 1 1
vector("character", 3)
# [1] "" "" ""
vector()
# logical(0)c() combines values. vector() allocates a structure of a specified type and length and initializes its positions. By default, it creates an empty logical vector; filling with zeros applies to numeric mode. The assignment a[1:4] <- 1 changes positions in an existing object. In algorithms that produce many results, allocating space before iteration avoids repeatedly rebuilding the vector.
Selecting positions, excluding them, and preserving names
v1[2:3]
# [1] 1 5
v1[c(1, 5)]
# [1] 3 7
v1[-c(2, 4)]
# [1] 3 5 7
v1[v1 >= 5]
# [1] 5 6 7
ages <- c(Ana = 20, Luis = 24, Eva = 21)
ages[c("Eva", "Ana")]
# Eva Ana
# 21 20Positive indices select positions; negative indices exclude them; logical indices select where a condition is true; names refer to identified elements. Positive and negative indices cannot be mixed in one selection, apart from zeros. Indexing starts at 1. Extraction can repeat and reorder elements, so it is not generally a subset operation in the strict set-theoretic sense, and still less a selection of “subgroups” in the algebraic sense.
seq(), rep(), and sequence(): different ways to generate sequences
seq(from = 1, to = 10, by = 2)
# [1] 1 3 5 7 9
seq(from = 10, to = -8, by = -3)
# [1] 10 7 4 1 -2 -5 -8
seq(from = 0, to = 1, length.out = 5)
# [1] 0.00 0.25 0.50 0.75 1.00
rep(3, times = 7)
# [1] 3 3 3 3 3 3 3
rep(c("A", "B"), times = 2)
# [1] "A" "B" "A" "B"
rep(c("A", "B"), each = 2)
# [1] "A" "A" "B" "B"
sequence(c(1, 2, 3))
# [1] 1 1 2 1 2 3
sequence(c(1, 3))
# [1] 1 1 2 3
seq_len(0)
# integer(0)
1:0
# [1] 1 0
seq_along(v1)
# [1] 1 2 3 4 5seq() supports noninteger increments or a prescribed number of positions. rep() distinguishes repeating a complete vector from repeating each element. In these examples, sequence() concatenates sequences beginning at 1 with the specified lengths. Its function name is lowercase. For loop indices, seq_len(n) and seq_along(x) avoid the problem created by 1:n when n is zero.
Vectorization and recycling
x <- c(1, 2, 3, 4)
x * 2
# [1] 2 4 6 8
x + c(10, 20)
# [1] 11 22 13 24
# Avoid accidental recycling: check lengths where appropriate.
y <- c(10, 20, 30, 40)
stopifnot(length(x) == length(y))
x + y
# [1] 11 22 33 44Vectorization expresses an operation over positions without an explicit loop. When lengths differ, many operations recycle the shorter vector. R usually warns when the longer length is not a multiple of the shorter one, but exact recycling can occur without warning and still make no sense for the data. If an operation represents paired measurements, verify the correspondence between observations as well as their lengths.
Summaries and locating results
base_vector <- seq(1, 9, by = 2)
c(length_value = length(base_vector), addition = sum(base_vector),
mean_value = mean(base_vector), product = prod(base_vector),
minimum = min(base_vector), maximum = max(base_vector),
variance = var(base_vector), standard_deviation = sd(base_vector))
# length 5; sum 25; mean 5; product 945;
# minimum 1; maximum 9; variance 10; standard deviation 3.162278.
range(base_vector)
# [1] 1 9
diff(range(base_vector))
# [1] 8
cumsum(base_vector)
# [1] 1 4 9 16 25
vector02 <- c(10, 11, 12, 13, 14, 15, 2, 3, 4, 6, 5, 6,
6, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11)
sort(vector02, decreasing = FALSE)
sort(vector02, decreasing = TRUE)
order(vector02) # Ordering positions, rather than the values.
which.max(vector02) # First position of the maximum: 6.
which.min(vector02) # First position of the minimum: 7.
unique(vector02) # Distinct values in order of first appearance.
letters[1:10]
LETTERS[1:10]range() returns the minimum and maximum; their difference is the range width. which.max() and which.min() return the first position of an extreme, whereas max() and min() return its value. unique() removes repetitions and does not calculate eigenvalues. Eigenvalues concern linear operators and square matrices through a different mathematical question. letters and LETTERS are built-in vectors of the 26 basic Latin letters in lowercase and uppercase; they do not themselves include the full alphabet of every language.
s2 = [1/(n − 1)] ∑i=1n (xi − x̄)2
s = √s2
var() uses the divisor n − 1. sd() returns its square root in the original units. Sample variance is undefined with fewer than two available values.The divisor n − 1 reflects the loss of one degree of freedom when the mean is estimated from the same sample. Under independent sampling with finite variance, this correction gives an unbiased estimator of population variance. To describe the dispersion of an entire finite population using its average squared deviation, the appropriate divisor may instead be n. The choice depends on the statistical object and should be explicit.
Pseudorandom sampling and reproducibility
set.seed(20210911)
sample_without_replacement <- sample(1:20, size = 5, replace = FALSE)
set.seed(20210911)
repeat_sample <- sample(1:20, size = 5, replace = FALSE)
stopifnot(identical(sample_without_replacement, repeat_sample))
set.seed(20210911)
sample_with_replacement <- sample(1:3, size = 10, replace = TRUE)
simulated_normal <- rnorm(100, mean = 0, sd = 1)sample() selects from a specified population; replace = TRUE permits repeated selection of the same source position. rnorm() generates values according to a normal distribution using a pseudorandom generator. A seed fixes a state of that generator: it does not make a sample representative or, on its own, guarantee identical results across every R version and configuration. For exact reproduction, also record the version and, where necessary, RNGkind().
Unknown information
4. Missing Values and Statistical Decisions
A missing value indicates absent information at a position belonging to the data structure. NA abbreviates Not Available. Depending on the database design, a cell may be missing because of nonresponse, recording error, or structural absence; these causes have different meanings. Code identifies missing-value markers, but explaining why data are missing requires knowledge of the observation process.
| Object | Basic meaning | Check |
|---|---|---|
NA | A missing-value indicator, with typed variants. | is.na(x) |
NaN | A numerical result that is not a number, such as 0/0. | is.nan(x) |
NULL | The null object; may indicate the absence of a component. | is.null(x) |
"NA" | A two-letter string unless explicitly converted during import. | is.character(x) |
Inf | Floating-point infinity; not a missing observation. | is.infinite(x) |
vector03 <- c(10, 11, 12, 13, NA_real_, NA_real_)
is.na(vector03)
# [1] FALSE FALSE FALSE FALSE TRUE TRUE
sum(vector03)
# [1] NA
sum(vector03, na.rm = TRUE)
# [1] 46
length(vector03)
# [1] 6
sum(!is.na(vector03))
# [1] 4
is.na(c(NA_real_, NaN, Inf))
# [1] TRUE TRUE FALSE
is.nan(c(NA_real_, NaN, Inf))
# [1] FALSE TRUE FALSEIn many operations, an unknown input produces an unknown result. This is not a question of adding objects with incompatible units: if an addend is unknown, the sum may also be unknown. is.na() returns the missingness mask; x == NA is not the correct detection procedure. A string such as "NA" remains text until explicitly translated into a missing-value marker.
Excluding from a calculation, removing from an object, and replacing
# 1. Exclude from the calculation: the original object is unchanged.
observed_mean <- mean(vector03, na.rm = TRUE)
observed_mean
# [1] 11.5
# 2. Create another vector without the missing positions.
without_na <- vector03[!is.na(vector03)]
without_na_alternative <- as.vector(na.omit(vector03))
stopifnot(identical(without_na, without_na_alternative))
# 3. Explicit replacement: demonstrates an operation, not an imputation recommendation.
vector04 <- replace(vector03, which(is.na(vector03)), 0)
mean(vector04)
# [1] 7.666667
fruit <- c("apple", "orange", "grape", "banana")
replace(fruit, list = 2, values = "blueberry")
# [1] "apple" "blueberry" "grape" "banana"The na.rm = TRUE option changes which inputs a function considers without rewriting the vector. The selection x[!is.na(x)] constructs another vector and reduces its length. na.omit() may add attributes recording omitted positions. replace() returns a modified object, which must be assigned to retain it. which() turns a logical condition into the positions where it is true; many replacements can also use a logical mask directly.
Replacing missing values with zero changes both the mean and the meaning of the information: it turns “unknown” into “zero.” The example demonstrates this numerically. Statistical imputation requires justification of the missingness model and procedure; excluding cases can also introduce bias. Report how many values were used and consider whether missingness relates to the variable of interest.
all_missing <- c(NA_real_, NA_real_)
sum(all_missing, na.rm = TRUE)
# [1] 0
mean(all_missing, na.rm = TRUE)
# [1] NaN
available_summary <- function(x) {
if (!is.numeric(x)) stop("A numeric vector is required.")
if (any(!is.na(x) & !is.finite(x))) {
stop("Infinite values were found.")
}
observed <- x[!is.na(x)]
n <- length(observed)
c(n_total = length(x), n_observed = n,
n_missing = sum(is.na(x)),
mean_value = if (n > 0L) mean(observed) else NA_real_,
median_value = if (n > 0L) median(observed) else NA_real_,
variance = if (n > 1L) var(observed) else NA_real_,
standard_deviation = if (n > 1L) sd(observed) else NA_real_,
minimum = if (n > 0L) min(observed) else NA_real_,
maximum = if (n > 0L) max(observed) else NA_real_)
}
available_summary(vector03)Logic with unknown information
FALSE & NA
# [1] FALSE
TRUE | NA
# [1] TRUE
TRUE & NA
# [1] NA
FALSE | NA
# [1] NA
!NA
# [1] NAA conjunction is already false if one term is false, whatever the unknown value of the other term. A disjunction is already true if one term is true. In the other two cases, the available information is insufficient. This extension explains why a table containing only TRUE and FALSE does not describe the full logical behavior of R.
Documentation and references: [5]
Heterogeneous information
5. Coercion, Lists, and Components
Implicit coercion and explicit conversion
Coercion changes an object’s representation from one type to another. When combining ordinary atomic vectors, R seeks a common type; among the types used here, a useful hierarchy is logical → integer → double → complex → character. This hierarchy describes a combination rule; it does not prohibit explicit conversions in the opposite direction. Some conversions are possible, while others produce missing values or lose information.
typeof(c(TRUE, 2L))
# [1] "integer"
typeof(c(2L, 3.5))
# [1] "double"
c(1, "two", TRUE)
# [1] "1" "two" "TRUE"
as.numeric(c("12", "3.5"))
# [1] 12.0 3.5
as.integer(3.9)
# [1] 3
# A failed conversion produces a warning and an NA.
failed_conversion <- tryCatch(
as.numeric("text"),
warning = function(w) NA_real_
)
stopifnot(is.na(failed_conversion))Conversion to integer truncates the fractional part, which can alter a measurement. The text "12" has a numeric interpretation; "text" does not. A conversion warning should be investigated rather than hidden in a real analysis. Here it is caught only to allow the reader to execute the whole block and observe the result defined for that case.
Lists preserve different components
record <- list(
name = "Ana",
grades = c(90, 55, 89, 68),
active = TRUE,
metadata = list(group = "A", date = as.Date("2021-09-11"))
)
record["grades"] # A one-component list.
record[["grades"]] # The numeric vector stored in that component.
record$grades # Another way to extract it by name.
record[["grades"]][2]
# [1] 55
record$metadata$group
# [1] "A"
length(record)
# [1] 4
str(record)A list organizes heterogeneous objects without forcing them into one atomic type. It can contain vectors, other lists, matrices, and model results. [ preserves a list of selected components; [[ extracts a component; $ makes extraction by a literal name convenient. For programmatic access using a name stored in a variable, prefer record[[column_name]].
A named list can serve purposes similar to a dictionary, but its names may be duplicated and access follows R list semantics. Flattening it with unlist() may coerce values to a common type, removing the structural separation that preserved different components. It should therefore not be used as an automatic substitute for extracting a particular component.
Documentation and references: [6]
From positions to axes
6. Matrices, Operations, and Arrays
Creating a matrix and naming its axes
vm01 <- c(10, 7, 1, 6, 8, 3)
matrix01 <- matrix(data = vm01, nrow = 2, ncol = 3, byrow = TRUE)
colnames(matrix01) <- c("Col_1", "Col_2", "Col_3")
rownames(matrix01) <- c("Row_1", "Row_2")
matrix01
# Col_1 Col_2 Col_3
# Row_1 10 7 1
# Row_2 6 8 3
matrix(vm01, nrow = 2, ncol = 3, byrow = FALSE)
# [,1] [,2] [,3]
# [1,] 10 1 8
# [2,] 7 6 3
dim(matrix01)
# [1] 2 3
length(matrix01)
# [1] 6A matrix has two axes: rows and columns. data supplies the elements, nrow the number of rows, and ncol the number of columns. byrow = TRUE distributes the sequence across rows; FALSE, the default, proceeds down columns. This option determines where the initial sequence is placed; it does not sort values from smallest to largest. Atomic matrices have one common element type; list matrices also exist.
Combining rows and columns
matrix03 <- matrix(c(10, 1, 8, 7, 6, 3), nrow = 2,
ncol = 3, byrow = TRUE)
colnames(matrix03) <- colnames(matrix01)
rownames(matrix03) <- c("New_1", "New_2")
row_bound_matrix <- rbind(matrix03, matrix01)
column_bound_matrix <- cbind(matrix01, matrix03)
dim(row_bound_matrix)
# [1] 4 3
dim(column_bound_matrix)
# [1] 2 6rbind() stacks rows and requires compatible columns; cbind() adds columns and requires compatible rows. When combining data, verify the identity and order of units as well as dimensions. Two matrices with the same number of rows may refer to different people, or to the same people in a different order.
Inspecting values, constructing masks, and filtering
row_bound_matrix[2, 3]
# [1] 3
row_bound_matrix[, 3]
# New_1 New_2 Row_1 Row_2
# 8 3 1 3
mask <- row_bound_matrix[, 3] > 2
mask
# [1] TRUE TRUE FALSE TRUE
row_bound_matrix[mask, , drop = FALSE]
row_bound_matrix[row_bound_matrix[, 3] > 2, , drop = FALSE]
matrix01[1, , drop = FALSE] # Preserves a one-row matrix.
matrix01[1, ] # Simplifies to a vector.The expression M[, 3] > 2 produces a mask indicating which positions satisfy the condition. The expression M[mask, ] uses that mask to extract rows. Distinguishing these operations clarifies what each line returns. An empty index before the comma keeps all rows; an empty index after it keeps all columns. drop = FALSE preserves two dimensions when extraction selects a single row or column.
Axis totals, sorting, and transposition
apply(X = row_bound_matrix, MARGIN = 2, FUN = sum)
# Col_1 Col_2 Col_3
# 33 22 15
apply(X = row_bound_matrix, MARGIN = 1, FUN = sum)
# New_1 New_2 Row_1 Row_2
# 19 16 18 17
stopifnot(
isTRUE(all.equal(colSums(row_bound_matrix),
apply(row_bound_matrix, 2, sum))),
isTRUE(all.equal(rowSums(row_bound_matrix),
apply(row_bound_matrix, 1, sum)))
)
apply(row_bound_matrix, 2, sort)
apply(row_bound_matrix, 1, sort)
t(row_bound_matrix)apply() receives an object, the axis or axes organizing the calculations, and the function to apply. For sums, rowSums() and colSums() express the intention directly. If the applied function returns several values, apply() may reorganize the result’s dimensions; for example, when sorting by row, it may place each result in a column. Inspect dim() to confirm the resulting structure.
Sorting each column separately breaks the original relationships between variables in a row. If each row represents a person, this transformation can combine ages, heights, and measurements from different people. To sort observations while preserving rows, construct a single index with order() and apply it to every column. Transposition with t() exchanges axes; it does not sort values.
Elementwise multiplication and matrix multiplication
matrix01 * matrix03
# Col_1 Col_2 Col_3
# Row_1 100 7 8
# Row_2 42 48 9
matrix04 <- t(matrix03)
matrix01 %*% matrix04
# New_1 New_2
# Row_1 115 115
# Row_2 92 99
u <- c(1, 2, 3)
v <- c(4, 5, 6)
u * v
# [1] 4 10 18
sum(u * v)
# [1] 32
drop(crossprod(u, v))
# [1] 32(AB)ij = ∑k=1p aikbkj
The symbol * denotes elementwise multiplication, also called the Hadamard product for matrices of the same size. %*% denotes matrix multiplication. For numeric vectors, the Euclidean inner product sums products of corresponding positions. Distinguishing these three operations avoids assigning the ambiguous label “vector multiplication” to a single expression.
Arrays and their relationship to tensors
A <- array(1:12, dim = c(2, 3, 2),
dimnames = list(row = c("f1", "f2"),
column = c("c1", "c2", "c3"),
slice = c("t1", "t2")))
A[, , "t1"]
dim(A)
# [1] 2 3 2
length(A)
# [1] 12In R, an array organizes a vector through a dimension attribute. It can have one, two, or more axes. A matrix is the two-axis case. Creating an array does not amount to performing a tensor product: it constructs a computational representation. A numeric array can store tensor components relative to chosen bases, but an abstract tensor and the container holding its components are not conceptually identical.
What is measured and how it is encoded
7. Statistical Variables and Factors
Statistical classification describes a variable’s nature and the operations that make sense on its values. Computational classification describes storage and behavior in the language. The two are connected through encoding decisions; neither determines the other on its own. A number used as a person’s identifier does not become a quantity suitable for averaging merely because it is stored numerically.
| Statistical nature | Example | Possible representation in R | Interpretive criterion |
|---|---|---|---|
| Discrete numeric | Number of visits | integer or double | Separated values, often counts. |
| Continuous numeric | Height | double | A magnitude modeled on a continuum; measurement and storage have finite precision. |
| Nominal categorical | Type of clothing | factor or character | Categories without intrinsic order. |
| Ordinal categorical | Rating: poor, fair, good | ordered / factor | Meaningful order; distances between categories need not be equal. |
Clothing <- factor(c("Shirt", "Shoes", "Coat", "Socks"))
levels(Clothing)
# [1] "Coat" "Shirt" "Shoes" "Socks"
Response <- factor(c("Good", "Fair", "Fair", "Poor"),
levels = c("Poor", "Fair", "Good"),
ordered = TRUE)
Response
# [1] Good Fair Fair Poor
# Levels: Poor < Fair < Good
Response > "Fair"
# [1] TRUE FALSE FALSE FALSE
encoded_measurement <- factor(c("10", "20", "10"))
as.numeric(encoded_measurement)
# [1] 1 2 1
as.numeric(as.character(encoded_measurement))
# [1] 10 20 10A factor represents categories through integer codes and labels called levels. Alphabetical order in a nominal factor’s levels does not create a substantive hierarchy. In an ordered factor, order is deliberately specified using levels and ordered = TRUE. Even then, the distance between “Poor” and “Fair” need not equal that between “Fair” and “Good.”
If the labels represent numbers, first recover them as text and then convert them. If they represent genuine categories, converting their internal codes to numbers does not justify averaging the categories. Decisions about measurement scales precede function calls. Models that accept factors may construct contrasts from their levels; these contrasts also depend on ordering and the chosen configuration.
Documentation and references: [11]
Tables with statistical structure
8. Data Frames: Observations and Variables
A data.frame brings together variables with the same number of rows. In the usual arrangement of individual-level data, each row corresponds to an observational unit and each column to a variable. A cell contains that variable’s observed value for that unit. In longitudinal data, a row may represent a combination of unit and time, so specify what identifies a record before analyzing it.
Data_01 <- data.frame(
Height = c(1.65, 1.70, 1.89),
Hair_length = c("Short", "Medium", "Long"),
stringsAsFactors = FALSE
)
Height <- c(1.65, 1.70, 1.89)
Hair_length <- c("Short", "Medium", "Long")
Data_02 <- data.frame(Height, Hair_length,
stringsAsFactors = FALSE)
stopifnot(identical(Data_01, Data_02))
Data_03 <- data.frame(Height = Height, Hair = Hair_length,
stringsAsFactors = FALSE)
Data_03
# Height Hair
# 1 1.65 Short
# 2 1.70 Medium
# 3 1.89 LongThe first two constructions produce the same table through different ways of organizing the program. The third provides column names explicitly. Since R 4.0.0, data.frame() uses stringsAsFactors = FALSE by default; it is written here to make the decision visible. When a variable is categorical, deliberately construct a factor with appropriate levels.
| Feature | Atomic matrix | Data frame |
|---|---|---|
| Organization | Two axes; one common atomic type. | Columns that may have different types. |
| Frequent use | Algebraic computation and homogeneous arrays. | Tables of observations and variables. |
| Inspection | dim(), typeof(), str(). | str(), names(), and column classes. |
| Selection | M[i, j]. | d[i, j], d[[j]], d$variable. |
| Conversion | as.data.frame(M). | as.matrix(d), checking coercion. |
A data frame is a list with class data.frame and shared-row constraints; its columns can include logical and numeric types, text, factors, dates, and more complex structures supported by its methods. There is no general guarantee that converting a matrix into a data frame makes every filter or calculation faster. The appropriate structure depends on the information, the operations, and actual performance measurements.
Extracting columns, rows, and subsets
matrix_df <- as.data.frame(matrix01)
column1 <- matrix_df$Col_1
stopifnot(identical(column1, matrix_df[["Col_1"]]))
matrix_df[, 1] # A vector.
matrix_df[1] # A one-column data frame.
matrix_df[, 1, drop = FALSE] # Preserves the tabular structure.
matrix_df[2, , drop = FALSE]
matrix_df[, c("Col_1", "Col_2"), drop = FALSE]
matrix_df[, c(1, 2), drop = FALSE]
Data_01[Data_01$Height >= 1.70, , drop = FALSE]
names(Data_03) <- c("Height_m", "Hair")Selecting one column may return a vector or a table depending on the form of indexing. The distinction matters when writing functions expecting a particular structure. Col_1 and col_1 are different names. When a name is stored in a variable, [[ extracts that exact column and helps distinguish the object’s name from its textual contents.
Inspection before analysis
head(Data_01)
tail(Data_01)
str(Data_01)
summary(Data_01)
dim(Data_01)
nrow(Data_01)
ncol(Data_01)
vapply(Data_01, typeof, character(1))
colSums(is.na(Data_01))head() shows the first six rows by default and tail() the last six; when fewer exist, they show those available. str() describes structure and types. summary() produces summaries depending on each column’s class. dim() returns rows and columns; nrow() and ncol() inspect them separately. View() opens an interactive viewer when the environment supports it, but reproducible checks must be preserved as instructions.
Documentation and references: [10]
From file to object
9. Importing, Exporting, and Preserving Data
Importing means interpreting a file’s bytes and constructing R objects. Exporting means writing a representation of those objects in another format. The extension provides a clue, but the procedure must match the actual contents, separators, header, decimal symbol, and missing-value markers. A .txt file may have a header or lack one; a CSV may use commas or, under regional conventions, semicolons. None of these properties is determined by the filename alone.
Paths, directories, and projects
# A temporary directory makes the example independent of existing files.
example_directory <- tempfile("r_chapter_")
dir.create(example_directory)
csv_path <- file.path(example_directory, "data.csv")
file.exists(csv_path)
# [1] FALSE: the file has not been written yet.
# Example paths stored as text; the files need not exist.
relative_path <- file.path("data", "Data01.xlsx")
windows_path <- "C:/Users/User/Downloads/Data01.xlsx"An absolute path identifies a file from the filesystem root. A relative path is interpreted from the working directory. A relative path can be sufficient when the file is in the appropriate location; writing a full path is not always necessary. file.path() constructs paths with suitable separators. Forward slashes / can be used on Windows; a literal backslash inside a string must be escaped. Hierarchical folders provide a useful visual analogy, but are not themselves inheritance or encapsulation in object-oriented programming.
Text, CSV, and regional conventions
write.csv(Data_01, file = csv_path, row.names = FALSE,
na = "", fileEncoding = "UTF-8")
reimported <- read.csv(csv_path, header = TRUE, sep = ",", dec = ".",
na.strings = c("", "NA"),
stringsAsFactors = FALSE,
fileEncoding = "UTF-8")
stopifnot(isTRUE(all.equal(Data_01, reimported)))
text_path <- file.path(example_directory, "data.txt")
write.table(Data_01, file = text_path, sep = "\t", dec = ".",
row.names = FALSE, col.names = TRUE, quote = TRUE,
na = "", fileEncoding = "UTF-8")
from_text <- read.table(text_path, header = TRUE, sep = "\t", dec = ".",
na.strings = c("", "NA"),
stringsAsFactors = FALSE,
fileEncoding = "UTF-8")
regional_path <- file.path(example_directory, "regional_data.csv")
write.csv2(Data_01, file = regional_path, row.names = FALSE)
from_regional <- read.csv2(regional_path, stringsAsFactors = FALSE)
stopifnot(isTRUE(all.equal(Data_01, from_text)),
isTRUE(all.equal(Data_01, from_regional)))| Option | Question it answers |
|---|---|
header / col.names | Does the first row contain column names when reading / will those names be written? |
sep | Which character separates fields? |
dec | Which symbol marks the decimal part? |
na.strings / na | Which strings will be read as missing / how will missing values be written? |
skip | How many initial lines will be skipped before interpreting the data? |
row.names | Are additional row identifiers interpreted or written? |
fileEncoding | Which character encoding is used? |
read.csv() defaults to a comma separator and decimal point; read.csv2() uses a semicolon separator and decimal comma. The option must match the file. For text without a header, use header = FALSE and assign names afterward. On export, row.names = FALSE avoids adding an index column that often reappears as an unwanted variable on reimport.
Excel: sheets, formats, and preliminary rows
# Install these packages once if needed:
# install.packages(c("readxl", "writexl"))
if (requireNamespace("readxl", quietly = TRUE) &&
requireNamespace("writexl", quietly = TRUE)) {
excel_path <- file.path(example_directory, "All_Data.xlsx")
writexl::write_xlsx(
list(Datos01 = Data_01, Datos02 = Data_03), path = excel_path
)
IMP_01 <- readxl::read_excel(excel_path, sheet = "Data01")
IMP_02 <- readxl::read_excel(excel_path, sheet = "Data02")
IMP_first <- readxl::read_excel(excel_path)
# One table per file:
writexl::write_xlsx(IMP_01,
path = file.path(example_directory, "Export1.xlsx"))
writexl::write_xlsx(IMP_02,
path = file.path(example_directory, "Export2.xlsx"))
stopifnot(nrow(IMP_01) == nrow(Data_01))
} else {
message("The Excel example requires the readxl and writexl packages.")
}read_excel() accepts .xls and .xlsx and attempts to detect the format. read_xls() and read_xlsx() specify it directly. The file must exist at the declared path and its actual format must match the reading procedure. Omitting sheet uses the first sheet even when the workbook contains others. readxl returns a tibble, a tabular class inheriting from data.frame, with printing and indexing differences from a base data frame.
skip = 3 is appropriate when the first three rows contain preliminary material and the header follows. Determine that number by inspecting the particular file. When an explicit range is specified, its rules take precedence over other sheet-geometry options. To export several sheets with writexl, provide a named list, one component per sheet. This approach avoids requiring Java, although other valid alternatives exist for more complex Excel editing and formatting tasks.
| Operation in a graphical interface | Instruction that must be recorded |
|---|---|
| Select a file | Path and verification with file.exists(). |
| Select a sheet | The sheet argument or a declared range. |
| Skip preliminary rows | skip, based on inspection of the document. |
| Declare headers and missingness | col_names, header, na, or na.strings. |
| Confirm the import | str(), dim(), names, types, and missing-value counts. |
RStudio’s import wizards can help configure a reading operation. To preserve the procedure, copy the generated code into the working script and inspect its arguments. The pane does not replace a record of the operation. An RDS file provides another option: it preserves an R object with its classes and attributes, whereas CSV preserves a textual table and may lose those properties.
rds_path <- file.path(example_directory, "data.rds")
saveRDS(Data_01, file = rds_path)
restored_data <- readRDS(rds_path)
stopifnot(identical(Data_01, restored_data))Connections with mathematics
10. Functions, Composition, and Functionals
A function and its call
An R function receives arguments, evaluates instructions in an environment, and returns a value. It may also produce effects such as displaying text or writing a file. The returned result and those effects are distinguishable. In mathematics, a function assigns each element of a domain to exactly one element of a codomain; a particular computational function may fail, fail to terminate, or depend on external state. Identifying it with a mathematical mapping therefore requires specifying its domain, conditions, and effects.
square <- function(x) {
x^2
}
square(3)
# [1] 9
square(c(1, 2, 3))
# [1] 1 4 9
is.function(square)
# [1] TRUE
formals(square)
body(square)In function(x), x is a formal argument. In square(3), the supplied value is 3. The function is vectorized because the ^ operation in its body accepts vectors. A user-defined function may be short even if built-in functions perform related tasks; defining one helps express an intention, validate inputs, and automate a repeatable procedure.
Composing calls is different from passing functions
sum(c(1:50))
# [1] 1275
f <- function(x) 2 * x
g <- function(x) x + 1
f(g(3))
# [1] 8
apply_to <- function(x, fun) {
fun(x)
}
apply_to(1:5, mean)
# [1] 3
apply_to(1:5, sum)
# [1] 15In sum(c(1:50)), a vector is constructed and its elements are then summed. sum() receives the resulting vector, not the function c as an object. This structure illustrates nested calls and composition of operations. By contrast, apply_to(1:5, mean) supplies the function mean itself as an argument. The second structure is an example of a higher-order function.
∑i=1n i = n(n + 1)/2
| Concept | Distinguishing property | Example |
|---|---|---|
| Nested calls | One call appears inside another. | sum(c(1, 2, 3)) |
| Inner function | Its definition occurs inside another function. | A function created by make_offset(). |
| Higher-order function | Receives a function or returns a function. | apply_to(x, mean). |
| Functional in R programming | Receives a function and produces results by applying it. | lapply(list_object, mean). |
| Mathematical functional | A scalar-valued mapping on a suitable function space or other appropriate objects. | A function is mapped to its definite integral. |
| Control-flow construct | Determines which instructions execute and when. | if, for, while. |
Mathematical functionals and numerical evaluation
𝓘(t ↦ t2) = 1/3
integrand <- function(t) t^2
integral_result <- integrate(integrand, lower = 0, upper = 1)
integral_result$value
# [1] 0.3333333
integral_result$abs.error
stopifnot(abs(integral_result$value - 1/3) < 1e-12)
groups <- list(A = c(1, 2, 3), B = c(4, 5, 6))
lapply(groups, mean)
vapply(groups, mean, numeric(1))
# A B
# 2 5The mathematical integral and its approximation algorithm are different objects: integrate() returns a list containing an approximate value, an estimated absolute error, and other components. lapply() applies a function to each component and preserves a list of results. vapply() adds an expected output type and shape; here each group must produce a numeric scalar. apply(), already used with matrices, also receives a function through FUN.
The uses of “functional” in programming and functional analysis are related but not universally identical. The computational terminology allows vector-valued outputs and data objects that require further specification to be interpreted within a mathematical function space. A precise connection identifies domain, operation, and codomain in each case, rather than inferring equivalence merely because one instruction contains another.
Operators and primitive functions
Many operators can be called as functions, for example `+`(2, 3). This does not make every built-in function an operator or every operator a low-level primitive function. is.primitive() inspects that technical property. An operator can be unary, binary, or user-defined, and may act on objects other than numbers. Its meaning depends on the operation and, when dispatch applies, on the classes of its arguments.
`+`(2, 3)
# [1] 5
is.primitive(sum)
# [1] TRUE
is.primitive(mean)
# [1] FALSEOrganizing execution
11. Conditions, Loops, and Student Grades
Scalar decisions and vectorized ifelse()
grades <- c(90, 55, 89, 68)
average_grade <- mean(grades)
average_grade
# [1] 75.5
if (average_grade >= 70) {
cat("Passed the course\n")
} else if (average_grade >= 60) {
cat("Resit examination\n")
} else {
cat("Failed the course\n")
}
demo_averages <- c(75.5, 66.75, 47.25, NA_real_)
ifelse(is.na(demo_averages), "No information",
ifelse(demo_averages >= 70, "Passed",
ifelse(demo_averages >= 60, "Resit", "Failed")))
# [1] "Passed" "Resit" "Failed" "No information"This illustration assumes a grading scheme in which 70 or above is a pass, 60 to below 70 allows a resit examination, and below 60 is a fail. Adapt these thresholds to the applicable regulations. Once the first condition has excluded values of 70 or above, the second need only ask whether 60 has been reached. Write a closing brace and else together as } else { to avoid parsing problems when running the code.
if requires a nonmissing condition of length one. ifelse() selects elements over a vector of conditions and returns an object with its shape. It is not a universal replacement for if: output types and attributes require inspection, and evaluated branches may calculate values for positions that are not ultimately selected. Scalar logic and side effects normally call for if.
What cat(), print(), and return() mean
| Instruction | Purpose | Relevant result |
|---|---|---|
cat(..., sep = " ", file = "") | Concatenate and write a textual representation. | Normally returns NULL invisibly; produces output. |
print(x) | Display an object through the method appropriate to its class. | Usually returns the object invisibly. |
return(x) | End a function’s evaluation and return a value. | The caller can use the returned value. |
| Last expression in the body | Implicit return when no earlier return is used. | Its value becomes the function result. |
In its usual uses, cat() accepts atomic arguments and names; a nonempty list needs a suitable representation or a function such as print(). The options sep, fill, labels, file, and append control separators, line filling, and destination. Writing a message does not automatically construct a reusable result. An analysis function commonly returns a table or list and leaves presentation to another instruction.
Iterating over known values
for (x in seq_len(5)) {
print(round(sqrt(x), digits = 2))
}
# [1] 1
# [1] 1.41
# [1] 1.73
# [1] 2
# [1] 2.24
round(sqrt(seq_len(5)), digits = 2)
# [1] 1.00 1.41 1.73 2.00 2.24
Accumulator <- 0
for (i in seq_len(5)) {
if (i %% 2 == 0) {
Accumulator <- Accumulator + i
}
}
Accumulator
# [1] 6A for loop assigns successive elements of a collection to the iteration variable and executes its body. The first example computes square roots of independent values: it is not recursion and does not use the previous root as the next input. Rounding to two decimal places controls numerical presentation; it does not achieve “infinitesimal precision.” The sum accumulator starts at zero because zero is the additive identity, not because the first even number is unknown.
Three students, four assessments, and a tabular result
grades1 <- c(90, 55, 89, 68)
grades2 <- c(70, 65, 65, 67)
grades3 <- c(40, 59, 70, 20)
grade_matrix <- rbind(Ana = grades1, Bruno = grades2, Carla = grades3)
colnames(grade_matrix) <- c("Exam_1", "Exam_2", "Exam_3", "Project")
grade_averages <- rowMeans(grade_matrix)
grade_averages
# Ana Bruno Carla
# 75.50 66.75 47.25
status <- character(length(grade_averages))
for (j in seq_along(grade_averages)) {
current_average <- grade_averages[j]
if (is.na(current_average)) {
status[j] <- "No information"
} else if (current_average >= 70) {
status[j] <- "Passed"
} else if (current_average >= 60) {
status[j] <- "Resit"
} else {
status[j] <- "Failed"
}
}
course_results <- data.frame(
student = names(grade_averages), average = unname(grade_averages), status,
stringsAsFactors = FALSE
)
course_resultsEach row identifies a student, so averages are calculated by row. If the interpretation changed so that each row represented a subject, the meaning of averaging that row would need review. Displaying three vectors separated by semicolons does not construct a matrix; rbind() does. Equal weights are an assumption: when assessments have different weights, use a weighted mean with coherent weights and a positive total weight.
weighted.mean(grades1, w = c(0.20, 0.20, 0.20, 0.40))
# [1] 74while, repeat, break, and next
# while: check before each execution.
counter <- 0L
while (counter < 3L) {
counter <- counter + 1L
}
stopifnot(counter == 3L)
# repeat: an explicit exit using break.
counter <- 0L
repeat {
counter <- counter + 1L
if (counter >= 3L) break
}
# next: skip the rest of the current iteration.
even_values <- integer(0)
for (i in seq_len(6)) {
if (i %% 2 != 0) next
even_values <- c(even_values, i)
}
even_values
# [1] 2 4 6while may never execute its body if its initial condition is false. repeat needs an explicit exit or may continue indefinitely. break exits the innermost loop; next advances to its next iteration. These are language control-flow constructs, not object classes. Clarity improves when the chosen structure expresses the actual repetition criterion and unnecessary nested conditions are reduced.
Automating without hiding assumptions
12. Custom Functions and Validation
Four decisions when defining a function
Choose a name expressing the task; declare arguments and defaults; build the calculation and its checks; and return an object with a useful structure. Braces delimit the body. return() may appear before the last line to end evaluation early; it is neither a compulsory typographical closing marker nor a printing instruction. Describe accepted inputs and the conditions that produce errors.
validate_integer <- function(x, minimum = 0, name = "x", maximum = 1e6) {
if (length(x) != 1L || !is.numeric(x) || is.na(x) ||
!is.finite(x) || x != floor(x) || x < minimum || x > maximum) {
stop(name, " must be a scalar integer between ", minimum, " and ", maximum, ".")
}
invisible(TRUE)
}
sum_even <- function(n) {
validate_integer(n, minimum = 0, name = "n")
accumulator <- 0
for (i in seq_len(n)) {
if (i %% 2 == 0) accumulator <- accumulator + i
}
accumulator
}
sum_even(50)
# [1] 650
sum_even(0)
# [1] 0The one-million limit keeps this iterative exercise manageable; it is not a mathematical boundary of the problem. Negative, fractional, infinite, missing, and incorrectly sized inputs are rejected. seq_len(0) creates an empty traversal and correctly preserves a zero sum. The function returns a number that can be used in further calculations.
2 + 4 + ⋯ + 2m = 2∑j=1mj = m(m + 1)
sum_even_formula <- function(n) {
validate_integer(n, minimum = 0, name = "n")
m <- floor(n / 2)
m * (m + 1)
}
stopifnot(sum_even(50) == sum_even_formula(50),
sum_even(0) == sum_even_formula(0),
sum_even(5) == sum_even_formula(5))
parity <- function(x) {
if (!is.numeric(x) || anyNA(x) || any(!is.finite(x)) ||
any(x != floor(x)) || any(abs(x) > 2^53 - 1)) {
stop("Finite integers exactly representable as double are required.")
}
ifelse(x %% 2 == 0, "Even", "Odd")
}
parity(1:6)
# [1] "Odd" "Even" "Odd" "Even" "Odd" "Even"
parity(50)
# [1] "Even"The parity function accepts a vector or a single value. Traversing 1:n answers “What is the parity of each integer in this traversal?”; evaluating only n answers “What is the parity of this value?” The vectorized function preserves the distinction through its input. The numerical bound avoids claiming exact parity for values whose representation no longer guarantees all consecutive integers.
A mathematical laboratory
13. Collatz: Orbits, Iteration, and Recursion
The Collatz problem allows us to study an elementary rule whose evolution can produce long trajectories. For positive integers, the transformation divides an even value by two and replaces an odd value with three times that value plus one. The assertion of eventual arrival at 1 concerns every positive seed. Traversing particular examples verifies those examples and explores patterns; it does not turn exploration into a universal proof.
n0 = a, nk+1 = T(nk)
One transition and a trajectory algorithm
exact_integer_limit <- 2^53 - 1
validate_collatz <- function(n) {
if (length(n) != 1L || !is.numeric(n) || is.na(n) ||
!is.finite(n) || n < 1 || n != floor(n) ||
n > exact_integer_limit) {
stop("The seed must be an exactly representable positive integer.")
}
invisible(TRUE)
}
collatz_step <- function(n) {
validate_collatz(n)
if (n %% 2 == 0) return(n / 2)
if (n > floor((exact_integer_limit - 1) / 3)) {
stop("3*n + 1 would exceed the chosen range of exact integers.")
}
3 * n + 1
}
collatz_iterative <- function(n, max_steps = 10000L) {
validate_collatz(n)
validate_integer(max_steps, minimum = 0, name = "max_steps", maximum = 1e6)
trajectory <- numeric(max_steps + 1)
trajectory[1] <- n
steps <- 0L
current <- as.double(n)
while (current != 1 && steps < max_steps) {
current <- collatz_step(current)
steps <- steps + 1L
trajectory[steps + 1L] <- current
}
trajectory <- trajectory[seq_len(steps + 1L)]
list(seed = n, trajectory = trajectory,
iterations = steps, terms = length(trajectory),
maximum = max(trajectory), reached_one = current == 1,
status = if (current == 1) "Reached 1" else "Step limit reached")
}
c8 <- collatz_iterative(8)
c27 <- collatz_iterative(27)
c8$trajectory
# [1] 8 4 2 1
c(c27$iterations, c27$terms, c27$maximum)
# [1] 111 112 9232
stopifnot(c8$iterations == 3L, c8$terms == 4L,
c27$iterations == 111L, c27$terms == 112L,
c27$maximum == 9232, c27$reached_one)The transition function performs one transformation. The iterative function organizes transformations and returns a list with results and termination status. Each call creates its own trajectory, independent of a global vector defined in another session. The seed occupies the first position, followed by each new state. This decision removes counting ambiguities.
| Seed | Trajectory or summary | Terms, including start and end | Transitions to 1 | Maximum |
|---|---|---|---|---|
| 1 | The stopping destination has already been reached. | 1 | 0 | 1 |
| 8 | 8, 4, 2, 1 | 4 | 3 | 8 |
| 27 | Full trajectory stored in c27$trajectory. | 112 | 111 | 9232 |
A stopped trajectory with L terms has L − 1 transitions. The distinction also matters for a = 1: one term is recorded and no transition is performed. c(a, v) concatenates values and does not remove duplicates; if v already contains a, that value appears twice unless the algorithm prevents it. This implementation uses a single trajectory with explicit positions.
unfinished <- collatz_iterative(27, max_steps = 10)
unfinished$status
# [1] "Step limit reached"
stopifnot(!unfinished$reached_one, unfinished$iterations == 10L)
seed_one <- collatz_iterative(1, max_steps = 0)
stopifnot(seed_one$reached_one, seed_one$iterations == 0L)
plot(seq_along(c27$trajectory) - 1L, c27$trajectory,
type = "l", col = "#8b4513", lwd = 1.5,
xlab = "Iteration k", ylab = "State n[k]",
main = "Collatz orbit starting at 27")The line makes the plot easier to follow but does not turn its states into a continuous flow. Starting at 27, the trajectory reaches values far above its initial value before descending to 1. A step bound stops an exploration; reaching it reports a limitation of the executed traversal, not a counterexample. Exploring arbitrarily large integers would require exact arbitrary-precision arithmetic and a suitably adapted implementation.
The recursive version: a base case and a call to itself
collatz_recursive <- function(n, max_steps = 300L) {
validate_collatz(n)
validate_integer(max_steps, minimum = 0, name = "max_steps", maximum = 300)
walk <- function(current, remaining, accumulator) {
accumulator <- c(accumulator, current)
if (current == 1 || remaining == 0L) return(accumulator)
walk(collatz_step(current), remaining - 1L, accumulator)
}
trajectory <- walk(as.double(n), max_steps, numeric(0))
reached <- tail(trajectory, 1) == 1
list(seed = n, trajectory = trajectory,
iterations = length(trajectory) - 1L,
terms = length(trajectory), maximum = max(trajectory),
reached_one = reached,
status = if (reached) "Reached 1" else "Step limit reached")
}
r8 <- collatz_recursive(8)
r27 <- collatz_recursive(27)
stopifnot(identical(c8$trajectory, r8$trajectory),
identical(c27$trajectory, r27$trajectory))The inner function walk() records the state and checks a stopping case. To continue, it calls itself with the next state, one fewer available step, and the accumulated trajectory. The object returned by the inner call becomes the outer call’s result. An argument need not be renamed simply because another function uses the same name: every call creates its own evaluation environment.
The recursive example deliberately uses a lower limit. Available depth depends on settings and environmental resources, so a step limit does not guarantee that every R implementation permits that call depth. R does not guarantee tail-call optimization. For long trajectories, the iterative version avoids accumulating recursive frames and is usually more appropriate.
What the call stack shows
Starting at 8 leaves calls pending for states 8, 4, and 2 before the state-1 base case is resolved. Return resolves the most recent call first and earlier calls afterward: this is LIFO, last in, first out, also called FILO. A stack-of-plates analogy helps visualize the order, but does not establish that recursion was designed to optimize cache memory. Call stacks and cache policies are different concepts.
A program that executes A1, then A2, and then calls itself again without a stopping condition leaves any subsequent instruction pending. Thus a final instruction to print “Hello, Fernanda” is not reached in an idealized execution continuing indefinitely. A real system may instead fail earlier through excessive call depth or resource use. A useful algorithm needs a base case or an explicit exit.
Formalizing the trajectory without assuming termination
𝒪(a) = (a, T(a), …, Tτ(a)(a))
A recursive formula saying only “keep calling until 1 is reached” describes a procedure, but termination must be established for it to represent a total function on every accepted seed. Moreover, returning only the scalar 1 does not preserve the trajectory, which requires another codomain, such as finite sequences or lists. Separating the transformation T, hitting time, and trajectory avoids conflating different objects in one notation.
A closer look at the language
14. Environments, Classes, and Expressions
The advanced concepts in this section clarify what R does when it evaluates a function, selects a method, or constructs an expression. They follow the first examples so that they can be linked to familiar operations. These are different explanatory levels: a storage type, a class, a reserved word, and a call are not interchangeable, although they may participate in one instruction.
Environments and lexical scope
offset <- 100
make_offset <- function(delta) {
force(delta)
function(x) x + delta
}
add_two <- make_offset(2)
add_two(5)
# [1] 7
offset
# [1] 100The returned function can look up delta in the environment where it was created. This combination of function and environment is called a closure. Ordinary assignment inside a function establishes names in the call environment; looking up a name absent there proceeds through enclosing environments according to lexical scope. Not every object accessible outside a function belongs to the global environment: there may be enclosing function environments and package environments.
force(delta) explicitly evaluates the argument before constructing the inner function; R uses lazy argument evaluation through promises. The example illustrates a function factory: it receives a number and returns a function. Its output makes it a higher-order function. Local objects from different calls do not conflict merely because they share a name.
Types, classes, and methods
date <- as.Date("2021-09-11")
c(type = typeof(date), mode_value = mode(date), class_value = class(date))
# type: "double"; mode: "numeric"; class: "Date".
measurement <- structure(1.65, class = "height_m")
print.height_m <- function(x, ...) {
cat(format(unclass(x)), "m\n")
invisible(x)
}
print(measurement)
# 1.65 m
inherits(measurement, "height_m")
# [1] TRUEtypeof() identifies the internal type. mode() supplies a historical classification that can group types; for example, integers and doubles have numeric mode. class() participates in method behavior. The date example stores numeric information, but its class allows it to be printed and interpreted as a date. In the height example, an S3 print() method defines how the object is displayed.
A generic function selects a method through a dispatch mechanism. In S3, many generics use UseMethod(); S4 has formal definitions of classes, methods, and inheritance, with dispatch that may depend on several arguments. A description of one system should not be projected onto all the others. methods::extends() examines inheritance relationships in the formal system, and genericFunction is an S4 class, not the universal definition of every generic call in R.
The image of a class as a blueprint or prototype for objects is a teaching aid suited to certain object-oriented models. R contains systems with different properties. A computational class is also not identical to a set-theoretic class: the latter is not simply a set of transfinite cardinality. In theories distinguishing sets and proper classes, many sets are already infinite or have transfinite cardinality without being proper classes.
Reserved words and names that mask functions
Reserved words include if, else, repeat, while, function, for, in, next, and break. Reserved constants include TRUE, FALSE, NULL, Inf, NaN, and typed variants of NA, alongside special names associated with .... Natural numbers are not a collection of reserved words. Ordinary function names are not all reserved: other assignments can mask them, often without an automatic warning preventing that design mistake.
# Avoid giving an object the name of a frequently used function.
my_data <- c(1, 2, 3)
base::sum(my_data)
# [1] 6
stats::median(my_data)
# [1] 2A package contains functions, data, and documentation. An R library is a directory where packages are installed; a repository, such as CRAN, distributes packages. install.packages() installs; library() loads and attaches a package; requireNamespace() checks availability and loads its namespace without attaching it. Distinguishing these concepts prevents confusion between a distribution network, packages, and installed directories.
The … argument and construction of names
sum_with_options <- function(x, ...) {
sum(x, ...)
}
sum_with_options(c(1, NA_real_, 3), na.rm = TRUE)
# [1] 4
named_table <- data.frame(height_m = c(1.65, 1.70))
names(named_table)
# [1] "height_m"... receives or forwards additional arguments. In data.frame(), column arguments may take the form name = value; when a name is omitted, the function can construct one from the supplied expression and adjust its syntax. Explicit column names clarify the structure and avoid dependence on name-transformation rules.
Calls, expressions, and deparse()
call_object <- call("sum", c(1, 2, 3))
typeof(call_object)
# [1] "language"
eval(call_object)
# [1] 6
expression_object <- expression(1 + 2, sqrt(9))
typeof(expression_object)
# [1] "expression"
eval(expression_object)
# [1] 3
call_text <- deparse(call_object)
call_text
# [1] "sum(c(1, 2, 3))"call() constructs a call object whose first argument identifies the function and whose remaining arguments are incorporated into the call. Its arguments are evaluated while constructing it, but the resulting call has not yet been executed. eval() evaluates it in an environment. Calling an already defined function and defining a function are different operations: call() does not replace a definition using function().
expression() stores expressions without evaluating them; evaluating the entire vector returns the value of the last expression. deparse() produces a textual representation of an expression. parse() turns text into language expressions, but converting text to code and evaluating it requires control over its provenance. These are metaprogramming tools, not prerequisites for creating your first data tables.
From economic data to analysis
15. Application: GDP Growth in Central America
This application uses the World Bank indicator NY.GDP.MKTP.KD.ZG, annual GDP growth expressed as a percentage. The objective is to select Belize, Guatemala, El Salvador, Honduras, Nicaragua, Costa Rica, and Panama; preserve observations by year; inspect missingness; and construct summaries with explicit meanings. A download’s size and time coverage depend on the particular file and are not permanent properties of the indicator.
An export may combine countries, other economies, and regional or income-group aggregates. Accordingly, each row is not interpreted as a country, and a row count is not treated as the number of countries in the world. The usual layout also includes metadata columns before the year columns. These text columns are not errors to be removed indiscriminately: they identify information and allow the correct units to be selected.
A complete teaching sample with explicit provenance
ca_codes <- c("BLZ", "GTM", "SLV", "HND", "NIC", "CRI", "PAN")
ca_names <- c("Belize", "Guatemala", "El Salvador", "Honduras",
"Nicaragua", "Costa Rica", "Panama")
demo_wb <- data.frame(
"Country Name" = ca_names,
"Country Code" = ca_codes,
"Indicator Name" = "GDP growth (annual %)",
"Indicator Code" = "NY.GDP.MKTP.KD.ZG",
"2018" = c(2, 3, 2, 4, -4, 3, 4),
"2019" = c(1, 4, 3, 3, -3, 2, 3),
"2020" = c(-12, -2, -8, -9, -2, -4, -16),
"2021" = c(10, 8, 10, 12, 10, 8, 15),
"2022" = c(7, 4, 3, 4, NA_real_, 5, 11),
check.names = FALSE, stringsAsFactors = FALSE
)
stopifnot(nrow(demo_wb) == 7L, ncol(demo_wb) == 9L)Three-letter codes provide a selection stable against changes in row order. The table uses check.names = FALSE to preserve year headings as written. If a reading procedure changes 2020 into X2020, explicitly correct or interpret that transformation. The procedure below expects four-digit year headings and reports an error if none are found.
Validating a download and separating metadata from years
convert_wb_column <- function(x) {
text_value <- trimws(as.character(x))
missing_value <- is.na(x) | text_value %in% c("", "..", "NA", "N/A")
text_value[missing_value] <- NA_character_
number <- suppressWarnings(as.numeric(text_value))
if (any(!missing_value & is.na(number))) {
stop("A year column contains text not recognized as a number or a missing value.")
}
if (any(!is.na(number) & !is.finite(number))) {
stop("A year column contains nonfinite values.")
}
number
}
prepare_growth <- function(input_data, codes = ca_codes,
start_year = NULL, end_year = NULL) {
d <- as.data.frame(input_data, check.names = FALSE,
stringsAsFactors = FALSE)
required_columns <- c("Country Name", "Country Code", "Indicator Code")
if (!all(required_columns %in% names(d))) {
stop("Required identification columns are missing.")
}
if (anyDuplicated(names(d))) stop("Column names are duplicated.")
if (anyNA(codes) || anyDuplicated(codes) || length(codes) == 0L) {
stop("Requested codes must be unique, nonmissing, and nonempty.")
}
selection <- !is.na(d[["Indicator Code"]]) &
d[["Indicator Code"]] == "NY.GDP.MKTP.KD.ZG" &
d[["Country Code"]] %in% codes
d <- d[selection, , drop = FALSE]
if (anyDuplicated(d[["Country Code"]])) {
stop("There is more than one row per code for the selected indicator.")
}
position <- match(codes, d[["Country Code"]])
if (anyNA(position)) {
stop("Not all requested codes were found.")
}
d <- d[position, , drop = FALSE]
year_names <- names(d)[grepl("^[0-9]{4}$", names(d))]
if (length(year_names) == 0L) stop("No year columns were found.")
years <- as.integer(year_names)
sort_index <- order(years)
years <- years[sort_index]
year_names <- year_names[sort_index]
if (!is.null(start_year)) {
validate_integer(start_year, minimum = 0, name = "start_year", maximum = 9999)
}
if (!is.null(end_year)) {
validate_integer(end_year, minimum = 0, name = "end_year", maximum = 9999)
}
if (!is.null(start_year) && !is.null(end_year) && start_year > end_year) {
stop("The start year cannot be later than the end year.")
}
keep <- rep(TRUE, length(years))
if (!is.null(start_year)) keep <- keep & years >= start_year
if (!is.null(end_year)) keep <- keep & years <= end_year
years <- years[keep]
year_names <- year_names[keep]
if (length(years) == 0L) stop("The interval contains no available years.")
values <- lapply(d[year_names], convert_wb_column)
M <- as.matrix(as.data.frame(values, check.names = FALSE))
dimnames(M) <- list(codes, as.character(years))
metadata <- d[, required_columns, drop = FALSE]
rownames(metadata) <- NULL
long_data <- data.frame(
country = rep(d[["Country Name"]], each = length(years)),
code = rep(codes, each = length(years)),
year = rep(years, times = length(codes)),
growth_pct = as.vector(t(M)),
stringsAsFactors = FALSE
)
list(metadata = metadata, years = years,
matriz = M, long_data = long_data)
}
ca <- prepare_growth(demo_wb, start_year = 2018, end_year = 2022)
dim(ca$matriz)
# [1] 7 5
head(ca$long_data)
stopifnot(identical(rownames(ca$matriz), ca_codes),
nrow(ca$long_data) == 35L, sum(is.na(ca$matriz)) == 1L)The function converts only annual columns and checks for unrecognized strings. Suppressing conversion warnings is paired with a check that stops the analysis when unexpected losses appear: the problem is not hidden. Metadata are retained separately. Long format has one row per country-year; the matrix preserves one row per country and one column per year. Both layouts preserve each measurement’s identity.
The transposition used to build long format organizes the order in which cells are traversed. It does not “fix” year names, nor is it needed because a named vector cannot be averaged. Names are attributes compatible with numeric computation. To extract a series from the matrix, select its row; remove names only when another operation requires it.
Guatemala <- ca$matriz["GTM", ]
mean(Guatemala)
# [1] 3.4
unname(Guatemala)
# [1] 3 4 -2 8 4
ca_summary <- t(vapply(seq_len(nrow(ca$matriz)),
function(i) available_summary(ca$matriz[i, ]),
numeric(9)))
rownames(ca_summary) <- rownames(ca$matriz)
ca_summary
empirical_mode <- function(x) {
x <- x[!is.na(x)]
if (length(x) == 0L) return(numeric(0))
frequencies <- table(x)
as.numeric(names(frequencies)[frequencies == max(frequencies)])
}
lapply(seq_len(nrow(ca$matriz)),
function(i) empirical_mode(ca$matriz[i, ]))The mean and median summarize different aspects of the level of rates; variance and standard deviation describe dispersion over the period. Their interpretation does not make years independent observations by definition. The empirical mode function returns every value tied for highest frequency, so several may be returned. With continuous measurements containing many distinct values, this mode may be uninformative and highly sensitive to rounding. R’s mode() inspects a computational mode; it does not calculate the statistical mode.
Four economic questions, four operations
| Question | Appropriate operation | Interpretive limitation |
|---|---|---|
| What was a country’s observed average annual rate? | Arithmetic mean of available rates. | Report period and coverage; this is not cumulative growth. |
| How much did it grow cumulatively across consecutive years? | Compound growth factors. | Requires the rates for every transition in the period. |
| What equivalent constant annual compound rate gives the same result? | Geometric mean of the factors, minus one. | Requires a defined number of transitions and valid factors. |
| What was the region’s GDP growth? | Aggregation using coherent levels and weights. | Not obtained simply by adding national rates; follow the aggregate’s methodology. |
geq = 100[(1 + G)1/T − 1]
compound_growth <- function(rates, years) {
if (!is.numeric(rates) || !is.numeric(years) ||
length(rates) != length(years) || length(rates) == 0L ||
anyNA(years) || any(!is.finite(years)) || any(years != floor(years))) {
stop("Rates and their years must be compatible numeric vectors.")
}
if (length(years) > 1L && any(diff(years) != 1)) {
stop("Years must be ordered and consecutive.")
}
if (anyNA(rates)) {
return(c(cumulative_pct = NA_real_, equivalent_annual_pct = NA_real_))
}
if (any(!is.finite(rates)) || any(rates <= -100)) {
stop("This example requires finite rates greater than -100%.")
}
log_sum <- sum(log1p(rates / 100))
c(cumulative_pct = 100 * expm1(log_sum),
equivalent_annual_pct = 100 * expm1(log_sum / length(rates)))
}
compound_growth(c(10, -10), c(2020, 2021))
# cumulative_pct: -1; equivalent_annual_pct: approximately -0.5012563.
compound_growth(ca$matriz["GTM", ], ca$years)
compound_growth(ca$matriz["NIC", ], ca$years)
# Both compounded summaries are NA for Nicaragua because one rate is missing.An increase of 10% followed by a decrease of 10% leaves a factor of 1.10 × 0.90 = 0.99: cumulative change is −1%, although the sum and arithmetic mean of the rates are zero. The implementation uses log1p() and expm1() to express compounding with improved precision near zero. If rates labeled 2018–2022 describe changes from the preceding year, compounding them compares the end-of-2022 level with the level preceding the first transition, 2017. Interpret time labels according to the indicator definition.
Dropping a missing rate and compounding the rest can join segments that do not reconstruct growth over the full interval. The function therefore returns missingness rather than presenting a partial accumulation as complete. Nor is the annual sum of different countries’ rates the region’s growth: GDP aggregation requires comparable levels and consistent weights, while avoiding the inclusion of both an aggregate and its members, which would double-count information.
Using an actual World Bank file
# Template for an XLS/XLSX download: adjust the path, sheet, and initial rows.
# This block does not download data or replace inspection of the file.
wb_path <- file.path("data", "world_bank_gdp_growth.xlsx")
if (file.exists(wb_path) && requireNamespace("readxl", quietly = TRUE)) {
sheets <- readxl::excel_sheets(wb_path)
print(sheets)
if (!"Data" %in% sheets) {
stop("Check the data sheet; this example expected Data.")
}
preview <- readxl::read_excel(wb_path, sheet = "Data",
col_names = FALSE, n_max = 8)
print(preview)
# skip = 3 fits the layout with column headings on the fourth row.
wb_input <- readxl::read_excel(wb_path, sheet = "Data", skip = 3,
col_types = "text", na = c("", ".."))
ca_actual <- prepare_growth(wb_input, start_year = 2000, end_year = 2020)
print(dim(ca_actual$matriz))
} else {
message("The empirical application requires the specified file and readxl.")
}If the sheet is named “Datos” or something else, change the argument after inspecting the workbook. skip = 3 is not a law of the Excel format. For a ZIP download containing CSV, first extract the data file and inspect its preliminary lines; then configure read.csv(), preserving headings with check.names = FALSE. The example years 2000–2020 are an analytical choice, not a promise of complete availability.
- Record the indicator address, retrieval date, original file, and sheet or table.
- Confirm country codes, indicator code, and percentage units.
- Report dimensions after selection without counting metadata as years.
- Inspect missingness by country and year; do not interpret blank cells as zeros.
- Preserve the original file and export results under names identifying the period and procedure.
write.csv(ca$long_data,
file = file.path(example_directory, "central_america_demo_long_format.csv"),
row.names = FALSE, na = "", fileEncoding = "UTF-8")
write.csv(ca_summary,
file = file.path(example_directory, "central_america_demo_summary.csv"),
row.names = TRUE, na = "", fileEncoding = "UTF-8")Understand, check, and reproduce
16. Working Principles and Exercises
A statistical program should make it possible to reconstruct the decisions separating source from result. Data size, file format, and function complexity do not alter that requirement. We need to know what each object represents, how observations were selected, which were excluded, and what the applied operation means. Visual inspection and automatic checks complement one another.
| Before calculating | During calculation | When interpreting |
|---|---|---|
| Identify unit, variable, scale, and provenance. | Check types, lengths, and dimensions. | Connect the output to the original question. |
| Distinguish observations from metadata. | Verify selection by names and codes. | Report coverage and missing-value treatment. |
| Preserve original data and program. | Validate assumptions and boundary cases. | Distinguish approximation, verification, and proof. |
| Record version and dependencies. | Separate returned results from effects. | Preserve reproducible output with units and labels. |
Errors worth recognizing
- Unnoticed reassignment: a later assignment changes the value bound to a name in that environment. Keeping a separate name for original data makes transformations easier to review.
- Misread equality:
<-assigns,==compares, and<=orders. Approximate numerical equality requires an appropriate tolerance. - A mask containing missing values: a condition can contain NA. Decide whether to preserve these as unknown information or explicitly exclude them before selection.
- Identity and order: equal length does not establish that vectors are correctly paired. Use stable identifiers.
- Mixed types: a label column can turn an entire matrix into text. Calculate summaries on the relevant numeric columns.
- Incompatible units: adding quantities with different units is a dimensional issue; converting text to numbers is a representation issue. They can coexist but are not identical.
- Excessive interpretation: numeric output does not establish temporal independence, sample representativeness, causality, or the universal validity of a conjecture.
Exercises with checkable results
| Exercise | Result or checking criterion |
|---|---|
| Construct a sequence from 0 to 1 with five positions. | 0, 0.25, 0.5, 0.75, 1. |
Extract values strictly between 3 and 8 from c(2, 5, 8, NA). | The result is 5; the condition must explicitly handle missingness. |
| Sum positive even integers no greater than 50 using a loop and a formula. | Both methods give 650. |
| Calculate the three students’ average grades. | 75.50, 66.75, and 47.25; pass, resit, and fail. |
| Distinguish elementwise and matrix multiplication for the chapter’s matrices. | The former preserves 2 × 3; matrix01 %*% t(matrix03) produces 2 × 2. |
| Compare Collatz starting at 8 and at 27. | 3 and 111 transitions; 4 and 112 terms. From 27 the maximum is 9232. |
| Compound a 10% increase and a 10% decrease. | Cumulative change is −1%. |
| Calculate a mean when every value is missing. | Identify that no observations are available; do not interpret the result as a zero mean. |
A minimal reproducibility record
# Information to accompany a shared analysis.
sessionInfo()
RNGkind()
# Brief checks of the integrated applications.
stopifnot(
sum_even(50) == 650,
isTRUE(all.equal(unname(grade_averages), c(75.5, 66.75, 47.25))),
c27$iterations + 1L == length(c27$trajectory),
nrow(ca$long_data) == nrow(ca$matriz) * ncol(ca$matriz),
abs(compound_growth(c(10, -10), c(2020, 2021))[1] + 1) < 1e-10
)The chosen checks contrast identities and results verifiable through another route: a formula against a loop, counts against structures, and compounded rates with a known result. They do not replace reading code or examining data. They make changes that alter important behavior visible when the program is modified.
Understanding an instruction requires knowing the object it receives, the transformation it performs, and the conclusion it supports.
A reading principle for this chapter
Documentation and references: [2]
Documentation and traceability
17. References and Provenance
Definitions of the language and its functions rely primarily on R and package documentation. Links to R-devel may describe behavior newer than a local installation; installed help, accessed with ?name, documents the version being used. This chapter makes relevant behavior explicit, including scalar conditions and the default value of stringsAsFactors.
- R Core Team. R Language Definition: objects, evaluation, functions, indexing, and syntax.
- R Core Team. An Introduction to R: vectors, structures, and programming.
- R Core Team. Logical Operators: elementwise logic and short-circuit evaluation.
- R Core Team. Control Flow: if, for, while, repeat, break, and next.
- R Core Team. Not Available / Missing Values.
- R Core Team. Vectors: Creation, Coercion, etc.
- R Core Team. Colon Operator and sequence generation.
- R Core Team. Apply Functions Over Array Margins.
- R Core Team. Multi-way Arrays.
- R Core Team. Data Frames.
- R Core Team. Numeric Vectors: types and factor conversion.
- R Core Team. Conditional Element Selection: ifelse().
- R Core Team. Correlation, Variance and Covariance.
- R Core Team. Integration of One-Dimensional Functions.
- Wickham, H. Advanced R, second edition: functionals.
- Wickham, H. Advanced R: functions and lexical scope.
- Wickham, H. Advanced R: object-oriented systems.
- The readxl team. Read xls and xlsx files.
- The writexl team / rOpenSci. Export to xlsx.
- World Bank. GDP growth (annual %), NY.GDP.MKTP.KD.ZG.
- World Bank. Methodologies: aggregation rules and methods.
- World Bank. Aggregate API Queries: economies and aggregates.
Sources used in the original document
The 2021 version drew on A. Ballestero’s materials on operators and functions in RStudio, Kolmogorov and Fomin’s text on function theory and functional analysis, and tutorials, documentation, and technical discussions. That bibliographical provenance is retained. Analogies and formulations revised in this edition should be assessed through the definitions and explanations developed in the chapter.
Provenance bibliography and original links
- Ballestero, A. (2020). Fundamentos de programación en R Studio. Operadores, vectores, listas, matrices, variables y data frame.
- Ballestero, A. (2021). Fundamentos de programación en R Studio. Funciones en R, Parte I.
- Banco Mundial (2021). Crecimiento del PIB (% anual), source for the original application.
- Codecademy (2021). What is function call?
- DataMentor (2021). R while Loop.
- R Documentation, originally cited as EHZ/ETH Zürich (2020). Unevaluated Expressions.
- Estadísticamente (2021). Sucesiones, repeticiones y factores en R.
- R Documentation, originally cited as ETH Zürich (2020). Generic Function Objects.
- R Documentation, originally cited as ETH Zürich (2020). Logical Vectors.
- R Documentation, originally cited as ETH Zürich (2021). Function Calls.
- GeeksforGeeks (2020). Matrix vs Dataframe in R.
- JournalDev (2021). How to replace values using replace() in R.
- Langohr, K. (2010). Introducción a R. Universitat Politècnica de Catalunya.
- Mendoza Vega, J. B. (2021). R para principiantes: coerción.
- Mendoza Vega, J. B. (2021). R para principiantes: if, else.
- Moore, K., and collaborators (2021). Classes (OOP). Brilliant.
- Stack Overflow (2013). Confused about R terminology: Attributes, parameters, and arguments.
- RDocumentation.org (2021). Expression Deparsing.
- Real Academia Española (2021). Anidar.
- Sánchez, G. (2020). Character Strings in R.
- Simpson, G. (2011). What evaluates to True/False in R?
- Stack Overflow (2016). What is the difference between mode and class in R?
- Stackoverrun (2021). Global and local variables in R.
- Wickham, H., cited in 2019. Functionals, first edition of Advanced R.
- Wickham, H., cited in 2019. Functions / Primitive functions, first edition of Advanced R.
- Wikipedia, cited in 2020. Implementation.
- Wikipedia, cited in 2020. Stack (abstract data type).
- TechTerms (2014). FILO, cited in the original note on stacks.
- Kolmogórov, A. N., y Fomin, S. V. (1978). Elementos de la teoría de funciones y del análisis funcional, third edition. Moscow: MIR. Originally cited at p. 121.
- Huamaní Ninahuanca (2019), mentioned in an original note supporting integer-vector terminology; the PDF does not provide a complete bibliographic reference.
- Stack Overflow (2015), a discussion cited in the original note on
cat()andprint(); the PDF provides no identifiable link. - Study.com (2021), cited in an original note on the class-as-blueprint analogy; the PDF does not include the complete reference.
Years and titles in this list retain the identification used in the source document; they do not imply that every older resource remains available or replace primary documentation supporting this edition’s corrections. Original Spanish bibliographic titles are retained where appropriate. The Stroustrup quotation cited through Wickham motivated attention to clear control flow; that concern is developed through examples here without extensively reproducing the quotation.










