8.1 数组排序
NumPy数组排序全面指南:快速排序、轴排序、间接排序与性能优化
本NumPy教程详细讲解数组排序方法,包括快速排序(np.sort和ndarray.sort)、按轴排序、稳定排序、降序排序、间接排序(如argsort、lexsort、partition)以及排序算法选择与性能优化,适合数据科学初学者和开发者轻松入门并掌握高效数据处理技巧。
NumPy数组排序详细教程
引言
在数据科学和机器学习中,数组排序是一个基础且重要的操作。NumPy作为Python中强大的数值计算库,提供了多种高效的排序方法,帮助用户快速处理和分析数据。本教程将从基础到高级,逐步介绍NumPy中的数组排序功能,确保新人能轻松理解。
快速排序:np.sort() 和 ndarray.sort()
NumPy提供了两种快速排序函数:np.sort() 和 ndarray.sort()。它们的核心区别在于是否修改原始数组。
- np.sort(): 返回一个新数组,原始数组保持不变。这适用于需要保留原始数据的情况。
- ndarray.sort(): 原地排序,直接在原始数组上修改,节省内存但改变原数据。
示例代码:
import numpy as np
# 创建一个示例数组
arr = np.array([3, 1, 4, 1, 5])
# 使用 np.sort(),返回新数组
sorted_arr = np.sort(arr)
print("np.sort() 结果:", sorted_arr) # 输出:[1 1 3 4 5]
print("原始数组:", arr) # 输出:[3 1 4 1 5],保持不变
# 使用 ndarray.sort(),原地排序
arr.sort()
print("ndarray.sort() 后:", arr) # 输出:[1 1 3 4 5],原数组被修改
默认情况下,NumPy使用快速排序算法('quicksort'),速度快但不稳定。可通过kind参数选择其他算法,如'mergesort'或'heapsort'。
按轴排序
在多维数组中,可以指定轴(axis)进行排序。例如,对二维数组按行或列排序。
- axis=0: 按列排序,每列独立排序。
- axis=1: 按行排序,每行独立排序。
- 如果不指定轴,默认对整个数组展平后排序。
示例代码:
# 创建一个二维数组
arr_2d = np.array([[3, 2], [1, 4]])
# 按轴排序
sorted_axis0 = np.sort(arr_2d, axis=0) # 按列排序
print("按列排序:", sorted_axis0) # 输出:[[1 2], [3 4]]
sorted_axis1 = np.sort(arr_2d, axis=1) # 按行排序
print("按行排序:", sorted_axis1) # 输出:[[2 3], [1 4]]
这在大数据处理中非常有用,例如对数据表的列进行排序。
稳定排序
稳定排序指排序后相等元素的相对顺序保持不变。在NumPy中,可以通过设置kind='stable'或使用'mergesort'来实现稳定排序。
- 稳定性: 如果数据中有重复值,稳定排序确保这些重复值的原始顺序在排序后不变。
- 应用场景:如处理带有多重键的数据,需要保持次级顺序。
示例代码:
# 创建一个带重复值的数组
arr_stable = np.array([('b', 2), ('a', 1), ('b', 1)], dtype=[('letter', 'U1'), ('number', 'i4')])
# 使用稳定排序(按字母排序,保持数字顺序)
sorted_stable = np.sort(arr_stable, order='letter', kind='stable')
print("稳定排序结果:", sorted_stable)
# 输出:[(‘a’, 1), (‘b’, 2), (‘b’, 1)],注意 'b' 的原始顺序保持
默认'quicksort'不稳定,所以对于需要稳定性的场景,推荐使用'mergesort'或'stable'。
降序排序
NumPy默认排序是升序(从小到大)。要实现降序(从大到小),有以下方法:
- 使用切片[::-1]: 对排序后的数组进行反转。
- 设置参数: 在
np.sort()或ndarray.sort()中,可以通过order参数或结合kind实现降序,但更常见的是反转。
示例代码:
arr_desc = np.array([5, 2, 8, 1])
# 方法1:先升序排序,再反转
sorted_asc = np.sort(arr_desc) # 升序排序
sorted_desc = sorted_asc[::-1] # 反转得到降序
print("降序排序(反转):", sorted_desc) # 输出:[8 5 2 1]
# 方法2:使用参数(适用于结构化数组,但通常推荐反转)
# 例如,对于数字数组,直接反转更简单
对于简单数组,反转是最直接的方法。
间接排序
间接排序不直接对数组排序,而是返回排序后的索引或其他信息,常用于数据索引或部分排序。
argsort()
返回排序后元素在原数组中的索引位置。
arr_indirect = np.array([3, 1, 2])
indices = np.argsort(arr_indirect)
print("argsort() 索引:", indices) # 输出:[1 2 0]
print("排序后数组:", arr_indirect[indices]) # 输出:[1 2 3]
lexsort()
按多个键排序,返回索引。常用于多列数据排序。
keys = (np.array([2, 1, 1]), np.array([3, 1, 2])) # 第一个键优先级高
lex_indices = np.lexsort(keys)
print("lexsort() 索引:", lex_indices) # 输出:[1 2 0],先按第二个键排序
partition()
部分排序,将数组分成两部分:前k个最小(或最大)元素在左边,其余在右边,但不完全排序。
arr_part = np.array([3, 4, 2, 1])
partitioned = np.partition(arr_part, 2) # 前2个最小元素在左边
print("partition() 结果:", partitioned) # 输出可能是 [2 1 3 4],左边是前2小
排序算法选择与性能
NumPy支持多种排序算法,通过kind参数指定:
- 'quicksort'(默认): 快速排序,平均时间复杂度O(n log n),不稳定,适合大多数情况。
- 'mergesort': 归并排序,稳定,时间复杂度O(n log n),但内存使用较高。
- 'heapsort': 堆排序,不稳定,时间复杂度O(n log n),内存效率好。
- 'stable': 稳定排序的别名,通常指向'mergesort'。
性能比较:
- 小数组:所有算法差异不大。
- 大数组:'quicksort'通常最快,但如果数据有重复或需要稳定排序,选择'mergesort'。
- 内存限制:'heapsort'内存使用较低。
如何选择:
- 默认使用'quicksort'以获得最佳速度。
- 如果需要稳定排序,使用'mergesort'或'stable'。
- 测试性能:对于特定数据集,可以测试不同算法以找到最优。
示例:
import time
arr_large = np.random.rand(10000)
# 测试不同算法的性能
for kind in ['quicksort', 'mergesort', 'heapsort']:
start = time.time()
np.sort(arr_large, kind=kind)
end = time.time()
print(f"{kind} 耗时: {end - start:.6f} 秒")
总结与最佳实践
- 基础排序: 使用
np.sort()或ndarray.sort(),注意原地与非原地区别。 - 轴排序: 利用axis参数处理多维数据。
- 稳定排序: 当数据有重复值时,选择'mergesort'或'stable'。
- 降序排序: 通过反转排序结果实现。
- 间接排序: 使用argsort、lexsort或partition进行灵活索引和部分排序。
- 算法选择: 根据数据大小、稳定性需求和内存限制选择合适的算法,通常'quicksort'是默认好选择。
通过本教程,您应该能熟练使用NumPy进行数组排序,并在实际项目中优化性能。多加练习,结合具体数据场景,将能更高效地处理数据任务。