这里主要是对《利用python进行数据分析》的学习,原书的电子版地址为:
https://github.com/iamseancheney/python_for_data_analysis_2nd_chinese_version
不知道这个项目是不是译者或者是什么好心人整理的。
这里主要记录下Numpy的常用内容。
ndarray
ndarray创建
ndarray对象的创建可以通过array函数完成,通过传入序列型对象,就可以据此产生对应的numpy数组。
一维数组
a = np.array(range(10))
print(a, type(a), a.shape, a.ndim)
结果为:
[0 1 2 3 4 5 6 7 8 9] <class 'numpy.ndarray'> (10,) 1
二维数组
二维数组可以通过传入多个嵌套序列进行转换:
a = np.array([range(10), range(10,20)])
print(a, type(a), a.shape, a.ndim)
结果为:
[[ 0 1 2 3 4 5 6 7 8 9]
[10 11 12 13 14 15 16 17 18 19]] <class 'numpy.ndarray'> (2, 10) 2
也可以将一维数组转换为多维数组:
a = np.array(range(10)).reshape(1,10)
print(a, type(a), a.shape, a.ndim)
结果为:
[[0 1 2 3 4 5 6 7 8 9]] <class 'numpy.ndarray'> (1, 10) 2
dtype
保存在ndarray中的数据会有自己的数据类型,也可以指定对应的数据类型,该数据类型以dtype类型对象的形式封装:
a = np.array(range(10)).reshape(1,10)
print(a, a.dtype, type(a.dtype))
结果为:
[[0 1 2 3 4 5 6 7 8 9]] int32 <class 'numpy.dtype'>
这个是默认的推断数据类型,也可以指定数据类型:
a = np.array(range(10), dtype=np.float64).reshape(1,10)
print(a, a.dtype, type(a.dtype))
结果为:
[[0. 1. 2. 3. 4. 5. 6. 7. 8. 9.]] float64 <class 'numpy.dtype'>
numpy中数据类型有:
astype、asarray
- astype:可以将ndarray对象的数据类型转换为其它数据类型
- asarray:可以将序列类型转换为ndarray数组
np.array(range(4), dtype = np.int16).reshape(2,2).astype(np.float32)
结果为:
array([[0., 1.],
[2., 3.]], dtype=float32)
不过有时候转换时会进行数据转换的处理,比如float转int就会截断小数部分,如果不能完成转换,就会报ValueError。
asarray则可以将序列类型转换为ndarray数组:
np.asarray(range(4), dtype = np.float32).reshape(2,2)
结果为:
array([[0., 1.],
[2., 3.]], dtype=float32)
zeros、ones、empty、arange、eye
- zeros:可以创建指定长度或形状的全0或全1数组
- ones:可以创建指定长度或形状的全0或全1数组
- empty:可以创建一个没有任何具体值的数组,即未初始化的垃圾值
- arange:类似内置的range函数
- eye:单位矩阵
a = np.zeros([2,2])
b = np.ones([2,2])
c = np.empty([2,2])
d = np.arange(4)
e = np.eye(2)
print(a,b,c,d,e,sep="\n\n")
结果为:
[[0. 0.]
[0. 0.]]
[[1. 1.]
[1. 1.]]
[[1. 1.]
[1. 1.]]
[0 1 2 3]
[[1. 0.]
[0. 1.]]
即zeros、ones、empty需要传入的参数为shape(表示数组维度的元组类型即可)。一些常见的数组生成函数有:
numpy数组运算
算术运算
大小相等的数组之间的任何算术运算都会将运算应用到对应元素:
a = np.array(range(9)).reshape(3,3)
print(a, a + a, a*a, sep="\n\n")
结果为:
[[0 1 2]
[3 4 5]
[6 7 8]]
[[ 0 2 4]
[ 6 8 10]
[12 14 16]]
[[ 0 1 4]
[ 9 16 25]
[36 49 64]]
数组与标量的算术运算会将标量值传播到各个元素:
a = np.array(range(1,10)).reshape(3,3)
print(a, a * 2, 1 / a, sep="\n\n")
结果为:
[[1 2 3]
[4 5 6]
[7 8 9]]
[[ 2 4 6]
[ 8 10 12]
[14 16 18]]
[[1. 0.5 0.33333333]
[0.25 0.2 0.16666667]
[0.14285714 0.125 0.11111111]]
布尔运算
大小相同的数组之间的比较会生成布尔值数组:
a = np.array(range(1,10)).reshape(3,3)
b = np.array(range(9,0,-1)).reshape(3,3)
print(a, b, a>b, sep = "\n\n")
结果为:
[[1 2 3]
[4 5 6]
[7 8 9]]
[[9 8 7]
[6 5 4]
[3 2 1]]
[[False False False]
[False False True]
[ True True True]]
数组与标量的布尔运算会将标量值传播到各个元素:
a = np.array(range(1,10)).reshape(3,3)
print(a, a>5, sep = "\n\n")
结果为:
[[1 2 3]
[4 5 6]
[7 8 9]]
[[False False False]
[False False True]
[ True True True]]
广播机制
不同大小的数组之间的运算叫做广播。
a = np.array(range(1,10)).reshape(3,3)
b = np.array(range(3,6))
print(a, b, a + b, a/b, a>b, sep = "\n\n")
结果为:
[[1 2 3]
[4 5 6]
[7 8 9]]
[3 4 5]
[[ 4 6 8]
[ 7 9 11]
[10 12 14]]
[[0.33333333 0.5 0.6 ]
[1.33333333 1.25 1.2 ]
[2.33333333 2. 1.8 ]]
[[False False False]
[ True True True]
[ True True True]]
广播机制的详细说明可以参考:Python 中 NumPy 的广播_numpy中的广播-CSDN博客
numpy索引和切片
在二维数组中,轴0表示行方向,即垂直方向,1表示列方向,即水平方向。
一维数组的索引和切片和列表等的方式是一样的,这里主要看二维数组。
基本索引和切片索引
a = np.array(range(1,10)).reshape(3,3)
print(a,a[:],a[1],a[:,1],a[1,1],a[0:2,0:2],sep="\n\n")
结果为:
[[1 2 3]
[4 5 6]
[7 8 9]]
[[1 2 3]
[4 5 6]
[7 8 9]]
[4 5 6]
[2 5 8]
5
[[1 2]
[4 5]]
从上面可以看出:
- 二维数组是有两个维度的,因此需要从两个方向上进行索引
- 同时也支持切片的形式
- 单独一个冒号表示所有元素
- 单独一个索引表示按行索引,按列索引需要使用冒号:固定行
同样写成下面的形式也是等效的:
a = np.array(range(1,10)).reshape(3,3)
print(a,a[:][:],a[1][:],a[:][:,1],a[1][1],a[0:2][:,0:2],sep="\n\n")
这种写法只是按行先获得了原数组的子集,然后再在该子集中获得对应的子集,没有上一种形式好用。
同时切片获取的是原数组的视图,因此如果是通过切片进行赋值修改,会直接反映到原数组上:
a = np.array(range(1,10)).reshape(3,3)
a[0:2,0:2] = 10
print(a,a[:],a[1,1],a[0:2,0:2],sep="\n\n")
结果为:
[[10 10 3]
[10 10 6]
[ 7 8 9]]
[[10 10 3]
[10 10 6]
[ 7 8 9]]
10
[[10 10]
[10 10]]
这样的结果也会发生在新建变量的赋值修改中:
a = np.array(range(1,10)).reshape(3,3)
b = a[0:2,0:2]
b[1,1] = 10
print(a,b,sep="\n\n")
结果为:
[[ 1 2 3]
[ 4 10 6]
[ 7 8 9]]
[[ 1 2]
[ 4 10]]
如果要获取副本的话,应该使用copy:
a = np.array(range(1,10)).reshape(3,3)
b = a.copy()
b[0:2,0:2]=10
print(a,b,sep="\n\n")
结果为:
[[1 2 3]
[4 5 6]
[7 8 9]]
[[10 10 3]
[10 10 6]
[ 7 8 9]]
布尔型索引
将布尔值变量与数组结合起来也可以对原数组进行索引:
a = np.array(range(1,10)).reshape(3,3)
a[a>5] = 10
print(a[a>5],a,sep="\n\n")
结果为:
[10 10 10 10]
[[ 1 2 3]
[ 4 5 10]
[10 10 10]]
或者是直接传入布尔型变量:
a = np.array(range(1,10)).reshape(3,3)
b = [True, True, False]
print(a[b,:],sep="\n\n")
结果为:
[[1 2 3]
[4 5 6]]
花式索引
在numpy,通过使用整数数组来进行索引称为花式索引:
a = np.array(range(1,26)).reshape(5,5)
print(a,a[[2,4,3]],sep="\n\n")
结果为:
[[ 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]]
[[11 12 13 14 15]
[21 22 23 24 25]
[16 17 18 19 20]]
即使用整数数组进行索引,会按照行索引的形式重新组织数据。
也可以按照坐标的形式选择:
a = np.array(range(1,26)).reshape(5,5)
print(a,a[[2,4,1,3],[4,2,1,3]],sep="\n\n")
结果为:
[[ 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]]
[15 23 7 19]
同时负数索引表示逆序:
a = np.array(range(1,26)).reshape(5,5)
print(a,a[[-2,-4,-3]],sep="\n\n")
结果为:
[[ 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]]
[[16 17 18 19 20]
[ 6 7 8 9 10]
[11 12 13 14 15]]
也可以利用该索引方式调整各列的位置:
a = np.array(range(1,26)).reshape(5,5)
print(a,a[:,[-5,-2,-4,-3,-1]],sep="\n\n")
结果为:
[[ 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]]
[[ 1 4 2 3 5]
[ 6 9 7 8 10]
[11 14 12 13 15]
[16 19 17 18 20]
[21 24 22 23 25]]
转置
在线性代数中,经常会用到转置的概念,在numpy中,转置可以通过T属性、transpose方法或者swapaxes方法实现:
a = np.array(range(1,10)).reshape(3,3)
print(a,a.T,a.transpose(1,0),a.swapaxes(0,1),sep="\n\n")
结果为:
[[1 2 3]
[4 5 6]
[7 8 9]]
[[1 4 7]
[2 5 8]
[3 6 9]]
[[1 4 7]
[2 5 8]
[3 6 9]]
[[1 4 7]
[2 5 8]
[3 6 9]]
transpose和swapaxes区别在于transpose可以调整多个轴的顺序,而swapaxes只能交换两根轴的顺序。
通用函数
通用函数(即ufunc)是一种对ndarray中的数据执行元素级运算的函数。
一元函数
常见的一元函数有:
这里看下modf,该函数会以数组的形式分别返回原数组的整数部分和小数部分:
a = np.random.randn(9).reshape(3,3) * 5
b,c = np.modf(a)
print(a,b,c,sep="\n\n")
结果为:
[[ 8.91763085 1.40326523 1.30172651]
[-5.6311037 -5.72307123 5.8722108 ]
[-4.67259404 0.5326401 -3.8980413 ]]
[[ 0.91763085 0.40326523 0.30172651]
[-0.6311037 -0.72307123 0.8722108 ]
[-0.67259404 0.5326401 -0.8980413 ]]
[[ 8. 1. 1.]
[-5. -5. 5.]
[-4. 0. -3.]]
二元函数
常见的二元函数有:
这里看下maximum的元素级比较是什么意思:
a = np.array(range(10))
b = np.array(range(9,-1,-1))
np.maximum(a,b)
结果为:
array([9, 8, 7, 6, 5, 5, 6, 7, 8, 9])
即会在两个参数之间进行逐元素比较,然后将满足逻辑的值重新构成新的数组。
三元函数
需要注意的三元函数只有一个,就是where:
a = np.array(range(10))
b = np.array(range(9,-1,-1))
np.where(a>=4,a,b)
结果为:
array([9, 8, 7, 6, 4, 5, 6, 7, 8, 9])
该函数会按照第一个参数的逻辑计算分别从第二个参数和第三个参数中选择对应元素,然后填充到和第一个参数同等规模的数组中。
数学和统计方法
常见的统计方法有:
这些方法的默认方向也是行方向,即垂直方向,不过可以通过axis参数进行更改统计方向。
用于布尔型数组的方法
- sum:用于对布尔型数组中的True值计数
- any:用于判断布尔型数组中是否存在True
- all:用于判断数组是否全部为True
a = np.random.randn(9).reshape(3,3) * 5
print(a,(a>5).sum(0),(a>5).any(0),(a>5).all(0),sep="\n\n")
如果需要计算某个轴向上的结果,需要指明是哪个轴。
排序
也是通过sort方法进行排序,不过需要注意的是,np.sort返回的是排序数组的副本,不会影响原数组,而通过数组调用sort方法则是直接对原数组进行排序:
a = np.random.randn(9).reshape(3,3) * 5
print(a)
print("*************************")
b = np.sort(a)
print(a,b,sep="\n\n")
print("*************************")
c = a.sort()
print(a,c,sep="\n\n")
结果为:
[[ 0.14847016 7.20760888 0.20309457]
[-5.88711909 -7.05237145 3.13436338]
[-5.43721813 -0.56226514 -1.19689088]]
*************************
[[ 0.14847016 7.20760888 0.20309457]
[-5.88711909 -7.05237145 3.13436338]
[-5.43721813 -0.56226514 -1.19689088]]
[[ 0.14847016 0.20309457 7.20760888]
[-7.05237145 -5.88711909 3.13436338]
[-5.43721813 -1.19689088 -0.56226514]]
*************************
[[ 0.14847016 0.20309457 7.20760888]
[-7.05237145 -5.88711909 3.13436338]
[-5.43721813 -1.19689088 -0.56226514]]
None
同样如果需要计算某个轴向上的结果,需要指明是哪个轴。
集合逻辑
这里提到的集合运算是针对一维数组的,主要有:
总体看来,和集合的运算差不多。
数据保存和读取
np.save和np.load是读写磁盘数组数据的两个主要函数。默认情况下,数组是以未压缩的原始二进制格式保存在扩展名为.npy的文件中的。使用方式为:
a = np.array(range(9)).reshape(3,3)
np.save("a",a)
# other actions
np.load("a.npy")
np.savez可以将多个数组保存到一个未压缩文件中,将数组以关键字参数的形式传入即可:
a = np.array(range(9)).reshape(3,3)
np.savez("a",var1 = a, var2 = a ** 2)
# other actions
a = np.load("a.npz")
print(a["var1"], a["var2"])
线性代数
线性代数的操作在数据分析中并不太常用,在一些机器学习的场景中可能会用到,主要会涉及到的的方法有:
另外上边的有些函数可能需要导入np.linalg才能够使用。
伪随机数
numpy中的random模块能够生成伪随机数,并且还能够生成多种概率分布的样本值的函数。常用的函数有: