- Consider this data frame operation:
df <- data.frame(
score = c(70, NA, 85, 90, 85),
team = c("A", "A", "B", "A", "B")
)
rows <- which((df$score >= 85 & df$team == "B") | df$score == 90)
What is stored in rows? Answer: c(3, 4, 5)
When working with which() and logical conditions in R, your job is to carefully evaluate each row against the full condition — paying special attention to NA values and operator precedence.
The condition is (df$score >= 85 & df$team == "B") | df$score == 90. Let's trace through each row:
- Row 1: score=70, team="A" → (70>=85 & "A"=="B") | 70==90 → (FALSE & FALSE) | FALSE → FALSE
- Row 2: score=NA, team="A" → (NA & FALSE) | FALSE → FALSE | FALSE → FALSE
- Row 3: score=85, team="B" → (85>=85 & "B"=="B") | 85==90 → (TRUE & TRUE) | FALSE → TRUE
- Row 4: score=90, team="A" → (90>=85 & "A"=="B") | 90==90 → (TRUE & FALSE) | TRUE → TRUE
- Row 5: score=85, team="B" → same as row 3 → TRUE
which() returns the indices where the condition is TRUE, giving c(3, 4, 5) — confirming C is correct.
A (c(2, 3, 4, 5)) is wrong because row 2 has NA in score. Crucially, which() silently drops NA results rather than including them, so row 2 is excluded. B (c(3, 5)) forgets row 4 — the | df$score == 90 clause independently makes row 4 TRUE regardless of the team condition. D (c(4)) ignores rows 3 and 5, missing that team "B" with score ≥ 85 satisfies the left side of the |.
Remember: which() is your friend with NA — unlike bracket subsetting, it never returns NA indices. Always trace compound conditions row-by-row, and remember | only requires one side to be TRUE.
- Consider the following named vector and operations:
v <- c(a = 8, b = 3, c = 8, d = 5)
idx <- which(v == max(v))
v[idx + 1]
What is the result? Answer: c(b = 3, d = 5)
When working with named vectors in R, you need to track both the values and the positional indices separately — they're not the same thing, and this question tests exactly that distinction.
Start by tracing the code step by step. max(v) returns 8. Then which(v == max(v)) finds the positions where v equals 8. Since a is at position 1 and c is at position 3, idx becomes the integer vector c(a = 1, c = 3). Now the critical part: v[idx + 1] adds 1 to each positional index, giving positions 2 and 4. Position 2 holds b = 3 and position 4 holds d = 5. So the result is c(b = 3, d = 5), confirming C is correct.
Choice A is the most tempting trap — it returns the elements where the max occurs, not the elements after them. This is what you'd get from v[idx], not v[idx + 1]. Choice D is a partial trap: b = 3 is correct (position 2), but c = 8 confuses the name "c" as a vector element with position 3+1=4, ignoring that +1 already shifts past c. Choice B suggests only one valid result with an NA, which would only happen if one shifted index exceeded the vector's length — but both positions 2 and 4 are valid here.
As a study habit, always mentally separate names, values, and positions in named vectors. When you see arithmetic on index results from which(), you're manipulating positions, not names or values.
- What does the following R code return?
x <- c(2, 5, 8, 11, 14, 17)
keep <- x > 6 & c(TRUE, FALSE)
x[which(keep)] Answer: [1] 8 14
When you see R code combining logical vectors with subsetting, slow down and trace through each operation separately before combining them.
Start with x > 6, which compares each element of x <- c(2, 5, 8, 11, 14, 17) against 6, producing c(FALSE, FALSE, TRUE, TRUE, TRUE, TRUE). Now notice that the second operand in the & is c(TRUE, FALSE) — only two elements long. R recycles this shorter vector, repeating it to match the length of the longer one, giving c(TRUE, FALSE, TRUE, FALSE, TRUE, FALSE). The element-wise & then produces: FALSE&TRUE, FALSE&FALSE, TRUE&TRUE, TRUE&FALSE, TRUE&TRUE, TRUE&FALSE, which simplifies to c(FALSE, FALSE, TRUE, FALSE, TRUE, FALSE). These are positions 3 and 5, corresponding to values 8 and 14. Finally, which(keep) extracts those indices, and x[which(keep)] returns c(8, 14) — confirming B is correct.
Choice A (8 11 14 17) ignores recycling entirely and just takes all values greater than 6. Choice C (11 17) picks positions 4 and 6, which would result from recycling starting with FALSE instead of TRUE — a common off-by-one error when misreading the recycled pattern. Choice D (2 8 14) incorrectly assumes the TRUE in the recycled vector always activates the first element, mixing up the indexing logic.
Your study tip: whenever you see a short vector used in a logical operation, immediately write out the recycled version before evaluating anything else — recycling mistakes are the number-one trap in R vector logic questions.
- Consider the following code:
x <- c(NA, 2, 5, NA, 2)
i <- which(is.na(x) | x %in% c(2, 4))
What is the value of i? Answer: c(1, 2, 4, 5)
When working with logical indexing in R, the key is to trace through each operation layer by layer. Here, two functions work together: is.na() checks for missing values, %in% checks for membership, and which() converts a logical vector into the positions where TRUE appears.
Start with x <- c(NA, 2, 5, NA, 2). Evaluating is.na(x) gives c(TRUE, FALSE, FALSE, TRUE, FALSE) — positions 1 and 4 are NA. Next, x %in% c(2, 4) gives c(FALSE, TRUE, FALSE, FALSE, TRUE) — positions 2 and 5 equal 2 (no element equals 4). Combining with | (OR), you get c(TRUE, TRUE, FALSE, TRUE, TRUE). Applying which() returns the indices where this is TRUE: positions 1, 2, 4, 5, making D the correct answer.
A (c(2, 5)) is tempting if you confuse the values at the matching positions with the positions themselves — which() always returns indices, never values. B (c(1, 4)) is what you'd get if you only ran which(is.na(x)) and forgot the %in% condition entirely. C (c(1, 2, 3, 4, 5)) would require every element to match, but position 3 (value 5) is neither NA nor in c(2, 4), so it evaluates to FALSE.
A reliable strategy: when you see which(), mentally remind yourself "this gives me where, not what." Always evaluate each sub-expression separately before combining them with logical operators.
- What does the following expression return?
x <- c(2, 3, 4, 3, 5)
which(!x %in% c(2, 4) & seq_along(x) > 1) Answer: c(2, 4, 5)
When you see chained logical conditions in R, break them into individual components first, then combine. This question tests your understanding of %in%, seq_along(), logical negation, and the which() function.
Start by evaluating each piece for x <- c(2, 3, 4, 3, 5). The expression !x %in% c(2, 4) asks: which elements of x are not in {2, 4}? Elements at positions 1, 2, 3, 4, 5 are 2, 3, 4, 3, 5 — so !x %in% c(2, 4) yields c(FALSE, TRUE, FALSE, TRUE, TRUE). Next, seq_along(x) > 1 generates indices 1:5 and checks which exceed 1, giving c(FALSE, TRUE, TRUE, TRUE, TRUE). Combining with &: position 1 is FALSE (fails both), position 2 is TRUE & TRUE = TRUE, position 3 is FALSE & TRUE = FALSE, position 4 is TRUE & TRUE = TRUE, position 5 is TRUE & TRUE = TRUE. So which(...) returns the positions where the combined condition is TRUE: **c(2, 4, 5), confirming B**.
Choice A, c(1, 3), mistakenly returns positions where x %in% c(2, 4) — the un-negated version — ignoring the index filter entirely. Choice C, c(2, 3, 4, 5), drops the seq_along > 1 filter and only applies the !x %in% condition, forgetting that position 3 (value 4) is excluded. Choice D, c(1, 2, 4, 5), ignores the negation on %in%, treating values 2 and 4 as matches rather than exclusions.
When you see !x %in% vector, mentally read it as "elements NOT found in that vector" — the ! negates the entire %in% result, not just x.
- Consider the following named vector computation:
x <- c(alpha = 6, beta = 2, gamma = 6, delta = 9)
i <- which(x == 6)
c(indices = sum(i), values = sum(x[i]), labels = length(names(i)))
What is returned? Answer: c(indices = 4, values = 12, labels = 2)
When working with which() on named vectors in R, you need to track three things simultaneously: what indices are returned, what names those indices carry, and what values they point to.
Starting with x <- c(alpha = 6, beta = 2, gamma = 6, delta = 9), calling which(x == 6) finds positions where the condition is true. That's positions 1 and 3 (alpha and gamma). Crucially, which() preserves the names of the original vector, so i becomes c(alpha = 1, gamma = 3) — a named integer vector with values 1 and 3.
Now evaluate each piece of the final c() call: sum(i) adds the index values 1 + 3 = 4. sum(x[i]) subsets x at positions 1 and 3, giving 6 + 6 = 12. length(names(i)) asks how many names i has — since which() preserved "alpha" and "gamma", names(i) is a character vector of length 2. This confirms D is correct.
A is wrong because it uses sum(i) = 2, confusing the count of matching elements with the sum of their positions. B is wrong on two counts — it correctly gets sum(i) = 4 but then computes sum(x[i]) = 6, as if only one element matched. C gets sum(i) and sum(x[i]) right but sets labels = 0, reflecting the mistaken belief that which() strips names from its output.
A good rule of thumb: which() in R behaves like a named subsetting operation — it inherits the names of whatever logical vector it evaluated, so the result is never anonymous.
- The following matrix is created in R:
m <- matrix(c(3, 8, 1, 8, 5, 2), nrow = 2)
which(m >= 5, arr.ind = TRUE)
In what order are the (row, column) coordinate pairs returned? Answer: (2, 1), (2, 2), (1, 3)
When working with which() in R, the critical concept to internalize is that R stores matrices column by column (column-major order). This means R scans through the first column entirely before moving to the second, and so on — and which() follows that same traversal order when identifying elements that meet a condition.
Here's how m actually looks after matrix(c(3, 8, 1, 8, 5, 2), nrow = 2) is created:
[,1] [,2] [,3]
[1,] 3 1 5
[2,] 8 8 2
Now apply m >= 5: the value 8 at position (2,1) qualifies, then 8 at (2,2) qualifies, then 5 at (1,3) qualifies. So the results are returned as (2,1), (2,2), (1,3) — which is answer A.
Answer B lists the same coordinates but in the wrong order (1,3), (2,1), (2,2), as if R scanned by value magnitude rather than position. Answer C includes (1,2) for the value 1, which doesn't satisfy >= 5 — this is simply a misread of the matrix layout. Answer D references position (2,3) where the value is 2, which also fails the condition, indicating a confusion about how the input vector maps to the matrix.
Your study tip: always reconstruct the matrix mentally before applying any operation. Fill values down each column first. When arr.ind = TRUE is used, remember that results are returned in the same column-major scan order R uses internally — row index alone does not determine position priority.
- Consider the following R code:
x <- c(4, NA, 7, 2, NA, 7)
x[which(x >= 4 & x != 7)]
What is returned? Answer: [1] 4
When working with subsetting in R, it's crucial to distinguish between logical subsetting with [ ] and index-based subsetting with which() — they handle NA values very differently.
Here's what the code actually does. First, the condition x >= 4 & x != 7 is evaluated across the vector c(4, NA, 7, 2, NA, 7). This produces c(TRUE, NA, FALSE, FALSE, NA, FALSE) — only position 1 (value 4) returns TRUE, because 4 >= 4 and 4 != 7. The two NA positions return NA (not TRUE), and 7 and 2 return FALSE. Then which() steps in: it returns only the integer indices where the condition is strictly TRUE, completely ignoring NA values. So which(...) returns 1. Finally, x[1] gives you 4. Answer A is correct.
Answer B (4 NA NA) is the trap most students fall into. This is what you'd get if you used logical subsetting directly — x[x >= 4 & x != 7] — because that approach preserves NA positions as NA in the output. which() eliminates that behavior entirely. Answer C (4 7 7) reflects a misreading of the condition: if you only applied x >= 4 without x != 7, the 7s would pass through — but they're explicitly excluded. Answer D (1) confuses the index returned by which() with the final output; which() gives you position 1, but you then use that to extract the value at that position.
The key takeaway: which() converts a logical vector to integer indices and drops NAs, while direct logical subsetting keeps them. Watch for this distinction whenever NA values are present in a subsetting question.