which.max / which.min()
which.min(x) / which.max(x) which.min() and which.max() are base R functions that return the index of the minimum and maximum values in a vector respectively. They are essential tools for finding the position of extreme values without having to manually scan through entire vectors.
Syntax
which.min(x)
which.max(x)
Both functions accept a single vector x and return an integer index. The input can be numeric, integer, character, or logical — the comparison semantics depend on the vector type. For numeric vectors, values are compared by magnitude; for character vectors, lexicographic order determines which element is smallest or largest. The functions always return the index of the first occurrence when ties exist, which is important to remember when the input contains repeated extreme values.
Parameters
| Parameter | Type | Description |
|---|---|---|
x | numeric or character | A vector of numeric or character values |
Examples
Basic usage with numeric vectors
scores <- c(85, 92, 78, 88, 95)
# Find position of minimum value
which.min(scores)
# [1] 3
# Find position of maximum value
which.max(scores)
# [1] 5
Character vectors are compared lexicographically, meaning which.min() returns the position of the alphabetically first element and which.max() returns the position of the alphabetically last. This is the same ordering that sort() uses for character data. The comparison is case-sensitive and locale-dependent: in the C locale, uppercase letters sort before lowercase, while in most other locales the ordering follows dictionary conventions where case is handled differently.
Using with character vectors
fruits <- c("apple", "banana", "cherry", "date")
# Find first alphabetically
which.min(fruits)
# [1] 1 (apple)
# Find last alphabetically
which.max(fruits)
# [1] 4 (date)
Once you have the index of an extreme value, you almost always want the value itself. The common two-step pattern — idx <- which.max(x) followed by x[idx] — extracts the maximum element by position in one line. This is more efficient than computing the maximum separately and then searching for it, because which.max() does both in a single pass through the vector. For large vectors, this single-pass approach matters: it halves the number of full-vector scans compared to calling max() and then which(x == max(x)).
Finding extreme values with their positions
temperatures <- c(72, 68, 75, 71, 69, 82, 77)
# Get the index of the coldest and warmest days
coldest <- which.min(temperatures)
warmest <- which.max(temperatures)
# Extract the values
temperatures[coldest]
# [1] 68
temperatures[warmest]
# [1] 82
When working with data frames, which.max() and which.min() combine naturally with row indexing to extract entire rows. The expression df[which.max(df$column), ] gives you the complete observation corresponding to the extreme value in a specific column. This is more concise than filtering with a logical condition and avoids creating intermediate logical vectors. For grouped operations, combine which.max() with ave() or tapply() to find extreme rows within each group.
Using with data frames
df <- data.frame(
product = c("A", "B", "C", "D"),
price = c(29.99, 45.00, 19.99, 35.50)
)
# Find cheapest product
df[which.min(df)]
# [1] "C"
# Find most expensive product
df[which.max(df)]
# [1] "B"
Common patterns
Beyond the basic examples, a few recurring patterns appear in real-world R code. which.max() and which.min() are often paired with order() for sorting, applied to matrices for locating extreme cells, and combined with NA-aware logic for handling missing data safely. Each of these patterns solves a specific class of problem that would otherwise require more verbose base R or tidyverse alternatives.
Combining with order() for sorting
values <- c(5, 2, 8, 1, 9, 3)
# Get indices of min and max
c(min = which.min(values), max = which.max(values))
# min max
# 4 5
When applied to a matrix, which.max() and which.min() treat the matrix as a vector in column-major order — the index returned corresponds to the position if the matrix were flattened by columns. To convert this flat index back to row and column coordinates, use arrayInd(idx, dim(m)). This gives you the exact cell location of the extreme value, which is useful for heatmap annotations, spatial data, or any context where both the value and its grid position matter.
Finding extremes in matrices
m <- matrix(1:9, nrow = 3)
# [,1] [,2] [,3]
# [1,] 1 4 7
# [2,] 2 5 8
# [3,] 3 6 9
which.min(m)
# [1] 1
which.max(m)
# [1] 9
Both functions ignore NA values by default — they return the index of the extreme among the non-missing elements. This behaviour differs from max() and min(), which return NA when any value is missing unless na.rm = TRUE is set. The silent NA skipping of which.max() and which.min() is convenient for quick exploratory work but can mask data quality issues, so it is worth explicitly checking for missing values with which(is.na(x)) when the presence of NAs might affect your analysis.
Handling NA values
data <- c(1, NA, 5, 2, NA, 8)
# NA values are ignored by default
which.min(data)
# [1] 1
which.max(data)
# [1] 6
which.max() and which.min() in practice
which.max() returns the index of the first element equal to the maximum. For vectors with a unique maximum, this is the position of that element. For vectors with ties, it returns the index of the first occurrence only, to get all positions of the maximum, use which(x == max(x)).
A common use: df[which.max(df$value), ] extracts the row with the highest value in a column. This is equivalent to df[df$value == max(df$value), ][1, ] but shorter and avoids creating an intermediate logical vector. In data frames with NA values in the comparison column, which.max() ignores NA automatically, while max() returns NA unless na.rm = TRUE is specified.
which.min() and which.max() work on numeric, integer, and logical vectors. On logical vectors, which.max(x) returns the index of the first TRUE (since TRUE > FALSE), which is identical to which(x)[1]. On character vectors, they compare lexicographically.
When you need the rank of every element (not just the extreme), use rank() for ranks or order() for the sort permutation. which.max() is a specialized shorthand for the single most-extreme case, which covers the majority of practical use cases efficiently.
which.max() returns the index of the first maximum value in a vector. If there are ties, only the first position is returned, use which(x == max(x)) to find all positions. which.min() returns the index of the first minimum. Both ignore NA values silently; use which(is.na(x)) separately to find missing positions. For named vectors, the result is a named integer with the element name preserved.
which.max() returns the index of the first maximum value in a vector. If there are ties, only the first position is returned. Use which(x == max(x)) to find all positions tied for the maximum.
which.min() is the complementary function. Both accept numeric, integer, character, and logical vectors, for character vectors, comparison is lexicographic. Both ignore NA values silently; the index returned refers to the original un-filtered position in the input vector.
For named vectors, which.max() returns a named integer preserving the element name, so names(which.max(x)) gives the name of the maximum element — useful for categorical or factor-keyed data where the category name matters more than its position.
When working with data frames, combining which.max() with row indexing returns the complete row of the maximum: df[which.max(df$score), ]. The dplyr equivalent is slice_max(df, score, n = 1).