Core Concepts of R

At the heart of R lies a fundamental concept: everything is a vector. Unlike many general-purpose programming languages where scalars are the primary building blocks, R is designed around the idea that data is inherently collective. Almost every data structure in R is built upon the foundation of the vector.

A vector is an ordered collection of elements of the same type. These are created using the c() function (short for "combine"). R categorizes these as atomic vectors, which include several key types:

  • Logical: TRUE or FALSE.
  • Integer: Whole numbers.
  • Double: Floating-point numbers (the default for numeric values).
  • Character: Text strings.
  • Complex: Numbers with imaginary parts.
  • Raw: Raw bytes.

For example:

num_vec <- c(1, 2, 3)
char_vec <- c("a", "b", "c")
log_vec <- c(TRUE, FALSE, TRUE)

One of the most powerful features of R is vectorization. When you perform an operation on a vector, R applies that operation to every element simultaneously. This leads to the recycling rule: if you attempt an operation on two vectors of unequal length, R will "recycle" the shorter vector by repeating its elements until it matches the length of the longer one. For instance, c(1, 2, 3) + c(10, 20) results in 11, 22, 13 because the second vector is treated as 10, 20, 10.

It is also vital to distinguish between special values:

  • NA: Represents missing data.
  • NULL: Represents an empty object or the absence of a value.
  • NaN: Represents a non-numeric result (e.g., 0/0).

R provides a variety of data structures, which can be broadly categorized by their dimensionality and whether they are homogeneous (same data type) or heterogeneous (mixed data types).

Homogeneous Structures

  • Vector: A one-dimensional sequence of elements of the same type.
  • Matrix: A two-dimensional structure where all elements must be of the same type, created via matrix().
  • Array: A multi-dimensional structure, also homogeneous, created via array().

Heterogeneous Structures

  • List: A one-dimensional collection that can hold elements of different types, including other lists. Created via list().
  • Data Frame: A two-dimensional, table-like structure where different columns can contain different types of data. This is the most critical structure for statistical analysis, created via data.frame().

Example of structure creation:

m <- matrix(1:6, nrow = 2, ncol = 3)
l <- list(name = "Alice", age = 30, scores = c(90, 85))
df <- data.frame(id = 1:3, name = c("A", "B", "C"), score = c(88, 92, 79))

To access elements, R uses different syntax depending on the structure: vectors use single brackets [], lists use double brackets [[]] or the $ operator, and data frames typically use $ for column access or [] for subsetting.

Functions and Lexical Scoping

R is a functional programming language, meaning functions are first-class citizens. You can pass functions as arguments, return them from other functions, and assign them to variables.

Defining a function is straightforward:

add <- function(a, b = 1) {
  a + b
}
add(2)      # Returns 3 (using default b=1)
add(2, 3)   # Returns 5

R employs lexical scoping to manage variables. When a function is executed, R searches for a variable in the following order: first in the function's local environment, then in the parent environment, and finally in the global environment. If you need to modify a variable in the parent environment rather than creating a local copy, you use the super-assignment operator <<-.

Efficiency via Vectorization and the apply Family

To write performant R code, one should avoid explicit for loops whenever possible and instead embrace vectorization. Vectorized operations are implemented in highly optimized C or Fortran code, making them significantly faster than manual iterations.

When working with complex structures like matrices or lists, R provides the apply family of functions to perform batch operations:

  • lapply(): Applies a function to each element of a list and returns a list.
  • sapply(): A "simplified" version of lapply that attempts to return a more user-friendly structure (like a vector).
  • apply(): Used for matrices or arrays to apply functions across specific margins (rows or columns).
  • tapply(): Applies a function to subsets of a vector, grouped by a factor.

Example of efficient operations:

v <- 1:5
v * 2  # Vectorized multiplication

lapply(1:3, function(i) i^2)
sapply(1:3, function(i) i^2)

m <- matrix(1:6, nrow = 2)
apply(m, 1, sum)  # Calculates the sum of each row

Categorical Data: The Role of Factors

In statistical modeling, it is common to encounter categorical variables (e.g., gender, country, or treatment groups). R handles these using the factor data type. Internally, factors are stored as integers with associated levels (the unique categories).

gender <- factor(c("male", "female", "male"), levels = c("male", "female"))
levels(gender)
table(gender)

Factors are essential because they allow R to treat categorical data correctly during modeling (e.g., automatically converting them into dummy variables). Note that in modern R (version 4.0+), the default behavior for strings has changed; they are no longer automatically converted to factors unless explicitly requested.

Data Frame Manipulation and the Tidyverse

The data frame is the workhorse of R. Mastering its manipulation is essential for any data scientist. Standard operations include:

  • Reading data: read.csv() or read.table().
  • Inspection: str() for structure, summary() for statistics, and head()/tail() to view the first or last few rows.
  • Subsetting and Sorting: Using df[df$age > 30, ] to filter rows or order() to sort data.

For more complex workflows, the modern standard is the dplyr package, which provides a grammar for data manipulation. It utilizes the pipe operator (%>% or the native |>) to chain operations together, making code significantly more readable.

library(dplyr)
df <- data.frame(name = c("A", "B", "C"), age = c(25, 35, 30))

df %>%
  filter(age > 28) %>%
  select(name, age) %>%
  arrange(desc(age))

The R Ecosystem: Packages, Modeling, and Graphics

The true power of R comes from its vast ecosystem of community-contributed packages. You can install new functionality using install.packages() and load it with library().

R was originally built for statistical computing, and its ability to perform complex modeling is unparalleled. For instance, a linear model can be fitted with a single command:

model <- lm(score ~ age, data = df)
summary(model)

Visualizing data is equally central. While R offers "base graphics" like plot(), hist(), and boxplot(), the ggplot2 package has become the industry standard. It is based on the "Grammar of Graphics," allowing users to build complex, multi-layered visualizations incrementally.

library(ggplot2)
ggplot(df, aes(x = age, y = score)) + geom_point()

Conclusion

Mastering R requires a shift in mindset: from thinking in individual values to thinking in vectors and structures. By understanding the nuances of data types, the efficiency of vectorization, the logic of lexical scoping, and the power of the tidyverse ecosystem, you lay the groundwork for sophisticated statistical analysis and data science.