Introduction to R

The language built for statistics, from the ground up

R is not a general-purpose language that happens to do statistics. It is a statistics language that happens to be Turing-complete. This distinction shapes everything about it: the syntax, the data structures, the standard library, and the way R programmers think.

Born in the early 1990s at the University of Auckland as a free implementation of the S language (Bell Labs, 1976), R was designed by statisticians for statisticians. Ross Ihaka and Robert Gentleman wanted a language where a linear regression was one line of code, where every operation naturally vectorized over data, and where plotting was a first-class activity rather than an afterthought.

Today R has over 20,000 packages on CRAN, dominates academic statistics and bioinformatics, and remains the language of choice for anyone whose primary job is data analysis rather than software engineering.

"R is a language for thinking about data."
— Hadley Wickham

I. The REPL and First Steps

R is interactive by default. You type an expression, R evaluates it and prints the result. There is no main() function, no compilation step, no boilerplate.

# This is a comment 2 + 3
[1] 5

The [1] prefix tells you this is the first element of the result vector. In R, there are no scalars. The number 5 is a numeric vector of length 1. This is the most important thing to understand about R: everything is a vector.

# Assignment uses <- (not =, though = works in most cases) x <- 42 name <- "R" # Print explicitly print(x) cat("Hello from", name, "\n")
[1] 42 Hello from R

The <- arrow is R's preferred assignment operator. It reads naturally: "x gets 42." The = sign also works for assignment, but <- is the convention and what you'll see in virtually all R code. The keyboard shortcut in RStudio is Alt+- (hyphen).

II. Vectors: The Fundamental Unit

In Python, the fundamental unit is the object. In C, it's the byte. In R, it's the vector. You create vectors with the c() function (for "combine"):

# Numeric vector heights <- c(5.8, 6.1, 5.5, 5.9, 6.3) # Character vector names <- c("Alice", "Bob", "Carol", "Dave", "Eve") # Logical vector tall <- heights > 6.0 print(tall)
[1] FALSE TRUE FALSE FALSE TRUE

That last line is key. The comparison heights > 6.0 didn't compare a single value—it compared every element of the vector at once, returning a logical vector of the same length. This is vectorization, and it is how R wants you to write code.

Vectorized Operations

Almost every operation in R is vectorized: arithmetic, comparisons, math functions, string functions. When you write x + y where both are vectors, R adds them element-by-element. When you write sqrt(x), R computes the square root of every element. You rarely need explicit loops.

# Arithmetic is element-wise a <- c(1, 2, 3, 4) b <- c(10, 20, 30, 40) a + b # [1] 11 22 33 44 a * b # [1] 10 40 90 160 a ^ 2 # [1] 1 4 9 16 sqrt(b) # [1] 3.162 4.472 5.477 6.325 # Recycling: shorter vector is repeated a + 100 # [1] 101 102 103 104 (100 is recycled) c(1,2,3,4,5,6) + c(10,20) # [1] 11 22 13 24 15 26

The recycling rule is powerful but dangerous. If the longer vector's length isn't a multiple of the shorter one's, R gives a warning—but still produces a result. This is a common source of subtle bugs.

Indexing: 1-Based and Powerful

R uses 1-based indexing. The first element is x[1], not x[0].

x <- c(10, 20, 30, 40, 50) x[1] # 10 (first element) x[3:5] # 30 40 50 (range, inclusive on both ends) x[c(1,4)] # 10 40 (specific positions) x[-2] # 10 30 40 50 (negative = exclude) x[x > 25] # 30 40 50 (logical indexing) # Named indexing temps <- c(mon=72, tue=68, wed=75, thu=71, fri=80) temps["wed"] # 75

Negative indexing in R means exclusion, not counting from the end. x[-2] returns everything except the second element. This is the opposite of Python's convention.

III. Types and Coercion

R has six atomic vector types, listed here from most to least restrictive:

TypeExampleNotes
logicalTRUE, FALSECan abbreviate as T, F (but don't—they can be overwritten)
integer1L, 42LThe L suffix forces integer storage
double3.14, 1e-5Default numeric type. numeric = double.
complex3+2iBuilt-in complex number support
character"hello"Strings. No separate char type.
rawas.raw(0xff)Raw bytes. Rarely used directly.

When you mix types in a vector, R silently coerces to the most general type. This hierarchy is: logical → integer → double → complex → character.

# Coercion happens automatically c(1, 2, "three") # "1" "2" "three" (all become character) c(TRUE, FALSE, 3) # 1 0 3 (logical becomes numeric) # This is useful: TRUE/FALSE as 1/0 x <- c(4, 7, 2, 9, 1, 8) sum(x > 5) # 3 (count of elements greater than 5) mean(x > 5) # 0.5 (proportion greater than 5)

The fact that sum(x > 5) counts how many elements exceed 5 is not a trick—it's a direct consequence of logical-to-numeric coercion. This pattern appears everywhere in R.

Special Values

NA # Missing value (Not Available) — R's first-class citizen NULL # The empty object (length 0, no type) Inf # Positive infinity: 1/0 NaN # Not a number: 0/0 # NA is contagious 5 + NA # NA (anything involving NA yields NA) mean(c(1, 2, NA, 4)) # NA mean(c(1, 2, NA, 4), na.rm=TRUE) # 2.333

NA is one of R's most important features. It represents missing data—an everyday reality in statistics. Rather than crashing or silently producing wrong results, R forces you to decide how to handle missingness. Almost every statistical function has an na.rm parameter.

IV. Matrices and Arrays

Matrices are 2D vectors. They store a single type and support vectorized linear algebra.

# Create a matrix (fills column-by-column by default) m <- matrix(1:12, nrow=3, ncol=4) print(m)
[,1] [,2] [,3] [,4] [1,] 1 4 7 10 [2,] 2 5 8 11 [3,] 3 6 9 12
# Matrix indexing: [row, col] m[2, 3] # 8 (row 2, column 3) m[1, ] # 1 4 7 10 (entire first row) m[, 2] # 4 5 6 (entire second column) m[1:2, 3:4] # submatrix: rows 1-2, cols 3-4 # Matrix operations A <- matrix(c(1,2,3,4), nrow=2) B <- matrix(c(5,6,7,8), nrow=2) A * B # Element-wise multiplication A %*% B # Matrix multiplication t(A) # Transpose det(A) # Determinant solve(A) # Inverse eigen(A) # Eigenvalues and eigenvectors

Note the %*% operator for matrix multiplication. The plain * is always element-wise in R. R's linear algebra is backed by LAPACK and BLAS, so matrix operations are genuinely fast.

V. Lists: R's Heterogeneous Container

Vectors require all elements to be the same type. Lists can hold anything: different types, different lengths, even other lists.

# A list can hold mixed types person <- list( name = "Alice", age = 30, scores = c(95, 88, 92), active = TRUE ) # Three ways to access list elements person[[1]] # "Alice" (by position, extracts the element) person$name # "Alice" (by name, using $) person["name"] # A list containing "Alice" (single [ returns a sub-list) # [[ ]] extracts the element. [ ] returns a sub-list. # This is one of R's most confusing distinctions.

The [ ] vs [[ ]] Distinction

Think of a list as a train with named cars. train[1] gives you a smaller train with just the first car (still a train/list). train[[1]] gives you the contents of the first car (the actual element). The $ operator is shorthand for [["name"]].

Lists are the building block of most complex R objects. Data frames are lists of equal-length vectors. Model outputs (lm(), t.test()) are lists. Almost any function that returns something structured returns a list.

VI. Data Frames: R's Killer Feature

The data frame is R's central data structure. It is a list of equal-length vectors—in other words, a table where each column can have a different type but all columns have the same number of rows.

# Create a data frame df <- data.frame( name = c("Alice", "Bob", "Carol", "Dave", "Eve"), age = c(28, 35, 42, 31, 26), score = c(88, 92, 95, 79, 85), passed = c(TRUE, TRUE, TRUE, FALSE, TRUE) ) print(df)
name age score passed 1 Alice 28 88 TRUE 2 Bob 35 92 TRUE 3 Carol 42 95 TRUE 4 Dave 31 79 FALSE 5 Eve 26 85 TRUE
# Accessing data df$name # Column as vector: "Alice" "Bob" "Carol" "Dave" "Eve" df[2, ] # Row 2 (all columns) df[, 3] # Column 3 as vector df[df$age > 30, ] # Filter: rows where age > 30 # Useful inspection functions str(df) # Structure: types and first values summary(df) # Statistical summary of each column head(df) # First 6 rows nrow(df) # Number of rows ncol(df) # Number of columns names(df) # Column names

Data frames predate Python's pandas by nearly two decades. When Wes McKinney created pandas in 2008, he was explicitly modeling it on R's data frame. The concept of a named, typed, rectangular data structure with row/column indexing is R's most influential contribution to programming.

Reading Data

# Read CSV files (base R) data <- read.csv("data.csv") # Read with options data <- read.csv("data.csv", header = TRUE, # First row is column names stringsAsFactors = FALSE, # Don't convert strings to factors na.strings = c("NA", "", ".") # Values to treat as NA ) # Built-in datasets (R ships with dozens) data(mtcars) # Motor Trend Car Road Tests (32 cars, 11 variables) data(iris) # Edgar Anderson's Iris data (150 flowers, 5 variables) head(mtcars)
mpg cyl disp hp drat wt qsec vs am gear carb Mazda RX4 21.0 6 160 110 3.90 2.620 16.46 0 1 4 4 Mazda RX4 Wag 21.0 6 160 110 3.90 2.875 17.02 0 1 4 4 Datsun 710 22.8 4 108 93 3.85 2.320 18.61 1 1 4 1 Hornet 4 Drive 21.4 6 258 110 3.08 3.215 19.44 1 0 3 1 Hornet Sportabout 18.7 8 360 175 3.15 3.440 17.02 0 0 3 2 Valiant 18.1 6 225 105 2.76 3.460 20.22 1 0 3 1

VII. Factors

Factors represent categorical data—variables with a fixed set of possible values. They are R's way of encoding what statisticians call "categorical" or "nominal" variables.

# Create a factor status <- factor(c("low", "med", "high", "low", "high", "med")) print(status) levels(status)
[1] low med high low high med Levels: high low med [1] "high" "low" "med"
# Ordered factor (for ordinal data) severity <- factor( c("mild", "severe", "moderate", "mild"), levels = c("mild", "moderate", "severe"), ordered = TRUE ) severity[1] < severity[2] # TRUE (mild < severe) # Factors in statistical models # When you put a factor in a regression, R automatically creates # dummy variables (indicator variables). This is essential for # regression with categorical predictors.

Factors confused a generation of R users because read.csv() used to convert all strings to factors by default. This was changed in R 4.0 (2020), but you'll still encounter factor-related confusion in older code and tutorials.

VIII. Control Flow

# if/else x <- 15 if (x > 10) { cat("Greater than 10\n") } else if (x > 5) { cat("Between 5 and 10\n") } else { cat("5 or less\n") } # Vectorized if: ifelse() ages <- c(15, 22, 17, 30, 12) category <- ifelse(ages >= 18, "adult", "minor") # "minor" "adult" "minor" "adult" "minor" # for loop for (i in 1:5) { cat(i, " ") } # 1 2 3 4 5 # while loop n <- 1 while (n <= 100) { n <- n * 2 } # n is now 128

Loops vs. Vectorization

In most languages, loops are the natural way to process collections. In R, loops are a code smell. If you find yourself writing for (i in 1:length(x)), there is almost certainly a vectorized alternative that is both faster and clearer. Use sum(), mean(), cumsum(), diff(), ifelse(), or the apply family instead. R's vectorized operations are written in C internally and can be 10-100x faster than equivalent loops.

IX. Functions

Functions are first-class objects in R. You create them with the function keyword and assign them to names like any other value.

# Define a function zscore <- function(x) { (x - mean(x)) / sd(x) } scores <- c(85, 90, 78, 92, 88) zscore(scores)
[1] -0.3216 0.6433 -1.6081 0.9649 0.3216
# Default arguments greet <- function(name, greeting = "Hello") { paste(greeting, name) } greet("World") # "Hello World" greet("World", "Goodbye") # "Goodbye World" # The last expression is the return value (explicit return() is optional) add <- function(a, b) { a + b # implicitly returned } # Functions can take ... (varargs) my_summary <- function(...) { vals <- c(...) list(mean = mean(vals), sd = sd(vals), n = length(vals)) } my_summary(1, 2, 3, 4, 5)
$mean [1] 3 $sd [1] 1.581139 $n [1] 5

R uses lexical scoping. A function can access variables from its enclosing environment (closure). Functions are commonly passed to other functions—this is the core of the apply family.

X. The Apply Family

The apply family replaces most loops. Each variant applies a function to elements of a data structure:

FunctionInputOutputUse Case
apply()matrix/arrayvector/matrixApply to rows or columns
sapply()vector/listvector/matrixSimplified output
lapply()vector/listlistAlways returns a list
tapply()vector + factorarrayGroup by and summarize
mapply()multiple vectorsvector/matrixMultivariate apply
# apply: operate on rows (1) or columns (2) of a matrix m <- matrix(1:12, nrow=3) apply(m, 1, sum) # Row sums: 22 26 30 apply(m, 2, mean) # Column means: 2 5 8 11 # sapply: apply a function to each element, simplify result words <- c("hello", "world", "R") sapply(words, nchar) # 5 5 1 # lapply: same but always returns a list lapply(1:3, function(n) n^2) # [[1]] 1 [[2]] 4 [[3]] 9 # tapply: group by a factor and apply a function scores <- c(88, 92, 75, 95, 80, 85) groups <- c("A", "A", "B", "B", "A", "B") tapply(scores, groups, mean) # A: 86.67 B: 85.00

The apply family is powerful but the interface is inconsistent. This is why the tidyverse's purrr package (with map(), map_dbl(), etc.) exists—it provides a cleaner, more predictable alternative.

XI. Built-in Statistics

R is the only major language where a t-test is one function call. Statistical functions are not in a library you import—they're built into the language.

# Descriptive statistics x <- c(23, 29, 31, 25, 27, 35, 22, 28, 33, 30) mean(x) # 28.3 median(x) # 28.5 sd(x) # 4.11 var(x) # 16.9 quantile(x) # 0%:22 25%:25.5 50%:28.5 75%:31 100%:35 range(x) # 22 35 cor(x, x^2) # correlation # Hypothesis testing t.test(x, mu = 25) # One-sample t-test: is the mean different from 25?
One Sample t-test data: x t = 2.5387, df = 9, p-value = 0.03181 alternative hypothesis: true mean is not equal to 25 95 percent confidence interval: 25.36 31.24 sample estimates: mean of x 28.3
# Linear regression data(mtcars) model <- lm(mpg ~ wt + hp, data = mtcars) summary(model)
Coefficients: Estimate Std. Error t value Pr(>|t|) (Intercept) 37.22727 1.59879 23.285 < 2e-16 *** wt -3.87783 0.63273 -6.129 1.12e-06 *** hp -0.03177 0.00903 -3.519 0.00145 ** --- Multiple R-squared: 0.8268, Adjusted R-squared: 0.8148

That linear regression output tells you everything a statistician needs: coefficients, standard errors, t-values, p-values, significance codes, and R-squared. In Python, this requires importing statsmodels and writing substantially more code.

The Formula Interface

The ~ (tilde) is R's formula syntax. It means "modeled as" or "predicted by." This interface is used throughout R's statistical functions:

# y ~ x simple regression # y ~ x1 + x2 multiple regression # y ~ x1 * x2 main effects + interaction # y ~ x1 + I(x1^2) polynomial term # y ~ . all other variables in the data frame # y ~ x - 1 no intercept # Used everywhere: lm(mpg ~ wt, data = mtcars) # linear model glm(am ~ hp + wt, data = mtcars, family = "binomial") # logistic regression aov(score ~ group, data = df) # ANOVA t.test(score ~ group, data = df) # two-sample t-test boxplot(mpg ~ cyl, data = mtcars) # boxplot by groups

The formula interface is elegant and powerful. mpg ~ wt + hp reads as "miles per gallon, as a function of weight and horsepower." No other language has anything like it.

XII. Plotting: Base R Graphics

R's base plotting is immediate and interactive. You call plot() and a window appears with your visualization. No imports, no setup.

# Scatter plot plot(mtcars$wt, mtcars$mpg, main = "Weight vs. MPG", xlab = "Weight (1000 lbs)", ylab = "Miles per Gallon", pch = 19, # solid circles col = "steelblue") # Add a regression line abline(lm(mpg ~ wt, data = mtcars), col = "red", lwd = 2) # Histogram hist(mtcars$mpg, breaks = 10, main = "Distribution of MPG", col = "lightblue", border = "white") # Multiple plots in one window par(mfrow = c(2, 2)) # 2x2 grid plot(model) # 4 diagnostic plots for a linear model

Base R plotting has an imperative API: you create a plot, then add to it with points(), lines(), abline(), text(), legend(). It's not pretty by default, but it's fast to iterate on. For publication-quality graphics, most R users switch to ggplot2.

XIII. The Tidyverse

The tidyverse is a collection of packages by Hadley Wickham and collaborators that reimagines how R code should look. It is not part of base R, but it is so widely used that many people learn it first and base R second.

# install.packages("tidyverse") # one-time install library(tidyverse) # The pipe operator: %>% (or |> in base R 4.1+) # Read left to right: "take mtcars, THEN filter, THEN select, THEN arrange" mtcars %>% filter(cyl == 6) %>% select(mpg, wt, hp) %>% arrange(desc(mpg))
mpg wt hp 1 21.4 3.215 110 2 21.0 2.620 110 3 21.0 2.875 110 4 19.2 3.440 123 5 18.1 3.460 105 6 17.8 3.440 123 7 15.2 3.435 175

The pipe operator %>% is the tidyverse's signature. It takes the result of the left side and passes it as the first argument to the right side. This transforms deeply nested function calls into readable, linear pipelines.

dplyr: Data Manipulation

# The five core dplyr verbs: # filter() - pick rows by condition # select() - pick columns by name # mutate() - create new columns # arrange() - sort rows # summarize() - collapse to summary statistics # Combined with group_by() for split-apply-combine: mtcars %>% group_by(cyl) %>% summarize( avg_mpg = mean(mpg), avg_hp = mean(hp), count = n() )
# A tibble: 3 x 4 cyl avg_mpg avg_hp count <dbl> <dbl> <dbl> <int> 1 4 26.7 82.6 11 2 6 19.7 122. 7 3 8 15.1 209. 14

ggplot2: Grammar of Graphics

ggplot2 is arguably the most influential data visualization library ever written. It implements Leland Wilkinson's "Grammar of Graphics"—the idea that any statistical graphic can be described as a combination of data, aesthetic mappings, and geometric objects.

# A ggplot is built in layers ggplot(mtcars, aes(x = wt, y = mpg, color = factor(cyl))) + geom_point(size = 3) + geom_smooth(method = "lm", se = FALSE) + labs( title = "Weight vs. Fuel Efficiency", x = "Weight (1000 lbs)", y = "Miles per Gallon", color = "Cylinders" ) + theme_minimal() # Faceting: small multiples ggplot(mtcars, aes(x = mpg)) + geom_histogram(bins = 10, fill = "steelblue") + facet_wrap(~ cyl, scales = "free_y") + theme_bw()

The + operator in ggplot2 layers components together. aes() maps data columns to visual properties. geom_*() functions add visual elements. This declarative approach means you describe what you want, not how to draw it.

tidyr: Reshaping Data

# Wide to long (pivot_longer) wide_data <- data.frame( name = c("Alice", "Bob"), math = c(90, 85), science = c(88, 92) ) wide_data %>% pivot_longer(cols = c(math, science), names_to = "subject", values_to = "score")
# A tibble: 4 x 3 name subject score <chr> <chr> <dbl> 1 Alice math 90 2 Alice science 88 3 Bob math 85 4 Bob science 92

XIV. String Manipulation

# Base R strings s <- "Hello, World" nchar(s) # 12 toupper(s) # "HELLO, WORLD" substring(s, 1, 5) # "Hello" paste("Hello", "World") # "Hello World" (space-separated by default) paste0("Hello", "World") # "HelloWorld" (no separator) gsub("o", "0", s) # "Hell0, W0rld" strsplit("a,b,c", ",") # list("a", "b", "c") # sprintf for formatted strings sprintf("The answer is %d (%.2f%%)", 42, 99.5) # "The answer is 42 (99.50%)" # Regex grepl("^[A-Z]", c("Hello", "world", "R")) # TRUE FALSE TRUE

Base R's string functions are functional but quirky—paste() for concatenation, nchar() instead of length(), gsub() for replacement. The stringr package (part of the tidyverse) provides a more consistent interface where every function starts with str_.

XV. Environments and Scoping

R's scoping rules are sometimes surprising. Every function creates a new environment. R searches for variables from the innermost scope outward:

x <- 10 f <- function() { x <- 20 # local x, does NOT modify global x cat("Inside f: x =", x, "\n") } f() cat("Global: x =", x, "\n")
Inside f: x = 20 Global: x = 10
# To modify a variable in the parent environment, use <<- counter <- 0 increment <- function() { counter <<- counter + 1 # superassignment: modifies parent scope } increment() increment() cat("Counter:", counter, "\n") # Counter: 2

The <<- superassignment operator searches parent environments until it finds the variable, then modifies it in place. Use it sparingly—it creates hidden side effects that make code harder to reason about.

XVI. Packages and CRAN

CRAN (Comprehensive R Archive Network) is R's package repository. It has over 20,000 packages, each of which has been checked for basic quality and cross-platform compatibility.

# Install a package (one-time) install.packages("ggplot2") # Load for use library(ggplot2) # Key packages by domain: # # Data manipulation: dplyr, tidyr, data.table # Visualization: ggplot2, plotly, leaflet # Statistics: lme4 (mixed models), survival, boot # Machine learning: caret, tidymodels, xgboost, randomForest # Bioinformatics: Bioconductor (3,000+ packages) # Time series: forecast, zoo, tseries # Reporting: rmarkdown, knitr, shiny # Text: stringr, tidytext, tm # Web scraping: rvest, httr

R vs. Python for Data Science

The R vs. Python debate is essentially: do you want a language designed for statistics that can also program, or a language designed for programming that can also do statistics? R has superior statistical modeling, formula syntax, built-in datasets, and visualization (ggplot2). Python has a vastly larger ecosystem, better software engineering tools, and dominates deep learning. In practice, many data scientists use both.

XVII. R Markdown and Reproducible Research

R Markdown (.Rmd files) lets you weave together prose, code, and output into a single document. This is R's solution to the reproducibility crisis in science: instead of copying results into a Word document, you write a document that generates its own results.

# An R Markdown document looks like this: # --- # title: "My Analysis" # output: html_document # --- # # ## Results # # The average MPG is `r mean(mtcars$mpg)`. # # ```{r} # ggplot(mtcars, aes(x=wt, y=mpg)) + geom_point() # ``` # # When you "knit" this document, R executes all the code chunks, # inserts the outputs (including plots), and produces HTML, PDF, # or Word output. Change the data, re-knit, and every number and # plot updates automatically.

Quarto (.qmd) is the next-generation successor to R Markdown, supporting R, Python, Julia, and Observable JS in the same document. It was released in 2022 and is gradually replacing R Markdown.

XVIII. S3: R's Object System

R has multiple object systems (S3, S4, R5/Reference Classes, R6). S3 is the simplest and most widely used. It's informal, duck-typed, and built on lists with a class attribute.

# S3: just set a class attribute on a list new_student <- function(name, grades) { obj <- list(name = name, grades = grades) class(obj) <- "student" obj } # Define methods by naming convention: generic.class print.student <- function(x, ...) { cat(sprintf("Student: %s (GPA: %.2f)\n", x$name, mean(x$grades))) } summary.student <- function(object, ...) { cat(sprintf("Name: %s\nCourses: %d\nGPA: %.2f\nBest: %.0f\nWorst: %.0f\n", object$name, length(object$grades), mean(object$grades), max(object$grades), min(object$grades))) } s <- new_student("Alice", c(92, 88, 95, 79)) print(s) summary(s)
Student: Alice (GPA: 88.50) Name: Alice Courses: 4 GPA: 88.50 Best: 95 Worst: 79

S3 dispatch works by naming convention: when you call print(s) and s has class "student", R looks for print.student(). This is how summary() produces completely different output for data frames, linear models, and t-tests—each has its own summary.class method.

XIX. Error Handling

# tryCatch: R's try/except result <- tryCatch({ log(-1) # produces NaN with a warning 10 / 0 # Inf, not an error in R stop("Something went wrong") # explicitly throw an error }, warning = function(w) { cat("Warning caught:", conditionMessage(w), "\n") }, error = function(e) { cat("Error caught:", conditionMessage(e), "\n") }, finally = { cat("Cleanup code runs always\n") }) # Simpler: try() wraps an expression and catches errors result <- try(log("not a number"), silent = TRUE) if (inherits(result, "try-error")) { cat("Failed gracefully\n") }

R distinguishes between errors (execution stops), warnings (execution continues but something is suspicious), and messages (informational). In interactive use, warnings are often deferred and printed after the result. In scripts, you should check for them explicitly.

XX. Putting It All Together

Here's a complete analysis that demonstrates how R's pieces fit together—reading data, cleaning, modeling, and visualizing:

# --------------------------------------------------- # Exploratory analysis of the built-in mtcars dataset # --------------------------------------------------- # 1. Load and inspect data(mtcars) cat("Rows:", nrow(mtcars), " Columns:", ncol(mtcars), "\n") cat("Variables:", paste(names(mtcars), collapse=", "), "\n\n") # 2. Descriptive statistics by group cat("=== MPG by Cylinder Count ===\n") tapply(mtcars$mpg, mtcars$cyl, function(x) { c(mean = round(mean(x), 1), sd = round(sd(x), 1), n = length(x)) }) # 3. Correlation matrix of key variables cat("\n=== Correlation Matrix ===\n") key_vars <- mtcars[, c("mpg", "wt", "hp", "disp")] round(cor(key_vars), 2) # 4. Linear model cat("\n=== Linear Model: mpg ~ wt + hp + cyl ===\n") model <- lm(mpg ~ wt + hp + cyl, data = mtcars) cat(sprintf("R-squared: %.3f\n", summary(model)$r.squared)) coeffs <- coef(model) for (nm in names(coeffs)) { cat(sprintf(" %-12s %8.4f\n", nm, coeffs[nm])) } # 5. Prediction new_car <- data.frame(wt = 3.0, hp = 150, cyl = 6) pred <- predict(model, new_car, interval = "confidence") cat(sprintf("\nPredicted MPG for a 3000lb, 150hp, 6-cyl car:\n")) cat(sprintf(" Estimate: %.1f (95%% CI: %.1f to %.1f)\n", pred[1], pred[2], pred[3]))
Rows: 32 Columns: 11 Variables: mpg, cyl, disp, hp, drat, wt, qsec, vs, am, gear, carb === MPG by Cylinder Count === $`4` mean sd n 26.7 4.5 11.0 $`6` mean sd n 19.7 1.5 7.0 $`8` mean sd n 15.1 2.6 14.0 === Correlation Matrix === mpg wt hp disp mpg 1.00 -0.87 -0.78 -0.85 wt -0.87 1.00 0.66 0.89 hp -0.78 0.66 1.00 0.79 disp -0.85 0.89 0.79 1.00 === Linear Model: mpg ~ wt + hp + cyl === R-squared: 0.843 (Intercept) 36.1461 wt -3.1910 hp -0.0213 cyl -0.7454 Predicted MPG for a 3000lb, 150hp, 6-cyl car: Estimate: 20.5 (95% CI: 19.3 to 21.8)

In 30 lines of code: data loading, grouped descriptive statistics, a correlation matrix, a multivariate regression with R-squared, and a prediction with confidence interval. This is what R was built for.

XXI. What R Trades Away

R's statistical focus comes with real costs:

TradeoffConsequence
Single-threaded by defaultComputation-heavy tasks need explicit parallelization (parallel, future packages)
Everything in memoryCan't natively handle datasets larger than RAM (unlike Python + Dask or SQL)
Inconsistent base APInchar() for string length, length() for vector length, nrow() for rows—no consistency
Copy-on-modify semanticsModifying a large data frame can trigger a full copy (though R is smart about this internally)
1-based indexingOff-by-one confusion for anyone coming from C/Python/JavaScript
Weak software engineering toolsNo static types, minimal IDE refactoring, package management is improving but still behind pip/npm
Slow loopsR loops are 10-100x slower than vectorized operations; you must learn the vectorized idioms

These tradeoffs are reasonable if your primary job is data analysis, statistical modeling, or scientific reporting. They become painful if you're building a web application, a CLI tool, or a production data pipeline. R knows what it is, and the things it sacrifices are the things a statistician can afford to lose.

Summary

R is a language that reflects its users. Statisticians wanted a language where they could explore data interactively, fit models in one line, and produce publication-quality graphics without writing boilerplate. They got exactly that, at the cost of some of the niceties that general-purpose programmers take for granted.

The key ideas to remember:

"The best thing about R is that it was written by statisticians. The worst thing about R is that it was written by statisticians."
— Bo Cowgill, Google