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.

  • 🔢 order() Basics: order() returns the row indices that put a column in ascending sequence, not the sorted values themselves.
  • 📌 Single Column: df[order(df$c1), ] rearranges every row of the frame by the values held in c1.
  • 🧮 Multiple Columns: Extra arguments act as tie-breakers, so order(df$c3, df$c4) sorts by c3 and then by c4.
  • 🔻 Descending Order: Prefix a numeric column with a minus sign, or pass decreasing = TRUE to sort().
  • 🧩 tidyverse Route: arrange() and desc() express exactly the same sorts as readable dplyr verbs.
  • ⚠️ Missing Values: order() places NA last by default, while sort() removes missing values unless na.last is set.

R Sort a Data Frame using 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”.

FAQs

Use df[order(rownames(df)), ]. Row names are character, so the sort follows text collation and “10” lands before “2”. Wrap them in as.numeric() when they are numeric. Tibbles drop row names entirely, so add an explicit id column instead.

Pass method = “radix” to order(), the fastest option for integers and short strings. For millions of rows, data.table::setorder() sorts in place without copying the table, and the dplyr verb arrange() already uses a radix sort internally.

Index with a reversed sequence: df[nrow(df):1, ]. This flips the current order instead of sorting it, which matters when the frame was shuffled or grouped. Reset the labels afterwards with rownames(df) <- NULL if they look confusing.

The column is stored as character or factor, so R compares it as text and puts “10” before “2”. Check with class(df$col), then convert using as.numeric(as.character(df$col)) before ordering.

No. order() returns an index vector and df[order(…), ] builds a new frame, so the original survives untouched until the result is assigned back. Row names keep their old values, which is a useful clue that the frame was reordered.

AI assistants can spot a column stored as text when a numeric sort looks wrong, translate a base R order() call into a dplyr pipeline, and suggest a stable tie-breaking column. Always run the code and inspect head() before trusting the result.

Yes. GitHub Copilot works in RStudio Desktop 2023.09.0 and later and completes order() and arrange() calls from a plain comment. Posit warns that suggestions are non-deterministic, so review each line before running it.

Reorder the column index rather than the row index. df[, order(names(df))] arranges columns alphabetically, and df[, c(“c3”, “c1”)] picks an explicit order. dplyr users can call select(df, c3, c1), which reorders and subsets in one step.

Summarize this post with: