NumPy 的核心是 ndarray:带有固定数据类型和形状的多维数组。真正值得掌握的不是函数清单,而是 shapedtypeaxis、广播和内存共享这几个心智模型。本文以当前 NumPy 2.x 的写法重新整理这些基础。

NumPy 是什么

NumPy 是数值计算生态中的基础库,主要用于大型、多维数组上的运算。它的优势可以概括为:

  • 使用连续、结构化的数组表达数据;
  • 通过向量化运算减少重复循环;
  • 为统计、科学计算和数据处理工具提供统一的数组基础。

安装基础依赖:

1
python -m pip install numpy

SciPy 是建立在 NumPy 之上的科学计算库,需要线性代数、优化、信号处理等扩展功能时再单独安装即可。

先记住四个属性

1
2
3
4
5
6
7
import numpy as np

a = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float64)
print(a.shape) # (2, 3):2 行 3 列
print(a.ndim) # 2:二维
print(a.dtype) # float64
print(a.itemsize) # 每个元素占用的字节数
属性或概念 要回答的问题
shape 每个轴有多长?
dtype 每个元素怎样编码、占多少空间?
axis 当前操作沿哪个维度归约或变换?
视图与副本 新数组是否与原数组共享底层数据?

1. 多维数组创建

1.1 创建一维数组

1
2
import numpy as np
print(np.__version__)
  • 方式1: np.array([1,2,3,4,5,6],dtype=np.int64) – 结果[1 2 3 4 5 6] 数组a

  • 方式2: np.array(range(1,6)) – 结果[1 2 3 4 5]

  • 方式3: np.arange(1,6,1) – 结果[1 2 3 4 5]

  • 方式4: np.array(list) 将一个列表或序列转化为数组

  • 方式 5:np.arange(a, b, c) 按起点、终点和步长生成数组。

更推荐把不同创建方式直接写成可运行代码:

1
2
3
4
5
6
values = np.array([1, 2, 3, 4], dtype=np.int64)
steps = np.arange(0, 10, 2)
points = np.linspace(0.0, 1.0, num=5)
zeros = np.zeros((2, 3), dtype=np.float32)
ones = np.ones((2, 3))
identity = np.eye(3)

arange 适合整数步长;需要指定区间内均匀取若干个浮点数时,linspace 通常更直观。

1.2 创建多维数组

  • 二维数组 单个维度的元素数量要保持一致,数组的元素类型要保持一致(不一致则自动被转换为一致的),否则报错
1
2
3
num_list2 = [[1,2,3,4,5],[6,7,8,9,10]]
num_list3 = [[1,2,3,4,"a"],[6,7,8,9,"b"]]
array = np.array(num_list3)
  • 三维数组(三维矩阵) : 列表维度和要创建的数组维度一致; 数据和维度如果是灵活的情况下创建复杂,
1
2
num_list4=[ [[1,2,3,4,"a"],[6,7,8,9,"b"]] ,  [[1,2,3,4,"a"],[6,7,8,9,"b"]] ]
array2 = np.array(num_list4)
  • PS:
    创建多维数组时可以
  • ​ 1、创建一个全0的数组: np.zeros((3,4))
  • ​ 2、创建一个全1的数组:np.ones((3,4))
  • ​ 3、创建一个对角线为1的正方形数组(方阵):np.eye(3)
  • ​ 4、随机数创建数组,见下一部分 (6)numpy生成随机数

1.3 数组的属性

​ a.ndim #数组维度

​ a.shape #数组形状

​ a.itemsize #数组每个元素大小

2. 常用数据类型

2.1 NumPy 支持的数值类型

符号 含义
bool_ TrueFalse
int_ 平台原生整数宽度
int8 8位的整形(-128~127)
int16 -32768~32767
int32 -2 ** 31 ~ 2 ** 31 - 1
int64 -2 ** 63 ~ 2 ** 63 - 1
uint8 8位的整形(0~255)
uint16 0~65535
uint32 0 ~ 2 ** 32 - 1
uint64 0 ~ 2 ** 64 - 1
float16 1位符号位,5位指数位,10位
float32 1位符号位,8位指数位,23位
float64 1位符号位,11位指数位,52位

数据要写入文件或跨平台交换时,优先明确使用 int32int64float32float64,不要依赖平台原生整数宽度。可用 np.dtype("int32") 构造 dtype,并通过 .itemsize 查看字节数。NumPy 2.x 已移除一批模糊的旧别名,新代码应使用明确的标量类型。

2.2 查看多维数组的数据类型

​ arr1.dtype

定义bool类型

​ bs = np.array([1, 1, 0, 1, 0], dtype=bool)

2.3 数据类型转换

​ 数组名.astype(numpy.数据类型),返回数组, 需要接收后才会改变

astype 返回转换后的新数组,默认不修改原数组。浮点数转整数会截断小数部分;窄整数还可能溢出,因此正式转换前应确认取值范围。

1
2
3
4
5
6
7
arr1 = np.array([1.2, 2.8, 3.5], dtype=np.float64)
ints = arr1.astype(np.int16)
rounded = np.round(arr1, 1)

print(arr1.dtype) # float64
print(ints) # [1 2 3]
print(type(arr1)) # numpy.ndarray

3. 多维数组形状

  1. 查看数组的形状

    语法: a.shape

  2. 修改数组的形状

    方法1: a.reshape(5,2) – 5是行, 2是列

    方法2: a.resize (3,4) –表示将arr数组修改成3行4列数组,原数组a形状改变

  3. 输出 a.shape 问题: 为何修改后,仍然是原来的形状?使用b接收

  4. 把数组转为1维数组

    方法1:a.reshape(1,10)

    方法2:a.flatten()

    方法3:a.reshape(a.shape[0] * a.shape[1], )

    方法4: a.ravel()

1
2
3
4
5
6
7
8
9
10
a = np.array([[1,2,3,4,5],[6,7,8,9,10]])
e = np.array([ [[1,2,3,4,5],[6,7,8,9,10]],[[1,2,3,4,5],[6,7,8,9,10]] ])

#查看多维数组形状
print(a.shape)
print(e.shape)

#改变多维数组形状
a.reshape(5,2)

reshape 返回形状不同的数组对象,原数组的 shape 不会随之改变;新对象在条件允许时可能与原数组共享内存。flatten() 总是创建副本,ravel() 则会尽可能返回视图。

PS:多维数组的轴(维度)

在numpy中轴(axis)可以理解为方向,使用0,1,2…数字表示,

对于一个一维数组,只有一个0轴, 对于2维数组(shape(2,2)),有0轴和1轴, 对于三维数组(shape(2,2, 3)),有0,1,2轴

4. 数组计算与向量化

数组与标量运算时,标量会广播到每个元素;形状相同的数组则逐元素运算。与 Python 循环相比,数组表达式通常更简洁,也更容易交给底层已优化的实现执行。

1
2
3
4
5
6
7
a = np.arange(1, 7).reshape(2, 3)

print(a + 10) # 标量广播
print(a * a) # 逐元素乘法
print(np.sqrt(a)) # 通用函数逐元素执行
print(a.sum(axis=0)) # 沿第 0 轴压缩,得到每列之和
print(a.sum(axis=1)) # 沿第 1 轴压缩,得到每行之和

axis 表示被归约掉的轴,而不是“结果朝哪个方向”。例如 (2, 3) 数组执行 sum(axis=0) 后,第 0 轴被压缩,结果形状是 (3,)

5. 广播原则

NumPy 从两个数组 shape 的最右侧开始逐维比较。每一维只要满足以下任一条件,就可以广播:

  1. 两个维度长度相等;
  2. 其中一个维度长度为 1;
  3. 某一方缺少该维度,可视为长度 1。
1
2
3
4
5
6
scores = np.array([[80, 90, 70], [75, 88, 92]])  # (2, 3)
subject_bonus = np.array([1, 2, 3]) # (3,)
student_bias = np.array([[0], [5]]) # (2, 1)

print(scores + subject_bonus) # (2, 3) + (3,) -> (2, 3)
print(scores + student_bias) # (2, 3) + (2, 1) -> (2, 3)

判断练习:(3, 3, 3)(3, 2) 不兼容,因为末尾的 32 冲突;(3, 3, 2)(3, 2) 兼容,结果为 (3, 3, 2)

形状实验

从最右侧判断广播

选择两个 shape。每一列表示对齐后的同一维,缺少的前导维会按 1 处理。

A
B

广播通常不会真的复制输入数组,但表达式产生的中间结果仍可能很大。例如给 (100000, 1)(1, 100000) 做运算会得到一个百亿元素数组,形状兼容并不代表内存安全。

6. 多维数组的轴

