Skip to content

NumPy 数组计算

NumPy 是 Python 数值计算的基础库。Pandas、Scikit-learn、图像处理、机器学习、深度学习框架底层都会大量使用数组计算。学习 NumPy 的重点不是背 API,而是理解:

NumPy 把大量同类型数据放在连续内存中,用底层高效循环一次性处理,避免 Python 解释器逐个元素执行。

如果你只知道 np.array()mean(),但不理解 shapedtype、广播、轴、切片视图、拷贝和向量化,后面学 Pandas、机器学习、AI 数据处理都会经常卡住。

学习目标

学完本页,你应该能回答:

  1. Python list 为什么不适合大规模数值计算。
  2. ndarrayshapedtypendim 分别是什么。
  3. NumPy 向量化为什么比 Python for 循环快。
  4. 广播机制怎么工作,为什么形状不匹配会报错。
  5. axis=0axis=1 到底沿哪个方向聚合。
  6. 切片为什么可能是视图,什么时候需要 .copy()
  7. 金额、缺失值、浮点精度在商业数据里要注意什么。
  8. 能写一个可运行的商业数组计算 Demo。
  9. 面试时能说清 NumPy 原理、常见坑和排查方法。

为什么 Python list 不够用

Python list 很灵活,可以混放整数、字符串、对象:

python
data = [1, "2", {"score": 3}]

灵活的代价是:每个元素都是 Python 对象,列表里存的是对象引用。做数值计算时,解释器要不断处理对象、类型和循环。

NumPy 数组要求同一个数组内元素通常是同一种类型,它可以把数据紧凑地放进连续内存。

mermaid
flowchart TD
    A["Python list"] --> B["保存一组对象引用"]
    B --> C["每个元素可能是不同 Python 对象"]
    C --> D["for 循环逐个进入解释器"]
    E["NumPy ndarray"] --> F["连续内存保存同类型数据"]
    F --> G["底层 C 循环批量计算"]
    G --> H["减少 Python 层解释器开销"]
对比Python listNumPy ndarray
元素类型可以混合通常同类型
存储方式存对象引用连续内存存原始值
计算方式Python 循环逐个处理底层批量处理
多维数据嵌套 list,容易不规则原生支持多维数组
适合场景小数据、业务对象、控制流大量数值、矩阵、向量计算

结论:不是 list 没用,而是 list 的目标是通用容器;NumPy 的目标是高效数值计算。

ndarray 是什么

ndarray 是 NumPy 的核心对象,意思是 N-dimensional array,多维数组。

python
import numpy as np

arr = np.array([
    [80, 90, 75],
    [88, 92, 70],
])

print(arr)
print("shape:", arr.shape)
print("ndim:", arr.ndim)
print("dtype:", arr.dtype)
print("size:", arr.size)

输出类似:

text
[[80 90 75]
 [88 92 70]]
shape: (2, 3)
ndim: 2
dtype: int32
size: 6

几个属性必须掌握:

属性含义例子
shape每个维度长度(2, 3) 表示 2 行 3 列
ndim维度数量二维数组是 2
dtype元素类型int64float64object
size元素总数2 * 3 = 6

遇到任何 NumPy 问题,第一步先打印:

python
print(arr.shape, arr.dtype, arr.ndim)

很多 bug 不是算法错,而是形状或类型和你想的不一样。

数组计算完整流程

mermaid
flowchart TD
    A["原始 Python 数据或文件列"] --> B["转换成 ndarray"]
    B --> C["检查 shape、dtype、缺失值"]
    C --> D["按业务含义对齐维度"]
    D --> E["向量化计算或广播"]
    E --> F["按 axis 聚合统计"]
    F --> G["输出结果或交给 Pandas"]

每一步的意义:

步骤为什么要做不做会怎样
转数组使用 NumPy 高效计算能力仍然在 Python 循环里慢慢算
检查 shape确认维度是否符合业务广播错误或结果对错方向
检查 dtype确认数值能正常计算字符串拼接、object 慢、统计错误
对齐维度保证行列含义正确把列权重乘到行上
向量化减少解释器循环开销大数据下性能差
聚合统计得到业务指标轴选错导致统计口径错

最小 Demo:成绩数组计算

python
import numpy as np


scores = np.array([80, 90, 75, 100])

print("平均分:", scores.mean())
print("最高分:", scores.max())
print("是否优秀:", scores >= 90)
print("优秀分数:", scores[scores >= 90])

输出:

text
平均分: 86.25
最高分: 100
是否优秀: [False  True False  True]
优秀分数: [ 90 100]

重点看这一句:

python
scores >= 90

它不是写了一个显式 for 循环,而是一次性对数组中所有元素做比较,返回布尔数组。再用布尔数组筛选:

python
scores[scores >= 90]

