第十一天
Numpy数组的排序
前面我们关注了用Numpy获取和操作数组。下面介绍用于排序Numpy数组的相关算法。这些算法也是计算机科学类课程非常喜欢的话题。比如插入排序、归并排序、快速排序、冒泡排序等。这些方法的任务:对一个列表或者数组进行排序。
1.Numpy快速排序
Python有内置的sort和sorted函数可以对列表进行排序,但是Numpy的sort函数实际上效率会更高。默认情况下,np.sort函数式快速排序,其实时间复杂度为O(NlogN)。
# 不修改原始数组的排序 x = np.array([2, 1, 4, 3, 5]) np.sort(x) # 用排好序的数组代替原数组 x.sort() print(x) # [1 2 3 4 5]
另外一个函数式argsort
,该函数返回的是原始数组排好序的索引值:
x = np.array([2, 1, 4, 3, 5]) i = np.argsort(x) print(i) # [1 0 3 2 4]
以上结果的第一个元素是数组中最小元素的索引值,第二个值给出的是次小元素的索引值,以此类推。这些索引值可以被用于创建有序的数组:
# 花哨的索引 x[i] array([1, 2, 3, 4, 5])
当然,Numpy排序算法的一个有用的功能是通过axis
参数,沿着多维数组的行或列进行排序。
rand = np.random.RandomState(42) x = rand.randint(0, 10, (4, 6)) print(x) # [[6 3 7 4 6 9] [2 6 7 4 3 7] [7 2 5 4 1 7] [5 1 4 0 9 5]] # 对x的每一列进行排序 np.sort(x, axis=0) # 对x的每一行进行排序 np.sort(x, axis=1)
2.部分排序:分割
有时候我们不希望对整个数组进行排序,仅仅希望找到数组中第K小的值,Numpy的np.partition函数提供了该功能。np.partition函数输入的是数组和数字K,输出结果是一个新数组,最左边的是第K小的值(数组中1-K个最小值,不论排序),往右是任意顺序的其他值。
x = np.array([7, 2, 3, 1, 6, 5, 4]) np.partition(x, 3) # array([2, 1, 3, 4, 6, 5, 7])
从上面看,结果数组中钱三个值是数组中最小的三个值,剩下的位置是原始数组中的值。在这两个发分割区中,元素都是任意排序的。
与排序类似,也可以沿着多维数组任意的轴进行分割:
rand = np.random.RandomState(42) x = rand.randint(0, 10, (4, 6)) np.partition(x, 2, axis=1)
结果输出的是一个数组,该数组每一行的前两个元素是该行最小的两个值,每一行其他的值分布在剩下的位置。
当然,正如np.argsort
函数计算的是排序的索引值,也有一个np.argpartition
函数计算的是分割的索引值。
二、Numpy的结构化数组
1.简单的复合类型
大多时候,我们的数据可以通过一个异构类型值组成的数组表示,但是有的时候并非如此简单。Numpy的结构化数组和记录数组,它们为复合的、异构的数据提供了非常有效的存储。
假定现在有关于一些人的分类数据(如姓名、年龄和体重),我们需要存储这些数据用于Python项目,那么一种可行的方法是将它们存在三个单独的数组中:
name = ['Alice', 'Bob', 'Cathy'] age = [25, 11, 26] weight = [55.0, 54.5, 61.0]
当然这个方法不好,因为没有将这三个数组联系在一起。而Numpy可以用结构化数组来实现单一结构存储这些数据。这些结构化数组是复合数据类型的。
# 和生成简单的数组类似 x = np.zeros(4, dtype=int) # 指定复合数据类型 data = np.zeros(4, dtype={'names':('name', 'age', 'weight'), 'formats':('U10', 'i4', 'f8')}) print(data.dtype) # [('name', '<U10'), ('age', '<i4'), ('weight', '<f8')] # 这里的U10表示长度不超过10的Unicode字符串 # i4表示4字节整型 # f8表示8字节的浮点型 ------------------ 然后将数据放入数组中 data['name'] = name data['age'] = age data['weight'] = weight # [('Alice', 25, 55.0) ('Bob', 11, 54.5) ('Cathy', 26, 61.0)]
结构化数组的方便之处,你可以通过索引或者名称查看相应的值:
# 获取所有name data['name'] # 获取数据的第一行 data[0] # 获取最后一行的名字 data[-1]['name']
还可以利用布尔掩码,做一些更复杂的操作,如按照年龄进行筛选:
# 获取年龄小于20岁的人的名字 data[data['age'] < 20]['name']
如果希望实现比上面更为复杂的操作,可以考虑为Pandas工具的DataFrame对象,构建在Numpy数组之上,提供了很多有用的数据操作功能,是数据科学中必不可少的工具。
2.生成结构化数组
结构化数组的数据类型有多种制定方式。前面我们是采用的字典的方法:
np.dtype({'names':('name', 'age', 'weight'), 'formats':('U10', 'i4', 'f8')}) # 也可以是元组列表 np.dtype([('name', 'S10'), ('age', 'i4'), ('weight', 'f8')]) # 或者类型名称不重要。仅仅用一个字符串来指定 np.dtype('S10, i4, f8') # dtype([('f0', 'S10'), ('f1', '<i4'), ('f2', '<f8')])
Numpy的数据类型表:
Numpy数据类型符号 | 描述 | 示例 |
---|---|---|
'b' | 字节型 | np.dtype('b') |
'i' | 有符号整型 | np.dtype('i4')==np.int32 |
'u' | 无符号整型 | np.dtype('u1')==np.uint8 |
'f' | 浮点型 | np.dtype('f8')==np.int64 |
'c' | 复数浮点型 | np.dtype('c16')==np.complex128 |
'S'、'a' | 字符串 | np.dtype('S5') |
'U' | Unicode字符串 | np.dtype('U')==np.str_ |
'V' | 原生数据 | np.dtype('V')==np.void |
3.高级的复合类型
Numpy中也可以定义更高级的复合数据类型。例如你可以创建一种类型,其中每个元素都包含一个数组或矩阵。
tp = np.dtype([('id', 'i8'), ('mat', 'f8', (3, 3))]) x = np.zeros(1, dtype=tp) print(x[0]) # (0, [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0])
现在x数组的每个元素都包含了一个id和一个3X3的矩阵。Numpy的dtype直接映射到C结构的定义,因此包含数组内容的缓存可以直接在C程序中使用,效率比较高。