参考链接: Python中的numpy.expm1
文章目录
1.理解Python中的数据类型1.1 Python整型不仅仅是一个整型1.2 Python列表不仅仅是一个列表1.3 从Python列表创建数组1.4 从头创建数组1.5 NumPy标准数据类型
2.NumPy数组基础2.1 NumPy数组的属性2.2 数组索引:获取单个元素2.3 数组切片:获取子数组多维子数组获取数组的行和列非副本视图的子数组创建数组的副本
2.4 数组的变形2.5 数组拼接和分裂数组的拼接数组的分裂
3.NumPy数组的计算:通用函数3.1 缓慢的循环3.2 通用函数介绍3.3 探索NumPy的通用函数数组的运算绝对值三角函数指数和对数专用的通用函数
3.4 高级的通用函数特性指定输出聚合外积
4.聚合:最小值、最大值和其他值4.1 数组值求和4.2 最小值和最大值多维度聚合其他聚合函数
5. 数组的计算:广播5.1 广播的介绍5.2 广播的规则广播示例1广播示例2广播示例3
5.3 广播的实际应用数组的归一化画一个二维函数
6.比较、掩码和布尔逻辑6.1 和通用函数类似的比较操作6.2 操作布尔数组统计记录的个数布尔运算符
6.3 将布尔数组作为掩码
7.花哨的索引7.1 探索花哨的索引7.2 组合索引
数组的排序8.1 NumPy中的快速排序:np.sort和np.argsort沿着行或列排序
8.2 部分排序:分隔
9.结构化数据:NumPy的结构化数组9.1 生成结构化数组9.2 更高级的复合类型9.3 记录数组:结构化数组的扭转
导入numpy并查看版本号
import numpy as np
np.__version__
#'1.18.5'
1.理解Python中的数据类型
静态类型的语言(如 C 或 Java)往往需要每一个变量都明确地声明,而动态类型的语言(例如 Python)可以跳过这个特殊规定。
1.1 Python整型不仅仅是一个整型
标准的 Python 实现是用 C 语言编写的。这意味着每一个 Python 对象都是一个伪 C语言结构体,该结构体不仅包含其值,还有其他信息。例如,当我们在 Python 中定义一个整型, x = 10000 时,x 并不是一个“原生”整型,而是一个指针,指向一个 C 语言的复合结构体,结构体里包含了一些值。查看 Python 3.4 的源代码,可以发现整型(长整型)的定义,如下所示(C 语言的宏经过扩展之后):
struct _longobject {
long ob_refcnt;
PyTypeObject *ob_type;
size_t ob_size;
long ob_digit[1];
};
• ob_refcnt 是一个引用计数,它帮助 Python 默默地处理内存的分配和回收。 • ob_type 将变量的类型编码。 • ob_size 指定接下来的数据成员的大小。 • ob_digit 包含我们希望 Python 变量表示的实际整型值。
区别:
两者的差异在于,C 语言整型本质上是对应某个内存位置的标签,里面存储的字节会编码 成整型。而 Python 的整型其实是一个指针,指向包含这个 Python 对象所有信息的某个内 存位置,其中包括可以转换成整型的字节。由于 Python 的整型结构体里面还包含了大量额 外的信息,所以 Python 可以自由、动态地编码。但是,Python 类型中的这些额外信息也 会成为负担,在多个对象组合的结构体中尤其明显。
1.2 Python列表不仅仅是一个列表
因为 Python 的动态类型特性,甚至可以创建一个异构的列表:
l = [True, "2", 0, 0.123]
[type(i) for i in l]
#[bool, str, int, float]
但是想拥有这种灵活性也是要付出一定代价的:为了获得这些灵活的类型,列表中的每一 项必须包含各自的类型信息、引用计数和其他信息;也就是说,每一项都是一个完整的 Python 对象。来看一个特殊的例子,如果列表中的所有变量都是同一类型的,那么很多信 息都会显得多余——将数据存储在固定类型的数组中应该会更高效。动态类型的列表和固 定类型的(NumPy 式)数组间的区别如图 :
在实现层面,数组基本上包含一个指向连续数据块的指针。另一方面,Python 列表包含一 个指向指针块的指针,这其中的每一个指针对应一个完整的 Python 对象(如前面看到的 Python 整型)。另外,列表的优势是灵活,因为每个列表元素是一个包含数据和类型信息 的完整结构体,而且列表可以用任意类型的数据填充。固定类型的 NumPy 式数组缺乏这 种灵活性,但是能更有效地存储和操作数据。
1.3 从Python列表创建数组
首先,可以用 np.array 从 Python 列表创建数组:
# 整型数组:
np.array([1, 4, 2, 5, 3])
# array([1, 4, 2, 5, 3])
#不同于 Python 列表,NumPy 要求数组必须包含同一类型的数据。如果类型不匹配,NumPy 将会向上转换(如果可行)。这里整型被转换为浮点型:
np.array([3.14, 4, 2, 3])
#array([ 3.14, 4. , 2. , 3. ])
#如果希望明确设置数组的数据类型,可以用 dtype 关键字:
np.array([1, 2, 3, 4], dtype='float32')
#array([ 1., 2., 3., 4.], dtype=float32)
#不同于 Python 列表,NumPy 数组可以被指定为多维的。以下是用列表的列表初始化多维数组的一种方法:
# 嵌套列表构成的多维数组
np.array([range(i, i + 3) for i in [2, 4, 6]])
'''array([[2, 3, 4],
[4, 5, 6],
[6, 7, 8]])'''
1.4 从头创建数组
# 创建一个长度为10的数组,数组的值都是0
np.zeros(10, dtype=int)
Out: array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
# 创建一个3×5的浮点型数组,数组的值都是1
np.ones((3, 5), dtype=float)
Out:array([[ 1., 1., 1., 1., 1.],
[ 1., 1., 1., 1., 1.],
[ 1., 1., 1., 1., 1.]])
# 创建一个3×5的浮点型数组,数组的值都是3.14
np.full((3, 5), 3.14)
Out: array([[ 3.14, 3.14, 3.14, 3.14, 3.14],
[ 3.14, 3.14, 3.14, 3.14, 3.14],
[ 3.14, 3.14, 3.14, 3.14, 3.14]])
# 创建一个3×5的浮点型数组,数组的值是一个线性序列
# 从0开始,到20结束,步长为2
# (它和内置的range()函数类似)
np.arange(0, 20, 2)
Out: array([ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18])
# 创建一个5个元素的数组,这5个数均匀地分配到0~1
np.linspace(0, 1, 5)
Out: array([ 0. , 0.25, 0.5 , 0.75, 1. ])
# 创建一个3×3的、在0~1均匀分布的随机数组成的数组
np.random.random((3, 3))
Out: array([[ 0.99844933, 0.52183819, 0.22421193],
[ 0.08007488, 0.45429293, 0.20941444],
[ 0.14360941, 0.96910973, 0.946117 ]])
# 创建一个3×3的、均值为0、方差为1的正态分布的随机数数组
np.random.normal(0, 1, (3, 3))
Out: array([[ 1.51772646, 0.39614948, -0.10634696],
[ 0.25671348, 0.00732722, 0.37783601],
[ 0.68446945, 0.15926039, -0.70744073]])
# 创建一个3×3的、[0, 10)区间的随机整型数组
np.random.randint(0, 10, (3, 3))
Out: array([[2, 3, 4],
[5, 7, 8],
[0, 5, 0]])
# 创建一个3×3的单位矩阵
np.eye(3)
Out: array([[ 1., 0., 0.],
[ 0., 1., 0.],
[ 0., 0., 1.]])
# 创建一个由3个整型数组成的未初始化的数组
# 数组的值是内存空间中的任意值
np.empty(3)
Out: array([ 1., 1., 1.])
1.5 NumPy标准数据类型
2.NumPy数组基础
数组的属性 确定数组的大小、形状、存储大小、数据类型。 数组的索引 获取和设置数组各个元素的值。 数组的切分 在大的数组中获取或设置更小的子数组。 数组的变形 改变给定数组的形状。 数组的拼接和分裂 将多个数组合并为一个,以及将一个数组分裂成多个。
2.1 NumPy数组的属性
import numpy as np
np.random.seed(0) # 设置随机数种子
x1 = np.random.randint(10, size=6) # 一维数组
x2 = np.random.randint(10, size=(3, 4)) # 二维数组
x3 = np.random.randint(10, size=(3, 4, 5)) # 三维数组
x3
#out:array([
[[8, 1, 5, 9, 8],
[9, 4, 3, 0, 3],
[5, 0, 2, 3, 8],
[1, 3, 3, 3, 7]],
[[0, 1, 9, 9, 0],
[4, 7, 3, 2, 7],
[2, 0, 0, 4, 5],
[5, 6, 8, 4, 1]],
[[4, 9, 8, 1, 1],
[7, 9, 9, 3, 6],
[7, 2, 0, 3, 5],
[9, 4, 4, 6, 4]]
])
每个数组有 nidm(数组的维度)、shape(数组每个维度的大小)、dtype(数组的数据类型)、itemsize(每个数组元素字节大小)、nbytes(数组总字节大小)和size(数组的总大小)属性:
print("x3 ndim: ", x3.ndim)
print("x3 shape:", x3.shape)
print("x3 size: ", x3.size)
print("dtype:", x3.dtype)
print("itemsize:", x3.itemsize, "bytes")
print("nbytes:", x3.nbytes, "bytes")
'''out:x3 ndim: 3
x3 shape: (3, 4, 5)
x3 size: 60
dtype: int64
itemsize: 8 bytes
nbytes: 480 bytes
'''
2.2 数组索引:获取单个元素
1 在一维数组中,你也可以通过中括号指定索引获取第 i 个值(从 0 开始计数) 2 为了获取数组的末尾索引,可以用负值索引 3 在多维数组中,可以用逗号分隔的索引元组获取元素 4 也可以用以上索引方式修改元素值 5 和 Python 列表不同,NumPy 数组是固定类型的。这意味着当你试图将一个浮点值插入一个整型数组时,浮点值会被截短成整型。
2.3 数组切片:获取子数组
NumPy 切片语法和 Python 列表的标准切片语法相同。为了获取数组 x 的一个切片,可以用以下方式:
x[start:stop:step]
如果以上 3 个参数都未指定,那么它们会被分别设置默认值 start=0、stop= 维度的大小(size of dimension)和 step=1。
多维子数组
x2 = array([[12, 5, 2, 4],
[ 7, 6, 8, 8],
[ 1, 6, 7, 7]])
In: x2[:2, :3] # 两行,三列
Out: array([[12, 5, 2],
[ 7, 6, 8]])
In: x2[:3, ::2] # 所有行,每隔一列
Out: array([[12, 2],
[ 7, 8],
[ 1, 7]])
#最后,子数组维度也可以同时被逆序:
In: x2[::-1, ::-1]
Out: array([[ 7, 7, 6, 1],
[ 8, 8, 6, 7],
[ 4, 2, 5, 12]])
获取数组的行和列
In: print(x2[:, 0]) # x2的第一列
[12 7 1]
In: print(x2[0, :]) # x2的第一行
[12 5 2 4]
#在获取行时,出于语法的简介考虑,可以省略空的切片:
In: print(x2[0]) #等于x2[0, :]
[12 5 2 4]
非副本视图的子数组
关于数组切片有一点很重要也非常有用,那就是数组切片返回的是数组数据的视图,而 不是数值数据的副本。这一点也是 NumPy 数组切片和 Python 列表切片的不同之处:在 Python 列表中,切片是值的副本。
In: print(x2)
[[12 5 2 4]
[ 7 6 8 8]
[ 1 6 7 7]]
#从中抽取一个 2×2 的子数组:
In: x2_sub = x2[:2, :2]
print(x2_sub)
[[12 5]
[ 7 6]]
#现在如果修改这个子数组,将会看到原始数组也被修改了!结果如下所示:
In: x2_sub[0, 0] = 99
print(x2_sub)
[[99 5]
[ 7 6]]
In: print(x2)
[[99 5 2 4]
[ 7 6 8 8]
[ 1 6 7 7]]
创建数组的副本
尽管数组视图有一些非常好的特性,但是在有些时候明确地复制数组里的数据或子数组也 是非常有用的。可以很简单地通过 copy() 方法实现:
In: x2_sub_copy = x2[:2, :2].copy()
print(x2_sub_copy)
[[99 5]
[ 7 6]]
#如果修改这个子数组,原始的数组不会被改变:
In: x2_sub_copy[0, 0] = 42
print(x2_sub_copy)
[[42 5]
[ 7 6]]
In: print(x2)
[[99 5 2 4]
[ 7 6 8 8]
[ 1 6 7 7]]
2.4 数组的变形
数组变形最灵活的实现方式是通过 reshape() 函数来实现。 另外一个常见的变形模式是将一个一维数组转变为二维的行或列的矩阵。一个切片操作中利用 newaxis 关键字:
In: x = np.array([1, 2, 3])
# 通过变形获得的行向量
x.reshape((1, 3))
Out: array([[1, 2, 3]])
In: # 通过newaxis获得的行向量
x[np.newaxis, :]
Out: array([[1, 2, 3]])
In: # 通过变形获得的列向量
x.reshape((3, 1))
Out: array([[1],
[2],
[3]])
In: # 通过newaxis获得的列向量
x[:, np.newaxis]
Out: array([[1],
[2],
[3]])
2.5 数组拼接和分裂
以上所有的操作都是针对单一数组的,但有时也需要将多个数组合并为一个,或将一个数组分裂成多个。
数组的拼接
拼接或连接 NumPy 中的两个数组主要由 np.concatenate、np.vstack 和 np.hstack 例程实现。
In: x = np.array([1, 2, 3])
y = np.array([3, 2, 1])
np.concatenate([x, y])
Out[43]: array([1, 2, 3, 3, 2, 1])
#你也可以一次性拼接两个以上数组:
In: z = [99, 99, 99]
print(np.concatenate([x, y, z]))
[ 1 2 3 3 2 1 99 99 99]
#np.concatenate 也可以用于二维数组的拼接:
In: grid = np.array([[1, 2, 3],
[4, 5, 6]])
In: # 沿着第一个轴拼接
np.concatenate([grid, grid])
Out: array([[1, 2, 3],
[4, 5, 6],
[1, 2, 3],
[4, 5, 6]])
In: # 沿着第二个轴拼接(从0开始索引)
np.concatenate([grid, grid], axis=1)
Out: array([[1, 2, 3, 1, 2, 3],
[4, 5, 6, 4, 5, 6]])
#沿着固定维度处理数组时,使用 np.vstack(垂直栈)和 np.hstack(水平栈)函数会更简洁:
In: x = np.array([1, 2, 3])
grid = np.array([[9, 8, 7],
[6, 5, 4]])
# 垂直栈数组
np.vstack([x, grid])
Out: array([[1, 2, 3],
[9, 8, 7],
[6, 5, 4]])
In: # 水平栈数组
y = np.array([[99],
[99]])
np.hstack([grid, y])
Out: array([[ 9, 8, 7, 99],
[ 6, 5, 4, 99]])
数组的分裂
与拼接相反的过程是分裂。分裂可以通过 np.split、np.hsplit 和 np.vsplit 函数来实现。
In: x = [1, 2, 3, 99, 99, 3, 2, 1]
x1, x2, x3 = np.split(x, [3, 5])
print(x1, x2, x3)
[1 2 3] [99 99] [3 2 1]
#值得注意的是,N 分裂点会得到 N + 1 个子数组。相关的 np.hsplit 和 np.vsplit 的用法也类似:
In: grid = np.arange(16).reshape((4, 4))
grid
Out: array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15]])
In: upper, lower = np.vsplit(grid, [2])
print(upper)
print(lower)
[[0 1 2 3]
[4 5 6 7]]
[[ 8 9 10 11]
[12 13 14 15]]
In: left, right = np.hsplit(grid, [2])
print(left)
print(right)
[[ 0 1]
[ 4 5]
[ 8 9]
[12 13]]
[[ 2 3]
[ 6 7]
[10 11]
[14 15]]
3.NumPy数组的计算:通用函数
NumPy 数组的计算有时非常快,有时也非常慢。使 NumPy 变快的关键是利用向量化操作,通常在 NumPy 的通用函数(ufunc)中实现。
3.1 缓慢的循环
Python 的相对缓慢通常出现在很多小操作需要不断重复的时候,比如对数组的每个元素做循环操作时。 测试一个很大量的输入数据运行上述代码的时间,这一操作将非常耗时,并且是超出意料的慢!
3.2 通用函数介绍
NumPy 为很多类型的操作提供了非常方便的、静态类型的、可编译程序的接口,也被称作向量操作。你可以通过简单地对数组执行操作来实现,这里对数组的操作将会被用于数组中的每一个元素。这种向量方法被用于将循环推送至 NumPy 之下的编译层,这样会取得更快的执行效率。
如果计算一个较大数组的运行时间,可以看到它的完成时间比 Python 循环花费的时间更短:
通过通用函数用向量的方式进行计算几乎总比用 Python 循环实现的计算更加有效,尤其是当数组很大时。只要你看到 Python 脚本中有这样的循环,就应该考虑能否用向量方式替换这个循环。
3.3 探索NumPy的通用函数
通用函数有两种存在形式:一元通用函数(unary ufunc)对单个输入操作,二元通用函数(binary ufunc)对两个输入操作。
数组的运算
NumPy 通用函数的使用方式非常自然,因为它用到了 Python 原生的算术运算符,标准的加、减、乘、除都可以使用
In: x = np.arange(4)
print("x =", x)
print("x + 5 =", x + 5)
print("x - 5 =", x - 5)
print("x * 2 =", x * 2)
print("x / 2 =", x / 2)
print("x // 2 =", x // 2) #地板除法运算
x = [0 1 2 3]
x + 5 = [5 6 7 8]
x - 5 = [-5 -4 -3 -2]
x * 2 = [0 2 4 6]
x / 2 = [ 0. 0.5 1. 1.5]
x // 2 = [0 0 1 1]
#还有逻辑非、** 表示的指数运算符和 % 表示的模运算符的一元通用函数:
In: print("-x = ", -x)
print("x ** 2 = ", x ** 2)
print("x % 2 = ", x % 2)
-x = [ 0 -1 -2 -3]
x ** 2 = [0 1 4 9]
x % 2 = [0 1 0 1]
#你可以任意将这些算术运算符组合使用。当然,你得考虑这些运算符的优先级:
In: -(0.5*x + 1) ** 2
Out: array([-1. , -2.25, -4. , -6.25])
#所有这些算术运算符都是 NumPy 内置函数的简单封装器,例如 + 运算符就是一个 add 函数的封装器:
In: np.add(x, 2)
Out: array([2, 3, 4, 5])
NumPy实现的算术运算符
运算符对应的通用函数描述+np.add加法运算(即 1 + 1 = 2)-np.subtract减法运算(即 3 - 2 = 1)*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)
绝对值
In: x = np.array([-2, -1, 0, 1, 2])
abs(x)
Out: array([2, 1, 0, 1, 2])
#对应的 NumPy 通用函数是 np.absolute,该函数也可以用别名 np.abs 来访问:
In[: np.absolute(x)
Out: array([2, 1, 0, 1, 2])
In: np.abs(x)
Out: array([2, 1, 0, 1, 2])
#这个通用函数也可以处理复数。当处理复数时,绝对值返回的是该复数的幅度:
In: x = np.array([3 - 4j, 4 - 3j, 2 + 0j, 0 + 1j])
np.abs(x)
Out: array([ 5., 5., 2., 1.])
三角函数
In: theta = np.linspace(0, np.pi, 3)
#现在可以对这些值进行一些三角函数计算:
In:
print("theta = ", theta)
print("sin(theta) = ", np.sin(theta))
print("cos(theta) = ", np.cos(theta))
print("tan(theta) = ", np.tan(theta))
Out:
theta = [ 0. 1.57079633 3.14159265]
sin(theta) = [ 0.00000000e+00 1.00000000e+00 1.22464680e-16]
cos(theta) = [ 1.00000000e+00 6.12323400e-17 -1.00000000e+00]
tan(theta) = [ 0.00000000e+00 1.63312394e+16 -1.22464680e-16]
#这些值是在机器精度内计算的,所以有些应该是 0 的值并没有精确到 0 。逆三角函数同样可以使用:
In:
x = [-1, 0, 1]
print("x = ", x)
print("arcsin(x) = ", np.arcsin(x))
print("arccos(x) = ", np.arccos(x))
print("arctan(x) = ", np.arctan(x))
x = [-1, 0, 1]
arcsin(x) = [-1.57079633 0. 1.57079633]
arccos(x) = [ 3.14159265 1.57079633 0. ]
arctan(x) = [-0.78539816 0. 0.78539816]
指数和对数
#NumPy 中另一个常用的运算通用函数是指数运算:
In:
x = [1, 2, 3]
print("x =", x)
print("e^x =", np.exp(x))
print("2^x =", np.exp2(x))
print("3^x =", np.power(3, x))
Out:
x = [1, 2, 3]
e^x = [ 2.71828183 7.3890561 20.08553692]
2^x = [ 2. 4. 8.]
3^x = [ 3 9 27]
#指数运算的逆运算,即对数运算也是可用的。最基本的 np.log 给出的是以自然数为底数的对数。如果你希望计算以 2 为底数或者以 10 为底数的对数,可以按照如下示例处理:
In:
x = [1, 2, 4, 10]
print("x =", x)
print("ln(x) =", np.log(x))
print("log2(x) =", np.log2(x))
print("log10(x) =", np.log10(x))
Out:
x = [1, 2, 4, 10]
ln(x) = [ 0. 0.69314718 1.38629436 2.30258509]
log2(x) = [ 0. 1. 2. 3.32192809]
log10(x) = [ 0. 0.30103 0.60205999 1. ]
#还有一些特殊的版本,对于非常小的输入值可以保持较好的精度:
In:
x = [0, 0.001, 0.01, 0.1]
print("exp(x) - 1 =", np.expm1(x))
print("log(1 + x) =", np.log1p(x))
Out:
exp(x) - 1 = [ 0. 0.0010005 0.01005017 0.10517092]
log(1 + x) = [ 0. 0.0009995 0.00995033 0.09531018]
#当 x 的值很小时,以上函数给出的值比 np.log 和 np.exp 的计算更精确。
专用的通用函数
除了以上介绍到的,NumPy 还提供了很多通用函数,包括双曲三角函数、比特位运算、比较运算符、弧度转化为角度的运算、取整和求余运算
还有一个更加专用,也更加晦涩的通用函数优异来源是子模块 scipy.special。如果你希望对你的数据进行一些更晦涩的数学计算,scipy.special 可能包含了你需要的计算函数。这些函数能列一个长长的列表,下面的代码片段展示了一些可能在统计学中用到的函数:
In:
from scipy import special
# Gamma函数(广义阶乘,generalized factorials)和相关函数
x = [1, 5, 10]
print("gamma(x) =", special.gamma(x))
print("ln|gamma(x)| =", special.gammaln(x))
print("beta(x, 2) =", special.beta(x, 2))
Out:
gamma(x) = [ 1.00000000e+00 2.40000000e+01 3.62880000e+05]
ln|gamma(x)| = [ 0. 3.17805383 12.80182748]
beta(x, 2) = [ 0.5 0.03333333 0.00909091]
In:
# 误差函数(高斯积分)
# 它的实现和它的逆实现
x = np.array([0, 0.3, 0.7, 1.0])
print("erf(x) =", special.erf(x))
print("erfc(x) =", special.erfc(x))
print("erfinv(x) =", special.erfinv(x))
Out:
erf(x) = [ 0. 0.32862676 0.67780119 0.84270079]
erfc(x) = [ 1. 0.67137324 0.32219881 0.15729921]
erfinv(x) = [ 0. 0.27246271 0.73286908 inf]
3.4 高级的通用函数特性
指定输出
在进行大量运算时,有时候指定一个用于存放运算结果的数组是非常有用的。不同于创建临时数组,你可以用这个特性将计算结果直接写入到你期望的存储位置。所有的通用函数都可以通过 out 参数来指定计算结果的存放位置:
In:
x = np.arange(5)
y = np.empty(5)
np.multiply(x, 10, out=y)
print(y)
Out:
[ 0. 10. 20. 30. 40.]
#这个特性也可以被用作数组视图,例如可以将计算结果写入指定数组的每隔一个元素的位置:
In:
y = np.zeros(10)
np.power(2, x, out=y[::2])
print(y)
Out:
[ 1. 0. 2. 0. 4. 0. 8. 0. 16. 0.]
聚合
二元通用函数有些非常有趣的聚合功能,这些聚合可以直接在对象上计算。
#对 add 通用函数调用 reduce 方法会返回数组中所有元素的和:
In:
x = np.arange(1, 6)
np.add.reduce(x)
Out:
15
#同样,对 multiply 通用函数调用 reduce 方法会返回数组中所有元素的乘积:
In:
np.multiply.reduce(x)
Out:
120
#如果需要存储每次计算的中间结果,可以使用 accumulate:
In:
np.add.accumulate(x)
Out:
array([ 1, 3, 6, 10, 15])
In:
np.multiply.accumulate(x)
Out:
array([ 1, 2, 6, 24, 120])
外积
任何通用函数都可以用 outer 方法获得两个不同输入数组所有元素对的函数运算结 果。
#这意味着你可以用一行代码实现一个乘法表:
In:
x = np.arange(1, 6)
np.multiply.outer(x, x)
Out:
array([[ 1, 2, 3, 4, 5],
[ 2, 4, 6, 8, 10],
[ 3, 6, 9, 12, 15],
[ 4, 8, 12, 16, 20],
[ 5, 10, 15, 20, 25]])
4.聚合:最小值、最大值和其他值
4.1 数组值求和
In:
import numpy as np
L = np.random.random(100)
sum(L)
Out:
55.61209116604941
#它的语法和 NumPy 的 sum 函数非常相似,并且在这个简单的例子中的结果也是一样的:
In: np.sum(L)
Out: 55.612091166049424
#但是,因为 NumPy 的 sum 函数在编译码中执行操作,所以 NumPy 的操作计算得更快一些:
In:
big_array = np.random.rand(1000000)
%timeit sum(big_array)
%timeit np.sum(big_array)
Out:
10 loops, best of 3: 104 ms per loop
1000 loops, best of 3: 442 µs per loop
4.2 最小值和最大值
In:
min(big_array), max(big_array)
Out:
(1.1717128136634614e-06, 0.9999976784968716)
#NumPy 对应的函数也有类似的语法,并且也执行得更快:
In:
np.min(big_array), np.max(big_array)
Out:
(1.1717128136634614e-06, 0.9999976784968716)
In:
%timeit min(big_array)
%timeit np.min(big_array)
10 loops, best of 3: 82.3 ms per loop
1000 loops, best of 3: 497 µs per loop
#对于 min、max、sum 和其他 NumPy 聚合,一种更简洁的语法形式是数组对象直接调用这些方法:
In: print(big_array.min(), big_array.max(), big_array.sum())
1.17171281366e-06 0.999997678497 499911.628197
多维度聚合
#一种常用的聚合操作是沿着一行或一列聚合。例如,假设你有一些数据存储在二维数组中:
In:
M = np.random.random((3, 4))
print(M)
Out:
[[ 0.8967576 0.03783739 0.75952519 0.06682827]
[ 0.8354065 0.99196818 0.19544769 0.43447084]
[ 0.66859307 0.15038721 0.37911423 0.6687194]]
#默认情况下,每一个 NumPy 聚合函数将会返回对整个数组的聚合结果:
In: M.sum()
Out: 6.0850555667307118
#聚合函数还有一个参数,用于指定沿着哪个轴的方向进行聚合。例如,可以通过指定axis=0 找到每一列的最小值:
In: M.min(axis=0)
Out: array([ 0.66859307, 0.03783739, 0.19544769, 0.06682827])
#这个函数返回四个值,对应四列数字的计算值。同样,也可以找到每一行的最大值:
In: M.max(axis=1)
Out: array([ 0.8967576 , 0.99196818, 0.6687194])
其他聚合函数
NumPy中可用的聚合函数
5. 数组的计算:广播
5.1 广播的介绍
广播允许这些二进制操作可以用于不同大小的数组。 观察以下将一个一维数组和一个二维数组相加的结果:
In:
a = np.array([0, 1, 2])
M = np.ones((3, 3))
M
Out: array([[ 1., 1., 1.],
[ 1., 1., 1.],
[ 1., 1., 1.]])
In:
M + a
Out:
array([[ 1., 2., 3.],
[ 1., 2., 3.],
[ 1., 2., 3.]])
#更复杂的情况会涉及对两个数组的同时广播
In:
a = np.arange(3)
b = np.arange(3)[:, np.newaxis]
print(a)
print(b)
Out:
[0 1 2]
[[0]
[1]
[2]]
In:
a + b
Out:
array([[0, 1, 2],
[1, 2, 3],
[2, 3, 4]])
浅色的盒子表示广播的值。同样需要注意的是,这个额外的内存并没有在实际操作中进行分配,但是这样的想象方式更方便我们从概念上理解。
5.2 广播的规则
• 规则 1:如果两个数组的维度数不相同,那么小维度数组的形状将会在最左边补 1。 • 规则 2:如果两个数组的形状在任何一个维度上都不匹配,那么数组的形状会沿着维度为 1 的维度扩展以匹配另外一个数组的形状。 • 规则 3:如果两个数组的形状在任何一个维度上都不匹配并且没有任何一个维度等于 1,那么会引发异常。
广播示例1
#将一个二维数组与一个一维数组相加:
In:
M = np.ones((2, 3))
a = np.arange(3)
Out:
#来看这两个数组的加法操作。两个数组的形状如下:
M.shape = (2, 3)
a.shape = (3,)
#可以看到,根据规则 1,数组 a 的维度数更小,所以在其左边补 1:
M.shape -> (2, 3)
a.shape -> (1, 3)
#根据规则 2,第一个维度不匹配,因此扩展这个维度以匹配数组:
M.shape -> (2, 3)
a.shape -> (2, 3)
#现在两个数组的形状匹配了,可以看到它们的最终形状都为 (2, 3):
In:
M + a
Out:
array([[ 1., 2., 3.],
[ 1., 2., 3.]])
广播示例2
#来看两个数组均需要广播的示例:
In:
a = np.arange(3).reshape((3, 1))
b = np.arange(3)
#同样,首先写出两个数组的形状:
a.shape = (3, 1)
b.shape = (3,)
#规则 1 告诉我们,需要用 1 将 b 的形状补全:
a.shape -> (3, 1)
b.shape -> (1, 3)
#规则 2 告诉我们,需要更新这两个数组的维度来相互匹配:
a.shape -> (3, 3)
b.shape -> (3, 3)
#因为结果匹配,所以这两个形状是兼容的,可以看到以下结果:
In:
a + b
Out:
array([[0, 1, 2],
[1, 2, 3],
[2, 3, 4]])
广播示例3
#现在来看一个两个数组不兼容的示例:
In:
M = np.ones((3, 2))
a = np.arange(3)
#和第一个示例相比,这里有个微小的不同之处:矩阵 M 是转置的。那么这将如何影响计算呢?两个数组的形状如下:
M.shape = (3, 2)
a.shape = (3,)
#同样,规则 1 告诉我们,a 数组的形状必须用 1 进行补全:
M.shape -> (3, 2)
a.shape -> (1, 3)
#根据规则 2,a 数组的第一个维度进行扩展以匹配 M 的维度:
M.shape -> (3, 2)
a.shape -> (3, 3)
#现在需要用到规则 3——最终的形状还是不匹配,因此这两个数组是不兼容的。当我们执行运算时会看到以下结果:
In:
M + a
Out:
"""
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
----> 1 M + a
ValueError: operands could not be broadcast together with shapes (3,2) (3,)
"""
5.3 广播的实际应用
数组的归一化
假设你有一个有 10 个观察值的数组,每个观察值包含 3 个数值。按照惯例,我们将用一个10×3 的数组存放该数据:
In:
X = np.random.random((10, 3))
#我们可以计算每个特征的均值,计算方法是利用 mean 函数沿着第一个维度聚合:
Xmean = X.mean(0)
Xmean
Out:
array([ 0.53514715, 0.66567217, 0.44385899])
#现在通过从 X 数组的元素中减去这个均值实现归一化(该操作是一个广播操作):
In:
X_centered = X - Xmean
#为了进一步核对我们的处理是否正确,可以查看归一化的数组的均值是否接近 0:
X_centered.mean(0)
Out: array([ 2.22044605e-17, -7.77156117e-17, -1.66533454e-17])
画一个二维函数
广播另外一个非常有用的地方在于,它能基于二维函数显示图像。我们希望定义一个函数 z = f (x, y),可以用广播沿着数值区间计算该函数:
In:
# x和y表示0~5区间50个步长的序列
x = np.linspace(0, 5, 50)
y = np.linspace(0, 5, 50)[:, np.newaxis]
z = np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x)
#我们将用 Matplotlib 来画出这个二维数组(这些工具将在 4.6 节中详细介绍):
%matplotlib inline
import matplotlib.pyplot as plt
plt.imshow(z, origin='lower', extent=[0, 5, 0, 5], cmap='viridis')
plt.colorbar();
6.比较、掩码和布尔逻辑
6.1 和通用函数类似的比较操作
In:
x = np.array([1, 2, 3, 4, 5])
x < 3 # 小于
Out: array([ True, True, False, False, False], dtype=bool)
In:
x > 3 # 大于
Out: array([False, False, False, True, True], dtype=bool)
In:
x <= 3 # 小于等于
Out: array([ True, True, True, False, False], dtype=bool)
In:
x >= 3 # 大于等于
Out: array([False, False, True, True, True], dtype=bool)
In:
x != 3 # 不等于
Out: array([ True, True, False, True, True], dtype=bool)
In:
x == 3 # 等于
Out: array([False, False, True, False, False], dtype=bool)
#另外,利用复合表达式实现对两个数组的逐元素比较也是可行的:
In:
(2 * x) == (x ** 2)
Out: array([False, True, False, False, False], dtype=bool)
6.2 操作布尔数组
给定一个布尔数组,你可以实现很多有用的操作。首先打印出此前生成的二维数组 x:
In: print(x)
[[5 0 3 3]
[7 9 3 5]
[2 4 7 6]]
统计记录的个数
#如果需要统计布尔数组中 True 记录的个数,可以使用 np.count_nonzero 函数:
In: # 有多少值小于6?
np.count_nonzero(x < 6)
Out: 8
#看到有 8 个数组记录是小于 6 的。另外一种实现方式是利用 np.sum。在这个例子中,False 会被解释成 0,True 会被解释成 1:
In:
np.sum(x < 6)
Out: 8
#sum() 的好处是,和其他 NumPy 聚合函数一样,这个求和也可以沿着行或列进行:
In: # 每行有多少值小于6?
np.sum(x < 6, axis=1)
Out: array([4, 2, 2])
#这是矩阵中每一行小于 6 的个数。如要快速检查任意或者所有这些值是否为 True,可以用np.any()或np.all():
In: # 有没有值大于8?
np.any(x > 8)
Out: True
In: # 有没有值小于0?
np.any(x < 0)
Out: False
In: # 是否所有值都小于10?
np.all(x < 10)
Out: True
In: # 是否所有值都等于6?
np.all(x == 6)
Out: False
#np.all() 和 np.any() 也可以用于沿着特定的坐标轴,例如:
In: # 是否每行的所有值都小于8?
np.all(x < 8, axis=1)
Out: array([ True, False, True], dtype=bool)
布尔运算符
6.3 将布尔数组作为掩码
更强大的模式是使用布尔数组作为掩码,通过该掩码选择数据的子数据集。
In:
x
Out:
array([[5, 0, 3, 3],
[7, 9, 3, 5],
[2, 4, 7, 6]])
#如前面介绍过的方法,利用比较运算符可以得到一个布尔数组:
In: x < 5
Out: array([[False, True, True, True],
[False, False, True, False],
[True, True, False, False]], dtype=bool)
#现在为了将这些值从数组中选出,可以进行简单的索引,即掩码操作:
In: x[x < 5]
Out: array([0, 3, 3, 3, 2, 4])
7.花哨的索引
7.1 探索花哨的索引
花哨的索引在概念上非常简单,它意味着传递一个索引数组来一次性获得多个数组元素。
In:
import numpy as np
rand = np.random.RandomState(42)
x = rand.randint(100, size=10)
print(x)
Out:
array([51 92 14 71 60 20 82 86 74 74])
#假设我们希望获得三个不同的元素,可以用以下方式实现:
In: [x[3], x[7], x[2]]
Out: [71, 86, 14]
#另外一种方法是通过传递索引的单个列表或数组来获得同样的结果:
In:
ind = [3, 7, 4]
x[ind]
Out:
array([71, 86, 60])
#利用花哨的索引,结果的形状与索引数组的形状一致,而不是与被索引数组的形状一致:
In:
ind = np.array([[3, 7],
[4, 5]])
x[ind]
Out:
array([[71, 86],
[60, 20]])
#花哨的索引也对多个维度适用。假设我们有以下数组:
In:
X = np.arange(12).reshape((3, 4))
X
Out:
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
#和标准的索引方式一样,第一个索引指的是行,第二个索引指的是列:
In:
row = np.array([0, 1, 2])
col = np.array([2, 1, 3])
X[row, col]
Out: array([ 2, 5, 11])
#在花哨的索引中,索引值的配对遵循 介绍过的广播的规则。因此将一个列向量和一个行向量组合在一个索引中时,会得到一个二维的结果:
In:
X[row[:, np.newaxis], col]
Out:
array([[ 2, 1, 3],
[ 6, 5, 7],
[10, 9, 11]])
#这里,每一行的值都与每一列的向量配对,正如我们看到的广播的算术运算:
In:
row[:, np.newaxis] * col
Out:
array([[0, 0, 0],
[2, 1, 3],
[4, 2, 6]])
7.2 组合索引
花哨的索引可以和其他索引方案结合起来形成更强大的索引操作:
In: print(X)
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
#可以将花哨的索引和简单的索引组合使用:
In: X[2, [2, 0, 1]]
Out: array([10, 8, 9])
#也可以将花哨的索引和切片组合使用:
In:
X[1:, [2, 0, 1]]
Out:
array([[ 6, 4, 5],
[10, 8, 9]])
#更可以将花哨的索引和掩码组合使用:
In:
mask = np.array([1, 0, 1, 0], dtype=bool)
X[row[:, np.newaxis], mask]
Out:
array([[ 0, 2],
[ 4, 6],
[ 8, 10]])
数组的排序
例如,一个简单的选择排序重复寻找列表中的最小值,并且不断交换直到列表是有序的。 可以在 Python 中仅用几行代码来实现:
In:
import numpy as np
def selection_sort(x):
for i in range(len(x)):
swap = i + np.argmin(x[i:])
(x[i], x[swap]) = (x[swap], x[i])
return x
In:
x = np.array([2, 1, 4, 3, 5])
selection_sort(x)
Out:
array([1, 2, 3, 4, 5])
8.1 NumPy中的快速排序:np.sort和np.argsort
#如果想在不修改原始输入数组的基础上返回一个排好序的数组,可以使用 np.sort:
In:
x = np.array([2, 1, 4, 3, 5])
np.sort(x)
Out: array([1, 2, 3, 4, 5])
#如果希望用排好序的数组替代原始数组,可以使用数组的 sort 方法:
In:
x.sort()
print(x)
Out:
[1 2 3 4 5]
#另外一个相关的函数是 argsort,该函数返回的是原始数组排好序的索引值:
In:
x = np.array([2, 1, 4, 3, 5])
i = np.argsort(x)
print(i)
Out:
[1 0 3 2 4]
#以上结果的第一个元素是数组中最小元素的索引值,第二个值给出的是次小元素的索引值,以此类推。这些索引值可以被用于(通过花哨的索引)创建有序的数组:
In: x[i]
Out: array([1, 2, 3, 4, 5])
沿着行或列排序
NumPy 排序算法的一个有用的功能是通过 axis 参数,沿着多维数组的行或列进行排序
In:
rand = np.random.RandomState(42)
X = rand.randint(0, 10, (4, 6))
print(X)
[[6 3 7 4 6 9]
[2 6 7 4 3 7]
[7 2 5 4 1 7]
[5 1 4 0 9 5]]
In: # 对X的每一列排序
np.sort(X, axis=0)
Out:
array([[2, 1, 4, 0, 1, 5],
[5, 2, 5, 4, 3, 7],
[6, 3, 7, 4, 6, 7],
[7, 6, 7, 4, 9, 9]])
In: # 对X每一行排序
np.sort(X, axis=1)
Out:
array([[3, 4, 6, 6, 7, 9],
[2, 3, 4, 6, 7, 7],
[1, 2, 4, 5, 7, 7],
[0, 1, 4, 5, 5, 9]])
8.2 部分排序:分隔
有时候我们不希望对整个数组进行排序,仅仅希望找到数组中第 K 小的值,NumPy 的np.partition 函数提供了该功能。np.partition 函数的输入是数组和数字 K,输出结果是一个新数组,最左边是第 K 小的值,往右是任意顺序的其他值:
In:
x = np.array([7, 2, 3, 1, 6, 5, 4])
np.partition(x, 3)
Out:
array([2, 1, 3, 4, 6, 5, 7])
#结果数组中前三个值是数组中最小的三个值,剩下的位置是原始数组剩下的值。
#在这两个分隔区间中,元素都是任意排列的。
#与排序类似,也可以沿着多维数组任意的轴进行分隔:
In: np.partition(X, 2, axis=1)
Out:
array([[3, 4, 6, 7, 6, 9],
[2, 3, 4, 7, 6, 7],
[1, 2, 4, 5, 7, 7],
[0, 1, 4, 5, 9, 5]])
9.结构化数据:NumPy的结构化数组
数据可以通过一个异构类型值组成的数组表示,但有时却并非如此。NumPy 的结构化数组和记录数组,它们为复合的、异构的数据提供了非常有效的存储。
假定现在有关于一些人的分类数据(如姓名、年龄和体重),我们需要存储这些数据用于Python 项目,那么一种可行的方法是将它们存在三个单独的数组中:
name = ['Alice', 'Bob', 'Cathy', 'Doug']
age = [25, 45, 37, 19]
weight = [55.0, 85.5, 68.0, 61.5]
#利用以下表达式可以生成一个简单的数组:
x = np.zeros(4, dtype=int)
#与之类似,通过指定复合数据类型,可以构造一个结构化数组:
data = np.zeros(4, dtype={'names':('name', 'age', 'weight'),
'formats':('U10', 'i4', 'f8')})
print(data.dtype)
Out:
[('name', ' #这里 U10 表示“长度不超过 10 的 Unicode 字符串”,i4 表示“4 字节(即 32 比特)整型”,f8 表示“8 字节(即 64 比特)浮点型”。后续的小节中将介绍更多的数据类型代码。 #现在生成了一个空的数组容器,可以将列表数据放入数组中: data['name'] = name data['age'] = age data['weight'] = weight print(data) out: [('Alice', 25, 55.0) ('Bob', 45, 85.5) ('Cathy', 37, 68.0) ('Doug', 19, 61.5)] #所有的数据被安排在一个内存块中。 #结构化数组的方便之处在于,你可以通过索引或名称查看相应的值: # 获取所有名字 data['name'] Out: array(['Alice', 'Bob', 'Cathy', 'Doug'], dtype=' # 获取数据第一行 data[0] Out: ('Alice', 25, 55.0) In: # 获取最后一行的名字 data[-1]['name'] Out: 'Doug' #利用布尔掩码,还可以做一些更复杂的操作,如按照年龄进行筛选: In: # 获取年龄小于30岁的人的名字 data[data['age'] < 30]['name'] Out: array(['Alice', 'Doug'], dtype=' 9.1 生成结构化数组 #结构化数组的数据类型有多种制定方式。此前我们看过了采用字典的方法: In: np.dtype({'names':('name', 'age', 'weight'), 'formats':('U10', 'i4', 'f8')}) Out: dtype([('name', ' #为了简明起见,数值数据类型可以用 Python 类型或 NumPy 的 dtype 类型指定: In: np.dtype({'names':('name', 'age', 'weight'), 'formats':((np.str_, 10), int, np.float32)}) Out: dtype([('name', ' #复合类型也可以是元组列表: In: np.dtype([('name', 'S10'), ('age', 'i4'), ('weight', 'f8')]) Out: dtype([('name', 'S10'), ('age', ' #如果类型的名称对你来说并不重要,那你可以仅仅用一个字符串来指定它。在该字符串中数据类型用逗号分隔: In: np.dtype('S10,i4,f8') Out: dtype([('f0', 'S10'), ('f1', ' 简写的字符串格式的代码可能看起来令人困惑,但是它们其实基于非常简单的规则。第一个(可选)字符是 < 或者 >,分别表示“低字节序”(little endian)和“高字节序”(bidendian),表示字节(bytes)类型的数据在内存中存放顺序的习惯用法。后一个字符指定的是数据的类型:字符、字节、整型、浮点型,等等。最后一个字符表示该S对象的字节大小。 9.2 更高级的复合类型 """ NumPy 中也可以定义更高级的复合数据类型。例如,你可以创建一种类型,其中每个元素 都包含一个数组或矩阵。我们会创建一个数据类型,该数据类型用 mat 组件包含一个 3×3 的浮点矩阵: """ tp = np.dtype([('id', 'i8'), ('mat', 'f8', (3, 3))]) X = np.zeros(1, dtype=tp) print(X[0]) print(X['mat'][0]) Out: (0, [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]]) [[ 0. 0. 0.] [ 0. 0. 0.] [ 0. 0. 0.]] 9.3 记录数组:结构化数组的扭转 NumPy 还提供了 np.recarray 类。它和前面介绍的结构化数组几乎相同,但是它有一个独 特的特征:域可以像属性一样获取,而不是像字典的键那样获取。 #前面的例子通过以下代码获取年龄: In: data['age'] Out: array([25, 45, 37, 19], dtype=int32) #如果将这些数据当作一个记录数组,我们可以用很少的按键来获取这个结果: In: data_rec = data.view(np.recarray) data_rec.age Out: array([25, 45, 37, 19], dtype=int32) #记录数组的不好的地方在于,即使使用同样的语法,在获取域时也会有一些额外的开销, In: %timeit data['age'] %timeit data_rec['age'] %timeit data_rec.age Out: 1000000 loops, best of 3: 241 ns per loop 100000 loops, best of 3: 4.61 µs per loop 100000 loops, best of 3: 7.27 µs per loop