NumPy教程:从零基础到精通科学计算的核心库

2026-04-20 10:37:37
文章摘要
这份教程带你从零掌握NumPy,涵盖ndarray数组的创建与操作、索引切片、向量化运算、广播机制、线性代数及随机数生成,用大量实例夯实你的科学计算基础,让数据处理又快又简洁。

这份教程带你从零掌握NumPy,涵盖ndarray数组的创建与操作、索引切片、向量化运算、广播机制、线性代数及随机数生成,用大量实例夯实你的科学计算基础,让数据处理又快又简洁。

NumPy教程

一、初识NumPy:为什么你的Python代码跑得不够快?

做数据分析的人大概都遇到过这种情况:一个简单的数值计算,用Python原生列表硬生生跑了好几秒,代码写了一大堆循环,结果还特别慢。我也曾经为这种事挠头不已,后来才发现问题出在了“工具选错了”上。

NumPy全称Numerical Python,它专门解决数值计算中的性能痛点。很多人以为Python自带的列表够用了,其实不然。列表里每个元素都是完整的Python对象,包含引用计数、类型信息等额外开销,循环计算时要逐个做类型检查,效率相当低下。假如你想把两个列表对应位置的数加起来,写出来的代码是c = [a[i] + b[i] for i in range(len(a))],看起来简单,但背后要跑一大堆Python解释器的操作。

NumPy就不一样了。它核心的ndarray(N维数组对象)存储的是同质数据——数组里所有元素类型必须一致,比如全是int64或全是float32。数据在内存中连续排列,直接调用底层C和Fortran库做向量化计算,压根不需要你手动写循环。这意味着什么?同样的任务,NumPy能比原生Python快几十倍甚至上百倍。

Pandas、SciPy、Matplotlib、scikit-learn这些大家熟知的库,底层都依赖NumPy。学好了NumPy,后面上手这些工具就会特别顺畅。

二、环境准备与数组创建

先把环境搭好。用pip安装就好:pip install numpy。我自己用习惯了import numpy as np这个别名,因为后面要频繁调用NumPy里的函数,每次都敲全称太累。

几种常用的数组创建方式

从列表转换是最直观的办法:

arr1 = np.array([1, 2, 3, 4, 5])          # 一维数组
arr2 = np.array([[1, 2], [3, 4]])         # 二维数组(矩阵)

预分配数组也很常见。有时你需要先搭好框架,再往里填东西:

zeros = np.zeros((3, 4))                  # 3行4列,全是0
ones = np.ones((2, 3))                    # 2行3列,全是1
empty = np.empty((2, 2))                  # 未初始化,里头是随机值
full = np.full((2, 3), 7)                 # 2行3列,全填7
eye = np.eye(4)                           # 4x4单位矩阵,对角线为1

序列生成函数特别适合生成等差数列:

arange_arr = np.arange(0, 10, 2)          # [0, 2, 4, 6, 8],步长为2
linspace_arr = np.linspace(0, 1, 5)       # [0, 0.25, 0.5, 0.75, 1],等间隔5个点

随机数组在模拟和机器学习中经常用到:

rand_arr = np.random.rand(3, 4)           # [0,1)均匀分布,3x4
randn_arr = np.random.randn(2, 3)         # 标准正态分布
randint_arr = np.random.randint(0, 10, size=(2, 3))  # 0到9随机整数

形状转换也很实用。先拉成一维再reshape成想要的形状:

arr = np.arange(12)
reshaped = arr.reshape(3, 4)              # 3行4列

NumPy有超过40种数组创建函数,官方文档分成了三大类:从Python数据结构转换、使用内置创建函数、复制拼接现有数组等。你不需要全部记住,先把上面这几个常用方法练熟就够了。

三、认识ndarray的核心属性

数组创建出来之后,想搞清楚它的“长相”和“体质”,就得看看它的几个核心属性。

.shape返回一个元组,告诉你每个维度的大小。比如(2, 3)表示2行3列的矩阵。.ndim告诉你数组的维度数(也叫“秩”),一维数组是1,二维是2。.size给出数组元素的总个数。.dtype标注每个元素的数据类型,比如int32、float64。.itemsize是每个元素占用的字节数。

看个例子你就明白了:

arr = np.array([[1, 2, 3], [4, 5, 6]])
print(arr.shape)    # (2, 3)
print(arr.ndim)     # 2
print(arr.dtype)    # int64(默认)
print(arr.size)     # 6
print(arr.itemsize) # 8(int64占8字节)

这些属性在调试和优化代码时特别有用。有时候你觉得数组的形状不对,先打印.shape看看,问题很快就能定位。

四、索引与切片——像专家一样访问数据

NumPy数组的索引从0开始,这和Python列表一样,但NumPy的索引功能强大得多。基本索引用整数定位元素:

arr = np.array([[1, 2, 3], [4, 5, 6]])
print(arr[0, 1])      # 2,第一行第二列
print(arr[1, 2])      # 6,第二行第三列

切片和Python列表类似,用start:stop:step的格式。要注意的是,数组切片返回的是原始数据的“视图”,不是副本——修改视图会影响原数组。这个小细节坑过不少人:

arr = np.array([1, 2, 3, 4, 5, 6])
sub = arr[1:4]        # [2, 3, 4]
sub[0] = 99           # 修改视图
print(arr)            # [1, 99, 3, 4, 5, 6] 原数组也被改了!

布尔索引是个很强大的功能。你想筛选出数组中大于5的元素?一行代码就够了:

arr = np.array([1, 6, 3, 8, 2, 9])
mask = arr > 5
print(arr[mask])      # [6, 8, 9]

二维数组的切片可以单独控制行和列。比如取所有行的第2列:

arr = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(arr[:, 1])      # [2, 5, 8]

五、数组变形与操作

实际工作中,你经常需要改变数组的形状。.reshape()是最常用的方法:

arr = np.arange(12)
reshaped = arr.reshape(3, 4)    # 变成3行4列

.ravel().flatten()都能把多维数组“拉直”成一维。区别在于.ravel()返回的是视图(能省内存),.flatten()返回的是副本:

arr = np.array([[1, 2], [3, 4]])
print(arr.ravel())    # [1, 2, 3, 4]
print(arr.flatten())  # [1, 2, 3, 4]

转置也很简单:

arr = np.array([[1, 2, 3], [4, 5, 6]])
print(arr.T)          # 变成3行2列

拼接数组时,np.vstack()(垂直堆叠)和np.hstack()(水平堆叠)是最常用的:

a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])
print(np.vstack((a, b)))   # 上下叠
print(np.hstack((a, b)))   # 左右并排

六、向量化运算——告别慢吞吞的循环

这是NumPy最让人心动的功能。用原生Python时,你想对数组每个元素加1,得写循环。用NumPy,直接加就行了,底层用C语言把操作广播到每个元素,速度飞快。

基本算术运算

a = np.array([1, 2, 3, 4])
b = np.array([5, 6, 7, 8])
print(a + b)    # [6, 8, 10, 12]
print(a * b)    # [5, 12, 21, 32]
print(a ** 2)   # [1, 4, 9, 16]

通用函数(ufunc) 让数学计算变得更简单。三角函数、指数对数这些,直接调用就能作用在整个数组上:

arr = np.array([0, np.pi/2, np.pi])
print(np.sin(arr))    # [0, 1, 0]
print(np.exp(arr))    # 指数运算
print(np.log(arr))    # 自然对数

聚合函数用来做统计非常顺手:

arr = np.array([1, 2, 3, 4, 5])
print(arr.sum())      # 15
print(arr.mean())     # 3.0
print(arr.max())      # 5
print(arr.std())      # 标准差

这就是向量化的魅力——不需要写循环,代码简洁,性能还高。

七、广播机制——不同形状也能愉快运算

广播是NumPy里一个既巧妙又容易让新手困惑的机制。简单说,不同形状的数组做运算时,NumPy会自动把较小的数组“拉伸”到和较大的数组一样的形状,然后再逐元素操作。

举个例子。一个3x2的数组加上一个1x2的数组:

a = np.array([[1, 2], [3, 4], [5, 6]])    # 形状 (3, 2)
b = np.array([10, 20])                    # 形状 (2,)
print(a + b)
# 结果:
# [[11, 22],
#  [13, 24],
#  [15, 26]]

NumPy把b当成[[10, 20], [10, 20], [10, 20]]来参与运算。这种机制省去了手动复制数组的麻烦。

广播也不是万能的。如果两个数组的形状在各个维度上都不兼容——比如一个维度大小既不相同也不是1——运算就会报错。了解这个规则后,你用np.newaxis或者.reshape()调整形状就能解决问题。

八、线性代数与随机数

做科学计算离不开线性代数。NumPy提供了numpy.linalg模块,包含了矩阵乘法、求逆、特征值、解线性方程组等常用操作。

矩阵乘法dot()@运算符:

x = np.array([[1, 2, 3], [4, 5, 6]])
y = np.array([[6, 23], [-1, 7], [8, 9]])
print(x.dot(y))   # 或 np.dot(x, y)
# [[28, 64],
#  [67, 181]]

这里要注意,*操作符做的是逐元素相乘,不是矩阵乘法。新手常在这犯错。

求逆矩阵

from numpy.linalg import inv
mat = np.array([[1, 2], [3, 4]])
inv_mat = inv(mat)
print(inv_mat)

