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.mediannp.percentilenp.quantile 没有数组方法形式,只能用 np.* 调用。

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

:基本聚合(难度⭐)

PYTHON
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 📖 仅展示
# 输出结果
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)。

PYTHON
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))
TEXT 📖 仅展示
> **输出:** 在本地 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),便于广播对齐。

PYTHON
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)
TEXT 📖 仅展示
原形状: (2, 3)
axis=0 形状: (3,)
axis=0 keepdims 形状: (1, 3)
axis=1 形状: (2,)
axis=1 keepdims 形状: (2, 1)

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

:沿轴聚合(难度⭐⭐)

PYTHON
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 📖 仅展示
# 输出结果
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) 沿轴聚合流程图

100%
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)"]
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

3. argmin 与 argmax

argmin / argmax 不返回最值本身,而是返回最值首次出现的索引

(1) 一维情况

PYTHON
arr = np.array([30, 10, 50, 10, 70])
print("最小值:", arr.min(), " 索引:", arr.argmin())
print("最大值:", arr.max(), " 索引:", arr.argmax())
TEXT 📖 仅展示
最小值: 10  索引: 1
最大值: 70  索引: 4

注意:最小值 10 出现两次,argmin 返回首次出现的索引 1。

(2) 多维情况与 axis

沿指定轴聚合时,返回的是该轴上的索引。对 shape (4, 3) 的数组,argmax(axis=0) 返回 shape (3,)——每列中最大值所在行号。

PYTHON
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))
TEXT 📖 仅展示
axis=0 argmax: [2 1 0]
axis=1 argmax: [2 1 0]

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

:argmin/argmax(难度⭐⭐)

PYTHON
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 📖 仅展示
# 输出结果
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)
输出形状 被压缩 与输入相同
含义 全部合为一个数 逐步累加的过程
可逆性 不可逆(丢失信息) 可从结果还原原数组

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

:cumsum/cumprod(难度⭐⭐)

PYTHON
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 📖 仅展示
# 输出结果
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) 多维累积

PYTHON
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))
TEXT 📖 仅展示
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 的分位,两者本质相同。

PYTHON
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))
TEXT 📖 仅展示
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 求百分位

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

:NaN 安全聚合(难度⭐⭐)

PYTHON
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 📖 仅展示
# 输出结果
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 聚合

PYTHON
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))
TEXT 📖 仅展示
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 个区域的销售数据,需要做全面的统计报表。

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

:12×4 月度销售数据全面统计(难度⭐⭐⭐)

PYTHON
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 📖 仅展示
# 输出结果
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

❓ 常见问题

Q axis 到底怎么理解?
A axis 就是你想消除的维度。sum(axis=0) 消除第 0 维(行),结果逐列汇总;sum(axis=1) 消除第 1 维(列),结果逐行汇总。
Q keepdims 有什么用?
A 聚合后保持被消除维度为 1,使结果形状与原数组可广播对齐。例如 (4,3) 沿 axis=1 聚合后 shape 从 (4,) 变为 (4,1),可以直接与原数组做运算。
Q NaN 对聚合有什么影响?
A 普通聚合函数遇到 NaN 就返回 NaN——哪怕只有一个 NaN,整个结果就废了。必须用 np.nan* 系列函数(如 np.nanmean)来忽略 NaN 进行计算。
Q argmax 返回的到底是什么?
A 返回最值首次出现的索引,而不是最值本身。对一维数组返回一个整数;沿 axis 聚合时,返回的是沿该轴的索引数组。如需最值本身,用 max()
Q std 和 var 是什么关系?
A 方差(var)是标准差(std)的平方:var = std 的平方。默认都按总体计算(除以 N),设 ddof=1 切换为样本计算(除以 N-1)。
Q percentile 和 quantile 有什么区别?
A 完全等价,只是参数范围不同。percentile 用 0~100,quantile 用 0~1。np.percentile(a, 50) == np.quantile(a, 0.5)
Q ptp 函数是做什么的?
A ptp = peak-to-peak,即极差,等于 max - min。名字来源于示波器术语。

📖 小节


📝 作业

  1. 基础题(难度⭐):创建一个 (5, 6) 随机整数数组,分别计算每行均值、每列标准差,以及全局中位数和极差。

  2. 进阶题(难度⭐⭐):对 (8, 5) 随机数组,找出每列最大值所在行索引(用 argmax(axis=0)),再找出每行最小值所在列索引(用 argmin(axis=1))。

  3. 挑战题(难度⭐⭐⭐):创建一个含 5 个 NaN 的 (6, 7) 浮点数组,计算:① 忽略 NaN 后每列均值;② 每行有效数据个数;③ 用 nancumsum 计算每行累积和(NaN 视为 0)。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