NumPy 详细笔记
目标:让完全零基础的小白也能看懂 NumPy,并能用它完成数组计算、数据统计、矩阵运算、条件筛选和简单数据分析。
这是一份 numpy 笔记:先讲清楚为什么,再讲怎么写,遇到复杂计算会拆开计算流程,最后给练习和答案。
你会学到什么
- NumPy 是什么,为什么比 Python 列表更适合数值计算。
ndarray、shape、dtype、axis这些核心概念。- 如何创建数组、取数、筛选、修改、变形。
- 如何理解广播机制和向量化。
- 如何做统计计算、矩阵运算、随机数模拟。
- 方差、标准差、分位数、矩阵乘法、标准化等复杂计算的完整过程。
- 常见错误和排查思路。
- 有练习,有答案,有手算过程。
学习建议
NumPy 不要靠死记硬背函数名。你只要抓住四个关键词:
数组 shape dtype axis
多数 NumPy 问题,本质都是:
我有什么形状的数据,要沿着哪个方向,用什么规则批量计算。
1. NumPy 是什么
NumPy 是 Python 中最重要的数值计算库。它的核心对象是 ndarray,也就是“多维数组”。
做数据分析、机器学习、科学计算、图像处理、金融计算,几乎绕不开 NumPy。
Python 列表的问题
Python 列表很灵活,可以放数字、字符串、对象,但这种灵活有代价:
- 每个元素都是 Python 对象,计算开销大。
- 列表相加是拼接,不是数学加法。
- 大量循环计算时速度慢。
NumPy 的优势
NumPy 数组通常存储同一种类型的数据,内存更紧凑,底层使用 C 语言做批量计算,所以更快。
一句话:
NumPy 擅长把一堆数字作为整体一次性计算,而不是让 Python 一个个循环处理。
import numpy as np
python_list_a = [1, 2, 3]
python_list_b = [10, 20, 30]
print("Python 列表相加:", python_list_a + python_list_b)
numpy_arr_a = np.array([1, 2, 3])
numpy_arr_b = np.array([10, 20, 30])
print("NumPy 数组相加:", numpy_arr_a + numpy_arr_b)
2. ndarray:NumPy 的核心对象
ndarray 是 N-dimensional array 的缩写,意思是 N 维数组。
你可以这样理解:
| 维度 | 例子 | 直观理解 |
|---|---|---|
| 0 维 | np.array(5) | 一个数 |
| 1 维 | [1, 2, 3] | 一排数 |
| 2 维 | [[1,2,3],[4,5,6]] | 表格或矩阵 |
| 3 维 | 多个二维矩阵叠起来 | 一摞表格 |
ndarray 最重要的属性
| 属性 | 含义 |
|---|---|
ndim | 维度数量 |
shape | 每个维度有多长 |
size | 总元素个数 |
dtype | 元素类型 |
itemsize | 每个元素占多少字节 |
nbytes | 整个数组占多少字节 |
最先要会看的就是 shape。很多错误都来自 shape 没看清。
arr0 = np.array(5)
arr1 = np.array([1, 2, 3])
arr2 = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.int32)
for name, arr in [("0维", arr0), ("1维", arr1), ("2维", arr2)]:
print(name)
print(arr)
print("ndim:", arr.ndim)
print("shape:", arr.shape)
print("size:", arr.size)
print("dtype:", arr.dtype)
print("-" * 30)
3. shape:数组形状怎么读
shape 是 NumPy 最重要的概念之一。
一维数组
arr = [1, 2, 3]
shape = (3,)
意思是:一维,里面有 3 个元素。
二维数组
arr = [[1, 2, 3],
[4, 5, 6]]
shape = (2, 3)
意思是:2 行 3 列。
三维数组
shape = (2, 3, 4)
意思可以理解为:
2 块,每块 3 行,每行 4 个数
size 的计算过程
如果:
shape = (2, 3, 4)
那么:
size = 2 * 3 * 4 = 24
如果 dtype=int32,每个元素占 4 字节:
nbytes = 24 * 4 = 96 字节
arr = np.arange(24, dtype=np.int32).reshape(2, 3, 4)
print("数组 shape:", arr.shape)
print("数组 size:", arr.size)
print("单个元素字节 itemsize:", arr.itemsize)
print("总字节 nbytes:", arr.nbytes)
print("手算 size:", 2 * 3 * 4)
print("手算 nbytes:", 2 * 3 * 4 * 4)
4. dtype:为什么 NumPy 要求同一种类型
NumPy 数组通常要求元素类型统一,这叫同质性。
这不是缺点,而是 NumPy 速度快的重要原因。
类型提升
如果你把不同类型的数据放进一个数组,NumPy 会自动找一个能兼容所有元素的类型。
常见情况:
| 输入 | 结果 dtype |
|---|---|
[1, 2, 3] | 整数 |
[1, 2.5, 3] | 浮点数 |
[1, "hello"] | 字符串 |
实用建议
做数值计算时,尽量保持数组是纯数字。
如果混入字符串,很多数学函数会不能正常使用。
print(np.array([1, 2, 3]).dtype)
print(np.array([1, 2.5, 3]).dtype)
print(np.array([1, "hello"]).dtype)
arr = np.array([1, 2, 3], dtype=np.float64)
print(arr)
print(arr.dtype)
5. 创建数组
NumPy 创建数组的方法很多。不要全部死记,按用途记就行。
| 用途 | 函数 |
|---|---|
| 从列表创建 | np.array() |
| 全 0 | np.zeros() |
| 全 1 | np.ones() |
| 固定值 | np.full() |
| 空数组 | np.empty() |
| 等差序列 | np.arange() |
| 指定个数等间隔 | np.linspace() |
| 单位矩阵 | np.eye() |
| 对角矩阵 | np.diag() |
| 按已有数组形状创建 | zeros_like、ones_like、full_like |
arange 和 linspace
arange 看步长:
np.arange(0, 10, 2)
表示从 0 到 10,不包含 10,步长是 2。
linspace 看数量:
np.linspace(0, 10, 6)
表示从 0 到 10,生成 6 个等间隔的数。
print("array:", np.array([1, 2, 3]))
print("zeros:\n", np.zeros((2, 3)))
print("ones:\n", np.ones((2, 3)))
print("full:\n", np.full((2, 3), 7))
print("arange:", np.arange(0, 10, 2))
print("linspace:", np.linspace(0, 10, 6))
print("eye:\n", np.eye(3))
print("diag:\n", np.diag([1, 2, 3]))
6. 随机数:生成模拟数据
随机数常用于:
- 生成练习数据。
- 模拟实验。
- 初始化机器学习模型参数。
- 抽样。
推荐使用:
rng = np.random.default_rng(种子)
种子的作用是让结果可复现。
同一个种子,每次生成的随机数相同,方便调试和写教程。
rng = np.random.default_rng(42)
print("随机整数:")
print(rng.integers(1, 10, size=(2, 3)))
print("0 到 1 的随机小数:")
print(rng.random((2, 3)))
print("正态分布:")
print(rng.normal(loc=0, scale=1, size=5))
7. 索引和切片:取出你想要的数据
一维数组
arr[0] # 第 1 个
arr[-1] # 最后 1 个
arr[1:4] # 第 2 个到第 4 个
arr[::2] # 每隔 1 个取
二维数组
二维数组写法:
arr[行, 列]
例如:
arr[1, 2]
表示第 2 行第 3 列。
计算过程
给定:
arr = [[ 1, 2, 3, 4],
[ 5, 6, 7, 8],
[ 9, 10, 11, 12]]
arr[1, 2]:
第 1 个索引 1 -> 第 2 行 [5, 6, 7, 8]
第 2 个索引 2 -> 第 3 个元素 7
结果 = 7
arr[:, 1]:
: 表示所有行
1 表示第 2 列
结果 = [2, 6, 10]
arr = np.arange(1, 13).reshape(3, 4)
print(arr)
print("第 2 行第 3 列:", arr[1, 2])
print("所有行第 2 列:", arr[:, 1])
print("前 2 行后 2 列:")
print(arr[:2, 2:])
8. 布尔索引:按条件筛选
布尔索引就是用条件筛选数组。
给定:
arr = [10, 25, 8, 30]
条件 arr >= 20
计算过程:
10 >= 20 -> False
25 >= 20 -> True
8 >= 20 -> False
30 >= 20 -> True
得到:
mask = [False, True, False, True]
再用 arr[mask]:
只保留 True 的位置 -> [25, 30]
多条件注意:
(arr > 10) & (arr < 30)
不要写成:
arr > 10 and arr < 30
arr = np.array([10, 25, 8, 30])
mask = arr >= 20
print("条件结果:", mask)
print("筛选结果:", arr[mask])
print("大于 10 且小于 30:", arr[(arr > 10) & (arr < 30)])
9. 修改数据:直接赋值和条件赋值
NumPy 可以直接修改数组。
常见写法:
arr[0] = 100
arr[arr < 0] = 0
条件替换常用 np.where:
np.where(条件, 条件为 True 的值, 条件为 False 的值)
np.where 计算过程
arr = [55, 80, 90]
条件 arr >= 60 -> [False, True, True]
np.where(arr >= 60, "及格", "不及格")
结果:
["不及格", "及格", "及格"]
score = np.array([55, 80, 90])
print(np.where(score >= 60, "及格", "不及格"))
arr = np.array([-2, 5, -1, 8])
arr[arr < 0] = 0
print(arr)
10. 视图和拷贝:为什么改一个,另一个也变
NumPy 中很重要的一点:
切片通常返回视图,不是新数组。
视图和原数组共享同一份数据。
改视图,原数组可能一起变。
如果你想要完全独立的一份数据,要使用:
copy()
直白理解
- 视图:同一份数据的另一个看法。
- 拷贝:重新复制一份数据。
数据清洗时,如果不希望污染原数据,记得 copy()。
arr = np.array([10, 20, 30, 40])
view = arr[1:3]
view[0] = 999
print("修改 view 后原数组:", arr)
arr = np.array([10, 20, 30, 40])
copy_arr = arr[1:3].copy()
copy_arr[0] = 999
print("修改 copy 后原数组:", arr)
print("copy_arr:", copy_arr)
11. axis:统计方向
axis 是 NumPy 最容易卡住的概念。
对二维数组:
arr = [[1, 2, 3],
[4, 5, 6]]
整体求和
1 + 2 + 3 + 4 + 5 + 6 = 21
axis=0
axis=0 表示沿着行的方向往下压,按列得到结果:
第 1 列: 1 + 4 = 5
第 2 列: 2 + 5 = 7
第 3 列: 3 + 6 = 9
结果: [5, 7, 9]
axis=1
axis=1 表示沿着列的方向往右压,按行得到结果:
第 1 行: 1 + 2 + 3 = 6
第 2 行: 4 + 5 + 6 = 15
结果: [6, 15]
记法:
axis=0:每列一个结果。axis=1:每行一个结果。
arr = np.array([[1, 2, 3],
[4, 5, 6]])
print("整体求和:", arr.sum())
print("axis=0 每列求和:", arr.sum(axis=0))
print("axis=1 每行求和:", arr.sum(axis=1))
print("axis=0 每列平均:", arr.mean(axis=0))
print("axis=1 每行平均:", arr.mean(axis=1))
12. 数组运算:逐元素计算
NumPy 的普通运算通常是逐元素计算。
a = [1, 2, 3]
b = [10, 20, 30]
a + b = [1+10, 2+20, 3+30] = [11, 22, 33]
a * b = [1*10, 2*20, 3*30] = [10, 40, 90]
这和 Python 列表完全不同。
Python 列表 + 是拼接,NumPy 数组 + 是数学加法。
a = np.array([1, 2, 3])
b = np.array([10, 20, 30])
print("a + b:", a + b)
print("a - b:", a - b)
print("a * b:", a * b)
print("b / a:", b / a)
print("a ** 2:", a ** 2)
13. 广播机制:不同形状也能计算
广播是 NumPy 的核心能力之一。
它允许小数组自动扩展成大数组的形状,再进行逐元素计算。
广播规则
从右往左对齐维度。每一维要满足:
- 两个维度相等。
- 或者其中一个维度是 1。
计算过程
a.shape = (2, 3)
b.shape = (3,)
对齐:
a: 2, 3
b: 3
最后一维都是 3,可以匹配。
于是 b 会被复制成:
[[10, 20, 30],
[10, 20, 30]]
然后逐元素相加:
[[1+10, 2+20, 3+30],
[4+10, 5+20, 6+30]]
a = np.array([[1, 2, 3],
[4, 5, 6]])
b = np.array([10, 20, 30])
print("a shape:", a.shape)
print("b shape:", b.shape)
print("b 广播后的样子:")
print(np.broadcast_to(b, a.shape))
print("a + b:")
print(a + b)
14. reshape、ravel、flatten、转置
数组变形是高频操作。
| 操作 | 作用 |
|---|---|
reshape | 改变形状,元素总数必须不变 |
ravel | 展平成一维,尽量返回视图 |
flatten | 展平成一维,一定返回拷贝 |
.T | 转置,二维中就是行列互换 |
reshape 计算过程
arr = [1, 2, 3, 4, 5, 6]
reshape(2, 3)
按原顺序填入:
第 1 行: [1, 2, 3]
第 2 行: [4, 5, 6]
结果:
[[1, 2, 3],
[4, 5, 6]]
-1 表示让 NumPy 自动推断:
arr.reshape(2, -1)
6 个元素,第一维是 2,所以第二维自动算出 3。
arr = np.arange(1, 7)
matrix = arr.reshape(2, 3)
print("原数组:", arr)
print("reshape(2, 3):")
print(matrix)
print("reshape(2, -1):")
print(arr.reshape(2, -1))
print("转置:")
print(matrix.T)
print("ravel:", matrix.ravel())
print("flatten:", matrix.flatten())
15. 常用数学函数
NumPy 提供了大量数学函数,而且都可以直接作用在数组上。
| 函数 | 作用 |
|---|---|
np.sqrt | 平方根 |
np.exp | 指数 |
np.log | 自然对数 |
np.sin / np.cos | 三角函数 |
np.abs | 绝对值 |
np.round | 四舍五入 |
np.ceil | 向上取整 |
np.floor | 向下取整 |
这些函数通常也是逐元素计算。
arr = np.array([1, 4, 9, 16])
print("sqrt:", np.sqrt(arr))
print("power:", np.power(arr, 2))
print("abs:", np.abs(np.array([-3, -1, 2])))
print("round:", np.round(np.array([1.2, 2.6, 3.5])))
print("ceil:", np.ceil(np.array([1.2, 2.6])))
print("floor:", np.floor(np.array([1.2, 2.6])))
16. 统计函数:从数据中提取信息
常见统计函数:
| 函数 | 含义 |
|---|---|
sum | 求和 |
mean | 平均值 |
median | 中位数 |
var | 方差 |
std | 标准差 |
min / max | 最小值 / 最大值 |
argmin / argmax | 最小值 / 最大值的位置 |
percentile | 分位数 |
cumsum | 累积和 |
cumprod | 累积积 |
平均值、方差、标准差计算过程
以 [1, 2, 3] 为例:
平均值 = (1 + 2 + 3) / 3 = 2
每个数减平均值:
1 - 2 = -1
2 - 2 = 0
3 - 2 = 1
平方:
(-1)^2 = 1
0^2 = 0
1^2 = 1
总体方差:
(1 + 0 + 1) / 3 = 0.666666...
标准差:
sqrt(0.666666...) = 0.816496...
arr = np.array([1, 2, 3])
mean = arr.mean()
deviation = arr - mean
square_deviation = deviation ** 2
var = square_deviation.mean()
std = np.sqrt(var)
print("平均值:", mean)
print("偏差:", deviation)
print("偏差平方:", square_deviation)
print("手算方差:", var)
print("手算标准差:", std)
print("NumPy 方差:", np.var(arr))
print("NumPy 标准差:", np.std(arr))
17. 中位数和分位数
中位数
先排序,再找中间。
奇数个:
[1, 3, 8] -> 中位数 3
偶数个:
[1, 3, 8, 10] -> (3 + 8) / 2 = 5.5
分位数计算过程
以 [44, 47, 64, 67] 的 25 分位数为例。
- 排序:
[44, 47, 64, 67] - 位置范围:0 到 3
- 25% 位置:
0.25 * (4 - 1) = 0.75 - 位置 0 是 44,位置 1 是 47
- 0.75 表示从 44 到 47 走 75%
44 + (47 - 44) * 0.75
= 44 + 3 * 0.75
= 46.25
arr = np.array([44, 47, 64, 67])
p = 25
position = p / 100 * (len(arr) - 1)
left = int(np.floor(position))
right = int(np.ceil(position))
ratio = position - left
manual = arr[left] + (arr[right] - arr[left]) * ratio
print("位置:", position)
print("左值:", arr[left])
print("右值:", arr[right])
print("比例:", ratio)
print("手算 25 分位数:", manual)
print("NumPy 25 分位数:", np.percentile(arr, 25))
print("中位数:", np.median(arr))
18. 缺失值 NaN
NaN 表示 Not a Number,常用来表示缺失值或非法结果。
注意:
np.nan == np.nan
结果是 False。
判断 NaN 要使用:
np.isnan()
如果数组里有 NaN,普通统计函数会得到 NaN。
可以使用忽略 NaN 的版本:
| 普通函数 | 忽略 NaN |
|---|---|
sum | nansum |
mean | nanmean |
std | nanstd |
max | nanmax |
min | nanmin |
data = np.array([10, 20, np.nan, 40])
print("是否缺失:", np.isnan(data))
print("普通平均:", np.mean(data))
print("忽略 NaN 平均:", np.nanmean(data))
filled = np.where(np.isnan(data), np.nanmean(data), data)
print("用均值填充:", filled)
19. 排序、去重、计数
常用函数:
| 函数 | 作用 |
|---|---|
np.sort | 排序,返回新数组 |
arr.sort() | 原地排序,修改原数组 |
np.argsort | 返回排序索引 |
np.unique | 去重并排序 |
np.unique(return_counts=True) | 去重并统计次数 |
argsort 的计算过程
scores = [82, 95, 78]
从小到大排序应该是:
78, 82, 95
它们在原数组的位置是:
78 -> 位置 2
82 -> 位置 0
95 -> 位置 1
所以:
argsort = [2, 0, 1]
scores = np.array([82, 95, 78])
names = np.array(["张三", "李四", "王五"])
order = np.argsort(scores)
print("排序索引:", order)
print("从低到高分数:", scores[order])
print("从低到高姓名:", names[order])
print("从高到低姓名:", names[order[::-1]])
arr = np.array([2, 1, 2, 3, 1, 4, 3])
values, counts = np.unique(arr, return_counts=True)
print("唯一值:", values)
print("次数:", counts)
20. 拼接和拆分
常用函数:
| 函数 | 作用 |
|---|---|
concatenate | 沿已有维度拼接 |
stack | 增加新维度后拼接 |
hstack | 水平方向拼接 |
vstack | 垂直方向拼接 |
split | 拆分数组 |
concatenate 和 stack 的区别
a = [1, 2, 3]
b = [4, 5, 6]
concatenate:
[1, 2, 3, 4, 5, 6]
stack:
[[1, 2, 3],
[4, 5, 6]]
stack 会多出一个维度。
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
print("concatenate:", np.concatenate([a, b]))
print("stack:")
print(np.stack([a, b]))
print("vstack:")
print(np.vstack([a, b]))
print("hstack:", np.hstack([a, b]))
arr = np.arange(10)
print("split:", np.split(arr, [3, 7]))
21. 矩阵运算
NumPy 里要区分:
| 写法 | 含义 |
|---|---|
A * B | 逐元素乘法 |
A @ B | 矩阵乘法 |
np.dot(A, B) | 点积或矩阵乘法 |
矩阵乘法要求
A.shape = (m, n)
B.shape = (n, k)
中间的 n 必须相等。
结果 shape:
(m, k)
矩阵乘法计算过程
A = [[1, 2],
[3, 4]]
B = [[5, 6],
[7, 8]]
第 1 行第 1 列:
1*5 + 2*7 = 19
第 1 行第 2 列:
1*6 + 2*8 = 22
第 2 行第 1 列:
3*5 + 4*7 = 43
第 2 行第 2 列:
3*6 + 4*8 = 50
结果:
[[19, 22],
[43, 50]]
A = np.array([[1, 2],
[3, 4]])
B = np.array([[5, 6],
[7, 8]])
manual = np.array([
[1 * 5 + 2 * 7, 1 * 6 + 2 * 8],
[3 * 5 + 4 * 7, 3 * 6 + 4 * 8],
])
print("逐元素乘法:")
print(A * B)
print("手算矩阵乘法:")
print(manual)
print("NumPy 矩阵乘法:")
print(A @ B)
22. 线性代数入门
线性代数相关函数在 np.linalg 中。
常用函数:
| 函数 | 作用 |
|---|---|
np.linalg.solve | 解线性方程组 |
np.linalg.inv | 求逆矩阵 |
np.linalg.det | 求行列式 |
np.linalg.eig | 特征值和特征向量 |
解方程组
方程:
2x + y = 8
x + 3y = 13
写成矩阵:
[[2, 1],
[1, 3]] @ [x, y] = [8, 13]
用 np.linalg.solve 可以直接求 [x, y]。
coef = np.array([[2, 1],
[1, 3]])
target = np.array([8, 13])
solution = np.linalg.solve(coef, target)
print("x, y:", solution)
print("验证:")
print(coef @ solution)
23. 标准化:机器学习常见预处理
标准化公式:
z = (x - mean) / std
作用:
- 减去均值,让数据中心变成 0 附近。
- 除以标准差,让波动尺度变成 1 附近。
计算过程
以 [10, 20, 30] 为例:
mean = (10 + 20 + 30) / 3 = 20
偏差:
10 - 20 = -10
20 - 20 = 0
30 - 20 = 10
方差:
((-10)^2 + 0^2 + 10^2) / 3 = 200 / 3 = 66.666...
标准差:
sqrt(66.666...) = 8.1649
标准化:
10 -> -10 / 8.1649 = -1.2247
20 -> 0 / 8.1649 = 0
30 -> 10 / 8.1649 = 1.2247
data = np.array([[10, 100],
[20, 200],
[30, 300]], dtype=float)
mean = data.mean(axis=0)
std = data.std(axis=0)
standardized = (data - mean) / std
print("每列均值:", mean)
print("每列标准差:", std)
print("标准化后:")
print(np.round(standardized, 4))
print("标准化后每列均值:", np.round(standardized.mean(axis=0), 10))
print("标准化后每列标准差:", np.round(standardized.std(axis=0), 10))
24. 综合案例:销售利润分析
给定 5 天销售额和成本:
销售额: [20, 25, 22, 30, 28]
成本: [15, 18, 16, 22, 20]
要求:
- 计算每天利润。
- 计算平均利润和标准差。
- 找出利润最高的天数。
- 计算利润率。
计算过程
每天利润:
第 1 天: 20 - 15 = 5
第 2 天: 25 - 18 = 7
第 3 天: 22 - 16 = 6
第 4 天: 30 - 22 = 8
第 5 天: 28 - 20 = 8
profit = [5, 7, 6, 8, 8]
平均利润:
(5 + 7 + 6 + 8 + 8) / 5 = 34 / 5 = 6.8
标准差:
偏差 = [-1.8, 0.2, -0.8, 1.2, 1.2]
偏差平方 = [3.24, 0.04, 0.64, 1.44, 1.44]
方差 = 6.8 / 5 = 1.36
标准差 = sqrt(1.36) = 1.166
利润最高是 8,出现在第 4 天和第 5 天。
sales = np.array([20, 25, 22, 30, 28])
cost = np.array([15, 18, 16, 22, 20])
profit = sales - cost
profit_rate = profit / sales
print("每天利润:", profit)
print("利润率:", np.round(profit_rate, 4))
print("平均利润:", profit.mean())
print("利润标准差:", profit.std())
print("最高利润:", profit.max())
print("最高利润天数:", np.where(profit == profit.max())[0] + 1)
25. 综合案例:成绩分析
有 5 名学生、3 门课成绩:
[[80, 85, 90],
[70, 75, 78],
[95, 88, 92],
[60, 65, 70],
[88, 90, 86]]
每行是一个学生,每列是一门课。
要求:
- 计算每个学生平均分。
- 计算每门课平均分。
- 找出总分最高的学生。
- 按平均分划分等级。
计算流程
每个学生平均分:按行算,axis=1。
每门课平均分:按列算,axis=0。
总分最高:先按行求和,再找最大值位置。
score = np.array([
[80, 85, 90],
[70, 75, 78],
[95, 88, 92],
[60, 65, 70],
[88, 90, 86],
])
student_mean = score.mean(axis=1)
course_mean = score.mean(axis=0)
student_total = score.sum(axis=1)
best_student = np.argmax(student_total) + 1
level = np.select(
[student_mean >= 90, student_mean >= 80, student_mean >= 60],
["优秀", "良好", "及格"],
default="不及格",
)
print("每个学生平均分:", np.round(student_mean, 2))
print("每门课平均分:", np.round(course_mean, 2))
print("每个学生总分:", student_total)
print("总分最高学生编号:", best_student)
print("等级:", level)
26. 常见错误和排查方法
错误 1:shape 不匹配
常见报错:
operands could not be broadcast together
解决:
print(a.shape)
print(b.shape)
从右往左检查维度是否相等或是否有 1。
错误 2:多条件筛选忘记括号
错误:
arr[arr > 3 & arr < 8]
正确:
arr[(arr > 3) & (arr < 8)]
错误 3:把 * 当矩阵乘法
A * B
是逐元素乘法。
矩阵乘法要写:
A @ B
错误 4:切片修改影响原数组
如果不想影响原数组,用:
arr.copy()
错误 5:整数数组里放 NaN
np.nan 是浮点概念。需要缺失值时,数组通常要是浮点类型。
27. NumPy 总结
一句话总结
NumPy 的本质是:用多维数组表达数据,用向量化运算批量处理数据。
必须掌握的 12 个点
ndarray是 NumPy 的核心。shape决定数组结构。dtype决定类型和内存。axis决定统计方向。- 数组普通运算通常是逐元素运算。
- 布尔索引用于按条件筛选。
np.where用于条件替换。- 切片通常是视图,
copy()才是独立副本。 - 广播机制让不同形状的数组可以一起算。
reshape改形状,元素总数不能变。A * B是逐元素乘法,A @ B是矩阵乘法。- 少写 Python 循环,多用向量化。
解题思路
遇到 NumPy 问题,先问:
1. 数据 shape 是什么?
2. 要按行算还是按列算?
3. 是筛选、替换、统计,还是矩阵运算?
4. 是否需要保留原数组?
5. 结果 shape 应该是什么?
28. 扩展学习路线
第一阶段:数组基础
ndarrayshapedtype- 索引切片
- 布尔筛选
第二阶段:数组计算
- 向量化
- 广播
- 统计函数
- 缺失值处理
- 排序去重
第三阶段:数学能力
- 矩阵乘法
- 线性方程组
- 标准化
- 随机模拟
- 简单概率统计
第四阶段:连接真实应用
- Pandas:表格数据分析
- Matplotlib / Seaborn:数据可视化
- SciPy:科学计算
- scikit-learn:机器学习
- PyTorch:深度学习张量计算
NumPy 学得越扎实,后面学 Pandas、机器学习、深度学习越轻松。
练习题
下面练习都有答案和计算过程。建议你先自己做,再看答案。
练习 1:温度分析
某城市一周最高气温:
[26, 30, 29, 31, 30, 32, 29]
要求:
- 计算平均气温。
- 找出最高气温和最低气温。
- 统计超过 30 度的天数。
- 找出超过 30 度的具体温度。
# 练习 1 答案
temps = np.array([26, 30, 29, 31, 30, 32, 29])
print("平均气温:", temps.mean())
print("最高气温:", temps.max())
print("最低气温:", temps.min())
print("超过 30 度天数:", np.count_nonzero(temps > 30))
print("超过 30 度温度:", temps[temps > 30])
练习 1 计算过程
平均气温:
(26 + 30 + 29 + 31 + 30 + 32 + 29) / 7
= 207 / 7
= 29.5714
超过 30 度:
26 > 30 -> False
30 > 30 -> False
29 > 30 -> False
31 > 30 -> True
30 > 30 -> False
32 > 30 -> True
29 > 30 -> False
所以超过 30 度的是 [31, 32],共 2 天。
练习 2:成绩统计
成绩:
[85, 90, 78, 92, 88]
要求:
- 计算平均分、中位数、标准差。
- 找出最高分的位置。
- 将成绩按规则分类:
>=90 优秀,>=80 良好,否则继续努力。
# 练习 2 答案
score = np.array([85, 90, 78, 92, 88])
level = np.select(
[score >= 90, score >= 80],
["优秀", "良好"],
default="继续努力",
)
print("平均分:", score.mean())
print("中位数:", np.median(score))
print("标准差:", score.std())
print("最高分位置:", score.argmax())
print("等级:", level)
练习 2 计算过程
平均分:
(85 + 90 + 78 + 92 + 88) / 5 = 433 / 5 = 86.6
中位数:
排序后: [78, 85, 88, 90, 92]
中间位置是 88
最高分:
最高分是 92,位置是 3
注意:Python 和 NumPy 的位置从 0 开始,所以第 4 个元素的位置是 3。
练习 3:矩阵运算
给定:
A = [[1, 2],
[3, 4]]
B = [[5, 6],
[7, 8]]
要求:
- 计算
A + B。 - 计算逐元素乘法
A * B。 - 计算矩阵乘法
A @ B。
# 练习 3 答案
A = np.array([[1, 2],
[3, 4]])
B = np.array([[5, 6],
[7, 8]])
print("A + B:")
print(A + B)
print("A * B:")
print(A * B)
print("A @ B:")
print(A @ B)
练习 3 计算过程
逐元素加法:
[[1+5, 2+6],
[3+7, 4+8]]
= [[6, 8],
[10, 12]]
逐元素乘法:
[[1*5, 2*6],
[3*7, 4*8]]
= [[5, 12],
[21, 32]]
矩阵乘法:
第 1 行第 1 列 = 1*5 + 2*7 = 19
第 1 行第 2 列 = 1*6 + 2*8 = 22
第 2 行第 1 列 = 3*5 + 4*7 = 43
第 2 行第 2 列 = 3*6 + 4*8 = 50
练习 4:数组变形和 axis
创建 1 到 12 的数组,并变成 3 行 4 列。
要求:
- 计算每行的和。
- 计算每列的平均值。
- 展平成一维数组。
# 练习 4 答案
arr = np.arange(1, 13).reshape(3, 4)
print("数组:")
print(arr)
print("每行的和:", arr.sum(axis=1))
print("每列的平均值:", arr.mean(axis=0))
print("展平:", arr.ravel())
练习 4 计算过程
数组:
[[ 1, 2, 3, 4],
[ 5, 6, 7, 8],
[ 9, 10, 11, 12]]
每行求和:
第 1 行: 1 + 2 + 3 + 4 = 10
第 2 行: 5 + 6 + 7 + 8 = 26
第 3 行: 9 + 10 + 11 + 12 = 42
每列平均:
第 1 列: (1 + 5 + 9) / 3 = 5
第 2 列: (2 + 6 + 10) / 3 = 6
第 3 列: (3 + 7 + 11) / 3 = 7
第 4 列: (4 + 8 + 12) / 3 = 8
练习 5:标准化
给定二维数据:
[[10, 100],
[20, 200],
[30, 300]]
要求:
- 计算每列均值。
- 计算每列标准差。
- 对每列做标准化。
- 验证标准化后每列均值接近 0,标准差接近 1。
# 练习 5 答案
data = np.array([[10, 100],
[20, 200],
[30, 300]], dtype=float)
mean = data.mean(axis=0)
std = data.std(axis=0)
z = (data - mean) / std
print("每列均值:", mean)
print("每列标准差:", std)
print("标准化结果:")
print(np.round(z, 4))
print("标准化后均值:", np.round(z.mean(axis=0), 10))
print("标准化后标准差:", np.round(z.std(axis=0), 10))
练习 5 计算过程
第 1 列 [10, 20, 30]:
均值 = 20
标准差 = 8.1649
标准化结果 = [-1.2247, 0, 1.2247]
第 2 列 [100, 200, 300]:
均值 = 200
标准差 = 81.6497
标准化结果 = [-1.2247, 0, 1.2247]
虽然两列数值尺度不同,但标准化后形状一样。
这就是标准化的作用:消除量纲和尺度差异。
练习 6:综合销售分析
给定 6 天销售额和成本:
销售额 = [100, 120, 90, 150, 130, 160]
成本 = [60, 80, 50, 100, 85, 110]
要求:
- 计算每天利润。
- 计算每天利润率。
- 找出利润最高的一天。
- 计算总销售额、总成本、总利润。
- 计算整体利润率。
- 找出利润率高于整体利润率的天数。
# 练习 6 答案
sales = np.array([100, 120, 90, 150, 130, 160])
cost = np.array([60, 80, 50, 100, 85, 110])
profit = sales - cost
profit_rate = profit / sales
total_sales = sales.sum()
total_cost = cost.sum()
total_profit = profit.sum()
overall_profit_rate = total_profit / total_sales
high_days = np.where(profit_rate > overall_profit_rate)[0] + 1
print("每天利润:", profit)
print("每天利润率:", np.round(profit_rate, 4))
print("利润最高的一天:", profit.argmax() + 1)
print("总销售额:", total_sales)
print("总成本:", total_cost)
print("总利润:", total_profit)
print("整体利润率:", round(overall_profit_rate, 4))
print("利润率高于整体利润率的天数:", high_days)
练习 6 计算过程
每天利润:
第 1 天: 100 - 60 = 40
第 2 天: 120 - 80 = 40
第 3 天: 90 - 50 = 40
第 4 天: 150 - 100 = 50
第 5 天: 130 - 85 = 45
第 6 天: 160 - 110 = 50
总销售额:
100 + 120 + 90 + 150 + 130 + 160 = 750
总成本:
60 + 80 + 50 + 100 + 85 + 110 = 485
总利润:
750 - 485 = 265
整体利润率:
265 / 750 = 0.3533 = 35.33%
注意:整体利润率不是每天利润率的简单平均。
正确方式是:
总利润 / 总销售额
最后
NumPy 的学习重点不是“背多少函数”,而是形成数组思维。
把问题翻译成 NumPy 时,先说清楚:
数据是什么 shape?
我要沿哪个 axis 算?
是逐元素、筛选、聚合、广播,还是矩阵运算?
当你能稳定回答这三个问题,NumPy 就不再是零散函数,而是一套清晰的数值计算语言。

1496

被折叠的 条评论
为什么被折叠?



