Chapter 2 Vectors and Data Structures

Choosing the correct data structure makes R code easier to reason about. The core structures differ by dimensionality and whether they permit mixed types.

Structure Dimensions Value types
Atomic vector 1 One underlying type
Matrix 2 One underlying type
List 1 Mixed objects allowed
Data frame 2 Each column may have a different type

2.1 Creating sequences

Use seq() when the start, end, or increment should be explicit. Use seq_along(x) when indices must follow the length of another object.

seq(from = 2, to = 10, by = 2)
#> [1]  2  4  6  8 10
seq_len(5)
#> [1] 1 2 3 4 5

labels <- c("A", "B", "C")
seq_along(labels)
#> [1] 1 2 3

seq_along() is safer than 1:length(x) because it correctly returns an empty integer vector when x has length zero.

2.2 Indexing vectors

R supports position, logical, and name-based indexing.

sales <- c(north = 120, south = 95, west = 140, east = 110)

sales[c(1, 3)]
#> north  west 
#>   120   140
sales[sales >= 120]
#> north  west 
#>   120   140
sales[c("north", "east")]
#> north  east 
#>   120   110

Negative positions exclude elements. Do not mix positive and negative positions in the same index.

sales[-2]
#> north  west  east 
#>   120   140   110

2.3 Matrices

A matrix is an atomic vector with a two-dimensional dim attribute. Because a matrix has one underlying type, inserting character data coerces numeric values to character.

measurements <- matrix(
  1:12,
  nrow = 3,
  byrow = TRUE,
  dimnames = list(NULL, c("a", "b", "c", "d"))
)

measurements
#>      a  b  c  d
#> [1,] 1  2  3  4
#> [2,] 5  6  7  8
#> [3,] 9 10 11 12
measurements[, c("a", "c"), drop = FALSE]
#>      a  c
#> [1,] 1  3
#> [2,] 5  7
#> [3,] 9 11

drop = FALSE preserves the matrix structure when a selection contains only one row or column.

2.4 Lists

A list can contain objects of different types and sizes.

model_summary <- list(
  model = "linear regression",
  coefficients = c(intercept = 1.2, slope = 0.8),
  converged = TRUE
)

model_summary[["coefficients"]]
#> intercept     slope 
#>       1.2       0.8
model_summary["coefficients"]
#> $coefficients
#> intercept     slope 
#>       1.2       0.8

[[ extracts the object stored in one list element. [ returns a sub-list. That distinction is a frequent interview question and a common source of unexpected results.

2.5 Data frames

A data frame is a list of equal-length columns. Columns may use different types, but each column must have a consistent underlying type.

customers <- data.frame(
  customer_id = 101:104,
  segment = c("new", "returning", "new", "returning"),
  revenue = c(35.0, 82.5, 41.0, NA_real_)
)

str(customers)
#> 'data.frame':    4 obs. of  3 variables:
#>  $ customer_id: int  101 102 103 104
#>  $ segment    : chr  "new" "returning" "new" "returning"
#>  $ revenue    : num  35 82.5 41 NA
customers[customers$segment == "new", c("customer_id", "revenue")]
#>   customer_id revenue
#> 1         101      35
#> 3         103      41

Use NA_real_, NA_integer_, and related typed missing values when the desired column type should be explicit.

2.6 Factors

A factor stores categorical values as integer codes with associated labels. Use an ordered factor only when the levels have a meaningful ranking.

priority <- factor(
  c("medium", "high", "low", "medium"),
  levels = c("low", "medium", "high"),
  ordered = TRUE
)

priority
#> [1] medium high   low    medium
#> Levels: low < medium < high
priority > "low"
#> [1]  TRUE  TRUE FALSE  TRUE

Never rely on alphabetical ordering when the domain defines the order.

2.7 Combining data

cbind() combines objects by columns and rbind() combines them by rows. R may recycle shorter vectors, sometimes with only a warning, so validate lengths before combining independent inputs.

customer_id <- 1:3
revenue <- c(25, 40, 30)

data.frame(customer_id, revenue)
#>   customer_id revenue
#> 1           1      25
#> 2           2      40
#> 3           3      30

For production analysis, prefer joining tables by explicit keys rather than assuming two independently created objects have the same row order.

2.8 Inspecting unfamiliar objects

Use a small set of inspection functions before manipulating unfamiliar data.

dim(customers)
#> [1] 4 3
names(customers)
#> [1] "customer_id" "segment"     "revenue"
head(customers, 2)
#>   customer_id   segment revenue
#> 1         101       new    35.0
#> 2         102 returning    82.5
summary(customers)
#>   customer_id      segment             revenue     
#>  Min.   :101.0   Length:4           Min.   :35.00  
#>  1st Qu.:101.8   Class :character   1st Qu.:38.00  
#>  Median :102.5   Mode  :character   Median :41.00  
#>  Mean   :102.5                      Mean   :52.83  
#>  3rd Qu.:103.2                      3rd Qu.:61.75  
#>  Max.   :104.0                      Max.   :82.50  
#>                                     NA's   :1

str() is usually the most concise first check because it exposes dimensions, column types, and representative values.

2.9 Interview checkpoints

  • Compare an atomic vector, matrix, list, and data frame.
  • Explain the difference between [ and [[ for lists.
  • Explain when matrix subsetting drops dimensions and how to prevent it.
  • Describe how factors store values and why explicit level order matters.
  • Explain why joining by keys is safer than relying on row order.