sort() Sorts a Vector's Values
For a plain vector, sort() does exactly what you expect - returns the values in ascending order (the original is untouched):
Two options worth knowing. decreasing = TRUE flips the direction. And sort() silently drops NA values by default - sort(c(3, NA, 1)) returns just 1 3. If missing values must survive the sort, say where they go: sort(x, na.last = TRUE) keeps them, placed after the ordered values.
So far, so obvious. The interesting part is why sort() is the wrong tool for data frames.
order() Returns Positions - The Mental Shift
sort() answers "what are the values, in order?" order() answers a different question: "which positions would I visit to see the values in order?"
Read order(times) as instructions: the smallest value sits at position 2, the next at position 4, then 1, then 3. Indexing with those instructions - times[order(times)] - reproduces sort(times) exactly.
Why does that matter? Because a data frame row is a team of values that must move together. Sorting the time column alone with sort() would reorder one column and leave its teammates behind - every row scrambled. order() gives you row positions instead, and putting positions in the row slot of the brackets moves whole rows:
Ben's 9.8 arrives with Ben's name still attached. That is the whole trick, and it is the idiom for sorting data frames in base R: df[order(df$column), ]. (The printed row labels - 2 4 1 3 - are the original row numbers tagging along; harmless.)
Descending and Multi-Column Sorts
For descending numeric sorts, negate the column - ordering -time ascending is ordering time descending. And order() takes multiple columns, earlier ones first, later ones breaking ties:
The second call sorts departments alphabetically and, within each department, salaries high to low. The negation trick only works on numbers - -df$name on a character column is an error. For descending text, use order(df$name, decreasing = TRUE) instead (which flips every sort key at once).
rank() Is the Third Sibling
In passing: rank(x) answers yet another question - "what place does each value hold?" - without moving anything:
sort() gives ordered values, order() gives positions to visit, rank() gives each value's standing in place. People conflate order() and rank() constantly; notice they are inverse permutations of each other, and reach for rank() only when you literally want rankings (leaderboards, percentiles).
The dplyr Way: arrange()
dplyr's arrange() wraps the whole order() dance in a verb - columns are sort keys, desc() flips one (static; the sandbox runs base R only):
library(dplyr)
runners |> arrange(time)
staff |> arrange(dept, desc(salary))
No brackets, no $, no negation trick - desc() works on character columns too. One behavioral difference worth knowing: arrange() puts NA values at the end regardless of direction, while base order() also defaults to na.last = TRUE. See the dplyr intro for how arrange() chains with filter() and friends.
Character Sorting Has Sharp Edges
Two caveats when sorting text. First, digits stored as text sort as text - character comparison goes symbol by symbol:
"10" sorts before "2" because the comparison stops at the first symbol: "1" < "2". If a column of numbers sorts weirdly, it is almost certainly stored as character - convert with as.numeric() first (a frequent aftermath of a messy CSV import).
Second, text ordering depends on your system locale - accents, case, and non-Latin scripts can sort differently on your laptop than on a server with a C locale. Sorted output that must be identical across machines should either set the locale explicitly or sort a normalized key.
What You Take Away
sort(x)orders a vector's values;decreasing = TRUEflips it;NAs are dropped unlessna.last = TRUE.order(x)returns positions, anddf[order(df$x), ]is the base idiom for sorting data frames.- Multi-column:
order(df$a, -df$b); negation for descending works on numbers only. rank()gives standings without reordering - not a substitute fororder().- dplyr's
arrange(dept, desc(salary))is the same sort with less punctuation. "10"sorts before"2"in text - check your column types.
Next up: collapsing rows into group summaries with table(), tapply(), aggregate(), and dplyr's group_by().
Frequently Asked Questions
How do I sort a data frame by a column in R?
Use order() inside the row brackets: df[order(df$price), ]. order() returns the row positions in sorted order, and indexing with them rearranges the whole data frame. sort() won't work here - it sorts one vector's values, losing their connection to the other columns.
What is the difference between sort() and order() in R?
sort(x) returns the values of x rearranged into order. order(x) returns the positions (indices) that would put x in order - which is what you need to sort a whole data frame by one of its columns: df[order(df$x), ].
How do I sort in descending order in R?
For a vector: sort(x, decreasing = TRUE). For a data frame: df[order(-df$x), ] (negate a numeric column) or df[order(df$x, decreasing = TRUE), ], which also works for character columns where negation doesn't. In dplyr: arrange(df, desc(x)).
How do I sort by multiple columns in R?
Pass them all to order(): df[order(df$dept, -df$salary), ] sorts by department, then by salary within each department, descending. Earlier arguments are the primary keys; later ones break ties. In dplyr: arrange(df, dept, desc(salary)).