Menu

Sorting in R: sort(), order() and arrange()

How sorting really works in R: sort() for vectors, why data frames need order()'s index trick, descending and multi-column sorts, and dplyr's arrange().

This page includes runnable editors - edit, run, and see output instantly.

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 = TRUE flips it; NAs are dropped unless na.last = TRUE.
  • order(x) returns positions, and df[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 for order().
  • 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)).

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED