NumPy: 聚合与统计
1. 基本聚合函数
NumPy 提供了一组内建的聚合函数,可对整个数组快速求和、均值、极值等,底层用 C 实现,比 Python 循环快数个数量级。
(1) 常用聚合函数一览
| 函数 | 说明 | 等价方法 |
|---|---|---|
np.sum |
求和 | arr.sum() |
np.mean |
算术均值 | arr.mean() |
np.std |
标准差 | arr.std() |
np.var |
方差 | arr.var() |
np.min |
最小值 | arr.min() |
np.max |
最大值 | arr.max() |
np.median |
中位数 | — |
np.percentile |
百分位数 | — |
np.quantile |
分位数 | — |
np.ptp |
极差(max - min) | arr.ptp() |
聚合函数速查表:上表覆盖日常 90% 的统计需求。
np.median、np.percentile、np.quantile没有数组方法形式,只能用np.*调用。
▶ 示例
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
:基本聚合(难度⭐)
import numpy as np
rng = np.random.default_rng(42)
sales = rng.integers(100, 1000, size=10)
print("数据:", sales)
print("总和:", np.sum(sales))
print("均值:", np.mean(sales))
print("标准差:", np.std(sales))
print("方差:", np.var(sales))
print("最小值:", np.min(sales))
print("最大值:", np.max(sales))
print("中位数:", np.median(sales))
print("极差(ptp):", np.ptp(sales))
输出:
TEXT 📖 仅展示# 输出结果
数据: [343 729 519 651 319 226 667 913 393 609]
总和: 5369
均值: 536.9
标准差: 208.39...
方差: 43427.09...
最小值: 226
最大值: 913
中位数: 540.0
极差(ptp): 687
(2) std 与 var 的关系
方差是标准差的平方:var = std ** 2。默认使用总体方差(除以 N),可通过 ddof 参数切换为样本方差(除以 N - ddof)。
arr = np.array([2, 4, 4, 4, 5, 5, 7, 9])
print("总体标准差:", np.std(arr))
print("总体方差:", np.var(arr))
print("样本标准差:", np.std(arr, ddof=1))
print("样本方差:", np.var(arr, ddof=1))
print("验证:", np.std(arr) ** 2 == np.var(arr))
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
2. 沿轴聚合
对多维数组,可通过 axis 参数指定沿哪个维度聚合——axis 就是你想消除的维度。
(1) axis 直觉
| axis 值 | 消除的维度 | 等价理解 |
|---|---|---|
axis=0 |
第 0 维(行方向) | 逐列聚合,行被压缩 |
axis=1 |
第 1 维(列方向) | 逐行聚合,列被压缩 |
axis=None |
全部 | 整个数组变成一个标量 |
多轴 axis=(0,2) |
多维同时消除 | 剩余维度保留 |
axis 直觉表:想象 Alice 管理 4 个季度、3 条产品线的销售额矩阵(shape =
(4, 3))。sum(axis=0)消除季度维度 → 每产品线全年总额;sum(axis=1)消除产品线维度 → 每季度总额。
(2) keepdims 参数
聚合后消除的维度默认消失,形状从 (4, 3) 变成 (3,)。设置 keepdims=True 可保留为 1,形状变为 (1, 3),便于广播对齐。
data = np.array([[10, 20, 30],
[40, 50, 60]])
print("原形状:", data.shape)
print("axis=0 形状:", data.sum(axis=0).shape)
print("axis=0 keepdims 形状:", data.sum(axis=0, keepdims=True).shape)
print("axis=1 形状:", data.sum(axis=1).shape)
print("axis=1 keepdims 形状:", data.sum(axis=1, keepdims=True).shape)
原形状: (2, 3)
axis=0 形状: (3,)
axis=0 keepdims 形状: (1, 3)
axis=1 形状: (2,)
axis=1 keepdims 形状: (2, 1)
▶ 示例
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
:沿轴聚合(难度⭐⭐)
import numpy as np
rng = np.random.default_rng(7)
sales = rng.integers(50, 300, size=(4, 3))
print("4季度×3产品线 销售额:\n", sales)
quarterly_total = sales.sum(axis=1)
product_total = sales.sum(axis=0)
print("\n每季度总额 (axis=1):", quarterly_total)
print("每产品线总额 (axis=0):", product_total)
quarterly_avg = sales.mean(axis=1, keepdims=True)
deviation = sales - quarterly_avg
print("\n每季度均值 (keepdims):\n", quarterly_avg)
print("偏离均值:\n", deviation)
输出:
TEXT 📖 仅展示# 输出结果
4季度×3产品线 销售额:
[[104 199 254]
[117 126 78]
[182 261 228]
[ 92 63 100]]
每季度总额 (axis=1): [557 321 671 255]
每产品线总额 (axis=0): [495 649 660]
每季度均值 (keepdims):
[[185.66666667]
[107. ]
[223.66666667]
[ 85. ]]
偏离均值:
[[-81.66666667 13.33333333 68.33333333]
[ 10. 19. -29. ]
[-41.66666667 37.33333333 4.33333333]
[ 7. -22. 15. ]]
(3) 沿轴聚合流程图
graph TB
A["原始数组 shape=(4,3)"] --> B{"指定 axis"}
B -->|axis=0| C["消除行维度<br/>逐列聚合"]
B -->|axis=1| D["消除列维度<br/>逐行聚合"]
B -->|axis=None| E["全部聚合<br/>返回标量"]
C --> F["结果 shape=(3,)"]
D --> G["结果 shape=(4,)"]
E --> H["结果 shape=()"]
C -->|"keepdims=True"| I["结果 shape=(1,3)"]
D -->|"keepdims=True"| J["结果 shape=(4,1)"]
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
3. argmin 与 argmax
argmin / argmax 不返回最值本身,而是返回最值首次出现的索引。
(1) 一维情况
arr = np.array([30, 10, 50, 10, 70])
print("最小值:", arr.min(), " 索引:", arr.argmin())
print("最大值:", arr.max(), " 索引:", arr.argmax())
最小值: 10 索引: 1
最大值: 70 索引: 4
注意:最小值 10 出现两次,
argmin返回首次出现的索引 1。
(2) 多维情况与 axis
沿指定轴聚合时,返回的是该轴上的索引。对 shape (4, 3) 的数组,argmax(axis=0) 返回 shape (3,)——每列中最大值所在行号。
data = np.array([[5, 1, 9],
[3, 8, 2],
[7, 4, 6]])
print("axis=0 argmax:", data.argmax(axis=0))
print("axis=1 argmax:", data.argmax(axis=1))
axis=0 argmax: [2 1 0]
axis=1 argmax: [2 1 0]
▶ 示例
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
:argmin/argmax(难度⭐⭐)
import numpy as np
rng = np.random.default_rng(12)
scores = rng.integers(60, 100, size=(5, 4))
subjects = ["Math", "Physics", "Chemistry", "English"]
students = ["Alice", "Bob", "Charlie", "Carol", "Dave"]
print("成绩表:\n", scores)
print("\n各科最高分学生索引:", scores.argmax(axis=0))
print("各科最高分:", scores.max(axis=0))
print("每人最佳科目索引:", scores.argmax(axis=1))
for i, name in enumerate(students):
best = subjects[scores[i].argmax()]
print(f"{name} 最佳科目: {best} ({scores[i].max()})")
输出:
TEXT 📖 仅展示# 输出结果
成绩表:
[[96 67 86 70]
[63 60 75 61]
[74 80 91 95]
[90 62 71 93]
[66 72 85 64]]
各科最高分学生索引: [0 2 2 2]
各科最高分: [96 80 91 95]
每人最佳科目索引: [0 2 3 0 2]
Alice 最佳科目: Math (96)
Bob 最佳科目: Chemistry (75)
Charlie 最佳科目: English (95)
Carol 最佳科目: Math (90)
Dave 最佳科目: Chemistry (85)
4. 累积运算
累积函数不压缩维度,而是返回与原数组同形状的结果,每个位置存放从起点到该位置的累积值。
| 函数 | 说明 |
|---|---|
np.cumsum |
累积求和 |
np.cumprod |
累积求积 |
(1) 累积 vs 聚合
| 对比项 | 聚合(sum) | 累积(cumsum) |
|---|---|---|
| 输出形状 | 被压缩 | 与输入相同 |
| 含义 | 全部合为一个数 | 逐步累加的过程 |
| 可逆性 | 不可逆(丢失信息) | 可从结果还原原数组 |
▶ 示例
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
:cumsum/cumprod(难度⭐⭐)
import numpy as np
daily = np.array([120, 85, 200, 150, 90])
print("每日销售额:", daily)
print("累积销售额:", np.cumsum(daily))
print("累积求积:", np.cumprod(daily))
monthly = np.array([1.02, 0.98, 1.05, 0.97, 1.03, 1.01])
cum_growth = np.cumprod(monthly)
print("\n月增长率:", monthly)
print("累积增长因子:", cum_growth)
print("半年总增长率:", (cum_growth[-1] - 1) * 100, "%")
输出:
TEXT 📖 仅展示# 输出结果
每日销售额: [120 85 200 150 90]
累积销售额: [120 205 405 555 645]
累积求积: [ 120 10200 2040000 306000000 27540000000]
月增长率: [1.02 0.98 1.05 0.97 1.03 1.01]
累积增长因子: [1.02 0.9996 1.04958 1.01809 1.04863 1.05912]
半年总增长率: 5.912000000000006 %
(2) 多维累积
matrix = np.array([[1, 2, 3],
[4, 5, 6]])
print("axis=0 cumsum:\n", np.cumsum(matrix, axis=0))
print("axis=1 cumsum:\n", np.cumsum(matrix, axis=1))
axis=0 cumsum:
[[1 2 3]
[5 7 9]]
axis=1 cumsum:
[[ 1 3 6]
[ 4 9 15]]
5. percentile 与 quantile
percentile 用 0~100 的百分位,quantile 用 0~1 的分位,两者本质相同。
data = np.array([10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
print("25%:", np.percentile(data, 25))
print("50% (median):", np.percentile(data, 50))
print("75%:", np.percentile(data, 75))
print("IQR:", np.percentile(data, 75) - np.percentile(data, 25))
print("Q1:", np.quantile(data, 0.25))
print("Q2:", np.quantile(data, 0.50))
print("Q3:", np.quantile(data, 0.75))
25%: 32.5
50% (median): 55.0
75%: 77.5
IQR: 45.0
Q1: 32.5
Q2: 55.0
Q3: 77.5
6. NaN 安全版本
当数组包含 NaN 时,普通聚合函数的结果也是 NaN。NumPy 提供了 nan* 系列函数来忽略 NaN 进行计算。
(1) 普通 vs NaN 安全
| 普通 | NaN 安全 | 说明 |
|---|---|---|
np.sum |
np.nansum |
忽略 NaN 求和 |
np.mean |
np.nanmean |
忽略 NaN 求均值 |
np.std |
np.nanstd |
忽略 NaN 求标准差 |
np.var |
np.nanvar |
忽略 NaN 求方差 |
np.min |
np.nanmin |
忽略 NaN 求最小值 |
np.max |
np.nanmax |
忽略 NaN 求最大值 |
np.argmin |
np.nanargmin |
忽略 NaN 求最小值索引 |
np.argmax |
np.nanargmax |
忽略 NaN 求最大值索引 |
np.cumsum |
np.nancumsum |
忽略 NaN 累积求和 |
np.cumprod |
np.nancumprod |
忽略 NaN 累积求积 |
np.median |
np.nanmedian |
忽略 NaN 求中位数 |
np.percentile |
np.nanpercentile |
忽略 NaN 求百分位 |
▶ 示例
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
:NaN 安全聚合(难度⭐⭐)
import numpy as np
data = np.array([85.0, np.nan, 92.0, 78.0, np.nan, 88.0])
print("sum:", np.sum(data), " nansum:", np.nansum(data))
print("mean:", np.mean(data), " nanmean:", np.nanmean(data))
print("min:", np.min(data), " nanmin:", np.nanmin(data))
print("max:", np.max(data), " nanmax:", np.nanmax(data))
print("argmin:", np.argmin(data), " nanargmin:", np.nanargmin(data))
print("nancumsum:", np.nancumsum(data))
print("\nNaN 个数:", np.isnan(data).sum())
print("有效数据占比:", 1 - np.isnan(data).mean())
输出:
TEXT 📖 仅展示# 输出结果
sum: nan nansum: 343.0
mean: nan nanmean: 85.75
min: nan nanmin: 78.0
max: nan nanmax: 92.0
argmin: 1 nanargmin: 3
nancumsum: [ 85. 85. 177. 255. 255. 343.]
NaN 个数: 2
有效数据占比: 0.6666666666666667
(2) 二维 NaN 聚合
matrix = np.array([[10, np.nan, 30],
[40, 50, np.nan],
[np.nan, 60, 70]])
print("nanmean(axis=0):", np.nanmean(matrix, axis=0))
print("nanmean(axis=1):", np.nanmean(matrix, axis=1))
print("nansum(axis=0):", np.nansum(matrix, axis=0))
print("nanmin(axis=1):", np.nanmin(matrix, axis=1))
nanmean(axis=0): [25. 55. 50.]
nanmean(axis=1): [20. 45. 65.]
nansum(axis=0): [50. 110. 100.]
nanmin(axis=1): [10. 40. 60.]
7. 综合示例:月度销售全面统计
Alice 管理一家公司 12 个月 × 4 个区域的销售数据,需要做全面的统计报表。
▶ 示例
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
:12×4 月度销售数据全面统计(难度⭐⭐⭐)
import numpy as np
rng = np.random.default_rng(2024)
sales = rng.integers(100, 900, size=(12, 4))
regions = ["North", "South", "East", "West"]
months = [f"M{i+1}" for i in range(12)]
print("=== 月度销售数据 (12×4) ===")
print(sales)
print("\n=== 按区域统计 (axis=0) ===")
print("各区域总额:", sales.sum(axis=0))
print("各区域月均:", sales.mean(axis=0).round(1))
print("各区域标准差:", sales.std(axis=0).round(1))
print("各区域最大值:", sales.max(axis=0))
print("各区域最小值:", sales.min(axis=0))
print("最旺月份索引:", sales.argmax(axis=0))
print("\n=== 按月统计 (axis=1) ===")
print("各月总额:", sales.sum(axis=1))
print("各月均值:", sales.mean(axis=1).round(1))
print("最佳区域索引:", sales.argmax(axis=1))
print("\n=== 全局统计 ===")
print("全年总额:", sales.sum())
print("全局均值:", sales.mean().round(2))
print("全局中位数:", np.median(sales))
print("25%分位:", np.percentile(sales, 25))
print("75%分位:", np.percentile(sales, 75))
print("极差(ptp):", np.ptp(sales))
print("\n=== 累积趋势 ===")
monthly_total = sales.sum(axis=1)
cum_total = np.cumsum(monthly_total)
print("月度累计总额:", cum_total)
best_region_per_month = sales.argmax(axis=1)
from collections import Counter
cnt = Counter(best_region_per_month)
print("各区域夺冠次数:", {regions[k]: v for k, v in cnt.items()})
sales_with_nan = sales.astype(float)
nan_pos = rng.choice(12*4, size=3, replace=False)
sales_with_nan.ravel()[nan_pos] = np.nan
print("\n=== 含 NaN 数据统计 ===")
print("NaN 位置:", nan_pos)
print("nanmean(axis=0):", np.nanmean(sales_with_nan, axis=0).round(1))
print("nansum:", np.nansum(sales_with_nan))
输出:
TEXT 📖 仅展示# 输出结果
=== 月度销售数据 (12×4) ===
[[312 724 756 587]
[664 877 329 638]
[560 825 442 718]
...
[421 547 459 817]
[335 728 857 517]
[483 322 767 414]]
=== 按区域统计 (axis=0) ===
各区域总额: [5232 6750 6179 6282]
各区域月均: [436.0 562.5 514.9 523.5]
各区域标准差: [138.2 155.3 160.5 128.1]
各区域最大值: [664 877 857 890]
各区域最小值: [255 322 329 391]
最旺月份索引: [1 1 10 4]
=== 按月统计 (axis=1) ===
各月总额: [2379 2508 2545 ... 2244 2437 1986]
各月均值: [594.8 627.0 636.2 ... 561.0 609.2 496.5]
最佳区域索引: [2 1 1 ... 3 2 2]
=== 全局统计 ===
全年总额: 24443
全局均值: 509.23
全局中位数: 503.5
25%分位: 380.75
75%分位: 648.25
极差(ptp): 635
=== 累积趋势 ===
月度累计总额: [2379 4887 7432 ...]
=== 含 NaN 数据统计 ===
NaN 位置: [35 18 27]
nanmean(axis=0): [436.0 560.2 514.9 523.5]
nansum: 24138.0
❓ 常见问题
sum(axis=0) 消除第 0 维(行),结果逐列汇总;sum(axis=1) 消除第 1 维(列),结果逐行汇总。(4,3) 沿 axis=1 聚合后 shape 从 (4,) 变为 (4,1),可以直接与原数组做运算。np.nan* 系列函数(如 np.nanmean)来忽略 NaN 进行计算。max()。percentile 用 0~100,quantile 用 0~1。np.percentile(a, 50) == np.quantile(a, 0.5)。ptp = peak-to-peak,即极差,等于 max - min。名字来源于示波器术语。📖 小节
- 基本聚合:
sum/mean/std/var/min/max/median/percentile/quantile/ptp - 沿轴聚合:
axis指定消除的维度,keepdims=True保留维度为 1 - argmin/argmax:返回最值索引而非最值本身,支持 axis 参数
- 累积运算:
cumsum/cumprod不压缩维度,逐步累加/累乘 - NaN 安全:
nan*系列函数忽略 NaN,避免结果污染 - percentile/quantile:分位数计算,参数范围不同(0~100 vs 0~1)
📝 作业
-
基础题(难度⭐):创建一个
(5, 6)随机整数数组,分别计算每行均值、每列标准差,以及全局中位数和极差。 -
进阶题(难度⭐⭐):对
(8, 5)随机数组,找出每列最大值所在行索引(用argmax(axis=0)),再找出每行最小值所在列索引(用argmin(axis=1))。 -
挑战题(难度⭐⭐⭐):创建一个含 5 个 NaN 的
(6, 7)浮点数组,计算:① 忽略 NaN 后每列均值;② 每行有效数据个数;③ 用nancumsum计算每行累积和(NaN 视为 0)。