NumPy,是Numerical Python的简称,它是利用Python进行数值运算的最为重要的基础包。
重要的原因可以归纳为三点:
下面的例子将展示NumPy的不同,定义一个NumPy数组,里面存放着1M个整数,再定义一个列表,同样存放着1M个整数,将这两个序列同时乘以2,比较它们运算时间的差异。
In [8]: import numpy as np
In [9]: my_arr = np.arange(1000000)
In [10]: my_list = list(np.arange(1000000))
In [12]: %timeit my_arr2=my_arr*2
2.21 ms ± 19.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [13]: %timeit my_list2=[x*2 for x in my_list]
200 ms ± 1.18 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
ndarray是Python中一个快速、灵活的多维同类数据容器,也就是说,它包含的每一个元素都是相同的数据类型。每一个数组都有一个shape属性,用来表征数组每一个维度的数量;每一个数组都有一个dtype属性,用来描述数组的数据类型。
In [14]: data = np.random.randn(2,3)
In [15]: data
Out[15]:
array([[ 0.1049642 , -0.52542354, -0.07641538],
[ 0.5484492 , -0.95387232, 0.84849499]])
In [18]: data.shape
Out[18]: (2, 3)
In [19]: data.dtype
Out[19]: dtype('float64')
建议使用标准的NumPy导入方式import numpy as np,numpy这个命名空间中包含了大量与Python内建函数重名的函数(比如min,max)。
In [9]: np.arange(10) Out[9]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) #arange是python内建函数range的数组版
不论是“数组”,“NumPy数组”还是“ndarray”,它们都表示同一个对象,ndarray对象。
1.1 使用array函数生成数组
In [21]: data1=[6.,3.4,9.,0.,1.]
In [22]: arr1=np.array(data1)
In [23]: arr1
Out[23]: array([6. , 3.4, 9. , 0. , 1. ])
In [24]: data2=[1,2,3],[4,5,6]
In [25]: arr2=np.array(data2)
In [26]: arr2
Out[26]:
array([[1, 2, 3],
[4, 5, 6]])
1.2 使用其他函数创建新数组
In [5]: np.zeros(10)
Out[5]: array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])
In [6]: np.zeros((3,6))
Out[6]:
array([[0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0.]])
In [7]: np.empty((2,3,2))
Out[7]:
array([[[6.23042070e-307, 1.89146896e-307],
[1.37961302e-306, 6.23053614e-307],
[6.23053954e-307, 9.34609790e-307]],
[[8.45593934e-307, 9.34600963e-307],
[2.13621758e-306, 6.89804132e-307],
[1.11261162e-306, 8.34443015e-308]]])
表1展示的是标准数组的生成函数。由于NumPy专注于数值计算,没有特别指明的话,默认的数值类型为浮点型(float64)。
表1:数组生成函数
函数名 | 描述 |
---|---|
array | 将输入数据(列表、元组、数组、序列或其他)转换成ndarray,如不显式指明数据类型,将自动推断;默认复制所有的输入数据 |
asarray | 将输入转换为ndarray,但如果输入已经是nadarray则不再复制 |
arange | Python内建函数arrange的数组版,返回一个数组 |
ones | 根据给定形状和数据类型生成一个全1数组 |
ones_like | 根据所给的数组生成一个形状一样的全1数组 |
zeros | 根据给定形状和数据类型生成一个全0数组 |
zeros_like | 根据所给的数组生成一个形状一样的全0数组 |
empty | 根据给定形状和数据类型生成一个没有初始化数值的空数组 |
empty_like | 根据所给的数组生成一个形状一样的没有初始化数值的空数组 |
full | 根据给定形状和数据类型生成指定数值的数组 |
full_like | 根据所给的数组生成一个形状一样的内容为指定数值的数组 |
eye,identity | 生成一个N*N特征矩阵(对角线位置为1,其余位置为0) |
数据类型 ,即dtype,表示数组里存储的数据类型,对于初学者,仅需要知道数据大类有哪些:浮点型、整数、布尔值、字符串以及某个python对象。
In [19]: arr1=np.array([1,2,3])
In [20]: arr1=np.array([1,2,3],dtype=np.float64)
In [21]: arr1.dtype
Out[21]: dtype('float64')
表2展示的是NumPy所支持的所有数据类型。
表2:NumPy数据类型
类型 | 类型代码 | 描述 |
---|---|---|
int8,uint8 | i1,u1 | 有符号和无符号的8数位整数 |
int16,uint16 | i2,u2 | 有符号和无符号的16数位整数 |
int32,uint32 | i3,u3 | 有符号和无符号的32数位整数 |
int64,uint64 | i4,u4 | 有符号和无符号的64数位整数 |
float16 | f2 | 半精度浮点数 |
float32 | f4 | 标准单精度浮点数,兼容C语言float |
float64 | f8 | 标准双精度浮点数,兼容C语言double和Python float |
float128 | f16 | 拓展精度浮点数 |
complex64,complex128,complex256 | c8,c16,c32 | 分别基于32位、64位、128位浮点数的复数 |
bool | ? | 布尔值 True or False |
object | O | Python object类型 |
string_ | S | 修正的ASCⅡ字符串类型;例如生成一个长度为10的字符串类型,使用’S10’ |
unicode_ | U | 修正的Unicode类型,生成一个长度为10的Unicode类型,使用’U10’ |
8数位代表的是二进制下的8位,即2的8次方,256,平均分到正负里,就是-128~127。
In [26]: arr=np.array(['Beijing','Wulumuqi','Laiwu'],dtype='S5') In [27]: arr Out[27]: array([b'Beiji', b'Wulum', b'Laiwu'], dtype='|S5')
转换数组的数据类型
In [28]: arr=np.array([1.1,1.2,1.3,1.4,1.5,-1.6,-1.7,-2.8,-1.9])
In [29]: arr
Out[29]: array([ 1.1, 1.2, 1.3, 1.4, 1.5, -1.6, -1.7, -2.8, -1.9])
In [30]: arr.astype(np.int32)
Out[30]: array([ 1, 1, 1, 1, 1, -1, -1, -2, -1])
如果有一个数组,里面的元素都是表达数字含义的字符串,也可以通过astype将字符串转换为数字:
In [31]: numeric_strings=np.array(['1.25','40','-9.6'],dtype=np.string_) In [32]: numeric_strings Out[32]: array([b'1.25', b'40', b'-9.6'], dtype='|S4') In [33]: numeric_strings.astype(float) Out[33]: array([ 1.25, 40. , -9.6 ])
In [38]: int_array=np.arange(10)
In [39]: float_array=np.array([.44,.380,.237,-.12],dtype=np.float64)
In [40]: int_array.astype(float_array.dtype)
Out[40]: array([0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])
In [41]: empty_uint32=np.empty(8,dtype='u4')
In [42]: empty_uint32
Out[42]:
array([3264175145, 1071393013, 2233382994, 1071141355, 412316860,
1070487044, 3951369912, 1069463633], dtype=uint32)
使用astype时总是生成一个新的数组,及时传入的dtype与之前一样。
运用数组,我们可以不用for循环实现批量操作,这称为数组的向量化 特性。两个相同尺寸的数组之间的运算都应用了逐元素操作的方式。
In [43]: arr=np.array([[1,2,3],[4,5,6]])
In [44]: arr
Out[44]:
array([[1, 2, 3],
[4, 5, 6]])
In [45]: arr*arr
Out[45]:
array([[ 1, 4, 9],
[16, 25, 36]])
In [46]: arr-arr
Out[46]:
array([[0, 0, 0],
[0, 0, 0]])
In [47]: 1/arr
Out[47]:
array([[1. , 0.5 , 0.33333333],
[0.25 , 0.2 , 0.16666667]])
In [48]: arr**0.5
Out[48]:
array([[1. , 1.41421356, 1.73205081],
[2. , 2.23606798, 2.44948974]])
相同尺寸的数组间比较,会返回一个布尔值数组:
In [49]: arr2=np.array([[1,3,5],[2,4,6]])
In [50]: arr2>arr
Out[50]:
array([[False, True, True],
[False, False, False]])
一维数组比较简单,类似于Python的列表。
In [13]: arr=np.arange(10)
In [14]: arr
Out[14]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
In [15]: arr[5]
Out[15]: 5
In [16]: arr[5:8]
Out[16]: array([5, 6, 7])
In [17]: arr[5:8]=6
In [18]: arr
Out[18]: array([0, 1, 2, 3, 4, 6, 6, 6, 8, 9])
数组的切片是原数组的视图 ,任何对于视图的修改都会反应到原数组上。
如果仅想得到数组切片的拷贝而不是一份视图的话,需用利用copy方法复制这个数组,例:arr[5:8].copy()
在二维数组中,每一个索引值对应的元素不再是一个值,而是一个一维数组。
In [20]: arr2d=np.array([[1,2,3],[4,5,6],[7,8,9]])
In [21]: arr2d[1]
Out[21]: array([4, 5, 6])
In [22]: arr2d[1,1]
Out[22]: 5
在多维数组中,省略后续索引值,返回的对象是降低一个维度的数组。
In [23]: arr3d=np.array([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]])
In [24]: arr3d
Out[24]:
array([[[ 1, 2, 3],
[ 4, 5, 6]],
[[ 7, 8, 9],
[10, 11, 12]]])
In [25]: arr3d[0] #返回2*3的二维数组
Out[25]:
array([[1, 2, 3],
[4, 5, 6]])
In [26]: arr3d[0,1] #返回的是一个一维数组
Out[26]: array([4, 5, 6])
以上的数组子集,都是原数组的视图,修改其值会改变原数组的值。
In [27]: arr3d[0,1]=3
In [28]: arr3d
Out[28]:
array([[[ 1, 2, 3],
[ 3, 3, 3]],
[[ 7, 8, 9],
[10, 11, 12]]])
与Python的列表索引语法相似,对于二维数组来说,第一个值切的是行,第二个值切的是列。
图1 二维数组的切片
我们建立两个数组,一个为保存着字符串类型的数组,一个为保存着浮点数的数组。
In [29]: names=np.array(['Zhang Jike','Xu Xin','Ma Long','Zhang Jike','Ma Long','Xu Xin','Xu Xin'])
In [31]: data=np.random.randn(7,4)
In [33]: names
Out[33]:
array(['Zhang Jike', 'Xu Xin', 'Ma Long', 'Zhang Jike', 'Ma Long',
'Xu Xin', 'Xu Xin'], dtype=')
In [34]: data
Out[34]:
array([[-0.42355046, -0.15376933, -0.42192499, -0.72783259],
[-0.55979452, 1.73036132, -0.76297639, -1.12233218],
[ 0.83649262, -0.47719457, -0.61221679, -0.34995665],
[-0.39913564, -1.66393256, 0.58009781, -0.63479108],
[-1.5643174 , -2.41139907, -1.45880713, 0.86300125],
[ 0.25906839, 0.5642445 , 2.21097353, -0.82552486],
[-0.41486512, -2.44196251, 0.65707701, 0.10321156]])
假设每个人名都和data数组中的一行相对应,我们想要选择所有’Zhang Jike’对应的行,可以在对data数组调用索引时,将索引内容设置为names数组的一个比较操作(该操作会生成一个布尔值数组)。
In [35]: names == 'Zhang Jike'
Out[35]: array([ True, False, False, True, False, False, False])
In [36]: data[names == 'Zhang Jike']
Out[36]:
array([[-0.42355046, -0.15376933, -0.42192499, -0.72783259],
[-0.39913564, -1.66393256, 0.58009781, -0.63479108]])
布尔值数组的长度必须与数组轴长度一致,如果长度不一,不会报错,在使用布尔值索引时一定要对照好两者的数量关系。
取反:
In [37]: names != 'Zhang Jike'
Out[37]: array([False, True, True, False, True, True, True])
In [39]: data[names != 'Zhang Jike']
Out[39]:
array([[-0.55979452, 1.73036132, -0.76297639, -1.12233218],
[ 0.83649262, -0.47719457, -0.61221679, -0.34995665],
[-1.5643174 , -2.41139907, -1.45880713, 0.86300125],
[ 0.25906839, 0.5642445 , 2.21097353, -0.82552486],
[-0.41486512, -2.44196251, 0.65707701, 0.10321156]])
In [40]: data[~(names == 'Zhang Jike')]
Out[40]:
array([[-0.55979452, 1.73036132, -0.76297639, -1.12233218],
[ 0.83649262, -0.47719457, -0.61221679, -0.34995665],
[-1.5643174 , -2.41139907, -1.45880713, 0.86300125],
[ 0.25906839, 0.5642445 , 2.21097353, -0.82552486],
[-0.41486512, -2.44196251, 0.65707701, 0.10321156]])
选择两个名字作为布尔值索引,需要使用布尔算术运算符,&和|:
In [41]: data[(names=='Zhang Jike')|(names=='Xu Xin')]
Out[41]:
array([[-0.42355046, -0.15376933, -0.42192499, -0.72783259],
[-0.55979452, 1.73036132, -0.76297639, -1.12233218],
[-0.39913564, -1.66393256, 0.58009781, -0.63479108],
[ 0.25906839, 0.5642445 , 2.21097353, -0.82552486],
[-0.41486512, -2.44196251, 0.65707701, 0.10321156]])
In [42]: data[(names=='Zhang Jike')&(names=='Ma Long')]
Out[42]: array([], shape=(0, 4), dtype=float64)
基于常识来设置布尔值数组的值:
In [43]: data[data<0]=0
In [44]: data
Out[44]:
array([[0. , 0. , 0. , 0. ],
[0. , 1.73036132, 0. , 0. ],
[0.83649262, 0. , 0. , 0. ],
[0. , 0. , 0.58009781, 0. ],
[0. , 0. , 0. , 0.86300125],
[0.25906839, 0.5642445 , 2.21097353, 0. ],
[0. , 0. , 0.65707701, 0.10321156]])
神奇索引 是NumPy中的术语,用来描述使用整数数组进行数据索引。
我们创建一个8*4的数组:
In [4]: arr=np.empty((8,4))
In [5]: for i in range(8):
...: arr[i]=i
...:
In [6]: arr
Out[6]:
array([[0., 0., 0., 0.],
[1., 1., 1., 1.],
[2., 2., 2., 2.],
[3., 3., 3., 3.],
[4., 4., 4., 4.],
[5., 5., 5., 5.],
[6., 6., 6., 6.],
[7., 7., 7., 7.]])
为了选出一个符合特定顺序的子集,可以通过传递一个包含指明所需顺序的列表或数组来完成:
In [7]: arr[[4,3,0,6]]
Out[7]:
array([[4., 4., 4., 4.],
[3., 3., 3., 3.],
[0., 0., 0., 0.],
[6., 6., 6., 6.]])
如果使用负的索引,将从尾部开始选择:
In [8]: arr[[-1,-3,-1]]
Out[8]:
array([[7., 7., 7., 7.],
[5., 5., 5., 5.],
[7., 7., 7., 7.]])
传递多个索引数组的情况有所不同,它会根据每个索引元组对应的元素选出一个一维数组:
In [9]: arr=np.arange(32).reshape((8,4))
In [10]: arr
Out[10]:
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15],
[16, 17, 18, 19],
[20, 21, 22, 23],
[24, 25, 26, 27],
[28, 29, 30, 31]])
In [11]: arr[[2,5],[1,0]]
Out[11]: array([ 9, 20])
In [12]: arr=np.arange(32).reshape(2,4,4)
In [13]: arr
Out[13]:
array([[[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15]],
[[16, 17, 18, 19],
[20, 21, 22, 23],
[24, 25, 26, 27],
[28, 29, 30, 31]]])
In [14]: arr[[0,1],[0,2],[3,1]]
Out[14]: array([ 3, 25])
在上述例子中,第一个神奇索引选择二维数组,元素(2,1)、(5,0)被选中,第二个神奇索引选择三维数组,元素(0,0,3)、(1,2,1)被选中。不论神奇索引选择的维度是几维,返回的结果总是一维的。
神奇索引与索引不同,它总是将数据复制到一个新的数组中。