R Sort Data Frame: order() Function with Example
⚡ Smart Summary
Sorting a data frame in R relies on the order() function, which returns row positions rather than values. Passing those positions inside square brackets rearranges the rows by one column, by several columns, or in descending order.
Sorting Data in R
In data analysis you can sort your data according to a certain variable in the dataset. In R, we can use the help of the function order(). In R, we can easily sort a vector of continuous variable or factor variable. Arranging the data can be of ascending or descending order.
Syntax:
sort(x, decreasing = FALSE, na.last = NA)
Argument:
- x: A vector containing a continuous or factor variable
- decreasing: Controls the direction of the sort. By default, decreasing is set to FALSE, which gives ascending order.
- na.last: Indicates whether NA values should be put last or not. sort() uses NA by default, which drops missing values; order() uses TRUE, which keeps them and places them last.
sort() vs order() vs rank() in R
The syntax above belongs to sort(), yet every example on this page calls order(). The two functions answer different questions, and mixing them up is the most common reason a sort appears to fail. rank() completes the trio.
| Function | Returns | Use it when | Default NA handling |
|---|---|---|---|
| sort() | The values themselves, in order | You only need a sorted vector | na.last = NA, so NA is dropped |
| order() | The row positions that produce the sorted order | You need to reorder a whole data frame | na.last = TRUE, so NA goes last |
| rank() | The rank of each element in its original position | You need a ranking column, not a reordered table | na.last = TRUE |
Because order() returns positions, it is the only one of the three that can drive the square-bracket subsetting used throughout this page. The R base documentation for order() lists every argument, including the method argument that selects the sorting algorithm.
Example 1: Sort a Data Frame by One Column
For instance, we can create a tibble data frame and sort one or multiple variables. A tibble data frame is a modern take on the data frame. It improves the syntax of the data frame and avoids frustrating data type formatting, especially character to factor conversion. It is also a convenient way to create a data frame by hand, which is our purpose here. To learn more about tibble, please refer to the vignette: https://tibble.tidyverse.org/articles/tibble.html
The code below builds a 50-row tibble of five random columns and then sorts every row by the values in c1. set.seed(1234) fixes the random draw, so the same numbers appear on any machine.
library(dplyr) set.seed(1234) data_frame <- tibble( c1 = rnorm(50, 5, 1.5), c2 = rnorm(50, 5, 1.5), c3 = rnorm(50, 5, 1.5), c4 = rnorm(50, 5, 1.5), c5 = rnorm(50, 5, 1.5) ) # Sort by c1 df <-data_frame[order(data_frame$c1),] head(df)
Output:
# A tibble: 6 x 5 ## c1 c2 c3 c4 c5 ## <dbl> <dbl> <dbl> <dbl> <dbl> ## 1 1.481453 3.477557 4.246283 3.686611 6.0511003 ## 2 1.729941 5.824996 4.525823 6.753663 0.1502718 ## 3 2.556360 6.275348 2.524849 6.368483 5.4787404 ## 4 2.827693 4.769902 5.120089 3.743626 4.0103449 ## 5 2.988510 4.395902 2.077631 4.236894 4.6176880 ## 6 3.122021 6.317305 5.413840 3.551145 5.6067027
The comma before the closing bracket matters: df[order(…), ] reorders rows, while df[, order(…)] would reorder columns instead.
Example 2: Sort by Multiple Columns
Every extra argument passed to order() acts as a tie-breaker for the argument before it. Here the frame is sorted by c3, and rows sharing the same c3 value are then ordered by c4.
# Sort by c3 and c4
df <-data_frame[order(data_frame$c3, data_frame$c4),]
head(df)
Output:
# A tibble: 6 x 5 ## c1 c2 c3 c4 c5 ## <dbl> <dbl> <dbl> <dbl> <dbl> ## 1 2.988510 4.395902 2.077631 4.236894 4.617688 ## 2 2.556360 6.275348 2.524849 6.368483 5.478740 ## 3 3.464516 3.914627 2.730068 9.565649 6.016123 ## 4 4.233486 3.292088 3.133568 7.517309 4.772395 ## 5 3.935840 2.941547 3.242078 6.464048 3.599745 ## 6 3.835619 4.947859 3.335349 4.378370 7.240240
Example 3: Sort One Column Descending and Another Ascending
Each column can carry its own direction. Prefixing a numeric column with a minus sign reverses that column only, so the sort below runs descending on c3 and ascending on c4.
# Sort by c3(descending) and c4(ascending)
df <-data_frame[order(-data_frame$c3, data_frame$c4),]
head(df)
Output:
# A tibble: 6 x 5 ## c1 c2 c3 c4 c5 ## <dbl> <dbl> <dbl> <dbl> <dbl> ## 1 4.339178 4.450214 8.087243 4.5010140 8.410225 ## 2 3.959420 8.105406 7.736312 7.1168936 5.431565 ## 3 3.339023 3.298088 7.494285 5.9303153 7.035912 ## 4 3.397036 5.382794 7.092722 0.7163620 5.620098 ## 5 6.653446 4.733315 6.520536 0.9016707 4.513410 ## 6 4.558559 4.712609 6.380086 6.0562703 5.044277
The minus-sign trick works on numeric columns only. For character or factor columns use order(xtfrm(df$col), decreasing = TRUE), or switch to the dplyr approach shown next.
Sort a Data Frame with dplyr arrange()
The square-bracket idiom becomes hard to read once several columns and directions are involved, because the frame name is repeated inside every argument. The dplyr verb arrange() removes that repetition: columns are named once, and desc() marks the ones that run backwards.
library(dplyr) # The same three sorts written with arrange() data_frame %>% arrange(c1) # Example 1 data_frame %>% arrange(c3, c4) # Example 2 data_frame %>% arrange(desc(c3), c4) # Example 3
Three practical differences are worth knowing:
- Readability: arrange(desc(c3), c4) states the intent directly, while order(-data_frame$c3, data_frame$c4) hides it behind a minus sign.
- Column types: desc() works on character and factor columns as well as numeric ones, so no xtfrm() workaround is needed.
- Missing values: arrange() always places NA last, whatever the direction, whereas order() lets you move them with na.last.
arrange() returns a new data frame and leaves the original untouched, exactly like the square-bracket version, so the result still has to be assigned to a name to be kept. Grouped frames are sorted within each group only when .by_group = TRUE is supplied.
Sorting Character, Factor and Missing Values in R
Numeric columns sort predictably. Text, categories and gaps do not, and each of the three needs a different decision.
Missing values. sort() and order() disagree on what to do with NA, which is why the same column can produce two different row counts:
x <- c(3, NA, 1, 2) sort(x) # missing values dropped sort(x, na.last = TRUE) # missing values pushed to the end order(x) # NA indexed last by default order(x, na.last = FALSE) # NA indexed first
Factors. A factor sorts by its level order, not alphabetically. That is exactly what you want for ordered categories such as low, medium and high, and exactly what you do not want if the levels were created in the order the values happened to appear:
grades <- factor(c("low", "high", "medium"), levels = c("low", "medium", "high")) sort(grades) # follows the level order sort(as.character(grades)) # plain alphabetical order
Character columns. Plain text sorts by locale collation, so capitalisation and accents can change the result between machines. Two habits avoid surprises: check class(df$col) before sorting, and convert digits stored as text with as.numeric() so that “10” does not land ahead of “2”.

