13  Operations on Lists: Appending, Nesting, and Manipulation

NoteWhat This Chapter Covers

This chapter goes beyond building lists and covers the operations that make them genuinely useful. You will learn how to append elements onto a list, how to combine two lists, how to work with nested lists where elements are themselves lists, and how to search, filter, and flatten them. You will meet the core iteration family for lists, lapply(), sapply(), and vapply(), which applies a function to every element and gives you back a list or a vector. You will also see Map(), Reduce(), and Filter(), which are R’s functional-programming cousins, and the modern alternatives in the purrr package. By the end of this chapter you will be able to treat a list as a small database and extract information from it without writing explicit loops.

flowchart LR
    L["A List"] --> AP["Append <br> c(), append()"]
    L --> NE["Nest & Unnest <br> nested lists, rapply()"]
    L --> IT["Iterate <br> lapply / sapply / vapply"]
    L --> FN["Functional <br> Map / Reduce / Filter"]
    style L fill:#e3f2fd,stroke:#1976D2
    style AP fill:#fff3e0,stroke:#F57C00
    style NE fill:#fff3e0,stroke:#F57C00
    style IT fill:#f3e5f5,stroke:#8E24AA
    style FN fill:#f3e5f5,stroke:#8E24AA


13.1 Appending to a List

Notec() on Two Lists: Flatten at the Top Level

c() does not just combine vectors; it also combines lists. When you pass two lists to it, the top-level elements of both are concatenated into a new list.

NoteAppending a Single Element
Noteappend() for Inserting at a Given Position

append(x, values, after) inserts new elements after a chosen position. It is handy when the order of elements matters.

WarningCommon Mistake: c(a, new_val) When new_val Is Not a List

If you want to append a vector as a single list element, wrap it in list(...). Otherwise c() flattens the vector into individual list elements.


13.2 Nested Lists

NoteCore Concept: Lists Inside Lists

List elements can themselves be lists. That lets you model arbitrary tree-shaped data: a report with sections and subsections, a configuration with categories, a JSON-like object returned from an API.

NoteReaching Deep with Chained $ or [[

To access nested elements, chain the access operators.

TipExpert Insight: Deep Access Is Fragile; Flatten When Safe

Chains like config$authors$primary$name work, but they break the moment any intermediate element is missing or renamed. In production code, use helpers like purrr::pluck(config, "authors", "primary", "name", .default = NA) that fail safely, or flatten the list to a data frame so each value has one canonical path.


13.3 Iterating over a List: lapply(), sapply(), vapply()

NoteThe Core Idea: Apply a Function to Every Element

R’s apply family lets you do to a list what vectorised arithmetic does to a vector: apply an operation to every element without writing a for loop.

Function Returns
lapply(x, FUN) A list of the same length as x, one result per element.
sapply(x, FUN) A simplified vector or matrix when possible; otherwise falls back to a list.
vapply(x, FUN, FUN.VALUE) Same as sapply() but you declare the expected return type up front.
TipBest Practice: Prefer vapply() for Production Code

sapply() is convenient because you rarely specify the output type; but that convenience is also a risk. If the data changes and one element returns something unexpected, sapply() silently gives you a different kind of object. vapply() rejects the mismatch, which is the kind of loud failure you want in a pipeline.


13.4 Anonymous Functions Inside lapply()

NoteSmall Functions on the Fly

You often do not need to name the function you are applying. R has always accepted function(x) ... in place, and from R 4.1 onwards the shorthand \(x) ... is available.


13.5 Map(), Reduce(), Filter()

NoteThree Functional Helpers
Function Purpose
Map(f, a, b, ...) Apply f element-wise to several lists or vectors in parallel.
Reduce(f, x) Combine the elements of x pairwise, left to right.
Filter(p, x) Keep the elements of x for which the predicate p returns TRUE.
NoteTurning Map() Output Into a Vector

Map() always returns a list. Wrap the call in unlist() (or vapply()) if you want a vector back.


13.7 Flattening Nested Lists

Noteunlist() Flattens All the Way

unlist() walks into every nested list and produces a single atomic vector, subject to the usual coercion rules. Pass recursive = FALSE if you want only the top-level elements flattened.

Noterapply(): Apply a Function Recursively

rapply() walks a nested list and applies a function to every atomic leaf. It is the apply-family member for nested structures.


13.8 A Worked Example: A Mini Gradebook

NotePutting the Whole Chapter Together

Imagine a small gradebook where each student is a nested list.

Every technique from this chapter appears: nesting, iteration with sapply(), filtering with Filter(), computing subject means with nested sapply() calls, and appending a record with c().


13.9 Summary

NoteKey Concepts at a Glance
Concept Key Takeaway
Combining lists c() concatenates top-level elements; append() inserts at a position.
Nesting Lists can contain lists; access with chained $ or [[.
lapply() Apply a function to each element; always returns a list.
sapply() Like lapply() but simplifies when it can; watch for type surprises.
vapply() sapply() with an asserted return type, safer for production code.
Anonymous functions function(x) ... or \(x) ... inside apply calls.
Map(), Reduce(), Filter() Functional helpers for parallel apply, folding, and filtering.
Flattening unlist() for atomic leaves; rapply() for deep nested work.
Lists of records When every element has the same shape, a data frame is usually better.
TipApplying This in Practice

Mastering lapply() / sapply() is the single biggest step you can take toward idiomatic R. Any time you are tempted to write a for loop that fills a vector, ask whether one of the apply-family functions fits. In the next chapter you will meet the matrix, the other 2-D structure in R, and the one you reach for whenever every cell in your table shares a type.