rguides

position

R has no single function called position(). Instead, several related functions handle positional indexing: which.max(), which.min(), rank(), and order(). This article covers them together, since they’re often used in combination to answer “where is the max?”, “what’s the rank order?”, and “what indices would sort this?“

which.max, position of the maximum

which.max returns the index of the first maximum value in a vector:

which.max(c(1, 3, 2, 3, 0))
# [1] 2

When the maximum value appears more than once in the vector, which.max() still returns a single index — specifically the position of the first occurrence, scanning from left to right. If you need all tied positions, you must combine which() with a logical comparison against the maximum value, a pattern shown later in the Common patterns section.

With ties, it returns the position of the first occurrence:

which.max(c(1, 3, 3, 3, 2))
# [1] 2  (first 3 is at position 2)

The mirror image of which.max() is which.min(), which locates the smallest value rather than the largest. It follows the same tie-breaking rule — first occurrence wins — and shares the same vectorized interface, so switching between maximum and minimum position queries requires only changing the function name.

which.min is the equivalent for the minimum:

which.min(c(5, 1, 3))
# [1] 2

Real-world vectors often contain missing values, and which.max() and which.min() silently skip over NA and NaN entries — they return the position of the maximum or minimum among the non-missing elements. This default behavior is convenient for quick inspection but can mask data quality problems if you are not expecting missing values.

Both functions ignore NA and NaN values by default:

which.max(c(1, NA, 3, 2))
# [1] 3

Although the functions skip NAs automatically, there are times when you want explicit control — for instance, to verify which observations were excluded or to apply a different missing-value policy. Prefiltering with !is.na() or na.omit() makes the exclusion step visible in your code and gives you the same result while documenting your intent.

To handle NAs explicitly, prefilter:

x <- c(1, NA, 3, 2)
which.max(x[!is.na(x)])
# [1] 3
which.max(na.omit(x))
# [1] 3

Finding the position of a single extreme value answers “where is it?”, but many analyses need the full ordering — which observation is first, second, third, and so on. R’s rank() function assigns an ordinal position to every element in a vector, giving you a complete picture of the value hierarchy rather than just the endpoints.

rank, rank values in a vector

rank assigns ranks to values. The default tie method is "average":

rank(c(3, 1, 2))
# [1] 3 1 2

With ties, "average" assigns the mean rank to tied values. Other tie methods handle duplicates differently: "first" assigns ranks in order of appearance, "min" gives every tied element the lowest rank in the group, and "max" gives the highest. Choose based on whether you want the ranking to reflect order within ties or to collapse ties to a single rank.

rank(c(1, 2, 2, 3))                    #> [1] 1.0 2.5 2.5 4.0  (average)
rank(c(1, 2, 2, 3), ties.method = "first")  #> [1] 1 2 3 4
rank(c(1, 2, 2, 3), ties.method = "min")   #> [1] 1 2 2 4
rank(c(1, 2, 2, 3), ties.method = "max")   #> [1] 1 3 3 4

na.last controls where NA values land in the ranking. Setting na.last = TRUE places NAs at the end, na.last = FALSE places them at the beginning, and na.last = NA removes them entirely from the output — matching the behavior of functions like mean(x, na.rm = TRUE).

rank(c(1, NA, 2), na.last = TRUE)   #> [1] 1 NA  2  — NA at end
rank(c(1, NA, 2), na.last = FALSE)  #> [1] NA  1  2  — NA at start
rank(c(1, NA, 2), na.last = NA)     #> [1] 1 2  — NA removed

order, indices for sorting

order() returns the permutation that would sort a vector — small values get small indices. The result can be used directly as a subscript to reorder another vector, a data frame, or a matrix. For descending order, set decreasing = TRUE.

order(c(3, 1, 2))          #> [1] 2 3 1  (position 2 is smallest)
x <- c("c", "a", "b")
x[order(c(3, 1, 2))]       #> [1] "a" "b" "c"

order(c(3, 1, 2), decreasing = TRUE)  #> [1] 1 3 2

The most practical use of order() is sorting entire data frames by one or more columns. You pass the column of interest to order() and use the resulting index vector to reorder the rows — the equivalent of clicking a spreadsheet column header to sort. This approach works with any rectangular data structure and scales to multi-column sorts by passing additional vectors to order().

To sort a data frame by a column, pass the column to order() and use the result as a row index. This is the base R equivalent of dplyr’s arrange().

df <- data.frame(name = c("bob", "alice", "carol"), score = c(85, 92, 78))
df[order(df$score), ]
#>     name score
#> 3  carol    78
#> 1    bob    85
#> 2  alice    92

Sorting and ranking operate on the whole vector, but sometimes you only care about one specific value — “where does 30 appear?” or “which rows exceed a threshold?” For these targeted queries, which() converts a logical condition into the set of indices where that condition holds true, making it the most general position-finding tool in base R.

Getting the position of a specific value

which() returns all indices where a logical condition is TRUE — the most general position-finding tool. For the first match only, match() is more efficient and returns NA rather than integer(0) when no match exists. Use %in% for existence checks where the position itself is not needed.

x <- c(10, 20, 30, 40, 50)
which(x == 30)        #> [1] 3
which(x > 25)         #> [1] 3 4 5

match(30, x)           #> [1] 3
30 %in% x              #> [1] TRUE

Common patterns and pitfalls

Forgetting that which.max() returns an index — not the value itself — is a common error. Use x[which.max(x)] to get the value at that position. When ties exist, which.max() only reports the first occurrence; use which(x == max(x)) to get all positions tied for the maximum.

x <- c(3, 1, 4, 1, 5, 9)
which.max(x)            #> [1] 6  (index, not value)
x[which.max(x)]         #> [1] 9  (the value itself)

which.max(c(1, 2, 2, 2))   #> [1] 2  — first occurrence only
which(x == max(x))      #> [1] 2 3 4  — all ties

# Top 3 indices by value
order(x, decreasing = TRUE)[1:3]   #> indices of largest values

position() is a purrr function, not a base R function. In base R, finding the position of an element uses which(): which(x == value)[1]. In purrr, detect_index() returns the position of the first element matching a predicate. For named vectors and lists, match() returns the position of each element of a lookup vector in a table vector.

See also

  • which() — Return indices of TRUE values in a logical vector.
  • match() — Find the position of first matches between vectors.
  • which.max() and which.min() — Position of the maximum or minimum value in a vector.