NumPy,全称 Numerical Python,是 Python 中用于数值计算的基础包。
为什么 NumPy 很重要? 🧐 许多科学计算包都依赖 NumPy 的数组对象进行数据交换。可以把它看作是 Python 数据分析的通用语言。
NumPy 专为高效处理大型数据数组而设计。
我们将学到什么? 我们将涵盖 NumPy 的基础知识,重点关注 NumPy 如何实现快速的、面向数组的操作,这对于使用 pandas 等库进行数据分析至关重要。
以下是 NumPy 提供的主要功能:
NumPy 的效率源于以下几个设计选择:
连续内存: 与 Python 列表不同,NumPy 数组将数据存储在单个连续的内存块中。这使得访问和操作数据更快。
基于 C 的算法: 许多 NumPy 操作都是用 C 实现的,避免了 Python 解释器的开销。
更少的内存使用: NumPy 数组通常比 Python 列表消耗更少的内存,特别是对于数值数据。
让我们看看 NumPy 的速度优势。我们将比较使用 NumPy 数组和 Python 列表将一百万个整数乘以 2 的速度:
现在,进行计时操作:
NumPy 数组操作:
Python 列表操作(使用列表推导式):
基于 NumPy 的算法通常比纯 Python 算法快 10 到 100 倍(或更多),并且使用的内存显著减少。
ndarray
(N 维数组)是 NumPy 中的核心数据结构。
同质数据: ndarray 中的所有元素必须具有相同的数据类型(例如,全是整数,全是浮点数)。
关键属性:
shape
: 一个元组,表示每个维度的大小。对于 2x3 数组,形状将是 (2, 3)
。dtype
: 一个描述元素数据类型(例如,int64
、float32
)的对象。创建 ndarray: 创建数组的最简单方法是使用 array
函数。它接受任何类似序列的对象(包括其他数组),并生成一个新的 NumPy 数组。
让我们创建一些数组:
NumPy 提供了几个方便的函数来创建数组:
[[[6.90879110e-310 6.98701945e-316]
[0.00000000e+000 5.33931192e-315]
[0.00000000e+000 1.25990588e-300]]
[[9.68577499e-042 2.03457905e-310]
[4.42632373e-220 2.10966031e-321]
[1.34042946e-272 5.58798330e-315]]]
下表总结了常见的数组创建函数:
函数 | 描述 |
---|---|
array |
将输入数据(列表、元组、数组等)转换为 ndarray。 |
asarray |
类似于 array ,但如果输入已经是 ndarray,则不复制。 |
arange |
类似于 Python 的 range ,但返回 ndarray。 |
函数 | 描述 |
---|---|
ones , ones_like |
创建全 1 数组。ones_like 接受另一个数组,并创建一个形状和 dtype 相同的全 1 数组。 |
zeros , zeros_like |
类似于 ones 和 ones_like ,但创建全 0 数组。 |
empty , empty_like |
创建未初始化值的数组。谨慎使用! |
函数 | 描述 |
---|---|
full , full_like |
创建一个用指定值填充的数组。full_like 接受另一个数组并使用其形状和 dtype。 |
eye , identity |
创建一个正方形单位矩阵(对角线上为 1,其他地方为 0)。 |
什么是 dtype? 一个特殊的对象,包含有关 ndarray 所持有的数据类型的信息(例如,float64
、int32
、bool
)。
为什么 dtype 很重要? 它们使您可以精细地控制数据在内存中的存储方式。这对于性能至关重要,尤其是在处理大型数据集时。它们还允许 NumPy 与数据接口。
指定 dtypes:
类型 | 类型代码 | 描述 |
---|---|---|
int8 , uint8 |
i1 , u1 |
有符号和无符号 8 位(1 字节)整数类型 |
int16 , uint16 |
i2 , u2 |
有符号和无符号 16 位整数类型 |
int32 , uint32 |
i4 , u4 |
有符号和无符号 32 位整数类型 |
int64 , uint64 |
i8 , u8 |
有符号和无符号 64 位整数类型 |
类型 | 类型代码 | 描述 |
---|---|---|
float16 |
f2 |
半精度浮点数 |
float32 |
f4 或 f |
标准单精度浮点数;与 C float 兼容 |
float64 |
f8 或 d |
标准双精度浮点数;与 C double 和 Python float 兼容 |
float128 |
f16 或 g |
扩展精度浮点数 |
类型 | 类型代码 | 描述 |
---|---|---|
complex64 , complex128 , complex256 |
c8 , c16 , c32 |
分别由两个 32、64 或 128 位浮点数表示的复数 |
bool |
? |
布尔类型,存储 True 和 False 值 |
object |
O |
Python 对象类型;值可以是任何 Python 对象 |
string_ |
S |
固定长度的 ASCII 字符串类型(每个字符 1 个字节)。使用 S10 表示长度为 10 的字符串。 |
unicode_ |
U |
固定长度的 Unicode 类型(字节数取决于平台) |
既有有符号整数类型,也有无符号整数类型。有符号整数可以表示正整数和负整数,而无符号整数只能表示非负整数。
astype
进行类型转换:整数到浮点数什么是类型转换? 将数组从一种 dtype 转换为另一种 dtype。
如何进行类型转换: 使用 astype
方法。astype
总是创建一个新数组(数据的副本),即使新的 dtype 与旧的 dtype 相同。
astype
进行类型转换:字符串到浮点数# `np.string_` 在 NumPy 2.0 版本中已移除。请改用 `np.bytes_`。
numeric_strings = np.array(['1.25', '-9.6', '42'], dtype=np.bytes_) # 创建一个包含字符串的数组
float_arr = numeric_strings.astype(float) # 将字符串数组转换为浮点数数组
print(float_arr) # 打印转换后的数组
[ 1.25 -9.6 42. ]
使用 numpy.string_
类型时要小心,因为 NumPy 中的字符串数据是固定大小的,可能会截断输入而不发出警告。
向量化: NumPy 中的一个核心概念。它意味着对整个数组执行操作,无需编写显式的 for
循环。这更快、更简洁。
逐元素操作: 等大小数组之间的算术运算是逐元素应用的:
什么是广播? 一种强大的机制,允许 NumPy 在某些条件下对不同形状的数组执行算术运算。
示例: 您可以将标量添加到任何形状的数组。标量实际上被“拉伸”以匹配数组的形状。
我们稍后会更详细地介绍广播。现在,只需知道它的存在即可。
[ 0 1 2 3 4 12 12345 12 8 9]
如果您想要 ndarray 切片的副本而不是视图,则需要显式复制该数组 - 例如,arr[5:8].copy()
。
graph LR
subgraph "Axis 0 (Rows)"
0 --> 1
1 --> 2
end
subgraph "Axis 1 (Columns)"
0 --> 0[0,0]
0 --> 1[0,1]
0 --> 2[0,2]
1 --> 3[1,0]
1 --> 4[1,1]
1 --> 5[1,2]
2 --> 6[2,0]
2 --> 7[2,1]
2 --> 8[2,2]
end
graph LR subgraph "Axis 0 (Rows)" 0 --> 1 1 --> 2 end subgraph "Axis 1 (Columns)" 0 --> 0[0,0] 0 --> 1[0,1] 0 --> 2[0,2] 1 --> 3[1,0] 1 --> 4[1,1] 1 --> 5[1,2] 2 --> 6[2,0] 2 --> 7[2,1] 2 --> 8[2,2] end
arr[:2,1:]
表达式 | 形状 |
---|---|
arr[:2,1:] |
(2,2) |
arr[2]
表达式 | 形状 |
---|---|
arr[2] |
(3,) |
arr[2,:]
表达式 | 形状 |
---|---|
arr[2,:] |
(3,) |
arr[2:,:]
表达式 | 形状 |
---|---|
arr[2:,:] |
(1,3) |
arr[:, :2]
表达式 | 形状 |
---|---|
arr[:, :2] |
(3,2) |
arr[1, :2]
表达式 | 形状 |
---|---|
arr[1, :2] |
(2,) |
arr[1:2, :2]
表达式 | 形状 |
---|---|
arr[1:2, :2] |
(1,2) |
~
: 对布尔数组取反(类似于 not
)&
: 组合条件(类似于 and
)|
: 组合条件(类似于 or
)mask = (names == 'Bob') | (names == 'Will') # 创建一个掩码,选择名字为 'Bob' 或 'Will' 的行
print(data[mask]) # 使用掩码选择数据
[[ 4 7]
[-5 6]
[ 0 0]
[ 1 2]]
Python 关键字 and
和 or
不适用于布尔数组。请改用 &
(and) 和 |
(or)。
import numpy as np # 导入 NumPy
arr = np.zeros((8, 4)) # 创建一个 8x4 的全零数组
for i in range(8): # 循环遍历每一行
arr[i] = i # 将每一行的值设置为行号
print(arr)
print(arr[[4, 3, 0, 6]]) # 按指定顺序选择第 4、3、0 和 6 行
[[0. 0. 0. 0.]
[1. 1. 1. 1.]
[2. 2. 2. 2.]
[3. 3. 3. 3.]
[4. 4. 4. 4.]
[5. 5. 5. 5.]
[6. 6. 6. 6.]
[7. 7. 7. 7.]]
[[4. 4. 4. 4.]
[3. 3. 3. 3.]
[0. 0. 0. 0.]
[6. 6. 6. 6.]]
[[ 4 7 5 6]
[20 23 21 22]
[28 31 29 30]
[ 8 11 9 10]]
.T
属性或 transpose
方法。np.dot
或 @
运算符进行矩阵乘法。swapaxes
方法接受一对轴编号并交换指定的轴。转置和 swapaxes
返回底层数据上的视图,而不进行复制。
numpy.random
模块: 提供生成随机数数组的函数。
default_rng
: 创建随机数生成器的推荐方法。
为什么要使用种子? 设置种子可确保可重复性。每次都会得到相同的随机数。
生成器隔离: 生成器对象 rng
与可能也使用 numpy.random
模块的其他代码隔离。
性能: numpy.random
在生成大型数组方面比 Python 的内置 random
模块快得多。
numpy.random
函数方法 | 描述 |
---|---|
permutation |
返回序列的随机排列,或排列后的范围。 |
shuffle |
就地随机排列序列。 |
uniform |
从均匀分布中抽取样本。 |
numpy.random
函数(续)方法 | 描述 |
---|---|
integers |
从给定的低到高范围中抽取随机整数。 |
standard_normal |
从标准正态分布(均值为 0,标准差为 1)中抽取样本。 |
binomial |
从二项分布中抽取样本。 |
numpy.random
函数(续)方法 | 描述 |
---|---|
normal |
从正态(高斯)分布中抽取样本。 |
beta |
从 Beta 分布中抽取样本。 |
chisquare |
从卡方分布中抽取样本。 |
gamma |
从伽马分布中抽取样本。 |
uniform |
从均匀 [0, 1) 分布中抽取样本。 |
什么是 ufuncs? 对 ndarray 执行逐元素操作的函数。它们是简单函数的快速向量化包装器。
一元 ufuncs: 接受单个数组作为输入。
import numpy as np # 导入 NumPy
arr = np.arange(10) # 创建一个包含 0 到 9 的数组
print(np.sqrt(arr)) # 每个元素的平方根
print(np.exp(arr)) # 每个元素的指数
[0. 1. 1.41421356 1.73205081 2. 2.23606798
2.44948974 2.64575131 2.82842712 3. ]
[1.00000000e+00 2.71828183e+00 7.38905610e+00 2.00855369e+01
5.45981500e+01 1.48413159e+02 4.03428793e+02 1.09663316e+03
2.98095799e+03 8.10308393e+03]
rng = np.random.default_rng(seed=12345) # 创建一个随机数生成器
x = rng.standard_normal(8) # 生成 8 个标准正态分布的随机数
y = rng.standard_normal(8) # 生成 8 个标准正态分布的随机数
print(np.maximum(x, y)) # 逐元素最大值
[ 0.36105811 1.26372846 2.34740965 0.96849691 -0.07534331 0.90219827
-0.46695317 0.6488928 ]
函数 | 描述 |
---|---|
abs , fabs |
逐元素计算绝对值。fabs 对于非复数数据更快。 |
sqrt |
计算每个元素的平方根(相当于 arr ** 0.5 )。 |
square |
计算每个元素的平方(相当于 arr ** 2 )。 |
exp |
计算每个元素的指数 ex。 |
函数 | 描述 |
---|---|
log , log10 , log2 , log1p |
自然对数(以 e 为底)、以 10 为底的对数、以 2 为底的对数和 log(1 + x)。 |
sign |
计算每个元素的符号:1(正数)、0(零)或 -1(负数)。 |
ceil |
计算每个元素的上限(大于或等于该数字的最小整数)。 |
floor |
计算每个元素的下限(小于或等于每个元素的最大整数)。 |
函数 | 描述 |
---|---|
rint |
将元素四舍五入到最接近的整数,保留 dtype。 |
modf |
将数组的小数部分和整数部分作为单独的数组返回。 |
isnan |
返回一个布尔数组,指示每个值是否为 NaN(非数字)。 |
isfinite , isinf |
返回一个布尔数组,指示每个元素是否有限或无限。 |
函数 | 描述 |
---|---|
cos , cosh , sin , sinh , tan , tanh |
常规和双曲三角函数。 |
arccos , arccosh , arcsin , arcsinh , arctan , arctanh |
反三角函数。 |
logical_not |
逐元素计算 not x 的真值(相当于 ~arr )。 |
函数 | 描述 |
---|---|
add |
将数组中的对应元素相加。 |
subtract |
从第一个数组中减去第二个数组中的元素。 |
multiply |
将数组元素相乘。 |
divide , floor_divide |
除法或向下取整除法(截断余数)。 |
函数 | 描述 |
---|---|
power |
将第一个数组中的元素提升到第二个数组中指示的幂。 |
maximum , fmax |
逐元素最大值。fmax 忽略 NaN。 |
minimum , fmin |
逐元素最小值。fmin 忽略 NaN。 |
mod |
逐元素取模(除法的余数)。 |
函数 | 描述 |
---|---|
copysign |
将第二个参数中的值的符号复制到第一个参数中的值。 |
greater , greater_equal , less , less_equal , equal , not_equal |
执行逐元素比较,产生一个布尔数组。 |
logical_and , logical_or , logical_xor |
计算逻辑运算的逐元素真值。 |
让我们计算函数 √(x^2 + y^2) 在一个值网格上的结果:
现在,让我们使用 Matplotlib 可视化结果:
此示例演示了面向数组编程的简洁和高效。
np.where
np.where
: 三元表达式 x if condition else y
的向量化版本。import numpy as np # 导入 NumPy
xarr = np.array([1.1, 1.2, 1.3, 1.4, 1.5]) # 创建数组 xarr
yarr = np.array([2.1, 2.2, 2.3, 2.4, 2.5]) # 创建数组 yarr
cond = np.array([True, False, True, True, False]) # 创建条件数组
result = np.where(cond, xarr, yarr) # 如果 cond 为 True,则选择 xarr 中的元素,否则选择 yarr 中的元素
print(result) # 打印结果
[1.1 2.2 1.3 1.4 2.5]
np.where
(示例)NumPy 提供了一组用于计算数组统计信息的方法:
axis
参数来计算沿特定轴的统计信息:cumsum
和 cumprod
cumsum
和 cumprod
: 计算累积和和累积积:方法 | 描述 |
---|---|
sum |
数组中所有元素或沿轴的元素总和;零长度数组的总和为 0。 |
mean |
算术平均值;对于零长度数组无效(返回 NaN)。 |
std , var |
分别为标准差和方差。 |
方法 | 描述 |
---|---|
min ,max |
最小值和最大值。 |
argmin , argmax |
分别为最小和最大元素的索引。 |
cumsum |
从 0 开始的元素累积和。 |
cumprod |
从 1 开始的元素累积积。 |
sum
: True
值被视为 1,False
值被视为 0。用于计算 True
值的数量。any
和 all
any
和 all
:
any
: 检查布尔数组中是否至少有一个值为 True
。all
: 检查布尔数组中是否所有值都为 True
。sort
方法sort
方法: 就地对数组进行排序。rng = np.random.default_rng(seed=12345) # 创建随机数生成器
arr = rng.standard_normal((5, 3)) # 创建一个 5x3 的标准正态分布数组
arr.sort(axis=0) # 在每一列内排序
print(arr) # 打印排序后的数组
[[-1.95286306 -0.07534331 -0.87066174]
[-1.42382504 0.6488928 -0.74088465]
[-1.3677927 0.90219827 -0.46695317]
[-0.75938718 1.26372846 0.36105811]
[-0.25917323 2.34740965 0.96849691]]
[[-1.95286306 -0.87066174 -0.07534331]
[-1.42382504 -0.74088465 0.6488928 ]
[-1.3677927 -0.46695317 0.90219827]
[-0.75938718 0.36105811 1.26372846]
[-0.25917323 0.96849691 2.34740965]]
np.sort
函数: 返回数组的排序副本(不修改原始数组)。np.unique
np.unique
: 返回数组中已排序的唯一值。np.in1d
np.in1d
: 测试一个数组中的值是否在另一个数组中。方法 | 描述 |
---|---|
unique(x) |
计算 x 中已排序的唯一元素。 |
intersect1d(x, y) |
计算 x 和 y 中已排序的公共元素。 |
方法 | 描述 |
---|---|
union1d(x, y) |
计算元素的排序并集。 |
in1d(x, y) |
计算一个布尔数组,指示 x 的每个元素是否在 y 中。 |
方法 | 描述 |
---|---|
setdiff1d(x, y) |
集合差:在 x 中但不在 y 中的元素。 |
setxor1d(x, y) |
集合对称差;在任一数组中但不同时在两个数组中的元素。 |
np.save
和 np.load
np.save
和 np.load
: 用于以 NumPy 的二进制格式 (.npy
) 保存和加载数组的函数。np.savez
np.savez
: 将多个数组保存到一个未压缩的存档 (.npz
) 中。np.savez('array_archive.npz', a=arr, b=arr) # 保存多个数组
arch = np.load('array_archive.npz') # 加载存档
print(arch['b']) # 按名称访问数组
[0 1 2 3 4 5 6 7 8 9]
np.savez_compressed
: 将多个数组保存到一个压缩存档中。numpy.linalg
模块: 提供用于线性代数运算的函数。
矩阵乘法:
from numpy.linalg import inv, qr # 从 numpy.linalg 导入 inv 和 qr
rng = np.random.default_rng(seed=12345) # 创建随机数生成器
X = rng.standard_normal((5, 5)) # 创建一个 5x5 的标准正态分布数组
mat = X.T @ X # 计算 X 的转置与 X 的乘积
print(inv(mat)) # 计算矩阵的逆
[[ 0.15548538 -0.36723081 -0.52638547 -0.2300642 -0.04646089]
[-0.36723081 2.54917814 3.47827334 1.48196722 0.22206454]
[-0.52638547 3.47827334 5.46389554 2.46214396 0.63467543]
[-0.2300642 1.48196722 2.46214396 1.38302896 0.33430132]
[-0.04646089 0.22206454 0.63467543 0.33430132 0.33879566]]
numpy.linalg
函数函数 | 描述 |
---|---|
diag |
将方阵的对角线(或非对角线)元素作为一维数组返回,或将一维数组转换为方阵。 |
dot |
矩阵乘法。 |
trace |
计算对角线元素的总和。 |
numpy.linalg
函数(续)函数 | 描述 |
---|---|
det |
计算矩阵行列式。 |
eig |
计算方阵的特征值和特征向量。 |
inv |
计算方阵的逆。 |
numpy.linalg
函数(续)函数 | 描述 |
---|---|
pinv |
计算矩阵的 Moore-Penrose 伪逆。 |
qr |
计算 QR 分解。 |
svd |
计算奇异值分解 (SVD)。 |
numpy.linalg
函数(续)函数 | 描述 |
---|---|
solve |
求解线性方程组 Ax = b,其中 A 是方阵。 |
lstsq |
计算 Ax = b 的最小二乘解。 |
让我们使用 NumPy 模拟一个简单的随机游走:
简单随机游走的前 100 步:
我们可以分析随机游走:
在这里使用 argmax
并不总是有效的,因为它总是对数组进行全面扫描。在这种特殊情况下,一旦观察到 True
,我们就知道它是最大值。
我们可以高效地一次模拟多个随机游走:
然后,例如,计算平均最小穿越时间:
hits30 = (np.abs(walks) >= 30).any(axis=1) # 检查是否有任何游走达到 30 或 -30
crossing_times = (np.abs(walks[hits30]) >= 30).argmax(axis=1) # 计算达到 30 或 -30 的步数
print(crossing_times.mean()) # 计算平均穿越时间
500.6182621502209
这种向量化方法需要创建一个包含 nwalks * nsteps 个元素的数组,这可能会在大型模拟中使用大量内存。如果内存更受限制,则需要采用不同的方法。
NumPy 的强大之处: NumPy 凭借其高效的 ndarray
和向量化操作,为 Python 中的数值计算提供了强大的基础。
关键概念:
ndarray
: 具有同质数据的多维数组。dtype
: 数组元素的数据类型。numpy.random
: 生成随机数数组。NumPy 中向量化的概念与您可能在您了解的其他编程语言中执行类似操作的方式相比如何?
您能想到哪些具体的数据分析任务,其中 NumPy 的面向数组方法比使用 Python 列表和循环更有效?
NumPy 数组切片是视图而不是副本,有哪些优点和潜在缺点?什么时候可能需要显式创建副本?
考虑 np.where
函数。您如何使用它来实现比我们看到的简单示例更复杂的条件逻辑?
为什么理解 NumPy 的数据类型 (dtypes
) 很重要?它们如何影响性能和内存使用?
讨论您可能使用 NumPy 线性代数函数(例如,dot
、inv
、eig
)的场景。