这就是 NumPy 的核心思维:不要逐个元素想,尽量按整列、整行、整块数组想。

向量化为什么快

Python for 循环:

python
result = []
for item in data:
    result.append(item * 2)

NumPy 向量化:

python
result = arr * 2

二者看起来都在乘 2,但执行路径不同。

mermaid
flowchart TD
    A["Python for 循环"] --> B["取一个元素"]
    B --> C["解释器判断对象和类型"]
    C --> D["执行乘法"]
    D --> E["追加结果"]
    E --> B
    F["NumPy 向量化"] --> G["一次提交整块数组运算"]
    G --> H["底层循环处理连续内存"]
    H --> I["返回新数组"]

Python 慢的关键不是“乘法慢”,而是循环次数多时,每次都要经过解释器、对象访问、类型处理和函数调用。NumPy 把循环放到底层实现里,Python 层只发出一次操作命令。

性能对比 Demo:

python
import time
import numpy as np


n = 1_000_000
data = list(range(n))
arr = np.arange(n)

start = time.perf_counter()
result1 = [x * 2 for x in data]
print("list cost:", time.perf_counter() - start)

start = time.perf_counter()
result2 = arr * 2
print("numpy cost:", time.perf_counter() - start)

注意:小数据量时 NumPy 不一定明显快,因为创建数组也有成本。NumPy 的优势在“大量同类型数据批量计算”。

dtype 为什么重要

dtype 决定数组元素怎么存、怎么计算、占多少内存。

python
import numpy as np


a = np.array([1, 2, 3])
b = np.array(["1", "2", "3"])

print(a.dtype)
print(b.dtype)

如果读入的是字符串:

python
b = np.array(["1", "2", "3"])
print(b + b)

可能不是你想要的数值加法。真实数据里,金额列经常包含 "120元"" 99.5 "、空字符串,直接转数组会变成字符串或 object。

正确思路:

python
raw = np.array(["1", "2", "3"])
numbers = raw.astype(np.int64)
print(numbers + numbers)

常见 dtype

dtype含义适合
int64整数数量、次数、编号内部计算
float64浮点数科学计算、均值、比例
bool布尔条件筛选
datetime64日期时间时间数组
objectPython 对象混合类型,但性能差

商业提醒:

  1. 财务金额的最终结算不要依赖二进制浮点,应该用数据库 decimal 或 Python Decimal
  2. 手机号、身份证号、资产编码不要随便转数值。
  3. object 类型通常意味着数据不干净或混合类型,计算会慢。

shape 和 reshape

shape 表示数组形状。

python
import numpy as np


arr = np.arange(12)
print(arr.shape)

matrix = arr.reshape(3, 4)
print(matrix)
print(matrix.shape)

输出:

text
(12,)
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
(3, 4)

reshape(3, 4) 的前提是元素总数对得上。12 个元素可以改成 (3, 4)(4, 3)(2, 6),但不能改成 (5, 3)

错误示例:

python
arr.reshape(5, 3)

这会报错,因为需要 15 个元素。

业务上更重要的是:不要为了让代码不报错而乱 reshape。先问清楚每个维度含义。

形状可能含义
(100,)100 个订单金额
(100, 3)100 个订单,每个订单 3 个指标
(7, 24)7 天,每天 24 小时
(1000, 768)1000 个文本,每个 768 维 embedding

axis 到底怎么理解

axis 是 NumPy 初学者最容易混乱的点。

二维数组:

python
import numpy as np


scores = np.array([
    [80, 90, 70],
    [88, 92, 76],
])

print(scores.sum(axis=0))
print(scores.sum(axis=1))

可以这样理解:

写法沿哪个维度压缩结果含义
axis=0压缩行方向,按列聚合每一列求和
axis=1压缩列方向,按行聚合每一行求和

流程图:

mermaid
flowchart TD
    A["二维数组 2 行 3 列"] --> B["axis=0"]
    A --> C["axis=1"]
    B --> D["把多行压成一行,得到每列统计"]
    C --> E["把多列压成一列,得到每行统计"]

业务例子:

python
sales = np.array([
    [100, 120, 130],
    [80, 90, 110],
])

print("每天总销售:", sales.sum(axis=0))
print("每个门店总销售:", sales.sum(axis=1))

如果行代表门店,列代表日期:

axis业务含义
axis=0按日期统计所有门店
axis=1按门店统计所有日期

所以不要死记“0 是列、1 是行”,要理解它是在压缩哪个维度。

广播机制

广播是 NumPy 的重要机制:不同形状的数组在满足规则时,可以自动扩展后计算。

标量广播

python
import numpy as np


prices = np.array([100, 200, 300])
discount = 0.8

print(prices * discount)

标量 0.8 会作用到每个元素上。

一维数组广播到二维

python
sales = np.array([
    [100, 120, 130],
    [80, 90, 110],
])

