base Position() in R: Find Index of First Element Matching a Predicate
base Position() in R returns the integer index of the first element in a vector or list for which a predicate function returns TRUE. It is the index-returning sibling of Find() and a higher-order alternative to which()[1] that short-circuits on the first match.
Position(\(x) x > 5, c(1, 3, 7, 2, 8)) # first index above 5 Position(is.character, list(1, "a", 2)) # first non-numeric slot Position(is.na, c(1, 2, NA, 4)) # first NA position Position(\(x) x == "b", letters) # locate a value Position(\(x) x > 100, 1:5, nomatch = 0L) # no match, return 0 Position(\(x) x > 0, c(-1, 2, 3), right = TRUE) # last match from the right Position(\(x) nchar(x) > 4, c("hi", "hello")) # first long string
Need explanation? Read on for examples and pitfalls.
What Position() does
Position() is a short-circuiting index finder. You pass a predicate function and a vector or list. R walks the elements in order, applies the predicate to each, and returns the integer index of the first one that yields TRUE. If nothing matches, it returns NA_integer_ by default. Unlike which(), it stops at the first hit, which matters when the input is long or the predicate is expensive. Unlike match(), the test is an arbitrary function, not equality against a lookup table.
The third element (7) is the first that exceeds 5, so the function returns 3.
Syntax
The signature has four arguments, two of them optional. The full form is Position(f, x, right = FALSE, nomatch = NA_integer_). Most calls use only the first two.
Position() and Find() share the same argument list. Find() returns the matched element; Position() returns its index. Pick by what your downstream code needs.| Argument | Purpose | Default |
|---|---|---|
f |
Predicate function applied to each element; must return a single logical | required |
x |
A vector or list to search | required |
right |
If TRUE, walk from the last element backward and return position of the last match |
FALSE |
nomatch |
Value returned when no element satisfies f |
NA_integer_ |
The predicate f must accept one argument and return a scalar TRUE or FALSE. A vectorized predicate that returns a logical vector raises an error.
Examples by use case
Four shapes cover most real uses. Each predicate stays in one expression; nothing else changes.
Locate the first value above a threshold
The canonical use case is finding where a sequence crosses a boundary. Pass an inline predicate and let Position() stop at the first qualifying value.
Wait, that printed 22.8, not a value above 25. The reason is that mtcars rows do not start in sorted order; index 3 is the first row whose mpg happens to satisfy. Let me sort:
After sorting ascending, index 25 is the first value above 25, namely 26.
Find the first non-numeric slot in a heterogeneous list
Mixed-type lists are where Position() shines. A predicate can ask any question about element type, structure, or content.
Find the first NA
is.na as the predicate. Position(is.na, x) returns the first NA position. For "is there any NA," anyNA(x) is faster; use Position() when you need the location.Find from the right with right = TRUE
The right argument flips the walk direction. The walker still returns the position in the original vector, just of the last match instead of the first.
Position() vs which() vs match() vs Find()
Four base R tools, four different answers to "where is it?". Pick by what you need back.
| Function | Returns | Predicate? | Stops at first? |
|---|---|---|---|
Position(f, x) |
First matching index (integer) | Yes, any function | Yes |
Find(f, x) |
First matching value | Yes, any function | Yes |
which(x > 5) |
All matching indices (integer vector) | No, condition only | No |
match(needle, haystack) |
Indices of needle in haystack |
No, equality lookup | Per-element first match |
Position() when the predicate is non-trivial and the vector is large. which(v > 5)[1] evaluates the condition on every element and allocates a full index vector before discarding all but one. Position() walks until the first match and stops, which is meaningfully faster when the predicate is expensive or the match is near the front.Use nomatch to avoid downstream NA propagation
The nomatch default is NA_integer_. That is usually correct, but if your downstream code feeds the result directly into an index expression, an NA will silently return an NA value. Set nomatch = 0L (or length(x) + 1L) when you want a sentinel instead.
Common pitfalls
Three mistakes trip beginners. Watch for these silently incorrect calls.
Try it yourself
Try it: Use Position() on airquality$Ozone to find the index of the first measurement above 100. Save the result to ex_first_high.
Click to reveal solution
Explanation: The predicate guards against NA first because NA > 100 returns NA, not FALSE, and Position() treats anything non-TRUE as no match. Index 30 is the first reading that survives both checks.
Related base R functions
- [
Find()](Find-in-R.html) returns the matching value; same signature asPosition(). - [
Filter()](base-Filter-in-R.html) keeps all elements satisfying a predicate. - [
Reduce()](base-Reduce-in-R.html) folds a list with a binary function. which()returns every index where a logical vector isTRUE.match()performs equality lookup of one vector against another.
FAQ
What does Position() return when no element matches?
By default Position() returns NA_integer_ when the predicate never holds. You can change this with the nomatch argument, for example nomatch = 0L to get a numeric sentinel. The NA default is consistent with match() and lets you detect "no hit" with is.na(). If you index back into the vector with the raw result, an NA will silently propagate, which is the most common Position() bug.
How is Position() different from which()?
which(condition) evaluates the condition on every element of the vector and returns all indices where it is TRUE. Position(predicate, x) walks element by element and stops at the first match. For finding "any" match in a large vector with an expensive test, Position() is faster because it short-circuits. For finding all matches or counting them, which() is the right tool.
Can Position() take a function with extra arguments?
Yes. Wrap your predicate in an anonymous function that closes over the extra arguments: Position(\(x) my_test(x, threshold = 5), v). R 4.1 added the \(x) shorthand for function(x). You can also pass a named function directly when it accepts a single argument, such as Position(is.na, v) or Position(is.character, v).
Does Position() work on lists as well as vectors?
Yes. Position() accepts any vector or list, and the predicate is applied to each element regardless of type. For a list of data frames, Position(\(d) nrow(d) > 100, list_of_dfs) finds the first one with more than 100 rows. This makes Position() a natural fit for searching heterogeneous structures where indexing by name is not enough.
Is Position() the same as purrr::detect_index()?
They serve the same purpose. purrr::detect_index() is the tidyverse counterpart with a small API improvement: it returns 0 instead of NA when no element matches, which is friendlier in downstream code. The base Position() requires explicit nomatch = 0L to match that behavior. For pure base R code with no tidyverse dependency, Position() is the right choice.