解线性方程组

from numpy.linalg import solve
A = np.array([[3, 1], [1, 2]])
b = np.array([9, 8])
x = solve(A, b)   # 解 Ax = b
print(x)          # [2, 3]

随机数生成在模拟实验里太常用了。numpy.random模块提供了各种分布的随机数生成函数,效率比Python内置的random高得多:

# 正态分布
normal = np.random.normal(0, 1, size=(3, 3))  # 均值0,标准差1
# 均匀分布
uniform = np.random.uniform(0, 1, 100)
# 随机整数
ints = np.random.randint(0, 10, size=(2, 5))
# 设置种子保证结果可重复
np.random.seed(42)

九、文件读写与NumPy 2.x新特性

处理完数据总得存下来。NumPy提供了简单的二进制文件保存方式。

.npy格式存单个数组:

arr = np.arange(10)
np.save('my_array', arr)        # 自动加.npy后缀
loaded = np.load('my_array.npy')

.npz格式存多个数组到同一个压缩文件:

a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
np.savez('archive.npz', x=a, y=b)
data = np.load('archive.npz')
print(data['x'])   # [1, 2, 3]

文本文件读写loadtxtsavetxt

data = np.loadtxt('data.csv', delimiter=',')
np.savetxt('output.csv', data, delimiter=',')

不过日常工作里,更多人还是用pandas来读写表格数据。


十、写在最后

NumPy 2.x系列已经正式发布了,这是自2006年以来的第一个大版本更新,实现了数组API标准的完全兼容,移除了大量历史弃用项。2.0版本在类型提升规则上更加统一一致,整数和浮点数运算的结果现在更符合直觉,广播规则也更严格了。英特尔AVX-512库的集成让数组排序速度提升了10-17倍。

学NumPy有点像学骑自行车,一开始觉得难,但上手之后你会发现它真的离不开。这里给你几个实用的小建议:

  1. 能用向量化就别用循环,代码又快又简洁
  2. 注意区分视图和副本,用copy()时考虑清楚是否需要独立副本
  3. 数据类型不一致时留意溢出问题,该转换就转换
  4. 多翻官方文档,每个函数都有详细的参数说明和示例
  5. 从简单任务开始练手,比如数组生成、矩阵运算,慢慢再挑战更复杂的

FAQ

Q1:NumPy和Python列表到底有啥区别?什么时候用哪个?
NumPy数组存储同类型数据且内存连续,适合大规模数值运算,速度比Python列表快几十上百倍。Python列表更灵活,可以存不同类型的数据。日常小规模数据操作用列表就够,科学计算、数据分析、机器学习必须用NumPy。

Q2:NumPy数组的副本和视图怎么区分?
切片和.ravel()通常返回视图,修改视图会影响原数组。.flatten().copy()返回副本,修改后原数组不变。如果不确定,打印数组内容验证一下。

Q3:装完NumPy后导入失败怎么办?
检查虚拟环境是否激活了,确认pip list里有numpy,或者试试pip install --upgrade numpy重新安装。

Q4:二维数组里,*@有啥区别?
*是逐元素相乘(Hadamard乘积),@是真正的矩阵乘法。初学者经常混用,记住这个区别能少踩很多坑。

Q5:随机数生成时怎么保证每次结果都一样?
np.random.seed(42)固定随机种子,42可以换成任意整数,种子相同随机序列就相同。

Q6:NumPy 2.x和旧版代码不兼容怎么办?
官方提供了迁移指南,主要留意类型提升规则和广播规则的收紧。建议先在测试环境运行代码,根据报错调整。大多数情况下,只需要显式指定数据类型或调整形状即可。

Q7:处理缺失值NaN时要注意什么?
np.isnan()判断NaN,用np.nanmean()np.nansum()跳过NaN做统计。注意两个NaN不相等,arr[arr == np.nan]是无效的。

Q8:大型数组内存不够怎么优化?
考虑用np.float32代替默认的float64减少内存占用,用视图而不是副本,分块处理大数据。还可以用np.memmap把大数组映射到磁盘而不是全读进内存。

Q9:哪里能找到最新的NumPy学习资料?
官方文档最权威(numpy.org/doc),GitHub上有源码和示例,Stack Overflow上有大量实战问答。中文社区可以关注CSDN、知乎上的技术专栏。

Q10:NumPy适合做深度学习吗?
深度学习框架(TensorFlow、PyTorch)底层张量运算的理念和NumPy高度相似,学NumPy是理解深度学习的基础。不过大规模神经网络训练直接用深度学习框架更方便,NumPy适合做轻量级模型或数据预处理。

声明:该内容由作者自行发布,观点内容仅供参考,不代表平台立场;如有侵权,请联系平台删除。
标签:
NumPy