Numpy库学习
NumPy,是Numerical Python的简称,它是目前Python数值计算中最为重要的基础包。大多数计算包都提供了基于NumPy的科学函数功能,将NumPy的数组对象作为数据交换的通用语。
以下内容将会出现在NumPy中:
- ndarray——一种高效多维数组,提供了基于数组的便捷算术操作以及灵活的广播功能。
- 对所有数据进行快速的矩阵计算,而无须编写循环程序。
- 对硬盘中数组数据进行读写的工具,并对内存映射文件进行操作。
- 线性代数、随机数生成以及傅里叶变换功能。
- 用于连接NumPy到C、C++和FORTRAN语言类库的C语言API。
NumPy本身并不提供建模和科学函数
学习目标
- 在数据处理、清洗、构造子集、过滤、变换以及其他计算中进行快速的向量化计算。
- 常见的数组算法,比sort、unique以及set操作等。
- 高效的描述性统计和聚合/概述数据。
- 数据排列和相关数据操作,例如对异构数据进行merge和join。
- 使用数组表达式来表明条件逻辑,代替if-elif-else条件分支的循环。- 分组数据的操作(聚合、变换以及函数式操作)
ndarray:多维数组对象
NumPy的核心特征之一就是N-维数组对象——ndarray。ndarray是Python中一个快速、灵活的大型数据集容器。数组允许你使用类似于标量的操作语法在整块数据上进行数学计算。
- numpy的批量计算
下面是一个展现NumPy如何使用类似于Python内建对象的标量计算语法进行批量计算的小例子
In [1]: import numpy as npIn [2]: data = np.random.randn(2,3)
#生成一个小的随机数组
In [3]: data
Out[3]:
array([[-0.0421879 , -0.21588562, -0.52903881],[-1.59346557, -1.19710799, -1.352975 ]])In [4]: data*100
Out[4]:
array([[ -4.21878966, -21.58856219, -52.90388053],[-159.34655725, -119.71079936, -135.29750021]])
#data数组内所有的元素都同时乘以了10
In [5]: data + data
Out[5]:
array([[-0.08437579, -0.43177124, -1.05807761],[-3.18693115, -2.39421599, -2.70595 ]])
#数组中的对应元素进行了相加。
使用标准的NumPy导入方式import numpy as np。你当然也可以在代码中写from numpy import*来省略多写的一个np.然而建议你保持写标准导入的方式。numpy这个命名空间包含了大量与Python内建函数重名的函数(比如min和max)
- 数组的shape属性与数据类型
一个ndarray是一个通用的多维同类数据容器,也就是说,它包含的每一个元素均为相同类型。每一个数组都有一个shape属性,用来表征数组每一维度的数量;每一个数组都有一个dtype属性,用来描述数组的数据类型.
In [6]: data.shape
Out[6]: (2, 3)In [7]: data.dtype
Out[7]: dtype('float64')
-
生成adarray
array函数接收任意的序列型对象(当然也包括其他的数组),生成一个新的包含传递数据的NumPy数组。例如:- 列表的转换
In [7]: data.dtype Out[7]: dtype('float64')In [8]: data1 = [5,7,4,8,7,0] In [9]: arr1 = np.array(data1) In [10]: arr1 Out[10]: array([5, 7, 4, 8, 7, 0])
- 嵌套序列,例如同等长度的列表,将会自动转换成多维数组:
In [11]: data2 = [[65,6,75,8],[76,8,3,1]]In [12]: arr2 = np.array(data2) In [13]: arr2 Out[13]: array([[65, 6, 75, 8],[76, 8, 3, 1]])
因为data2是一个包含列表的列表,所以Numpy数组arr2形成了二维数组。我们可以通过检查ndim和shape属性来确认这一点:
In [14]: arr2.ndim Out[14]: 2In [15]: arr2.shape Out[15]: (2, 4)
除了np.array,还有很多其他函数可以创建新数组。例如,给定长度及形状后,zeros可以一次性创造全0数组,ones可以一次性创造全1数组。empty则可以创建一个没有初始化数值的数组。想要创建高维数组,则需要为shape传递一个元组
- 创建特殊数组
In [16]: np.zeros(10) Out[16]: array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])In [17]: np.ones(5) Out[17]: array([1., 1., 1., 1., 1.])In [18]: np.empty((2,3)) Out[18]: array([[0.08437579, 0.43177124, 1.05807761],[3.18693115, 2.39421599, 2.70595 ]])
使用np.empty来生成一个全0数组,并不安全,有些时候它可能会返回未初始化的垃圾数值。
- arange是Python内建函数range的数组版
In [19]: np.arange(10) Out[19]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
由于NumPy专注于数值计算,如果没有特别指明的话,默认的数据类型是float64(浮点型).
- 数据生成函数
函数名 描述 array 将输入数据转换为ndarray,如不显示表示数据类型.则自动推断;默认复制所有的数据类型 asarray 将输入转换为ndarray,如果输入已经是ndarray类型,则不复制 arange Python内建函数range的数组版,返回一个数组 - 数据类型
数据类型,即dytpe,是一个特殊的对象,它包含了ndarray需要为某一种类型数据所申明的内存块信息(也称为元数据,即表示数据的数据).
In [24]: arr = np.array([654,56,67,56,65,65])In [25]: arr.dtype
Out[25]: dtype('int32')
#使用astype方法显式地转换数组的数据类型:
In [26]: float_arr = arr.astype(np.float64)In [27]: float_arr.dtype
Out[27]: dtype('float64')
如果有一个数组,里面的元素都是表达数字含义的字符串,也可以通过astype将字符串转换为数字
In [29]: arr = np.array([-3.5,65.4,65,6],dtype=np.string_)In [31]: arr.astype(float)
Out[31]: array([-3.5, 65.4, 65. , 6. ])
NumPy可以使用相同别名来表征与Python精度相同的Python数据类型。也可以使用另一个数组的dtype属性:
In [39]: int_arr.dtype
Out[39]: dtype('int32')In [40]: float_arr = np.array([5.5,6.4,7.8,2.5,6.8],dtype=float)
In [41]: int_arr.astype(float_arr.dtype)
Out[41]: array([0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])
也可以使用类型代码来传入数据类型:
In [39]: int_arr.dtype
Out[39]: dtype('int32')In [40]: float_arr = np.array([5.5,6.4,7.8,2.5,6.8],dtype=float)
In [41]: int_arr.astype(float_arr.dtype)
Out[41]: array([0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])In [42]: int_arr.astype('u4')
Out[42]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=uint32)
- NumPy数组算术
数组之所以重要是因为它允许你进行批量操作而无须任何for循环。NumPy用户称这种特性为向量化.
注意:任何在两个等尺寸数组之间的算术操作都应用了逐元素操作的方式.
- 带有标量计算的算术操作,会把计算参数传递给数组的每一个元素
In [48]: arr = np.array([[5,6,5,8],[6,7,6,3]])In [49]: arr
Out[49]:
array([[5, 6, 5, 8],[6, 7, 6, 3]])In [50]: arr*10
Out[50]:
array([[50, 60, 50, 80],[60, 70, 60, 30]])In [51]: arr*arr
Out[51]:
array([[25, 36, 25, 64],[36, 49, 36, 9]])In [52]: arr-arr
Out[52]:
array([[0, 0, 0, 0],[0, 0, 0, 0]])In [53]: arr+arr
Out[53]:
array([[10, 12, 10, 16],[12, 14, 12, 6]])
- 同尺寸数组之间的比较,会产生一个布尔值数组
In [2]: arr1 = np.random.randn(3,4)In [3]: arr2 = np.random.randn(3,4)In [4]: arr1
Out[4]:
array([[ 0.03381384, 1.87791581, 1.39403355, -0.58645012],[ 1.83638141, 0.91968343, 0.44893528, -0.03805066],[-1.71230329, -0.6489988 , 0.63053987, 0.09063347]])In [5]: arr2
Out[5]:
array([[ 1.4200954 , 0.1432846 , 1.00890235, 0.81021525],[ 0.42412295, 0.33147589, 1.05764754, 0.08025581],[-0.35912561, 0.51876235, 1.43744617, 0.99076419]])In [6]: arr1>arr2
Out[6]:
array([[False, True, True, False],[ True, True, False, False],[False, False, False, False]])
-
基础索引与切片
- 一维数组切片比较简单,看起来和Python的列表很类似:
In [7]: arr = np.arange(10)In [8]: arr[5]
Out[8]: 5In [9]: arr[3:8]
Out[9]: array([3, 4, 5, 6, 7])In [10]: arr[:5]
Out[10]: array([0, 1, 2, 3, 4])In [11]: arr[3:8] = 6In [12]: arr
Out[12]: array([0, 1, 2, 6, 6, 6, 6, 6, 8, 9])
如果你传入了一个数值给数组的切片,例如arr[3:8] = 6,数值被传递给了整个切片。区别于Python的内建列表,数组的切片是原数组的视图。这意味着数据并不是被复制了,任何对于视图的修改都会反映到原数组上.原因是:由于NumPy被设计成适合处理非常大的数组,你可以想象如果NumPy持续复制数据会引起很多内存问题。
注意:如果你还是想要一份数组切片的拷贝而不是一份视图的话,你就必须显式地复制这个数组,例如arr[5:8].copy()
- 在一个二维数组中,每个索引值对应的元素不再是一个值,而是一个一维数组:
In [14]: arr
Out[14]:
array([[0, 1, 2],[3, 4, 5],[6, 7, 8]])In [15]: arr[1]
Out[15]: array([3, 4, 5])
因此,单个元素可以通过递归的方式获得。但是要多写点代码,你可以通过传递一个索引的逗号分隔列表去选择单个元素,以下两种方式效果一样:
In [16]: arr[1,2]
Out[16]: 5In [17]: arr[1][2]
Out[17]: 5
在多维数组中,你可以省略后续索引值,返回的对象将是降低一个维度的数组。
以上的数组子集选择中,返回的数组都是视图.
In [18]: arr[:2]
Out[18]:
array([[0, 1, 2],[3, 4, 5]])In [19]: arr[:2,:2]
Out[19]:
array([[0, 1],[3, 4]])
如你所见,第18组IO,数组沿着轴0进行了切片。表达式arrzd[:2]的含义为选择arr2d的前两行;第19就组IO,数组沿着轴0,1进行了切片表达式arr[:2,:2]的含义为选择arr的前两行和前两列的交集.
注意:单独一个冒号表示选择整个轴上的数组
- 布尔索引
考虑以下例子,假设我们的数据都在数组中,并且数组中的数据是一些存在重复的人名。使用numpy.random中的randn函数来生成一些随机正态分布的数据:
In [20]: names = np.array(['Bob','Will','Lee','Will','Lee','Lee'])
In [21]: data = np.random.randn(6,5)In [22]: names
Out[22]: array(['Bob', 'Will', 'Lee', 'Will', 'Lee', 'Lee'], dtype='<U4')In [23]: data
Out[23]:
array([[-2.09778316, 1.0283271 , 0.93938557, -0.2247455 , -0.32755643],[ 1.32232264, 0.12762422, -0.70816897, 1.48471576, 0.3999583 ],[ 0.89986716, 0.08729917, 0.24970591, -1.03332823, 0.34245957],[ 0.72876191, -0.87117186, -0.03504858, -0.86879753, -0.42663472],[ 0.90960067, 0.01485512, -0.89126664, -0.00999851, 0.82528367],[ 0.22279861, -0.11088761, -0.48246116, -1.17185184, 0.33091438]])In [24]: names == 'Will'
Out[24]: array([False, True, False, True, False, False])
可以利用数组的比较操作(比如==)进行向量化,通过比较names数组和字符串’Bob’会产生一个布尔值数组,因此在索引数组时可以传入布尔值数组:
In [27]: data[names == 'Lee']
Out[27]:
array([[ 0.89986716, 0.08729917, 0.24970591, -1.03332823, 0.34245957],[ 0.90960067, 0.01485512, -0.89126664, -0.00999851, 0.82528367],[ 0.22279861, -0.11088761, -0.48246116, -1.17185184, 0.33091438]])In [28]: data[names == 'Will']
Out[28]:
array([[ 1.32232264, 0.12762422, -0.70816897, 1.48471576, 0.3999583 ],[ 0.72876191, -0.87117186, -0.03504858, -0.86879753, -0.42663472]])
注意:布尔值数组的长度必须和数组轴索引长度一致。当布尔值数组的长度不正确时,布尔值选择数据的方法并不会报错,因此在使用该特性的时候要小心。
为了选择除了’Lee’以外的其他数据,你可以使用!=或在条件表达式前使用~对条件取反
In [29]: data[names != 'Lee']
Out[29]:
array([[-2.09778316, 1.0283271 , 0.93938557, -0.2247455 , -0.32755643],[ 1.32232264, 0.12762422, -0.70816897, 1.48471576, 0.3999583 ],[ 0.72876191, -0.87117186, -0.03504858, -0.86879753, -0.42663472]])In [30]: data[~(names == 'Lee')]
Out[30]:
array([[-2.09778316, 1.0283271 , 0.93938557, -0.2247455 , -0.32755643],[ 1.32232264, 0.12762422, -0.70816897, 1.48471576, 0.3999583 ],[ 0.72876191, -0.87117186, -0.03504858, -0.86879753, -0.42663472]])
当要选择三个名字中的两个时,可以对多个布尔值条件进行联合,需要使用数学操作符如&(and)和|(or):
In [33]: data[(names == 'Lee')|(names == 'Will')]
Out[33]:
array([[ 1.32232264, 0.12762422, -0.70816897, 1.48471576, 0.3999583 ],[ 0.89986716, 0.08729917, 0.24970591, -1.03332823, 0.34245957],[ 0.72876191, -0.87117186, -0.03504858, -0.86879753, -0.42663472],[ 0.90960067, 0.01485512, -0.89126664, -0.00999851, 0.82528367],[ 0.22279861, -0.11088761, -0.48246116, -1.17185184, 0.33091438]])
注意不能使用and以及or,只能使用&或|,每个布尔条件需用圆括号括起.
布尔值索引选择数据时,总是生成数据的拷贝,即使返回的数组并没有任何变化。
总结:data后的方括号所需要的是布尔表达式.
- 神奇索引
神奇索引是numpy中的术语,用于描述使用整数数组进行数据索引。
假设我们有一个8×4的数组,为了选出一个符合特定顺序的子集,你可以简单地通过传递一个包含指明所需顺序的列表或数组来完成,如果使用负的索引,将从尾部进行选择.
In [36]: arr = np.arange(32).reshape(8,4)In [37]: arr[[4,6,0,2]]
Out[37]:
array([[16, 17, 18, 19],[24, 25, 26, 27],[ 0, 1, 2, 3],[ 8, 9, 10, 11]])In [38]: arr[[-4,5,-1,7]]
Out[38]:
array([[16, 17, 18, 19],[20, 21, 22, 23],[28, 29, 30, 31],[28, 29, 30, 31]])
传递多个索引数组时情况有些许不同,这样会根据每个索引元组对应的元素选出一个一维数组:
In [40]: arr[[5,5,4,2],[1,3,1,0]]
Out[40]: array([21, 23, 17, 8])
在上述例子中,元素(5, 1)、(5, 3)、(4, 1)和(2, 0)被选中。如果不考虑数组的维数(本例中是二维),神奇索引的结果总是一维的。
注意:牢记神奇索引与切片不同,它总是将数据复制到一个新的数组中。
- 数组转置和换轴
转置是一种特殊的数据重组形式,可以返回底层数据的视图而不需要复制任何内容.数组拥有transpose方法,也有特殊的T属性:
In [41]: arr = np.arange(12).reshape(3,4)In [42]: arr
Out[42]:
array([[ 0, 1, 2, 3],[ 4, 5, 6, 7],[ 8, 9, 10, 11]])In [43]: arr.T
Out[43]:
array([[ 0, 4, 8],[ 1, 5, 9],[ 2, 6, 10],[ 3, 7, 11]])
当进行矩阵计算时,可能会经常进行一些特定操作,比如,当计算矩阵内积会使用np.dot:
In [44]: np.dot(arr,arr.T)
Out[44]:
array([[ 14, 38, 62],[ 38, 126, 214],[ 62, 214, 366]])
对于更高维度的数组,transpose方法可以接收包含轴编号的元组,用于置换轴:
In [45]: arr.transpose((1,0))
Out[45]:
array([[ 0, 4, 8],[ 1, 5, 9],[ 2, 6, 10],[ 3, 7, 11]])In [46]: arr.transpose((0,1))
Out[46]:
array([[ 0, 1, 2, 3],[ 4, 5, 6, 7],[ 8, 9, 10, 11]])
正如我们所看到的,第一个代码块,轴已经被重新排序,使得原先的第二个轴变为第一个,原先的第一个轴变成了第二个;第二个代码块,轴没有变.
使用.T进行转置是换轴的一个特殊案例。ndarray有一个swapaxes方法,该方法接收一对轴编号作为参数,并对轴进行调整用于重组数据:
In [47]: arr.swapaxes(0,1)
Out[47]:
array([[ 0, 4, 8],[ 1, 5, 9],[ 2, 6, 10],[ 3, 7, 11]])
swapaxes返回的是数据的视图,而没有对数据进行复制。