sort_values() в Pandas — сортировка данных

Проверь себя · 1/3разбор после ответа
Что вернёт sorted(['слон', 'як', 'кот'], key=len)?

Коротко

Сортировка — это первое, что вы делаете с таблицей, когда хотите хоть что-то в ней увидеть: кто принёс больше всего выручки, какие заказы самые свежие, у кого просел рейтинг. Без сортировки DataFrame — это просто куча строк в случайном порядке загрузки, и глазами в ней ничего не найти. Поэтому sort_values() встречается буквально в каждой второй задаче на pandas, и на собеседовании её просят показать почти всегда.

sort_values() сортирует DataFrame или Series по значениям одного или нескольких столбцов. Метод умеет сортировать в обе стороны, по нескольким ключам с разными направлениями, аккуратно складывать пропуски в начало или в конец и принимать кастомную функцию-ключ. Разберём все параметры по порядку — с кодом, который можно скопировать и запустить.

Сортировка по одному столбцу

import pandas as pd

df = pd.DataFrame({
    'user': ['Иван', 'Анна', 'Мария', 'Пётр'],
    'revenue': [5000, 12000, 3200, 8500],
    'orders': [3, 7, 2, 5]
})

# По убыванию выручки
df.sort_values(by='revenue', ascending=False)
#     user  revenue  orders
# 1   Анна    12000       7
# 3   Пётр     8500       5
# 0   Иван     5000       3
# 2  Мария     3200       2

Первый аргумент — by, имя столбца. Его можно писать позиционно (df.sort_values('revenue')) или явно через by=. По умолчанию ascending=True — от меньшего к большему. Для «топа по выручке» нужен ascending=False. Обратите внимание: исходные индексы строк сохраняются и едут вместе со строками, поэтому после сортировки они идут не по порядку (1, 3, 0, 2) — это нормально и про сброс индекса будет отдельный раздел.

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

df = pd.DataFrame({
    'department': ['Sales', 'Sales', 'Dev', 'Dev', 'Sales'],
    'name': ['Иван', 'Анна', 'Пётр', 'Мария', 'Олег'],
    'salary': [90000, 120000, 150000, 130000, 90000]
})

# Сначала по отделу (возрастание), внутри отдела — по зарплате (убывание)
df.sort_values(by=['department', 'salary'], ascending=[True, False])
#   department   name  salary
# 2        Dev   Пётр  150000
# 3        Dev  Мария  130000
# 1      Sales   Анна  120000
# 0      Sales   Иван   90000
# 4      Sales   Олег   90000

Передайте в by список столбцов и в ascending список направлений той же длины. Сортировка идёт по первому столбцу, а при равных значениях — по второму, потом по третьему и так далее. Это прямой аналог ORDER BY department ASC, salary DESC в SQL. Если передать в ascending одно значение вместо списка (ascending=False), оно применится сразу ко всем столбцам из списка — удобно, когда направление одинаковое.

Обработка пропусков na_position

df = pd.DataFrame({
    'name': ['Иван', 'Анна', 'Пётр', 'Мария'],
    'score': [85, None, 92, None]
})

# NaN в конце (по умолчанию)
df.sort_values('score')
#     name  score
# 0   Иван   85.0
# 2   Пётр   92.0
# 1   Анна    NaN
# 3  Мария    NaN

# NaN в начале
df.sort_values('score', na_position='first')
#     name  score
# 1   Анна    NaN
# 3  Мария    NaN
# 0   Иван   85.0
# 2   Пётр   92.0

Пропуски (NaN) не участвуют в сравнении и всегда складываются в одну кучу — вопрос лишь, в начало или в конец. По умолчанию na_position='last', то есть NaN уходят вниз независимо от направления сортировки. Поставьте na_position='first', когда хотите сразу увидеть строки с дырами в данных — это удобный приём при анализе пропусков перед заполнением или удалением.

reset_index и ignore_index

# Индексы перемешаны после сортировки
sorted_df = df.sort_values('revenue', ascending=False)
print(sorted_df.index)  # Index([1, 3, 0, 2])

# Способ 1: reset_index
sorted_df = df.sort_values('revenue', ascending=False).reset_index(drop=True)
print(sorted_df.index)  # RangeIndex(start=0, stop=4, step=1)

# Способ 2: ignore_index прямо в sort_values
sorted_df = df.sort_values('revenue', ascending=False, ignore_index=True)
print(sorted_df.index)  # RangeIndex(start=0, stop=4, step=1)

