Subsetting & Indexing

Reviewed & published by Brayan K

Subsetting is the act of pulling specific elements out of an R object using square brackets, with [ ] keeping a subset, [[ ]] extracting one element, and $ reaching a named element of a list.

Part of the free R course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

By the end you'll confidently index by position, by negative position, by logical condition, and by name, and you'll know exactly when to use [ ] versus [[ ]] versus $ on vectors and lists.

What You'll Learn in This Lesson

1️⃣ Indexing a Vector: Four Ways

Single brackets accept four kinds of index. A positive number selects by position, a vector of numbers selects several, a negative number drops positions, and a name (if your vector is named) selects by label. R indexes from 1.

# Single brackets [ ] subset a vector. Four ways to choose:
x <- c(a = 10, b = 20, c = 30, d = 40)

x[2]               # by position     -> b
                   #                     20
x[c(1, 3)]         # several          -> a  c
                   #                     10 30
x[-1]              # drop position 1  -> b  c  d
                   #                     20 30 40
x["c"]             # by name          -> c
                   #                     30

2️⃣ Logical Indexing & which()

The most powerful form: pass a logical vector and keep the elements where it is TRUE. The comparison itself (nums > 10) produces that logical vector. Use which() when you want the positions instead of the values, and combine tests with & (and) / | (or).

# Logical indexing keeps elements where the test is TRUE.
nums <- c(5, 12, 8, 20, 3)

nums > 10          # the test itself -> [1] FALSE  TRUE FALSE  TRUE FALSE
nums[nums > 10]    # keep the TRUEs  -> [1] 12 20

# which() turns a logical vector into the POSITIONS that are TRUE
which(nums > 10)   # -> [1] 2 4

# Combine conditions with & (and) and | (or)
nums[nums > 4 & nums < 15]  # -> [1]  5 12  8

3️⃣ Lists: [ ] vs [[ ]] vs $

Lists hold elements of different types, so the bracket you choose matters. [ ] keeps the list wrapper and returns a shorter list. [[ ]] reaches inside and returns the bare element. $ is a friendly shorthand for [[ ]] by name. Once you have the element with [[ ]] or $, you can index into it normally.

# Lists need different tools. [ ] keeps the list wrapper;
# [[ ]] and $ reach INSIDE to the element itself.
person <- list(name = "Ada", age = 36, langs = c("R", "C"))

person[1]          # a LIST of length 1 (still wrapped)
person[[1]]        # the VALUE inside  -> [1] "Ada"
person$name        # same thing, by name -> [1] "Ada"
person[["age"]]    # by name with [[ ]]  -> [1] 36

# $ and [[ ]] dig out the real object so you can use it:
person$langs[2]    # second language    -> [1] "C"

Memorise the one-liner: [ ] keeps the box, [[ ]] and $ open the box.

Your turn. Fill in the ___ blanks, run it, and compare with the expected output.

# 🎯 YOUR TURN — replace each ___ then run it.
temps <- c(mon = 18, tue = 22, wed = 25, thu = 19, fri = 30)

# 1) Get Wednesday's temperature BY NAME
print(temps[___])        # 👉 replace ___ with "wed"

# 2) Keep only the days warmer than 20 (logical indexing)
warm <- temps[temps ___ 20]   # 👉 replace ___ with >
print(warm)

# 3) Print the POSITIONS of those warm days with which()
print(which(temps > 20))

# ✅ Expected output:
# wed
#  25
# tue wed fri
#  22  25  30
# tue wed fri
#   2   3   5

Mini-Challenge: Dig Into a List

This challenge forces you to switch between $, [[ ]], and [ ] on the same object — exactly the muscle memory you need for real data work.

# 🎯 MINI-CHALLENGE: dig into a nested list.
#
# 1. Build:  order <- list(id = 1007,
#                          items = c("pen", "ink", "pad"),
#                          paid = TRUE)
# 2. Use $ to print the items vector.
# 3. Use [[ ]] to print just the SECOND item ("ink").
# 4. Use [ ] (single bracket) on items to keep the first and third.
# 5. Print which(nchar(order$items) == 3) to find 3-letter items.
#
# ✅ Example output:
#    [1] "pen" "ink" "pad"
#    [1] "ink"
#    [1] "pen" "pad"
#    [1] 1 2 3

# your code here

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Subsetting

TaskR Syntax
By positionx[2]
Drop a positionx[-1]
By conditionx[x > 10]
Positions that matchwhich(x > 10)
List element (value)lst[[1]] / lst$name
List subset (wrapped)lst[1]

