NumPy,是Numerical Python的简称,它是目前Python数值计算中最为重要的基础包。大多数计算包都提供了基于NumPy的科学函数功能,将NumPy的数组对象作为数据交换的通用语。
以下内容将会出现在NumPy中:
NumPy本身并不提供建模和科学函数
NumPy的核心特征之一就是N-维数组对象——ndarray。ndarray是Python中一个快速、灵活的大型数据集容器。数组允许你使用类似于标量的操作语法在整块数据上进行数学计算。
In [1]: import numpy as np
In [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)
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]: 2
In [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数组,并不安全,有些时候它可能会返回未初始化的垃圾数值。
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的数组版,返回一个数组 |
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)
数组之所以重要是因为它允许你进行批量操作而无须任何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]])
基础索引与切片
In [7]: arr = np.arange(10)
In [8]: arr[5]
Out[8]: 5
In [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] = 6
In [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]: 5
In [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=')
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返回的是数据的视图,而没有对数据进行复制。