После сортировки индекс перемешан. Если он вам нужен чистым (0, 1, 2, ...), есть два пути. Классический — дописать .reset_index(drop=True): без drop=True старый индекс не выбросится, а превратится в новый столбец. Короче — передать ignore_index=True прямо в sort_values(), тогда результат сразу получит свежий RangeIndex без лишнего вызова. Параметр ignore_index доступен начиная с pandas 1.0 и делает ровно то же, что reset_index(drop=True), только в одну строку.

inplace — менять оригинал или нет

df = pd.DataFrame({'revenue': [5000, 12000, 3200]})

# НЕ меняет df — возвращает новый DataFrame
df.sort_values('revenue')

# Меняет df на месте, возвращает None
df.sort_values('revenue', inplace=True)

# Рекомендуемый способ — переприсваивание
df = df.sort_values('revenue')

По умолчанию sort_values() ничего не портит в оригинале — он возвращает новый отсортированный DataFrame, а исходный остаётся как был. Это главная ловушка новичков: написали df.sort_values('revenue'), посмотрели на оригинал — и он не отсортирован. Чтобы зафиксировать результат, либо переприсвойте (df = df.sort_values('revenue')), либо передайте inplace=True. Важно: при inplace=True метод возвращает None, поэтому такую запись нельзя ставить в цепочку — df.sort_values('revenue', inplace=True).head() упадёт с AttributeError. На практике лучше избегать inplace и работать через переприсваивание: код читается яснее и спокойно встраивается в цепочки методов.

sort_values vs sort_index

df = pd.DataFrame(
    {'value': [30, 10, 20]},
    index=['c', 'a', 'b']
)

df.sort_values('value')   # по значениям столбца value: 10, 20, 30
#    value
# a     10
# b     20
# c     30

df.sort_index()           # по меткам индекса: a, b, c
#    value
# a     10
# b     20
# c     30

Два разных метода, которые часто путают. sort_values() упорядочивает строки по данным внутри столбца, а sort_index() — по меткам индекса (строковым или числовым), не глядя на содержимое. Типичный кейс для sort_index() — вернуть таблицу к исходному порядку после группировки или восстановить хронологию, когда индексом служит дата. У sort_index() есть тот же набор параметров: ascending, na_position, inplace, и можно сортировать по столбцам через axis=1.

Закрепи Python для аналитика
200+ задач по pandas, numpy и работе с данными — с разборами
Тренировать Python в Telegram

Сортировка Series

s = pd.Series([30, 10, 20], index=['c', 'a', 'b'])

s.sort_values()                 # по значениям: a=10, b=20, c=30
s.sort_values(ascending=False)  # по убыванию: c=30, b=20, a=10
s.sort_index()                  # по индексу: a=10, b=20, c=30

У Series те же два метода, только без аргумента by — сортировать всё равно нечего, кроме единственной колонки значений. Это пригождается сплошь и рядом: результат value_counts() или groupby().sum() — это Series, и его почти всегда хочется отсортировать. Например, df['city'].value_counts().sort_values(ascending=False) даёт частоты от самых популярных городов к редким (хотя value_counts() и так возвращает результат отсортированным по убыванию — об этом подробнее в гайде по value_counts).

Кастомная сортировка через key

# Сортировка без учёта регистра
df.sort_values('name', key=lambda x: x.str.lower())

# Сортировка по длине строки
df.sort_values('name', key=lambda x: x.str.len())

# Сортировка по абсолютному значению
df.sort_values('change', key=lambda x: x.abs())

Параметр key принимает функцию, которая преобразует столбец перед сравнением. Важная деталь: функция получает на вход всю колонку как Series (а не отдельные ячейки), поэтому внутри нужны векторные операции — x.str.lower(), x.str.len(), x.abs(). Сами данные при этом не меняются: ключ влияет только на порядок строк, в таблице остаются исходные значения. Так удобно сортировать без учёта регистра, по модулю отклонения или по любому производному признаку, не заводя отдельный столбец.

nlargest и nsmallest — быстрый топ

# Топ-3 по выручке
df.nlargest(3, 'revenue')

# Антитоп-3
df.nsmallest(3, 'revenue')

# По нескольким столбцам (для разрешения ничьих)
df.nlargest(5, ['revenue', 'orders'])

Когда нужен не весь отсортированный DataFrame, а только верхушка, nlargest(n, col) и nsmallest(n, col) короче и быстрее, чем sort_values().head(n). Они не сортируют всю таблицу целиком, а используют partial sort и вытаскивают только нужные n строк — на больших данных разница в скорости заметна. По второму столбцу в списке разрешаются ничьи: при равной выручке выше встанет строка с большим числом заказов.

Практические задачи

Топ-10 пользователей по выручке

top_users = (
    df.groupby('user_id')['amount']
    .sum()
    .sort_values(ascending=False)
    .head(10)
)

Сортировка событий по времени