🎉 Lesson Complete!

Practice quiz

In R, what does x[1] return for a vector x?

  • The first element (R indexes from 1)
  • An empty result
  • The second element
  • The whole vector

Answer: The first element (R indexes from 1). R is 1-indexed, so x[1] is the first element.

What does x[-1] do?

  • Returns the last element
  • Returns everything except the first element
  • Errors
  • Returns NA

Answer: Returns everything except the first element. A negative index drops that position, so x[-1] is all but the first.

What does x[0] return in R?

  • The first element
  • An error
  • An empty vector of the same type
  • NULL

Answer: An empty vector of the same type. Index 0 selects nothing, returning a length-0 vector of x's type.

Which operator extracts a single VALUE from a list, dropping the wrapper?

  • [ ] single brackets
  • Curly braces
  • Parentheses
  • [[ ]] double brackets

Answer: [[ ]] double brackets. [[ ]] reaches inside and returns the bare element; [ ] keeps the list wrapper.

What does lst$name do on a list?

  • Returns a list of length 1
  • Shorthand for lst[["name"]] by name
  • Errors on lists
  • Returns all elements

Answer: Shorthand for lst[["name"]] by name. $ is a friendly shorthand for [[ ]] by name on lists and data frames.

Given nums, what does nums[nums > 10] return?

  • The positions where nums > 10
  • The count of elements over 10
  • The elements whose value exceeds 10
  • TRUE or FALSE

Answer: The elements whose value exceeds 10. A logical vector inside [ ] keeps the elements where the test is TRUE.

What does which(nums > 10) return?

  • The integer positions that are TRUE
  • The matching values
  • A single TRUE/FALSE
  • The number of matches

Answer: The integer positions that are TRUE. which() converts a logical vector into the positions where it is TRUE.

Why does person[1] give a list but person[[1]] give the value?

  • person[1] errors silently
  • Single brackets keep the container; double brackets extract contents
  • They are identical
  • [[1]] keeps the wrapper

Answer: Single brackets keep the container; double brackets extract contents. [ ] preserves the list wrapper; [[ ]] extracts the element itself.

Which correctly combines two element-wise conditions inside [ ]?

  • nums[nums > 4 && nums < 15]
  • nums[nums > 4 AND nums < 15]
  • nums[nums > 4 & nums < 15]
  • nums[nums > 4 to 15]

Answer: nums[nums > 4 & nums < 15]. Single & is element-wise; && only compares the first element.

What happens with x[c(1, -2)] mixing positive and negative indices?

  • Returns elements 1 and 2
  • Drops element 2 only
  • Returns the whole vector
  • It is an error

Answer: It is an error. You cannot mix positive and negative indices in one bracket.

Continue this course

Frequently asked questions

What is the difference between [ ], [[ ]], and $ in R?

Single brackets [ ] always return the same kind of object you started with — subsetting a vector gives a (shorter) vector, and subsetting a list gives a (shorter) list, keeping the wrapper. Double brackets [[ ]] reach inside and extract a single element as its own object, dropping the list wrapper. The dollar sign $ is a shorthand for [[ ]] by name that only works on lists and data frames: person$name is the same as person[["name"]]. Rule of thumb: [ ] to keep a subset, [[ ]] or $ to pull one thing out.

When should I use negative indexing?

A negative index drops elements rather than keeping them: x[-1] returns everything except the first element, and x[-c(1, 2)] drops the first two. It is concise when you want 'all but a few' positions. Two cautions: you cannot mix positive and negative indices in the same bracket, and negative indexing does not work with names — x[-"a"] is an error. For name-based removal, use x[!names(x) %in% "a"] instead.

What does which() do?

which() converts a logical vector (TRUE/FALSE) into the integer positions where the value is TRUE. So if nums > 10 gives FALSE TRUE FALSE TRUE, then which(nums > 10) returns 2 4 — the index positions. It is useful when you need the locations rather than the values, for example to update those positions or to find the index of a maximum with which.max(). You often do not need which() for filtering — nums[nums > 10] works directly on the logical vector.

Why does person[1] give a list but person[[1]] give the value?

This is the single-versus-double bracket rule applied to lists. person[1] uses single brackets, which preserve the container, so you get a list of length 1 that still wraps the value. person[[1]] uses double brackets, which extract the contents, so you get the bare value (a number, a character vector, etc.). If you try to do math or string operations and get a confusing 'list' error, you almost certainly used [ ] where you needed [[ ]] or $.