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"]
    classDef default fill:#003366,color:#ffffff,stroke:#ffcc00,stroke-width:3px,rx:10px,ry:10px;


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().



Summary

Concept Description
Adding and Nesting
Appending with c() c() on two lists flattens them at the top level
append() with Position append() inserts at a specific position in the list
Nested Lists Lists can hold other lists, building tree-like structures
Deep Access with $ or [[ Chain $ or [[ to reach deeply nested elements
Iteration and Flattening
lapply() Apply a function to every element, always returns a list
sapply() Like lapply but simplifies the result when possible
vapply() Like sapply but type-safe — you specify the expected output type
Flattening with unlist() Convert a list to an atomic vector by collapsing structure