Numpy介绍
NumPy(Numerical Python 的简称)提供了高效存储和操作密集数据缓存的接口,可以理解是一个数组,与python列表相似。Numpy的官网:https://numpy.org/
1.Numpy数据类型
1.1 基础类型
首先,需要先聊聊Python的基础类型,与java不一致的是,Python的数据变量赋值是很随意的,对于一个变量我可以给其赋值字符串,同时也可以为其赋值整型,从这一点发现Python 变量不仅是它们的值,还包括了关于值的类型的一些额外信息。
x = "jp Morgan"
x = 6
x = ["GE",16,4.23,True]
python的整型并不是单纯的整型,python是由C实现,下边是一个long的定义:
struct _longobject {
long ob_refcnt;
PyTypeObject *ob_type;
size_t ob_size;
long ob_digit[1];
};
除了实际存储数值的ob_digit,还有很多其他信息,这意味着与 C 语言这样的编译语言中的整型相比,在 Python 中存储一个整型会有一些额外开销。
C 语言整型本质上是对应某个内存位置的标签,里面存储的字节会编码成整型。而 Python 的整型其实是一个指针,指向包含这个 Python 对象所有信息的某个内存位置。
1.2 列表和Numpy
通过之前的基本理解,对于Python的列表,列表的每一项都会包含各自的类型信息、引用计数和其他信息,这样效率很低。动态类型的列表和固定类型的(NumPy 式)数组间的区别如下:
每个列表元素是一个包含数据和类型信息的完整结构体,而且列表可以用任意类型的数据填充。固定类型的 NumPy 式数组缺乏这种灵活性,但是能更有效地存储和操作数据。
1.3 固定类型数组
Python也提供了固定类型数组的解决方案,可以使用array模块下的方法创建,array.array有2个参数,第一个是数组类型,后边的数组必须是第一个参数指定的类型。
import array
y = array.array('c',list("ford"))
y[0]
更实用的是 NumPy 包中的 ndarray 对象。Python 的数组对象提供了数组型数据的有效存储,而 NumPy 为该数据加上了高效的操作。下面介绍常见的numpy数组的创建方式:
不同于 Python 列表,NumPy 要求数组必须包含同一类型的数据。
1.可以使用np.array()来创建numpy数组,第一个参数是一个列表,第二个参数指定数组类型。(不适合大数据量)
2.可以使用np.zeros()来创建全0数组。
3.可以使用np.ones()来创建全1数组。
4.可以使用np.full()来创建同值数组。
5.np.arange(x1,x2,x3) 创建一个序列,从x1~x2(不包含x2),步长是x3。
6.np.linspace(x1,x2,x3) 创建一个包含x3个元素的序列,均匀分布在x1~x2。
7.np.random.random(x1)生成在0~1均匀分布的随机数组成的数组。该方法只有1个参。
8.np.random.normal(x1,x2,x3)均值为x1、方差为x2的正态分布的随机数数组,数组shape是x3的数组。
9.np.random.randint(x1,x2,x3)创建在[x1, x2)区间的随机的shape为x3的整型数组。
10.np.eye(x1)创建一个x1 * x1维的单位阵。
11.np.empty(x1)创建一个由x1个整型数组成的未初始化的数组,数组的值是内存空间中的任意值。
y1 = np.array([range(i,i+5) for i in [2, 7, 9]],dtype='float')
y2 = np.zeros((4,3),dtype=int)
y3 = np.ones((2,5),dtype=float)
y4 = np.full([3,5],1986,dtype=float)
y5 = np.arange(0,21,3)
y6 = np.linspace(0,10,5)
y7 = np.random.random((3,6))
y8 = np.random.normal(0,1,(2,3))
y9 = np.random.randint(0,10,(4,2))
y10 = np.eye(6)
y11 = np.empty((4,3))
Numpy支持的数据类型可以参考官网:https://numpy.org/doc/1.14/user/basics.types.html
2.Numpy基础
- Numpy的属性:
Numpy的常用属性包括:nidm(数组的维度)、shape(数组每个维度的大小)、size(数组的总大小)、dtype(数组的数据类型)、itemsize(数组元素字节大小)、nbytes(数组总字节大小)。 - Numpy的数据访问:
访问数组的某个元素和普通数组完全一致,a[i] , 多维数组采用a[i,j,k]指定不同维度。 - Numpy数组切片:
数组切片方法:x[start:stop:step],默认值:默认值 start=0、stop= 维度的大小(size of dimension)和 step=1 。 切片的结果是包含start,不包含stop元素。
关于数组切片有一点很重要也非常有用,那就是数组切片返回的是数组数据的视图,而不是数值数据的副本。这一点也是 NumPy 数组切片和 Python 列表切片的不同之处。也就是如果在切片时,改变了numpy的值,原数组也会改变。
如果不想改变原数组,可以使用copy()方法创建一个副本。 - 数据变形:
数组变形最灵活的实现方式是通过 reshape() 函数来实现。
另外一个常见的变形模式是将一个一维数组转变为二维的行或列的矩阵。你也可以通过 reshape 方法来实现,或者更简单地在一个切片操作中利用 newaxis 关键字。 - 数组拼接和分裂:
np.concatenate可以拼接一维和多维数组。沿着固定维度处理数组时,使用 np.vstack(垂直栈)和np.hstack(水平栈)函数会更简洁。
分裂可以通过 np.split、np.hsplit和 np.vsplit 函数来实现。可以向以上函数传递一个索引列表作为参数,索引列表记录的是分裂点位置(这个是从1开始的,1表是第1一个元素)。N 分裂点会得到 N + 1 个子数组。
# numpy数组属性和访问
x1 = np.random.randint(0,10,size=(3,4,5))
print("x1的ndim属性:{0}; x1的shape属性:{1};".format(x1.ndim,x1.shape))
print("x1的size属性:{0}; x1的dtype属性:{1};".format(x1.size,x1.dtype))
print("x1的itemsize属性:{0}; x1的nbytes属性{1}".format(x1.itemsize,x1.nbytes))
x1[0,2,1] #数组的访问方式
# numpy数组的切片
x2 = np.arange(0,15)
x3 = np.array([np.arange(i,i+3) for i in [3,6,9,1]])
print("切片包含左,不包含右:{0}".format(x2[4:6]))
x2[5::-1] #步长为负表示逆序,从第5个元素开始逆序展示
x2[::-1]
x3
x3[1,:] # 拿到第2行
x3[:,2] # 拿到第3列
x3[0,2] = 1986 #切片改变了子numpy的值,原数组也会改变。
x4 = x3[:,:].copy()
x4[0,2] = 1989 #改变的是副本copy的值,原numpy数组x3不变
x4
# numpy数组变形
x6 = np.arange(0,9)
x5 = x6.reshape(3,3) #将一维变为3*3维数组。
x6[:,np.newaxis] # 转变为二维列向量
x6[np.newaxis,:] # 转变为二维行向量
#数组拼接和分裂
y1 = np.array([1,2,3])
y2 = np.array([6,8,9])
np.concatenate([y1,y2])
y3 = np.array([[1,3,5],[4,5,2]])
y4 = np.array([[99],[98]])
np.concatenate([y3,y3]) # 默认按行来拼接
np.concatenate([y3,y3],axis = 1) #按列来拼接
np.vstack([y3,y1])
np.hstack([y4,y3])
y5 = np.arange(1,17).reshape([4,4])
y6 = np.arange(1,17)
z1,z2,z3 = np.split(y6,[7,12])
print("z1:{0} ;z2:{1} ;z3:{2}".format(z1,z2,z3))
z3,z4 = np.vsplit(y5,[3])
print("z3:{0} ;z4:{1}".format(z3,z4))
z5,z6 = np.hsplit(y5,[3])
print("z5:{0} ;z6:{1}".format(z5,z6))
3.通用函数
3.1 通用函数介绍和Numpy的通用函数种类
NumPy 数组的计算有时非常快,有时也非常慢。使 NumPy 变快的关键是利用向量化操作。NumPy 为很多类型的操作提供了非常方便的、静态类型的、可编译程序的接口,也被称作向量操作。这种向量方法被用于将循环推送至 NumPy 之下的编译层,这样会取得更快的执行效率。通用函数的主要目的是对 NumPy 数组中的值执行更快的重复操作。
下表列举了常用的通用函数:
运算符 | 对应的通用函数 | 描述 |
---|---|---|
+ | np.add | 加法运算(即 1 + 1 = 2) |
- | np.subtract | 减法运算(即 3 - 2 = 1) |
- | np.negative | 负数运算( 即 -2) |
* | np.multiply | 乘法运算(即 2 * 3 = 6) |
/ | np.divide | 除法运算(即 3 / 2 = 1.5) |
// | np.floor_divide | 地板除法运算(floor division,即 3 // 2 = 1) |
** | np.power | 指数运算(即 2 ** 3 = 8) |
% | np.mod | 模 / 余数( 即 9 % 4 = 1) |
除了上边经常使用的运算符,numpy还提供了绝对值:np.abs() ;三角函数:np.sin(), np.cos() ;指数函数: e^x , np.exp(x) 2^x , exp2(x) 3^x , power(3, x) ;对数函数:ln(x), 以e为底x的对数 log2(x), 以2为底x的对数 log10(x), 以10为底x的对数。
更加专用,也更加晦涩的通用函数优异来源是子模块scipy.special。
x1 = np.arange(1,7)
y1 = 1.0 / x1 #一元通用函数
x2 = np.linspace(0,12,6)
y2 = x2 / x1 #二元通用函数
x1,x2,y2
np.add(x1,3) #使用通用函数替代运算符
3.2 高级通用函数
- 指定输出:
在进行大量运算时,有时候指定一个用于存放运算结果的数组是非常有用的。但是对于较大的数组,通过慎重使用 out 参数将能够有效节约内存。还有,需要注意的是out指定的数组必须事先有定义好(例如全0数组)。 - 聚合:
参照Hadoop的Map/Reduce思想,reduce 方法会对给定的元素和操作重复执行,直至得到单个的结果。可以用任何通用函数的 reduce 方法。如果需要存储每次计算的中间结果,可以使用 accumulate。 - 外积:
任何通用函数都可以用 outer 方法获得两个不同输入数组所有元素对的函数运算结果。
# 输出结果到数组变量
x3 = np.arange(5)
z3 = np.zeros(5)
p3 = np.ones(10)
np.multiply(x3,4,out=z3) #z3需要事先初始化一下,例如全0数组
np.subtract(x3,6,out = p3[::2]) # 每隔2进行一次替换
# reduce方法
x4 = np.arange(6)
np.add.reduce(x4) #reduce 方法会对给定的元素和操作重复执行
np.add.accumulate(x4) #需要存储每次计算的中间结果
#外积
x5 = np.arange(1,6)
np.add.outer(x5,x5) # 对应元素[2,2] 表示第3个元素和第3个元素的和,即6
4.聚合
在python中,存在sum()、min()、max()等聚合函数,与之相对地,在numpy中存在np.sum()、np.min()、np.max()等函数也功能类似,但是执行效率会更高。默认情况下,每一个 NumPy 聚合函数将会返回对整个数组的聚合。
聚合函数还有一个参数,用于指定沿着哪个轴的方向进行聚合。axis=0 :表示按列执行。axis 关键字指定的是数组将会被折叠的维度,而不是将要返回的维度。
下边列举了一些常用的numpy的聚合函数:
函数名称 | NaN安全版本 | 描述 |
---|---|---|
np.sum | np.nansum | 计算元素的和 |
np.prod | np.nanprod | 计算元素的积 |
np.mean | np.nanmean | 计算元素的平均值 |
np.std | np.nanstd | 计算元素的标准差 |
np.var | np.nanvar | 计算元素的方差 |
np.min | np.nanmin | 找出最小值 |
np.max | np.nanmax | 找出最大值 |
np.argmin | np.nanargmin | 找出最小值的索引 |
np.argmax | np.nanargmax | 找出最大值的索引 |
np.median | np.nanmedian | 计算元素的中位数 |
np.percentile | np.nanpercentile | 计算基于元素排序的统计值 |
np.any | N/A | 验证任何一个元素是否为真 |
np.all | N/A | 验证所有元素是否为真 |
x1 = np.random.random((4,3))
np.sum(x1) #默认所有元素的和
np.sum(x1,axis=0) #按列聚合
np.sum(x1,axis=1) #按行聚合
5.广播计算
首先,说明下广播计算的定义,简单讲来广播计算就是不同维度的数组的计算。也就是为用于不同大小数组的二进制通用函数(加、减、乘等)的一组规则。
下边是3个极为简单的广播示例:
d1 = np.arange(3)
d2 = np.ones((3,3))
d3 = np.ones((3,1))
d1+5
d1+d2
d1+d3
M = np.ones((2, 3))
a = np.arange(3)
M+a
a1 = np.arange(3).reshape((3, 1))
a2 = np.arange(3)
a1 + a2
a3 = np.ones((3, 2))
a4 = np.arange(3)
# a3 + a4 #could not be broadcast together with shapes (3,2) (3,)
np.newaxis
广播的原则是,看相加(可以其他任何运算)的两个数组的每个维度的最大值,例如(3,1) 和(1,3) ,很明显是两个维度的最大值都是3,那么最终的结果就是2个维度最大值的组合,也就是(3,3)。
6.比较、掩码和布尔逻辑
当你希望统计数组中有多少值大于某一个给定值,或者删除所有超出某些门限值的异常点时,布尔掩码可以起到作用。
6.1 和通用函数类似的比较函数
除了第3部分介绍的通用函数,NumPy 还实现了如 <(小于)和 >(大于)的逐元素比较的通用函数,这些函数在计算后,会得到一个布尔数组。常见的比较操作符如下:
运算符 | 对应的通用函数 |
---|---|
== | np.equal |
!= | np.not_equal |
< | np.less |
<= | np.less_equal |
> | np.greater |
>= | np.greater_equal |
x1 = np.arange(12).reshape(4,3)
x1>6 # 比较操作符得到布尔数组。
6.2 布尔数组的操作:
给定一个布尔数组,你可以实现很多有用的操作。
- 统计记录数
(1)np.count_nonzero:如果需要统计布尔数组中 True 记录的个数,可以使用np.count_nonzero。
(2)np.sum():另外一种实现方式是利用np.sum,False 会被解释成 0,True 会被解释成1。
sum() 的好处是,和其他 NumPy 聚合函数一样,这个求和也可以沿着行或列进行。
(3)np.any():数组中是否有任何一个元素符合布尔条件,也可以指定某个维度进行。
(4)np.all():数组中是否所有元素都符合布尔条件,也可以指定某个维度进行。 - 布尔运算符
这部分很简单,实际就是与、或、非等逻辑条件。通过&、|、^ 和 ~ 来实现。
运算符 | 对应通用函数
-|-
& | np.bitwise_and
| | np.bitwise_or
^ | np.bitwise_xor
~ | np.bitwise_not
rainfall = pd.read_csv('data/Seattle2014.csv')['PRCP'].values #直接获取数组
inches = rainfall / 254
inches.shape
#
x1 = np.arange(12).reshape(4,3)
x1>6 # 比较操作符得到布尔数组。
np.count_nonzero(x1>6) #布尔数组x1>6中True的个数是5个
np.sum(x1>6) #替代np.count_nonzero的一种方式
np.sum(x1>6,axis=0) #返回每一列大于6的元素的个数
np.sum(x1>6,axis=1) #返回每一行大于6的元素的个数
np.any(x1>6)
np.all(x1>6)
np.any(x1>6,axis=0) #按列维度,是否每一列都有大于6的元素
print("Number days without rain: ", np.sum(inches == 0))
print("Number days with rain: ", np.sum(inches != 0))
print("Days with more than 0.5 inches:", np.sum(inches > 0.5))
print("Rainy days with < 0.1 inches :", np.sum((inches > 0) & (inches < 0.2)))
6.3 布尔数组做掩码:
一种更强大的模式是使用布尔数组作为掩码,通过该掩码选择数据的子数据集。为了将这些值从数组中选出,可以进行简单的索引,即掩码操作。掩码操作在显示的时候是以中括号来访问值的。
and 和 or 对整个对象执行单个布尔运算,而 & 和| 对一个对象的内容(单个比特或字节)执行多个布尔运算。对于NumPy 布尔数组,后者是常用的操作。
y1 = np.array([[5, 0, 3, 3],[7, 9, 3, 5],[2, 4, 7, 6]])
y1[y1>5] #所有的返回值是掩码数组对应位置为 True 的值
np.arange(365) - 172 < 90
A = np.array([1, 0, 1, 0, 1, 0])
B = np.array([1, 1, 1, 0, 1, 1])
A | B #每个元素来进行或操作
7. 花哨索引
简单的索引值(如 arr[0])、切片(如 arr[:5])和布尔掩码(如 arr[arr > 0])获得并修改部分数组。花哨的索引的索引参数传递的是索引数组。
利用花哨的索引,结果的形状与索引数组的形状一致,而不是与被索引数组的形状一致。
同样地,花哨的索引也对多个维度适用。和标准的索引方式一样,第一个索引指的是行,第二个索引指的是列。
花哨的索引可以和其他索引方案结合起来形成更强大的索引操作。
正如花哨的索引可以被用于获取部分数组,它也可以被用于修改部分数组。操作中重复的索引会导致一些出乎意料的结果产生。at() 函数在这里对给定的操作、给定的索引(这里是 i)以及给定的值(这里是 1)执行的是就地操作
t1 = np.array([82, 74, 79, 85, 71, 87, 90, 97, 52, 4, 84, 62, 74, 50, 73])
t1[[3,8,9]]
i1 = np.array([[2,5],[9,1]])
t1[i1]
t2 = np.arange(12).reshape(3,4)
print("t2[[0,2],[1,2]]:{0}".format(t2[[0,2],[1,2]]))
print("t2[2, [2, 0, 1]]:{0}".format(t2[2, [2, 0, 1]]))
print("t2[1:, [2, 0, 1]]:{0}".format(t2[1:, [2, 0, 1]]))
t2
u1 = np.arange(12)
ui1 = np.array([2,1,4,6])
u1[ui1] = -99
u2 = np.zeros(12)
u2[[4,4]] = [12,4] #对于重复的索引,实际上是二次赋值。
ui2 = np.array([1,3,3,3,4,4,9,9,9,9])
u2[ui2] += 1 #并没有完成累加的预期效果
u3 = np.zeros(12)
np.add.at(u3,ui2,1) #如果要完成累加,可以使用np.add.at来实现。
u3
8. 数组排序
1.如果想在不修改原始输入数组的基础上返回一个排好序的数组,可以使用np.sort。
2.如果希望用排好序的数组替代原始数组,可以使用数组的 sort 方法。
3.另外一个相关的函数是argsort,该函数返回的是原始数组排好序的索引值。
4.NumPy排序算法的一个有用的功能是通过 axis 参数,沿着多维数组的行或列进行排序。
5.有时候不希望对整个数组进行排序,仅仅希望找到数组中第K小的值,NumPy的np.partition函数提供了该功能。例如对于np.partition(x, 3)来说,结果数组中前三个值是数组中最小的三个值,剩下的位置是原始数组剩下的值。在这两个分隔区间中,元素都是任意排列的。
6.同样地,np.partition也支持按照维度执行,同时也支持np.argpartition函数。
x1 = np.array([189, 148, 82, 140, 37, 142, 21, 174, 112, 53, 152, 163, 83, 71, 20])
x3 = np.array([189, 148, 82, 140, 37, 142, 21, 174, 112, 53, 152, 163, 83, 71, 20])
x2 = np.sort(x1) #该排序操作并不会修改x1的值
x3.sort() #该操作会修改x3的值
x4 = np.argsort(x1) #排序后的序号组成的数组
x1[x4] #利用花哨索引,返回了排序后的数组
x5 = np.array([[15, 17, 13],
[ 9, 6, 7],
[18, 10, 13],
[ 2, 3, 2]])
np.sort(x5,axis=0) #多维数据按列排序
np.sort(x5,axis=1) #多维数据按行排序
np.partition(x1,5)
np.partition(x5,2,axis=0)
np.partition(x5,1,axis=1)
np.argpartition(x1,5)