对于形状为 (2, 3, 4) 的三维数组:第 0 轴长度为 2,第 1 轴长度为 3,第 2 轴长度为 4。不要把轴固定记成“横轴”或“纵轴”,应结合具体数据含义命名,例如“样本、时间、特征”。

7. 索引、切片与布尔筛选

1
2
3
4
5
6
7
8
a = np.arange(12).reshape(3, 4)

print(a[1, 2]) # 第 2 行、第 3 列的元素
print(a[1]) # 第 2 行
print(a[:, 2]) # 第 3 列
print(a[0:2, 1:3]) # 连续的行和列
print(a[[0, 2], :]) # 高级索引:第 1、3 行
print(a[a % 2 == 0]) # 布尔索引:筛出偶数

布尔数组还可用于原位修改,np.where 则适合生成一个新数组。判断缺失值应使用 np.isnan,不要写 x == np.nan,因为 NaN 按定义不等于自身。

1
2
3
b = a.copy()
b[b < 5] = 0
labels = np.where(a < 5, "low", "high")

切片是视图,高级索引是副本

基本切片通常返回视图,与原数组共享数据;整数数组或布尔数组构成的高级索引会返回副本。这是最容易造成隐蔽错误的规则之一。

1
2
3
4
5
6
7
8
9
a = np.arange(6)

view = a[1:4]
view[0] = 99
print(a) # [ 0 99 2 3 4 5],原数组被修改

copy = a[[1, 2, 3]]
copy[0] = -1
print(a) # 原数组不受这次修改影响

如果代码依赖独立数据,请显式调用 .copy();如果依赖共享内存,可用 np.shares_memory(a, view) 检查。

常用索引写法

(1) 一维数组的索引与切片

​ 1.获取指定元素 语法:数组名[索引(下标)] a[3]

​ 2.数组的切片 语法:数组名[start : end : step] a[2:10:2]

(2) 二维数组的索引与切片

​ 1. 获取指定元素 语法:数组名[行索引,列索引] a[1 , 1]

​ 2. 数组的切片 语法:数组名[rows_start:rows_end:rows_step,cols_start:cols_end:cols_step]

a[1 : 2 , 0: 1]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
取一行  a[1]
取多行 a[1:3]
取连续多行 a[2:]
取不连续多行 a[[0,2]]

取一列 a[:,2] 第3列
取多列 a[:, 1:3]
print(a[1,:]) #第2行所有列
print(a[1: , :]) #第2至结尾行所有列
print(a[[0,2],:]) #第1和3行所有列
print(a[: , 0]) #所有行的第1列
取连续的多列 a[: , 1:]
取不连续的多列 a[: , [0,2]]
取多行多列 第1到2, 第1-2列 print(a[0:2, 0:2])

连续多行应写作 a[1:3, :];切片终点超出范围时最多取到末尾
取多个不相邻的点 (选择的点是(1,1) (2,2))
a[[1,2] , [1,2]]

(3)NumPy 中数值的修改

1
2
3
#数值的修改
a2[:, 0:2] = 0
print(a2)

如果希望将小于 10 的数字替换为 0,可以使用布尔索引:

(4)NumPy 中布尔索引

1
2
3
4
5
6
7
8
9
10
#小于10的替换为0
a3 = np.array([[1,2,3,4,5,6],[7,8,9,10,11,12]])
print(a3<10)
a3[a3<10] = 0
print(a3)
# 小于 10 的替换为 0,其余值替换为 10
a4 = np.array([[1,2,3,4,5,6],[7,8,9,10,11,12]])
print(np.where(a4<10,0,10)) #三元运算符
#特殊情况, nan类型,判断结果不相等
print(np.nan == np.nan)

8. NumPy 转置和轴对换

转置和轴置换用于重新排列数组的轴。对二维数组来说,转置相当于交换行、列两个轴;对更高维数组,需要明确指定完整的轴顺序或要交换的两个轴。

​ 2.方法1: a.transpose() 行和列互换 ,原来的数组从2行3列,变为3行2列.

​ 3.方法2: a.swapaxes(1,0) 交换轴

​ 4.方法3: a.T

​ 5. 更高维的轴移动可使用 np.moveaxis(arr, source, destination),语义通常比旧式 rollaxis 更清楚。

​ 转换和交换轴的效果一样.

1
2
3
4
5
6
7
8
9
 a 是二维数组

a.transpose()

a.swapaxes(1,0) # 交换轴,也能实现转置

a.T

以上三种方法都能实现转置

9. NumPy 常用方法

(1)数组的连接

1.concatenate连接NumPy数组

​ 连接意味着将两个或多个数组的内容放在单个数组中。

​ 我们传递了一系列要与轴一起加入concatenate()函数的数组。 如果未显式传递轴,则将其视为0。

​ 例如:

​ 连接两个数组

1
2
3
4
5
6
7
8
9
import numpy as np

arr1 = np.array([4, 2, 3])

arr2 = np.array([4, 7, 6])

arr = np.concatenate((arr1, arr2))

print(arr)

上面代码运行的成果:[4 2 3 4 7 6]

例如:

沿列方向(axis=1)联接两个二维数组:

1
2
3
4
5
6
7
arr1 = np.array([[1, 7], [3, 4]])

arr2 = np.array([[5, 9], [2, 8]])

arr = np.concatenate((arr1, arr2), axis=1)

print(arr)

上面代码运行的成果:

1
2
[[1 7 5 9]
[3 4 2 8]]

2.使用stack()函数连接数组

stack()与concatenate()相同,唯一的不同是stack()是沿着新轴完成的。

我们可以沿着第二个轴连接两个一维数组,这将导致它们一个放在另一个之上。

我们传递一个要连接到stack()方法的数组序列和axis。如果axis没有显式传递,则将其视为0。

例如:

1
2
3
4
5
6
7
8
9
import numpy as np

arr1 = np.array([1, 7, 3])

arr2 = np.array([4, 8, 6])

arr = np.stack((arr1, arr2), axis=1)

print(arr)

上面代码运行的成果:

1
2
3
[[1 4]
[7 8]
[3 6]]

3. 使用 hstack() 水平堆叠

对二维数组而言,hstack() 沿列方向拼接,要求各数组的行数兼容。

例如:

1
2
3
4
5
6
7
8
9
import numpy as np

arr1 = np.array([1, 77, 31])

arr2 = np.array([4, 88, 61])

arr = np.hstack((arr1, arr2))

print(arr)

上面代码运行的成果:

1
[ 1 77 31  4 88 61]

4. 使用 vstack() 垂直堆叠

vstack() 沿第 0 轴拼接,相当于把数组按行叠放。

例如:

1
2
3
4
5
6
7
8
9
import numpy as np

arr1 = np.array([11, 12, 3])

arr2 = np.array([41, 5, 16])

arr = np.vstack((arr1, arr2))

print(arr)

上面代码运行的成果:

1
2
[[11 12  3]
[41 5 16]]

5. 使用 dstack 沿第三个轴堆叠

np.dstack 把输入数组沿第三个轴(axis=2)依次堆叠。对一维输入,它会先把每个数组变成形状 (1, N, 1)

例如:

1
2
3
4
5
6
7
8
9
import numpy as np

arr1 = np.array([1, 2, 3])

arr2 = np.array([4, 5, 6])

arr = np.dstack((arr1, arr2))

print(arr)

上面代码运行的成果:

1
2
3
[[[1 4]
[2 5]
[3 6]]]

(2)数组的分割

1. split()函数

​ 沿特定的轴将数组分割为子数组

​ 语法: numpy.split(arr,indices_or_sections,axis)

​ indices_or_sections表示将arr数组创建为大小相同的子数组的数量

1
2
3
4
5
6
7
8
#对1维数组分割 按指定位置分割,2,5位置
sp1 = np.array([ 1,2,3,4,5,6])
# print(np.split(sp1,[2,5]))
sp2 = np.array([ [1,2,3] , [4,5,6]])
#将数组分割为2个大小相同的子数组
print(np.split(sp2, 2))
#3个2维数组 结果: [[1],[4]] [[2],[5]] [[3],[6]]
print(np.split(sp2, 3 , 1 ))

2.hsplit()函数

​ 将数组沿着水平方向分割

​ 语法:numpy.hsplit(arr,indices_or_sections)

3.vsplit()函数

​ 将数组沿着竖直方向分割

​ 语法: numpy.vsplit(arr,indices_or_sections)

1
2
3
4
5
#hsplit()函数  沿着水平方向分割 结果3个数组 [[1],[4]]    [[2],[5]]   [[3],[6]]
print(sp2)
print(np.hsplit(sp2,3))
#vsplit()函数 沿着垂直方向分割 结果2个数组 [[1, 2, 3]] [[4, 5, 6]]
print(np.vsplit(sp2,2))

(3)数组转换

​ 数组名.tolist() 将数组转换成列表

(4)添加删除数组元素

1. append()函数

​ 数组的末尾添加元素,该函数会返回一个新数组,而原数组不变。

​ 方法:numpy.append(arr,values,axis)

1
2
3
4
5
6
7
8
9
a = np.array([1, 2, 3, 4, 5, 6])
extended = np.append(a, [0, 0, 0])

a1 = np.array([[1, 2, 3], [4, 5, 6]])
extra = np.array([[7, 8, 9], [10, 11, 12]])
# 沿 0 轴添加两行
print(np.append(a1, extra, axis=0))
# 沿 1 轴添加三列
print(np.append(a1, extra, axis=1))
1
2
3
4
5
6
7
8
#沿0轴添加运行结果
[[ 1 2 3]
[ 4 5 6]
[ 7 8 9]
[10 11 12]]
#沿1轴添加运行结果
[[ 1 2 3 7 8 9]
[ 4 5 6 10 11 12]]

2.insert()函数

​ 沿给定轴在输入数组中插入值,该函数会返回一个新数组,原数组不变。

​ 方法:numpy.insert(arr,obj,values,axis)

1
2
3
4
5
6
7
8
#一维数组插入
# print(np.insert(a,2,[111,222]))
#二维数组插入 会被展开后插入
print(np.insert(a1,2,[111,222]))
#加入axis=0, 则会以广播数组来匹配输入数组 插入值[0,0,0]
print(np.insert(a1,2,[0],axis=0))
#加入axis=1, 则会以广播数组来匹配输入数组 插入值[[0],[0]]
print(np.insert(a1,2,[0],axis=1))
1
2
3
4
5
6
7
8
9
#二维数组插入 会被展开后插入运行结果
[ 1 2 111 222 3 4 5 6]
#加入axis=0运行结果
[[1 2 3]
[4 5 6]
[0 0 0]]
#加入axis=1运行结果
[[1 2 0 3]
[4 5 0 6]]

3.delete()函数

​ 从输入数组中删除指定子数组的新数组,原数组不变。

​ 方法:numpy.delete(arr,obj,axis)

1
2
3
4
5
6
7
8
9
# delete() 返回删除指定索引后的新数组;不传 axis 时会先展开
a2 = np.array([[1,2,3],[4,5,6]])
print(np.delete(a2,[2,4]))
#删除第3列 结果 [[1 2] [4 5]]
print(np.delete(a2,2,axis=1))
#删除第1行 结果[[4 5 6]]
print(np.delete(a2,0,axis=0))
#删除切片范围 展开后切片 结果[4 5 6]
print(np.delete(a2, np.s_[0:3]))

10. 通用函数

(1)常见的一元函数

函数 说明
abs、fabs 计算绝对值;fabs 只处理实数,复数应使用 abs
sqrt 计算各元素的平方根,相当于arr ** 0.5
square 计算个元素的平方,
exp 计算各元素的指数
log、log10、log2、log1p 分别计算自然对数、常用对数、以 2 为底的对数,以及 log(1+x)
sign 计算各元素的符号:1(正数)、0(零)、-1(负数)
ceil 计算各元素的ceiling值,即大于等于该值的最小整数
floor 计算各元素的floor值,即小于等于该值的最大整数
rint 将各元素的值四舍五入到最接近的整数,保留dtype
modf 将数组各元素的小数和整数部分以两个独立的数组的形式返回
isnan 返回一个表示“哪些值是NaN(Not a Number)”的布尔型数组
isfinite、isinf 分别返回一个表示“哪些元素是有穷的”或“哪些元素是无穷的”的布尔型数组
cos、cosh、sin、sinh、tan、tanh 普通型和双曲型三角函数
arccos、arccosh、arcsin、arcsinh、arctan、arctanh 普通型和双曲型反三角函数
logical_not 计算各元素not x的真值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#通用函数
#常用一元函数
arr = np.arange(10)
# print(arr)
#arr数组的平方根
arr1 = np.sqrt(arr)
print(arr1)
#每个数字+2
# print(np.add(arr,2))
#分解数组的浮点数为整数和小数部分
# print(np.modf(arr1))
#比较两个数组大小 结果[3 4 5]
arr2 = np.arange(3)
arr3 = np.arange(3,6)
print(np.maximum(arr2,arr3))
# 点积与逐元素乘法
arr4 = np.arange(6).reshape(2,3)
arr5 = np.arange(3)
arr6 = np.arange(3)
print(arr4)
print(arr5)
print("点积", np.dot(arr6, arr5))
print("逐元素乘法", arr4 * arr5)

