NumPy, фундаментальная библиотека для научных вычислений в Python, предоставляет мощные инструменты для работы с многомерными массивами. Одной из частых задач при анализе данных является сортировка двумерных массивов (матриц) по значениям в определенном столбце. Это позволяет упорядочить данные для дальнейшего анализа, визуализации или обработки.
Зачем сортировать двумерные массивы по столбцам?
Сортировка по столбцу необходима во многих сценариях анализа данных:
- Ранжирование: Определение записей (строк) с наивысшими или наинизшими показателями по конкретному признаку (столбцу). Например, сортировка данных о рекламных кампаниях по столбцу CTR (click-through rate) для выявления самых эффективных.
- Поиск: Ускорение поиска определенных значений или диапазонов в отсортированном столбце.
- Предварительная обработка: Некоторые алгоритмы машинного обучения или статистические методы требуют предварительно отсортированных данных.
- Визуализация: Упорядочивание данных перед построением графиков для лучшей интерпретации.
Краткий обзор функций NumPy для сортировки
NumPy предлагает несколько функций для сортировки массивов:
numpy.sort(): Возвращает отсортированную копию массива.ndarray.sort(): Сортирует массив на месте (in-place).numpy.argsort(): Возвращает индексы, которые бы отсортировали массив.numpy.lexsort(): Выполняет непрямую стабильную сортировку по последовательности ключей (столбцов).
Для сортировки двумерного массива по одному столбцу наиболее часто используется argsort(), но lexsort() и структурированные массивы также предоставляют гибкие решения.
Сортировка двумерного массива NumPy по одному столбцу с использованием argsort()
Метод argsort() является наиболее идиоматичным и часто используемым способом сортировки многомерного массива по значениям определенной оси (в нашем случае, по строкам на основе значений в столбце).
Принцип работы argsort()
Функция argsort() не возвращает отсортированный массив. Вместо этого она возвращает массив индексов той же формы, что и исходный массив (или указанная ось), значения которых указывают на позиции элементов в отсортированном порядке. Применительно к сортировке 2D массива по столбцу, мы применяем argsort() к выбранному столбцу, получая индексы строк, которые нужно переставить для достижения сортировки.
Реализация сортировки по столбцу с помощью argsort()
Алгоритм прост:
- Выбрать столбец, по которому будет производиться сортировка.
- Применить
argsort()к этому столбцу. Это даст одномерный массив индексов. - Использовать этот массив индексов для индексации исходного двумерного массива по строкам.
Пример кода: сортировка массива по возрастанию и убыванию
Рассмотрим пример данных о пользователях веб-сайта: [user_id, session_duration, pages_viewed].
import numpy as np
from typing import Tuple
def sort_2d_array_by_column(
arr: np.ndarray,
column_index: int,
ascending: bool = True
) -> np.ndarray:
"""Сортирует двумерный массив NumPy по указанному столбцу.
Args:
arr (np.ndarray): Исходный двумерный массив.
column_index (int): Индекс столбца для сортировки (начиная с 0).
ascending (bool): True для сортировки по возрастанию, False - по убыванию.
Returns:
np.ndarray: Отсортированная копия массива.
"""
# Получаем индексы, которые бы отсортировали выбранный столбец
# arr[:, column_index] выбирает весь столбец
sorted_indices: np.ndarray = np.argsort(arr[:, column_index])
if not ascending:
# Для сортировки по убыванию разворачиваем массив индексов
sorted_indices = sorted_indices[::-1]
# Используем полученные индексы для сортировки строк исходного массива
sorted_arr: np.ndarray = arr[sorted_indices]
return sorted_arr
# Пример данных: [user_id, session_duration, pages_viewed]
data: np.ndarray = np.array([
[101, 350, 5],
[102, 120, 2],
[103, 800, 15],
[104, 120, 3]
])
# Сортировка по длительности сессии (столбец 1) по возрастанию
sorted_by_duration_asc: np.ndarray = sort_2d_array_by_column(data, 1, ascending=True)
print("Отсортировано по длительности сессии (возрастание):\n", sorted_by_duration_asc)
# Ожидаемый вывод:
# [[102 120 2]
# [104 120 3]
# [101 350 5]
# [103 800 15]]
# Сортировка по количеству просмотренных страниц (столбец 2) по убыванию
sorted_by_pages_desc: np.ndarray = sort_2d_array_by_column(data, 2, ascending=False)
print("\nОтсортировано по просмотрам страниц (убывание):\n", sorted_by_pages_desc)
# Ожидаемый вывод:
# [[103 800 15]
# [101 350 5]
# [104 120 3]
# [102 120 2]]
Сортировка с использованием lexsort()
Функция lexsort() предназначена для выполнения лексикографической сортировки, то есть сортировки по нескольким ключам (столбцам) последовательно.
Описание функции lexsort()
lexsort() принимает кортеж из массивов (ключей сортировки) и возвращает массив индексов, который сортирует данные. Важно отметить, что сортировка происходит начиная с последнего ключа в кортеже. Если передать только один ключ (один столбец), она будет сортировать только по нему.
Применение lexsort() для сортировки по столбцу
Хотя lexsort() чаще используется для сортировки по нескольким столбцам, ее можно применить и для сортировки по одному столбцу. Для этого нужно передать кортеж, содержащий только один элемент — столбец для сортировки.
import numpy as np
def sort_2d_array_lexsort(arr: np.ndarray, column_index: int) -> np.ndarray:
"""Сортирует двумерный массив NumPy по одному столбцу с использованием lexsort.
Args:
arr (np.ndarray): Исходный двумерный массив.
column_index (int): Индекс столбца для сортировки.
Returns:
np.ndarray: Отсортированная копия массива (по возрастанию).
"""
# Важно: передаем столбец как кортеж с одним элементом
# np.lexsort сортирует по возрастанию
sorted_indices: np.ndarray = np.lexsort((arr[:, column_index],))
return arr[sorted_indices]
# Пример данных: [campaign_id, impressions, clicks]
campaign_data: np.ndarray = np.array([
[1, 10000, 50],
[2, 5000, 30],
[3, 12000, 65],
[4, 5000, 25]
])
# Сортировка по количеству кликов (столбец 2) по возрастанию
sorted_by_clicks: np.ndarray = sort_2d_array_lexsort(campaign_data, 2)
print("Отсортировано по кликам (lexsort, возрастание):\n", sorted_by_clicks)
# Ожидаемый вывод:
# [[ 4 5000 25]
# [ 2 5000 30]
# [ 1 10000 50]
# [ 3 12000 65]]
Для сортировки по убыванию с lexsort потребуется либо инвертировать столбец перед передачей (-arr[:, column_index]), либо развернуть полученные индексы ([::-1]).
Сравнение argsort() и lexsort() в контексте сортировки по столбцу
- Простота:
argsort()часто воспринимается как более прямой и интуитивный метод для сортировки по одному столбцу. - Назначение:
lexsort()оптимизирована для сортировки по нескольким столбцам. Использование ее для одного столбца возможно, но может быть избыточным. - Производительность: Для сортировки по одному столбцу
argsort()может быть незначительно быстрее из-за меньших накладных расходов. - Стабильность:
lexsort()гарантирует стабильную сортировку.argsort()по умолчанию не гарантирует стабильность, но ее можно включить параметромkind='stable', что может повлиять на производительность.
В большинстве случаев для сортировки по одному столбцу предпочтительнее использовать argsort().
Сортировка с использованием структурированных массивов NumPy
Структурированные массивы NumPy позволяют хранить в одном массиве данные разных типов, обращаясь к столбцам по именам (полям). Это открывает еще один способ сортировки.
Создание структурированного массива из двумерного массива
Сначала необходимо определить dtype (тип данных) для структурированного массива, указав имена и типы для каждого столбца. Затем исходный массив преобразуется в этот формат.
import numpy as np
# Исходный массив (например, [product_id, price, stock])
data: np.ndarray = np.array([
[10, 150.0, 20],
[11, 99.5, 50],
[12, 210.0, 15],
[13, 99.5, 30]
])
# Определение типа данных для структурированного массива
# 'i4' - 4-байтовый integer, 'f8' - 8-байтовый float
dt = np.dtype([('id', 'i4'), ('price', 'f8'), ('stock', 'i4')])
# Создание пустого структурированного массива нужного размера
structured_arr = np.empty(data.shape[0], dtype=dt)
# Заполнение структурированного массива данными
# Способ 1: По полям
# structured_arr['id'] = data[:, 0]
# structured_arr['price'] = data[:, 1]
# structured_arr['stock'] = data[:, 2]
# Способ 2: Преобразование строк в кортежи (более универсальный)
for i, row in enumerate(data):
structured_arr[i] = tuple(row)
print("Структурированный массив:\n", structured_arr)
Сортировка структурированного массива по именам полей (столбцам)
Сортировка структурированного массива выполняется с помощью функции np.sort() или метода .sort(), указав имя поля (столбца) в параметре order.
# Сортировка по полю 'price' (по возрастанию)
sorted_structured_by_price = np.sort(structured_arr, order='price')
print("\nОтсортировано по цене:\n", sorted_structured_by_price)
# Ожидаемый вывод (порядок строк с одинаковой ценой может варьироваться):
# [(11, 99.5, 50) (13, 99.5, 30) (10, 150. , 20) (12, 210. , 15)]
# Сортировка по полю 'stock' (по убыванию - через argsort и реверс индексов)
# Прямой сортировки по убыванию в np.sort нет, но можно отсортировать
# и затем развернуть или использовать argsort
indices_stock_desc = np.argsort(structured_arr['stock'])[::-1]
sorted_structured_by_stock_desc = structured_arr[indices_stock_desc]
print("\nОтсортировано по запасам (убывание):\n", sorted_structured_by_stock_desc)
# Ожидаемый вывод:
# [(11, 99.5, 50) (13, 99.5, 30) (10, 150. , 20) (12, 210. , 15)]
Преобразование отсортированного структурированного массива обратно в обычный двумерный массив
После сортировки структурированный массив можно преобразовать обратно в обычный ndarray, если это необходимо. Однако это может привести к потере информации о типах данных, если они были разными.
from numpy.lib.recfunctions import structured_to_unstructured
# Преобразование обратно в обычный массив (требует NumPy >= 1.16)
# Все столбцы будут приведены к общему типу данных (вероятно, float)
unstructured_sorted: np.ndarray = structured_to_unstructured(sorted_structured_by_price)
print("\nПреобразовано обратно в обычный массив:\n", unstructured_sorted)
# Ожидаемый вывод (типы могут быть float):
# [[ 11. 99.5 50. ]
# [ 13. 99.5 30. ]
# [ 10. 150. 20. ]
# [ 12. 210. 15. ]]
Использование структурированных массивов особенно полезно, когда столбцы имеют осмысленные имена и разные типы данных, и вы хотите использовать эти имена для доступа и сортировки.
Альтернативные методы и соображения
Сортировка по нескольким столбцам: комбинирование методов
Для сортировки по нескольким столбцам lexsort() является стандартным выбором. Также можно использовать np.sort() со списком полей в параметре order для структурированных массивов. В качестве альтернативы можно применить argsort(kind='stable') последовательно к нужным столбцам в обратном порядке.
Производительность: выбор оптимального метода сортировки для больших массивов
argsort(): Обычно самый быстрый для сортировки по одному столбцу.lexsort(): Эффективен для нескольких столбцов, но может иметь небольшой оверхед для одного.- Структурированные массивы: Создание и преобразование могут добавить накладные расходы, но сама сортировка по полю (
order=) может быть быстрой. Доступ по именам полей удобен.
Для критичных к производительности приложений на больших данных рекомендуется провести профилирование на реальных данных.
Обработка сложных случаев: пропущенные значения и пользовательские функции сравнения
- Пропущенные значения (
NaN): Стандартные функции сортировки NumPy обычно помещаютNaNв конец массива при сортировке по возрастанию. Если требуется иное поведение, может понадобиться предварительная обработка (например, заменаNaNили их отдельная сортировка). - Пользовательские функции сравнения: NumPy напрямую не поддерживает произвольные функции сравнения, как стандартная функция
sorted()в Python. Для сложной логики сортировки может потребоваться либо преобразование данных в формат, подходящий для стандартных сортировок, либо использование других библиотек (например, Pandas), либо реализация сортировки через итеративные подходы (что обычно менее эффективно).