Menu

Сортировка в R: sort(), order() и arrange()

Как на самом деле работает сортировка в R: sort() для векторов, почему датафреймам нужен приём с индексами order(), сортировка по убыванию и по нескольким столбцам и arrange() из dplyr.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

sort() сортирует значения вектора

Для обычного вектора sort() делает ровно то, что вы ожидаете, — возвращает значения по возрастанию (оригинал не трогается):

Две опции, которые стоит знать. decreasing = TRUE переворачивает направление. И sort() по умолчанию молча отбрасывает значения NAsort(c(3, NA, 1)) возвращает просто 1 3. Если пропущенные значения должны пережить сортировку, укажите, куда их деть: sort(x, na.last = TRUE) сохранит их, поместив после упорядоченных значений.

Пока всё очевидно. Интересное начинается с того, почему sort()неправильный инструмент для датафреймов.

order() возвращает позиции — смена мышления

sort() отвечает на вопрос «какие значения, по порядку?». order() отвечает на другой вопрос: «какие позиции мне посетить, чтобы увидеть значения по порядку?»

Читайте order(times) как инструкцию: наименьшее значение находится на позиции 2, следующее — на позиции 4, затем 1, затем 3. Индексация по этой инструкции — times[order(times)] — в точности воспроизводит sort(times).

Почему это важно? Потому что строка датафрейма — это команда значений, которые должны двигаться вместе. Сортировка одного столбца time через sort() переупорядочила бы один столбец и бросила его товарищей — все строки перемешались бы. order() вместо этого даёт позиции строк, а помещение позиций в слот строк внутри скобок двигает целые строки:

Результат Бена 9.8 приезжает вместе с его именем. В этом весь приём, и это та самая идиома сортировки датафреймов в базовом R: df[order(df$column), ]. (Напечатанные метки строк — 2 4 1 3 — это исходные номера строк, увязавшиеся следом; безвредно.)

Сортировка по убыванию и по нескольким столбцам

Для убывающей числовой сортировки поменяйте знак столбца — упорядочивание -time по возрастанию это упорядочивание time по убыванию. И order() принимает несколько столбцов: более ранние идут первыми, более поздние разрешают совпадения:

Второй вызов сортирует отделы по алфавиту и внутри каждого отдела — зарплаты от больших к меньшим. Приём со сменой знака работает только на числах: -df$name на символьном столбце даст ошибку. Для убывающей сортировки текста используйте order(df$name, decreasing = TRUE) (она переворачивает сразу все ключи сортировки).

rank() — третий родственник

Мимоходом: rank(x) отвечает ещё на один вопрос — «какое место занимает каждое значение?» — ничего не перемещая:

sort() даёт упорядоченные значения, order() даёт позиции для обхода, rank() даёт положение каждого значения на месте. order() и rank() постоянно путают; заметьте, что они являются обратными перестановками друг друга, и берите rank(), только когда вам буквально нужны ранги (турнирные таблицы, процентили).

Способ dplyr: arrange()

arrange() из dplyr оборачивает весь танец с order() в глагол — столбцы служат ключами сортировки, а desc() переворачивает один из них (сниппет статичный; песочница выполняет только базовый R):

library(dplyr)

runners |> arrange(time)
staff   |> arrange(dept, desc(salary))

Ни скобок, ни $, ни приёма со сменой знака — desc() работает и на символьных столбцах. Одно поведенческое отличие стоит знать: arrange() помещает значения NA в конец независимо от направления, тогда как базовый order() тоже по умолчанию использует na.last = TRUE. О том, как arrange() сцепляется с filter() и родственниками, см. введение в dplyr.

У сортировки текста острые края

Две оговорки при сортировке текста. Во-первых, цифры, хранящиеся как текст, сортируются как текст — символьное сравнение идёт посимвольно:

"10" идёт раньше "2", потому что сравнение останавливается на первом символе: "1" < "2". Если столбец чисел сортируется странно, он почти наверняка хранится как character — сначала преобразуйте через as.numeric() (частое последствие грязного импорта CSV).

Во-вторых, упорядочивание текста зависит от системной локали — диакритика, регистр и нелатинские письменности могут сортироваться на вашем ноутбуке иначе, чем на сервере с локалью C. Отсортированный вывод, который должен быть идентичным на разных машинах, должен либо явно задавать локаль, либо сортироваться по нормализованному ключу.

Что вы уносите с собой

  • sort(x) упорядочивает значения вектора; decreasing = TRUE переворачивает порядок; NA отбрасываются, если не указать na.last = TRUE.
  • order(x) возвращает позиции, а df[order(df$x), ]та самая базовая идиома сортировки датафреймов.
  • Несколько столбцов: order(df$a, -df$b); смена знака для убывания работает только на числах.
  • rank() даёт места без переупорядочивания — это не замена order().
  • arrange(dept, desc(salary)) из dplyr — та же сортировка с меньшим числом знаков препинания.
  • "10" идёт раньше "2" в тексте — проверяйте типы своих столбцов.

Дальше: схлопывание строк в групповые сводки через table(), tapply(), aggregate() и group_by() из dplyr.

Часто задаваемые вопросы

Как отсортировать датафрейм по столбцу в R?

Используйте order() внутри скобок для строк: df[order(df$price), ]. order() возвращает позиции строк в отсортированном порядке, а индексация ими переставляет весь датафрейм. sort() здесь не подойдёт — она сортирует значения одного вектора, теряя их связь с остальными столбцами.

В чём разница между sort() и order() в R?

sort(x) возвращает значения x, переставленные по порядку. order(x) возвращает позиции (индексы), которые расставят x по порядку, — а это и нужно, чтобы отсортировать целый датафрейм по одному из его столбцов: df[order(df$x), ].

Как отсортировать по убыванию в R?

Для вектора: sort(x, decreasing = TRUE). Для датафрейма: df[order(-df$x), ] (смена знака у числового столбца) или df[order(df$x, decreasing = TRUE), ], что работает и для символьных столбцов, где смена знака невозможна. В dplyr: arrange(df, desc(x)).

Как отсортировать по нескольким столбцам в R?

Передайте их все в order(): df[order(df$dept, -df$salary), ] сортирует по отделу, а затем по зарплате внутри каждого отдела по убыванию. Более ранние аргументы — основные ключи; более поздние разрешают совпадения. В dplyr: arrange(df, dept, desc(salary)).

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