Skip to content

第 4 章 数组运算与通用函数

学习目标

  • 掌握逐元素算术运算与就地运算
  • 掌握比较运算,理解比较产生布尔数组
  • 认识通用函数(ufunc):sqrtabsexploground、三角函数
  • 会用数组方法做简单聚合:summeanmaxmin

4.1 逐元素算术运算

逐元素运算(element-wise)指两个数组对应位置上的元素各自运算,结果仍是数组。形状相同的数组之间、数组与标量之间都可以:

python
import numpy as np

a = np.array([10, 20, 30])
b = np.array([1, 2, 3])

print(a + b)    # 加法
print(a - b)    # 减法
print(a * b)    # 乘法
print(a / b)    # 除法(结果浮点)
print(a // b)   # 整除
print(a % b)    # 取余
print(a ** 2)   # 幂

输出:

[11 22 33]
[ 9 18 27]
[10 40 90]
[10. 10. 10.]
[10 10 10]
[0 0 0]
[100 400 900]

与 Python 列表对比,再次强调:数组的 *+ 是逐元素运算;列表的 +* 是拼接和重复。 这是数组最核心的语义。

数组与标量运算时,标量被「广播」到每个元素(广播规则第 7 章正式讲):

python
import numpy as np

prices = np.array([3.5, 4.0, 5.5])
print(prices * 2)      # 单价翻倍
print(prices + 1)      # 每件加 1 元

输出:

[ 7.   8.  11. ]
[4.5 5.  6.5]

4.2 就地运算

+=*=就地运算直接修改原数组,不创建新数组:

python
import numpy as np

c = np.array([1, 2, 3])
c += 10
print(c)

输出:

[11 12 13]

注意区分:c + 10 返回新数组、c 不变;c += 10 修改 c 本身。需要保留原数据时用前者。

4.3 比较运算:结果是布尔数组

比较运算符(> >= < <= == !=)逐元素比较,结果数组的元素是 True/False:

python
import numpy as np

a = np.array([10, 20, 30])
print(a > 15)
print(a == 20)

输出:

[False  True  True]
[False  True False]

布尔数组可以直接传给 sum/mean 统计满足条件的个数与比例(第 8 章展开):

python
import numpy as np

scores = np.array([85, 92, 78, 90, 88])
print((scores >= 90).sum())     # 90 分以上的人数
print((scores >= 90).mean())    # 90 分以上占的比例

输出:

2
0.4

4.4 通用函数(ufunc)

通用函数(ufunc)是 NumPy 提供的逐元素数学函数,用法和算术运算符一样,作用于整个数组。

python
import numpy as np

print(np.sqrt(np.array([1, 4, 9])))     # 平方根
print(np.abs(np.array([-1, 2, -3])))    # 绝对值
print(np.round(np.array([1.234, 5.678]), 1))   # 四舍五入,保留 1 位小数
print(np.exp(np.array([0, 1])))         # e 的幂
print(np.log(np.array([1, np.e])))      # 自然对数
print(np.sin(np.pi / 2))                # 正弦

输出:

[1. 2. 3.]
[1 2 3]
[1.2 5.7]
[1.         2.71828183]
[0. 1.]
1.0

几点说明:

  • round 的第二个参数是保留的小数位数;也可以调用数组方法 数组.round(1),效果相同。
  • 对负数开方会产生 nan(Not a Number,非数)并伴随 RuntimeWarning,数据中出现 nan 说明有非法输入:
>>> np.sqrt(np.array([-1.0, 4.0]))
<stdin>:1: RuntimeWarning: invalid value encountered in sqrt
array([nan,  2.])
  • 三角函数等以弧度为单位:np.pi 是圆周率。

ufunc 的特征:输入数组、输出数组,逐元素计算,内部用编译代码批量执行。需要什么数学运算,先去 np 命名空间找,大概率已有现成函数。

4.5 数组方法:简单聚合

聚合把整个数组压缩成一个数。第 1 章见过,这里集中列出:

python
import numpy as np

a = np.array([10, 20, 30])
print(a.sum())      # 总和
print(a.mean())     # 平均值
print(a.max())      # 最大值
print(a.min())      # 最小值

输出:

60
20.0
30
10

mean 返回浮点,sum/max/min 保持原 dtype。按行、按列聚合(axis)在第 6 章讲。

动手实践

  1. 创建两个数组 np.array([1, 3, 5, 7])np.array([2, 4, 6, 8]),计算和、差、积、商,观察除法结果的 dtype。
  2. 摄氏温度转华氏:f = c * 9 / 5 + 32,对 c = np.array([0, 10, 20, 30, 40]) 计算并打印。
  3. np.array([-3.7, 0, 2.4, -1.2]) 分别取绝对值、四舍五入到整数,打印结果。
  4. 统计 np.array([72, 85, 90, 65, 88, 95]) 中及格(≥60)与优秀(≥90)的人数与比例。

常见错误

错误写法现象原因
a + b 时形状不同ValueError: operands could not be broadcast...形状不兼容,不能逐元素运算(广播规则见第 7 章)
a > 15 后直接用 if 判断ValueError: truth value of an array... is ambiguous布尔数组有多个真值,不能整体作条件;用 .all()/.any() 或布尔索引
np.sqrt 负数数组输出 nan + RuntimeWarning负数没有实数平方根,结果是 nan
误以为 c + 10 修改了 c原数组没变+ 返回新数组;就地修改要用 +=
a * b 计算矩阵乘法结果与数学课本不符* 是逐元素乘法;矩阵乘法用 @(第 10 章)
在列表上调用 np.sqrt(列表)返回元素是 float 的数组(可以)可以,但仍是逐元素转换;列表本身没有向量化运算

章末练习

基础

  1. a = np.array([2, 4, 6, 8]) 计算 a ** 2a + 5a / 2,写出结果。
  2. 对数组 [12, 7, 9, 15, 3] 分别计算总和、平均、最大、最小。
  3. np.sqrt 计算 [1, 4, 9, 16, 25] 的平方根。

提高

  1. 已知某商品原价数组 [99, 199, 299],打八折再满 100 减 20,写代码计算最终价格数组(注意:折扣与满减都要逐元素处理)。
  2. 用比较运算统计 np.array([58, 72, 45, 90, 66]) 中不及格(<60)的个数,并打印不及格比例。

挑战

  1. np.roundnp.array([2.675, 1.225]) 保留两位小数,运行并观察结果,思考为什么 2.675 变成了 2.67(提示:浮点数在二进制中的表示不精确)。
  2. 写一个 ufunc 链:对 np.array([1, 4, 9, 16]) 先开方、再乘 2、再四舍五入到 1 位小数,输出最终数组。

章末自测

每题选择一个最佳答案。本书不附答案:完成后交由老师或 AI 老师批改讲解。

  1. np.array([1, 2, 3]) * 2 的结果是?
    • A. [1, 2, 3, 1, 2, 3]
    • B. [2, 4, 6]
    • C. 2
    • D. 报错
  2. np.array([7, 8]) / 2 的结果 dtype 是?
    • A. int64
    • B. float64
    • C. bool
    • D. 报错
  3. np.array([10, 20, 30]) > 15 的结果是?
    • A. [10, 20, 30]
    • B. [False, True, True]
    • C. True
    • D. [0, 1, 1]
  4. np.sqrt(np.array([1, 4, 9])) 的结果是?
    • A. [1, 2, 3]
    • B. [1.0, 2.0, 3.0]
    • C. [1, 16, 81]
    • D. [0, 1, 2]
  5. np.round(np.array([1.234, 5.678]), 1) 的结果是?
    • A. [1.2, 5.7]
    • B. [1.2, 5.6]
    • C. [1.23, 5.68]
    • D. [1, 6]
  6. 下列哪个是就地修改数组的写法?
    • A. a = a + 10
    • B. a += 10
    • C. np.add(a, 10)
    • D. B 和 A 都会修改原数组
  7. np.array([85, 92, 78]) >= 90sum() 结果是?
    • A. 85
    • B. 2
    • C. 1
    • D. False
  8. 对负数数组开方会出现?
    • A. 直接报错
    • B. 输出 nan 并伴随 RuntimeWarning
    • C. 自动取绝对值
    • D. 返回原数组
  9. a.sum()a.mean() 的区别是?
    • A. 没有区别
    • B. sum 求和,mean 求平均
    • C. sum 求平均,mean 求和
    • D. 都返回数组
  10. 数组 * 与列表 * 的区别是?
    • A. 数组是逐元素乘法,列表是重复
    • B. 列表是逐元素乘法,数组是重复
    • C. 两者相同
    • D. 数组不能使用 *