提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
NumPy作为Python的拓展程序库,为利用Python实现数据的统计分析提供了很好的平台环境。上次笔记记录NumPy基础操作的学习过程,这次来点NumPy的进阶操作的笔记。
NumPy 进阶操作
1.NumPy dtype 层次结构
数组中的元素类型可能是整数,浮点数,字符串或者Python对象。为了方便检查像浮点数这种有多种类型的数据(float16 到 float 128),我们可以通过dtype 的超类 ,如np.integer和np.floating来实现数据类型的检查
import numpy as np
arr1 = np.ones(8,dtype=np.uint16)
arr2 = np.ones(8,dtype=np.float32)
print(np.issubdtype(arr1.dtype,np.integer))
print(np.issubdtype(arr2.dtype,np.floating))
print(np.float32.mro())
#/usr/bin/python3.8 /home/ljm/PycharmProject/pythoncode1/code4.py
True
True
[<class 'numpy.float32'>, <class 'numpy.floating'>, <class 'numpy.inexact'>, <class 'numpy.number'>, <class 'numpy.generic'>, <class 'object'>]
#Process finished with exit code 0
我们可以借助np.xxx.mro()函数来查看某种数据类型的父类,也可以用np.issubdtype结合超类来查看。下面是dtype的层次结构父类-子类关系图
2.NumPy 高阶数组操作
2.1数组的重塑
在NumPy的基础操作中我们经常用到np.arange函数来重塑数组的形状,我们传递(5,3)来重构一个五行三列的数组,其实传递的形状维度可以有一个值是-1来表示维度通过数据来判断
import numpy as np
arr1 = np.arange(12).reshape(3,4)
print(arr1.reshape((4,-1)))
arr2 = np.zeros((2,6))
print(arr1.reshape(arr2.shape))
print(arr1.ravel())
#/usr/bin/python3.8 /home/ljm/PycharmProject/pythoncode1/code4.py
[[ 0 1 2]
[ 3 4 5]
[ 6 7 8]
[ 9 10 11]]
[[ 0 1 2 3 4 5]
[ 6 7 8 9 10 11]]
[ 0 1 2 3 4 5 6 7 8 9 10 11]
#Process finished with exit code 0
我们也可以通过传递其他数组的shape值来重塑数组形状,因为数组的shape属性是一个元组。想要使数组一维扁平化可以利用ravel函数和flatten函数,两者差别在于前者不会生成底层数值的副本,而后者会。
2.2数组在行列不同方向重塑
NumPy给我们提供了内存中数据布局操控的灵活性,具体体现在NumPy的二维数组默认是按行方向创建的,这意味着每行的元素储存在相邻的存储单元内,而也可以通过传参的形式来规定数组按列方向创建,让每列中相邻的元素储存在相邻的存储单元内。行方向被称为C顺序而列方向被称为Fortran顺序,向reshape或者reval函数传递一个参数(大部分是’C’和’F’)可以得到行列不同方向的构建方式
import numpy as np
arr1 = np.arange(12).reshape(3,4)
print(arr1.ravel())
print(arr1.ravel('F'))
#/usr/bin/python3.8 /home/ljm/PycharmProject/pythoncode1/code4.py
[ 0 1 2 3 4 5 6 7 8 9 10 11]
[ 0 4 8 1 5 9 2 6 10 3 7 11]
#Process finished with exit code 0
2.3数组的分隔和连接
NumPy中想要实现数组的连接和分隔只需调用np.concatenate函数和np.split函数,相较于C需要利用指针通过循环一个一个挪进去高效一些,而且NumPy中数组的连接和分隔也可以指定不同的轴
import numpy as np
arr1 = np.arange(9).reshape(3,3)
arr2 = np.random.randn(3,3)
print(np.concatenate([arr1,arr2],axis=0))
arr1_1,arr1_2,arr1_3 = np.split(arr1,[1,3])
print(arr1_1)
#/usr/bin/python3.8 /home/ljm/PycharmProject/pythoncode1/code4.py
[[ 0. 1. 2. ]
[ 3. 4. 5. ]
[ 6. 7. 8. ]
[ 0.95107948 0.77077158 -0.43549062]
[-1.03306465 0.59867961 -0.70753325]
[ 0.95402297 1.16307793 0.398256 ]]
[[0 1 2]]
#Process finished with exit code 0
我们也可以直接使用NumPy自带的堆叠助手np.r_或者np.c_来分别按沿第二个轴(按列)连接或沿第一个轴(按行)连接
import numpy as np
arr1 = np.arange(9).reshape(3,3)
arr2 = np.random.randn(3,3)
print(np.c_[arr1,arr2])
print(np.r_[arr1,arr2])
#/usr/bin/python3.8 /home/ljm/PycharmProject/pythoncode1/code4.py
[[ 0. 1. 2. -1.07719508 -1.75519444 -1.7342581 ]
[ 3. 4. 5. 1.29285674 1.23222015 1.60785859]
[ 6. 7. 8. 0.85652942 -1.20217759 0.92190763]]
[[ 0. 1. 2. ]
[ 3. 4. 5. ]
[ 6. 7. 8. ]
[-1.07719508 -1.75519444 -1.7342581 ]
[ 1.29285674 1.23222015 1.60785859]
[ 0.85652942 -1.20217759 0.92190763]]
#Process finished with exit code 0
以下是连接和分隔数组的函数汇总
函数 | 描述 |
---|---|
concatenate | 沿一个轴向连接数组的集合 |
vstack,row_stack | 按行堆叠数组(沿轴0) |
hstack | 按列堆叠数组(沿轴1) |
column_stack | 类似hstack,但会把1维数组转换为2维列向量 |
dstack | 按深度堆叠数组(沿轴2) |
split | 沿指定轴,在传递的位置上分隔数组 |
hsplit/vsplit | 分别沿轴0和轴1分隔 |
2.4数组的元素重复
处理数组时我们经常要重复数组的元素使数组扩大化,或者复制数组的元素,NumPy提供了强大的函数tile和repeat函数来帮助我们更快更方便的操作。repeat函数按照传递的次数来对数组的元素进行复制,若想让每个元素复制的次数不一样需要传递一个数组
import numpy as np
arr1 = np.arange(3)
arr2 = np.random.randn(2,2)
print(np.repeat(arr1,3))
print(np.repeat(arr1,[1,2,3]))
print(np.repeat(arr2,2,axis=0))
print(np.repeat(arr2,[1,2],axis=0))
#/usr/bin/python3.8 /home/ljm/PycharmProject/pythoncode1/code4.py
[0 0 0 1 1 1 2 2 2]
[0 1 1 2 2 2]
[[ 0.43054745 -0.1838459 ]
[ 0.43054745 -0.1838459 ]
[ 1.41489256 -0.41716036]
[ 1.41489256 -0.41716036]]
[[ 0.43054745 -0.1838459 ]
[ 1.41489256 -0.41716036]
[ 1.41489256 -0.41716036]]
#Process finished with exit code 0
repeat函数也可以沿指定轴进行复制,当遇到二维数组或更高维数组指定轴复制元素时,传递一个数组作为索引参数就会沿着该轴以索引值的数值来确定复制的次数。
tile函数提供了一种更加快捷的方式,使用tile函数就像在铺瓷砖,它会根据轴的索引值来确定每个轴铺设的瓷砖数并加以扩散
import numpy as np
arr1 = np.arange(3)
print(np.tile(arr1,3))
print(np.tile(arr1,(2,3)))
#/usr/bin/python3.8 /home/ljm/PycharmProject/pythoncode1/code4.py
[0 1 2 0 1 2 0 1 2]
[[0 1 2 0 1 2 0 1 2]
[0 1 2 0 1 2 0 1 2]]
#Process finished with exit code 0
2.5 获取和设置数组子集
上篇笔记提到过切片操作数组,不同的索引方式会带来不同的切片设置效果。NumPy自带的take函数和put函数也可以实现神奇索引切片操作的类似效果,take函数主要用来选取数组的元素,put函数主要用来改变数组的元素
import numpy as np
arr1 = np.arange(8)*10
arr2 = np.array([1,3,5,7])
print(np.take(arr1,arr2))
np.put(arr1,arr2,6)
print(arr1)
#/usr/bin/python3.8 /home/ljm/PycharmProject/pythoncode1/code4.py
[10 30 50 70]
[ 0 6 20 6 40 6 60 6]
#Process finished with exit code 0
3.NumPy的特色–广播
3.1 广播的基本用法
广播是NumPy的一大特色也是NumPy的强大功能,它描述了算法如何在不同形状的数组间进行运算。其实我们之间在批量化操作数组元素就涉及到广播的内容,比如你给一个数组乘以某个数,这个数组的所有元素都会乘以这个数
import numpy as np
arr1 = np.random.randn(4,3)
np.set_printoptions(suppress=True)
arr2 = np.mean(arr1,axis=1)
print(arr1)
arr3 = arr1 - arr2.reshape((4,1))
print(np.mean(arr3,axis=1))
#/usr/bin/python3.8 /home/ljm/PycharmProject/pythoncode1/code4.py
[[-0.48354536 1.44914734 0.27297141]
[-1.0310191 0.46953759 1.42502657]
[ 0.3584732 -0.76505065 1.42715655]
[ 1.9474032 0.02127999 0.22738225]]
[0. 0. 0. 0.]
#Process finished with exit code 0
广播的基础是要保证不同数组要广播的轴的长度相互匹配,上述代码刚好阐明了广播的操作原理,通过求一个二维随机数组arr1轴1上的各行的平均值赋值给一个数组arr2,然后将arr2重塑为4行1列的数组方便两个数组广播,再将arr2的每一行的值广播到arr1中的每一行进行相减,效果的等价于arr1的每行元素减去行均值,最终得到的数组arr3的平均值必然是0。
3.2 三维数组的广播和生成新轴
三维数组的广播在任何一个维度都能完成,上述图片很好的展示了三维数组的广播所需要的形状,我们只需要将数据重塑为形状兼容的数组即可与三维数组一起广播。
有时我们可以专门为数组新建一个轴来达成广播的目的,这也意味着我们构建了一个新形状的元组。我们可以使用NumPy中的np.newaxis属性和完整的切片来插入新轴
import numpy as np
arr1 = np.random.randn(3,3)
arr1_3d = arr1[:,np.newaxis,:]
arr2 = np.arange(3)
arr2_2d = arr2[np.newaxis]
print(arr1_3d)
print(arr2_2d)
print(arr1_3d*arr2_2d)
#/usr/bin/python3.8 /home/ljm/PycharmProject/pythoncode1/code4.py
[[[-1.06544789 1.20202634 -0.79756292]]
[[-1.55838561 -0.4717286 -0.14519221]]
[[ 0.86662742 -1.79613568 0.17729315]]]
[[0 1 2]]
[[[-0. 1.20202634 -1.59512584]]
[[-0. -0.4717286 -0.29038442]]
[[ 0. -1.79613568 0.3545863 ]]]
#Process finished with exit code 0
插入新轴的方式让广播的适用性得到拓展,原来不可广播的两个数组,在插入新轴后可以广播来完成数组间的操作交互。
3.3 通过广播改变数组的值
除了使用广播来给数组进行算术运算来改变数组的元素,我们也可以通过广播结合切片索引的方式来完成数组局部的广播和修改。
import numpy as np
arr1 = np.random.randn(3,3)
print(arr1)
arr2 = np.arange(3)
arr1[:] = arr2[:,np.newaxis]
print(arr1)
arr1[:2] = [[6],[9]]
print(arr1)
#/usr/bin/python3.8 /home/ljm/PycharmProject/pythoncode1/code4.py
[[ 1.84002043 0.47256311 -0.17711423]
[-2.42566254 -0.64492733 -1.2086769 ]
[-0.95518592 -1.91514306 0.13883272]]
[[0. 0. 0.]
[1. 1. 1.]
[2. 2. 2.]]
[[6. 6. 6.]
[9. 9. 9.]
[2. 2. 2.]]
#Process finished with exit code 0
通过广播修改数组元素时首要考虑形状是否匹配,还有考虑广播内容的维度要和元组的写法相一致。
4.高阶ufunc用法
NumPy的每个二元通用函数(ufunc)都有特殊的方法来执行某些向量化操作,减小了我们代码的复杂性,不需要经常写循环来达到想要的效果
import numpy as np
arr1 = np.arange(8).reshape(4,2)
print(np.add.reduce(arr1))
print(np.add.accumulate(arr1,axis=1))
#/usr/bin/python3.8 /home/ljm/PycharmProject/pythoncode1/code4.py
[12 16]
[[ 0 1]
[ 2 5]
[ 4 9]
[ 6 13]]
#Process finished with exit code 0
np.add.reduce函数用于数组元素的求和,等价于np.sum函数;np.add.accumulate函数用于对数组元素求累加和,而且可以处理多维数组不同轴上的累加和问题
import numpy as np
arr1 = np.arange(8)
arr2 = np.arange(4)
arr2 = np.repeat(arr2,[1,2,2,3])
print(np.multiply.outer(arr1,arr2))
#/usr/bin/python3.8 /home/ljm/PycharmProject/pythoncode1/code4.py
[[ 0 0 0 0 0 0 0 0]
[ 0 1 1 2 2 3 3 3]
[ 0 2 2 4 4 6 6 6]
[ 0 3 3 6 6 9 9 9]
[ 0 4 4 8 8 12 12 12]
[ 0 5 5 10 10 15 15 15]
[ 0 6 6 12 12 18 18 18]
[ 0 7 7 14 14 21 21 21]]
#Process finished with exit code 0
outer方法主要用于操作两个数组的所有元素对,相当于排列组合。当给它加上multiply的运算,就相当于元素对内部相乘,然后以积的结果返回。
import numpy as np
arr1 = np.arange(9)
print(np.add.reduceat(arr1,[0,4,8]))
arr1 = arr1.reshape(3,3)
print(np.add.reduceat(arr1,[0,2],axis=1))
#/usr/bin/python3.8 /home/ljm/PycharmProject/pythoncode1/code4.py
[ 6 22 8]
[[ 1 2]
[ 7 5]
[13 8]]
#Process finished with exit code 0
reduceat方法用于执行本地缩聚,所以np.add.reduceat函数就是对数组的缩聚区间的元素求和,当然该方法也可以对多维数组的指定轴进行区间缩聚。
以下是常用高阶ufunc的汇总
方法 | 描述 |
---|---|
reduce | 按操作的连续程序对数组聚合 |
accumulate | 求聚合值,保留所有部分聚合 |
reduceat | 本地缩聚或"group by", 减少连续的数据切片已生成聚合数组 |
outer | 操作数组x和y中的所有元素对,结果数组的形状为x.shape + y.shape |
5.数组的结构化
ndarray是一个同构数据的容器,内存块中的每个元素占用相同数量的字节。结构化数据类型旨在能够模仿C语言中的“结构”,并共享类似的内存布局,结构化后的数组能具备更好的数据统一能力。
import numpy as np
arr1 = np.array([('Jack',18,65),('Janes',17,50),('James',16,60)],dtype=[('name','U10'),('age','i4'),('weight','f8')])
print(arr1)
print(arr1[0])
#/usr/bin/python3.8 /home/ljm/PycharmProject/pythoncode1/code4.py
[('Jack', 18, 65.) ('Janes', 17, 50.) ('James', 16, 60.)]
('Jack', 18, 65.)
#Process finished with exit code 0
初始化结构体数组时,我们需要传递元组和定义元组内部元素的数据类型,和C初始化结构体数组时思路大致相同,都是一一对应。当我们访问结构体数组里的特定内容时,我们就可以利用数组的整数索引或者初始化时定义的字段名称
import numpy as np
arr1 = np.array([('Jack',18,65),('Janes',17,50),('James',16,60)],dtype=[('name','U10'),('age','i4',3),('weight','f8')])
arr1 = np.zeros(4,dtype=[('name','U10'),('age','i4',3),('weight','f8')])
print(arr1)
print(arr1['age'])
#/usr/bin/python3.8 /home/ljm/PycharmProject/pythoncode1/code4.py
[('', [0, 0, 0], 0.) ('', [0, 0, 0], 0.) ('', [0, 0, 0], 0.)
('', [0, 0, 0], 0.)]
[[0 0 0]
[0 0 0]
[0 0 0]
[0 0 0]]
#Process finished with exit code 0
操作结构化数组时就可以很方便的批量处理数据了,但操作时如果数据类型不匹配的话也是无法实现目的的,比如想将数组的元素全部变为0,遇到字符串就无法匹配。
6.数组的排序方法
6.1直接排序和间接排序
数组内元素的排序是经常需要用到的数据处理手段,和Python内建列表类似,ndarray的sort方法是一种原位排序,意味着数组的内容进行重排列而非生成一个新数组
import numpy as np
arr1 = np.random.randn(8)
print(np.sort(arr1))
arr1 = arr1.reshape(4,2)
print(np.sort(arr1,axis=1))
#/usr/bin/python3.8 /home/ljm/PycharmProject/pythoncode1/code4.py
[-1.21876466 -0.38296528 -0.38000787 -0.04394856 0.3712407 0.40709617
0.90497186 2.8527074 ]
[[-1.21876466 0.40709617]
[-0.38296528 -0.38000787]
[-0.04394856 2.8527074 ]
[ 0.3712407 0.90497186]]
#Process finished with exit code 0
sort方法是一种直接排序,而生活中也会遇到需要间接排序来解决的问题,比如学生的数据需要按姓氏排序,然后按名字排序,这里的姓氏和名字就可以作为一种间接排序的索引
import numpy as np
arr1 = np.random.randn(5)
print(arr1)
arr2 = np.argsort(arr1)
print(arr2)
print(arr1[arr2])
#/usr/bin/python3.8 /home/ljm/PycharmProject/pythoncode1/code4.py
[-0.62580584 0.46200503 0.11229284 -0.29320359 1.03045597]
[0 3 2 1 4]
[-0.62580584 -0.29320359 0.11229284 0.46200503 1.03045597]
#Process finished with exit code 0
argsort方法可以给无序的数字数组生成一个整数索引数组用于间接排序,而lexsort方法则会给无序的字符数组生成一个整数索引数组用于间接排序。以下是排序方法的汇总
方法 |
---|
argsort |
lexsort |
quicksort |
mergesort |
heapsort |
6.2部分排序和元素查找
部分排序可以用于确认多个最小元素或者多个最大元素,partition方法可用于部分排序,它需要传递数组和索引位参数。np.partition的工作流程可以看做是先对数组排序(升序),然后以索引是i的元素为基准,将元素分成两部分,即大于该元素的放在其后面,小于该元素的放在其前面
import numpy as np
arr1 = np.random.randn(10)
print(arr1)
print(np.partition(arr1,5))
#/usr/bin/python3.8 /home/ljm/PycharmProject/pythoncode1/code4.py
[-0.68816612 0.29039709 1.00776513 1.10963259 -0.42822786 0.16016132
-0.82137976 0.34690288 -0.73512737 1.09590751]
[-0.82137976 -0.68816612 -0.73512737 -0.42822786 0.16016132 0.29039709
0.34690288 1.00776513 1.09590751 1.10963259]
#Process finished with exit code 0
searchsorted是一种对已排序数组执行二分搜索的方法,它会将搜索的对象放入数组进行不断的比较,然后返回数组中需要插入值的位置以保持排序
import numpy as np
arr1 = np.array([1,3,5,7,9])
arr2 = np.array([1,1,2,2,3,3])
print(np.searchsorted(arr1,4))
print(np.searchsorted(arr2,[1,3]))
#/usr/bin/python3.8 /home/ljm/PycharmProject/pythoncode1/code4.py
2
[0 4]
#Process finished with exit code 0
总结
以上就是NumPy进阶笔记的全部内容,本文简单介绍了NumPy的高阶使用方式,比之前NumPy基础更进一步总结了NumPy的高阶操作,NumPy库的学习就告一段落,继续学习pandas库。