weights = np.array([0.2, 0.3, 0.5])

print(sales * weights)

sales 形状是 (2, 3)weights 形状是 (3,)。NumPy 会把 weights 当成每一列的权重,广播到每一行。

mermaid
flowchart TD
    A["sales shape=(2, 3)"] --> C["逐列相乘"]
    B["weights shape=(3,)"] --> C
    C --> D["weights 作用到每一行"]

广播规则

从右往左比较两个数组的维度:

  1. 两个维度相等,可以广播。
  2. 其中一个维度是 1,可以广播。
  3. 否则不能广播。
形状 A形状 B是否可广播
(2, 3)(3,)可以
(2, 3)(1, 3)可以
(2, 3)(2, 1)可以
(2, 3)(2,)不可以
(3,)() 标量可以

错误例子:

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

print(a + b)

(3,)(2,) 既不相等,也没有一个是 1,所以不能广播。

布尔索引和条件筛选

布尔索引是 NumPy 做筛选的核心。

python
import numpy as np


amounts = np.array([99.5, 120.0, -80.0, 300.0])
paid_mask = amounts > 0

print(paid_mask)
print(amounts[paid_mask])

多个条件要用 &|,并且每个条件加括号:

python
amounts = np.array([99.5, 120.0, -80.0, 300.0])
counts = np.array([1, 2, 1, 5])

mask = (amounts > 0) & (counts >= 2)
print(amounts[mask])

常见错误:

python
mask = amounts > 0 and counts >= 2

这会报错,因为 and 是 Python 标量布尔逻辑,不适合数组逐元素判断。数组条件要用 &|

切片、视图和拷贝

NumPy 的切片经常返回视图,而不是新数组。视图和原数组共享底层数据。

python
import numpy as np


arr = np.array([1, 2, 3, 4])
part = arr[1:3]
part[0] = 99

print(arr)

输出:

text
[ 1 99  3  4]

为什么?因为 partarr 的视图,修改 part 会影响原数组。

mermaid
flowchart TD
    A["原数组 arr"] --> B["底层数据"]
    C["切片 part"] --> B
    D["修改 part"] --> B
    B --> E["arr 也看到变化"]

如果你需要独立数据,用 .copy()

python
part = arr[1:3].copy()

商业数据处理里,视图和拷贝的区别很重要。你以为只是在修改临时结果,实际可能改了原始数据。

缺失值和 NaN

NumPy 用 np.nan 表示浮点缺失值。

python
import numpy as np


amounts = np.array([100.0, np.nan, 200.0])

print(amounts.mean())
print(np.nanmean(amounts))

普通 mean() 遇到 NaN 会得到 NaN;np.nanmean() 会忽略 NaN。

常用函数:

函数作用
np.isnan(arr)判断是否 NaN
np.nanmean(arr)忽略 NaN 求平均
np.nansum(arr)忽略 NaN 求和
np.nan_to_num(arr)把 NaN 转成指定值

注意:忽略 NaN 不是永远正确。金额缺失、日期缺失、状态缺失都可能代表数据质量问题。是否忽略要看业务。

浮点精度问题

python
print(0.1 + 0.2)

输出可能是:

text
0.30000000000000004

这是二进制浮点表示限制,不是 NumPy 特有问题。

NumPy 适合统计、分析、科学计算,但如果是支付、结算、发票金额这类强精度业务,最终落库和结算应使用数据库 decimal 或 Python Decimal

python
from decimal import Decimal


amount = Decimal("0.1") + Decimal("0.2")
print(amount)

商业 Demo:资产评分批量计算

假设医疗数据资产平台要给一批数据资产打分,评分由三部分组成:

  1. 数据完整率 completeness,权重 40%。
  2. 更新及时性 freshness,权重 30%。
  3. 访问热度 usage,权重 30%。

每一行代表一个数据资产。

python
import numpy as np


asset_names = np.array(["门诊明细", "检验报告", "药品库存", "设备台账"])

# columns: completeness, freshness, usage
metrics = np.array([
    [0.95, 0.90, 0.80],
    [0.88, 0.70, 0.92],
    [0.76, 0.85, 0.60],
    [0.99, 0.60, 0.40],
])

weights = np.array([0.4, 0.3, 0.3])

scores = (metrics * weights).sum(axis=1)
qualified_mask = scores >= 0.8

for name, score, qualified in zip(asset_names, scores, qualified_mask):
    status = "合格" if qualified else "待治理"
    print(f"{name}: {score:.3f}, {status}")

这段 Demo 涉及:

知识点在 Demo 中的作用
二维数组保存多个资产的多个指标
广播metrics * weights 把列权重应用到每一行
axis=1每个资产按列求加权总分
布尔索引判断哪些资产合格
向量化不写循环逐项计算分数