df['event_time'] = pd.to_datetime(df['event_time'])
df = df.sort_values('event_time')

Хронологическая сортировка — первый шаг для построения воронок и анализа пользовательских путей: пока события не выстроены по времени, посчитать «что было раньше» невозможно.

Ранжирование внутри групп

df['rank'] = (
    df.sort_values(['department', 'salary'], ascending=[True, False])
    .groupby('department')
    .cumcount() + 1
)

Или через rank():

df['salary_rank'] = df.groupby('department')['salary'].rank(
    ascending=False, method='dense'
)

Это прямой аналог ROW_NUMBER / RANK / DENSE_RANK в SQL. Такие задачи на сортировку, агрегацию и оконные функции — самая частая часть практической секции на собесе аналитика; отработать их в формате квиза можно в тренажёре Python.

sort_values и SQL ORDER BY

Pandas SQL
df.sort_values('col') ORDER BY col ASC
df.sort_values('col', ascending=False) ORDER BY col DESC
df.sort_values(['a', 'b'], ascending=[True, False]) ORDER BY a ASC, b DESC
df.nlargest(10, 'col') ORDER BY col DESC LIMIT 10

Если вы пришли в pandas из SQL, проще всего держать в голове это соответствие: sort_values — это ORDER BY, а nlargest — это ORDER BY ... DESC LIMIT n. Разница в деталях: в SQL порядок NULL зависит от диалекта, а в pandas им управляет na_position. Подробнее о сортировке на стороне базы — в гайде по ORDER BY.

Частые ошибки

Забывают ascending=False. По умолчанию сортировка идёт по возрастанию. Для «топ по выручке» или «самые свежие даты» нужно явно указать ascending=False, иначе наверху окажутся минимальные значения.

Думают, что sort_values меняет оригинал. Метод возвращает новый DataFrame и не трогает исходный. Чтобы изменения сохранились — переприсвойте (df = df.sort_values('col')) или передайте inplace=True. Самая частая причина бага «я отсортировал, а ничего не изменилось».

Ставят inplace=True в цепочку. При inplace=True метод возвращает None, поэтому df.sort_values('col', inplace=True).head() падает с AttributeError: 'NoneType' object has no attribute 'head'. В цепочках методов используйте обычный sort_values() без inplace.

Сортируют числа как строки. Если столбец хранит числа в виде строк ('10', '2', '9'), сравнение будет лексикографическим и порядок получится '10', '2', '9'. Перед сортировкой приведите тип: df['col'] = df['col'].astype(int).

Путают sort_values и sort_index. sort_values сортирует по данным столбца, sort_index — по меткам индекса. Если после sort_values индекс «перемешался», это не баг — добавьте ignore_index=True или reset_index(drop=True).

Связанные темы

FAQ

Как отсортировать DataFrame по нескольким столбцам в разных направлениях?

Передайте список столбцов и список направлений той же длины: df.sort_values(['col1', 'col2'], ascending=[True, False]). Первый столбец сортируется по возрастанию, второй — по убыванию, при равенстве первого значения сравнивается второй.

Чем nlargest лучше, чем sort_values + head?

nlargest использует partial sort со сложностью порядка O(n + k·log n) и не сортирует всю таблицу, а sort_values делает полную сортировку O(n·log n). На больших данных, когда нужна только верхушка, nlargest заметно быстрее и читается короче.

Как поставить NaN в начало при сортировке?

Передайте na_position='first': df.sort_values('col', na_position='first'). По умолчанию значение 'last', и пропуски уходят в конец независимо от направления сортировки.

Чем sort_values отличается от sort_index?

sort_values упорядочивает строки по значениям выбранного столбца, а sort_index — по меткам индекса, не глядя на содержимое. sort_index обычно нужен, чтобы вернуть исходный порядок строк или восстановить хронологию, когда индексом служит дата.

Как сохранить отсортированный DataFrame?

Используйте переприсваивание: df = df.sort_values('col'). Это явнее, чем inplace=True, и не ломает цепочки методов. Чтобы заодно обнулить перемешанный индекс, добавьте ignore_index=True.

Стабильная ли сортировка в pandas?

По умолчанию pandas использует quicksort, который не гарантирует стабильность. Если важно сохранить относительный порядок строк с одинаковым ключом, передайте kind='mergesort' (или kind='stable') — это стабильный алгоритм. При сортировке по нескольким столбцам стабильность не нужна: порядок задают сами ключи.

Где потренировать сортировку и pandas?

Сортировка — базовая операция, но на собесе её спрашивают вместе с группировкой и оконными функциями. Отработать такие задачи в формате квиза можно в тренажёре Карьерник, а больше примеров вопросов — в разделе с примерами.