(2)常见的二元函数

函数 说明
add 将数组中对应的元素相加
subtract 第一个数组减去第二个数组
multiply 数组元素相乘
divide、floor_divide 除法、向下整除法(丢弃余数)
power 逐元素计算第一个数组的值对第二个数组的值的幂
maximum、fmax 元素级的最大值计算,fmax将忽略NaN
minimum、fmin 元素级的最小值计算,fmin将忽略NaN
mod 元素级的取模计算
copysign 将第二个数组各元素值的符号复制给第一个数组中各元素
greater、greater_equal、less、less_equal、equal、not_equal 执行元素级的比较运算,最终产生布尔型数组
logical_and、logical_or、logical_xor 执行元素级真值逻辑运算

11. 线性代数:使用 ndarray 与 @

NumPy 2.0 已移除 np.mat,新代码统一使用二维 ndarray。这样不会出现 * 在数组与矩阵对象上含义不同的问题:* 始终是逐元素乘法,@np.matmul 才是矩阵乘法。

1
2
3
4
5
6
a = np.array([[1.0, 2.0], [3.0, 4.0]])
b = np.array([[2.0, 0.0], [1.0, 2.0]])

elementwise = a * b # 逐元素相乘
product = a @ b # 矩阵乘法
transposed = a.T # 转置

多个小数组拼成分块矩阵时,可使用 np.block

1
2
3
4
5
6
left = np.eye(2)
right = np.ones((2, 1))
block = np.block([
[left, right],
[np.zeros((1, 2)), np.array([[2.0]])],
])

求解线性方程 Ax = b 时,优先使用 np.linalg.solve,通常不要显式计算逆矩阵:

1
2
3
4
5
6
a = np.array([[3.0, 1.0], [1.0, 2.0]])
b = np.array([9.0, 8.0])
x = np.linalg.solve(a, b)

print(x) # [2. 3.]
print(np.allclose(a @ x, b)) # True

常用入口还包括 np.linalg.norm(范数)、np.linalg.det(行列式)、np.linalg.eig(特征值)和 np.linalg.svd(奇异值分解)。计算前应先检查形状,并留意病态矩阵带来的数值误差。

12. 随机数生成

新代码推荐先创建 Generator,再从同一个生成器取样。这比依赖全局 np.random 状态更容易测试,也方便显式控制可复现性。

1
2
3
4
5
6
rng = np.random.default_rng(seed=2026)

uniform = rng.random((2, 3)) # [0, 1) 均匀分布
normal = rng.normal(0.0, 1.0, (2, 3)) # 均值 0、标准差 1
integers = rng.integers(0, 10, size=5) # [0, 10) 的整数
choices = rng.choice(["A", "B", "C"], size=4)

固定种子适合教程、测试和可复现实验,但不代表随机结果“更随机”。正式模拟中应记录种子与 NumPy 版本;密码、令牌等安全用途则应使用 Python 的 secrets 模块,而不是 NumPy 随机数生成器。

13. 一段完整的向量化示例

假设每行是一名学生、每列是一门课程,需要做标准化并求每名学生的平均标准分:

1
2
3
4
5
6
7
8
9
10
11
12
scores = np.array([
[80.0, 92.0, 75.0],
[70.0, 88.0, 90.0],
[95.0, 85.0, 78.0],
])

mean = scores.mean(axis=0, keepdims=True)
std = scores.std(axis=0, keepdims=True)
z_scores = (scores - mean) / std
student_summary = z_scores.mean(axis=1)

print(student_summary)

这里同时用到了轴、广播和向量化:keepdims=True 保留被归约轴为长度 1,使 (3, 3) 可以直接与 (1, 3) 的均值、标准差广播。真实数据还要先处理缺失值与标准差为 0 的列。

14. 学习检查清单

  • 能否只看 shape 判断两个数组是否可广播?
  • 能否说清 axis=0 被压缩的是哪一维?
  • 修改切片后,是否知道原数组会不会变化?
  • 是否区分 * 的逐元素乘法与 @ 的矩阵乘法?
  • 是否用 default_rng 管理随机状态?
  • 面对循环时,能否先尝试通用函数、广播或归约?

掌握这些问题后,再学习 pandas、SciPy、scikit-learn 或深度学习框架时,数组的形状与计算语义会顺畅得多。

参考资料