如果不用 NumPy,你可能会写多层循环。数据量大时,代码更慢也更容易写错。

商业 Demo:批量识别异常订单金额

python
import numpy as np


order_ids = np.array(["1001", "1002", "1003", "1004", "1005"])
amounts = np.array([99.5, 120.0, -80.0, 30000.0, np.nan])

invalid_mask = np.isnan(amounts) | (amounts < 0) | (amounts > 10000)

print("异常订单:")
for order_id, amount in zip(order_ids[invalid_mask], amounts[invalid_mask]):
    print(order_id, amount)

这个例子说明:NumPy 不只是科学计算,也可以用于批量业务校验。但真实项目如果涉及表格字段、列名、导出错误文件,Pandas 会更方便。

NumPy 和 Pandas 怎么配合

Pandas 的一列 Series 可以转成 NumPy 数组:

python
amount_array = df["amount"].to_numpy()

常见配合方式:

场景做法
读取 CSV/ExcelPandas
清洗字段、去重、分组Pandas
对数值列做批量计算NumPy 或 Pandas 向量化
构造机器学习输入矩阵NumPy
输出报表Pandas

不要为了用 NumPy,把有列名、有业务语义的表格硬转成裸数组。裸数组快,但列名和业务含义容易丢失。

常见坑

后果正确做法
不看 shape维度错,广播错每次关键计算前打印 shape
不看 dtype字符串、object 导致计算错或慢转换并校验类型
axis 选错把行统计成列,口径错明确每个维度业务含义
切片视图被修改原数组被意外改变需要独立数据时 .copy()
and/or 判断数组报错或逻辑错&、`
无脑忽略 NaN掩盖数据质量问题先分析缺失原因
财务金额用 float 结算精度风险结算用 Decimal 或数据库 decimal
为小数据强行向量化代码难读,收益小先正确,再优化

生产排查流程

NumPy 计算结果不对时,按这个顺序查:

mermaid
flowchart TD
    A["NumPy 结果不对"] --> B["打印 shape、dtype、ndim"]
    B --> C{"shape 是否符合业务维度"}
    C -- "否" --> D["检查 reshape、转置、输入数据"]
    C -- "是" --> E{"dtype 是否为预期数值类型"}
    E -- "否" --> F["清洗字符串、object、缺失值"]
    E -- "是" --> G{"广播是否按预期发生"}
    G -- "否" --> H["检查维度对齐和权重方向"]
    G -- "是" --> I{"axis 是否选对"}
    I -- "否" --> J["重新确认行列含义"]
    I -- "是" --> K["检查 NaN、异常值、精度和业务口径"]

排查时常用:

python
print(arr.shape)
print(arr.dtype)
print(arr[:5])
print(np.isnan(arr).sum())
print(arr.min(), arr.max())

面试标准回答

NumPy 为什么比 list 快?

可以这样答:

text
Python list 是通用容器,里面存的是 Python 对象引用,做数值计算时通常要通过解释器循环逐个处理元素。NumPy ndarray 通常把同类型数据放在连续内存中,数组运算会交给底层 C 实现批量处理,减少 Python 层循环、对象访问和类型判断开销。所以大量数值计算时 NumPy 通常比 list 快。

shape、dtype、axis 怎么理解?

shape 表示数组每个维度的长度,dtype 表示元素类型,axis 表示沿哪个维度进行聚合或操作。二维数组里 axis=0 通常是压缩行方向得到每列统计,axis=1 是压缩列方向得到每行统计。真正使用时不能死记,要结合业务含义判断行和列分别代表什么。

广播机制是什么?

广播是 NumPy 在数组形状不完全相同时自动扩展较小数组的机制。从右往左比较维度,维度相等或其中一个为 1 时可以广播,否则报错。广播能让列权重、标量折扣、批量偏移等计算不用手写循环,但前提是维度语义正确。

视图和拷贝有什么区别?

NumPy 切片很多时候返回视图,视图和原数组共享底层数据,修改视图可能影响原数组;拷贝是独立数据,修改不会影响原数组。数据处理里如果只是临时查看可以用视图,如果要独立修改一份数据,应该显式 .copy()

关联知识点

知识点作用
Python 数据处理总览理解数据从读取到导出的完整流程
Pandas 清洗分析学习带列名和业务语义的表格处理
端到端实践完整跑通 CSV 清洗和报表输出
Python 数据结构对比 list、dict、set 和数组思维
Python 面试题查看标准回答和追问

本章小结

NumPy 的核心不是函数名,而是数组思维。你要能看懂 shapedtypeaxis,理解向量化为什么快、广播怎么对齐、切片什么时候是视图、NaN 和浮点精度有什么风险。掌握这些后,再学 Pandas、机器学习和 AI 数据处理时,底层数组计算就不会是黑盒。