入门
什么是 NumPy?
NumPy 的强大源于 ndarray:一种同质、连续内存的 n 维数组。由于所有元素共享同一类型并存储在同一块内存中,运算可在编译后的 C/Fortran 代码中执行(向量化),速度比等效的 Python 循环快 10-100 倍。几乎整个科学 Python 栈(pandas、scikit-learn、PyTorch、TensorFlow)都构建在 NumPy 数组之上。
# NumPy: the fundamental package for numerical computing in Python.
# Core data structure: the n-dimensional array (ndarray).
# Key advantages over pure Python lists:
# - Homogeneous, contiguous memory -> fast vectorized ops
# - Broadcasting: operate on arrays of different shapes
# - Rich set of math / linear algebra / FFT / random routines
# - C/Fortran backend -> orders of magnitude faster than Python loops
import numpy as np
print(np.__version__) # e.g. 1.26.0安装与导入
np 是 NumPy 几乎通用的别名——每个教程和库都期望如此,所以保持这个约定。如果需要特定的 CPU 优化版本,conda 通常提供 MKL 链接的 NumPy,其线性代数运算比 pip 默认版本更快。
# Install NumPy
pip install numpy
# Or with conda
conda install numpy
# The universal convention is to alias as np
import numpy as np
a = np.array([1, 2, 3])
print(a) # [1 2 3]
print(type(a)) # <class 'numpy.ndarray'>ndarray 属性
了解这些属性是调试 NumPy 代码的基础。shape 和 dtype 最常用:shape 告诉你结构,dtype 告诉你内存占用。itemsize * size = nbytes 让你在分配大型数组前估算内存使用量。
import numpy as np
a = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.int64)
a.ndim # 2 -> number of axes (dimensions)
a.shape # (2, 3) -> tuple of axis sizes
a.size # 6 -> total elements = prod(shape)
a.dtype # int64 -> element type
a.itemsize # 8 -> bytes per element
a.nbytes # 48 -> total bytes = size * itemsize
a.T # shape (3, 2) transposed view
a.flat # flat iterator over all elements数据类型(dtype)
选择正确的 dtype 对内存和正确性至关重要。float32 比 float64 节省一半内存,对大多数机器学习工作几乎没有精度损失。int8/int16 可能会静默溢出——np.array([200], dtype=np.int8) 会变成 -56。始终选择能安全容纳你数据范围的最小 dtype,尤其是在大型数组和嵌入式/边缘部署中。
import numpy as np
# Specifying dtype at creation
a = np.array([1, 2, 3], dtype=np.float32)
b = np.array([1, 2, 3], dtype="float32") # string form works too
# Integer types: int8 int16 int32 int64 (and unsigned uint*)
# Float types: float16 float32 float64 (default)
# Complex: complex64 complex128
# Others: bool, string_, unicode_, object
# Cast an existing array
c = a.astype(np.int32)
# Default int/float match the platform
print(np.dtype(int).itemsize) # 4 or 8
print(np.dtype(float).itemsize) # 8
# Beware overflow:
small = np.array([200], dtype=np.int8) # wraps around!数组 vs Python 列表
列表推导式版本在 Python 解释器中运行(每个元素一条字节码);NumPy 版本分派到对连续内存的单次 C 循环。对于数值工作负载,NumPy 通常快 20-100 倍,且内存占用少得多(没有每个元素的 Python 对象)。只有在需要异构类型或非数值数据时才使用列表。
import numpy as np
import time
# Python list: element-wise multiply needs a loop or comprehension
py_list = list(range(1_000_000))
start = time.perf_counter()
py_sq = [x * 2 for x in py_list]
print("list:", time.perf_counter() - start)
# NumPy: vectorized, runs in C
arr = np.arange(1_000_000)
start = time.perf_counter()
np_sq = arr * 2
print("numpy:", time.perf_counter() - start)
# NumPy is typically 20-100x faster for numeric workloads.
# Lists are heterogeneous & flexible; arrays are homogeneous & fast.数组创建
从 Python 结构创建
np.array() 将列表/元组(或任何嵌套可迭代对象)转换为 ndarray。嵌套深度决定维度数。在创建时指定 dtype 可避免后续额外的复制——np.array([1,2,3]) 在大多数平台上默认为 int64,所以如果你想要浮点数就传入 dtype=float。
import numpy as np
# From a list (1-D)
a = np.array([1, 2, 3])
# From a list of lists (2-D)
b = np.array([[1, 2, 3],
[4, 5, 6]])
# From nested lists (3-D)
c = np.array([[[1, 2], [3, 4]],
[[5, 6], [7, 8]]])
# Specify dtype up front
d = np.array([1, 2, 3], dtype=np.float32)
# From any iterable
e = np.array(range(5)) # [0 1 2 3 4]
f = np.array((1, 2, 3)) # from a tuple内置范围与网格
对于浮点数优先使用 linspace 而非 arange——arange 的步长由于浮点舍入可能产生差一端点错误。meshgrid 是为 3D 绘图、图像处理和有限差分计算构建坐标网格的标准方法。使用 np.mgrid / np.ogrid 可创建开放式(节省内存)的网格。
import numpy as np
# arange: like range but returns array (watch float edge!)
np.arange(0, 10, 2) # [0 2 4 6 8]
np.arange(0, 1, 0.1) # may or may not include 1.0 (float!)
# linspace: exactly num points, endpoints included by default
np.linspace(0, 1, 5) # [0. 0.25 0.5 0.75 1. ]
# logspace: 10**start .. 10**stop
np.logspace(0, 3, 4) # [ 1. 10. 100. 1000.]
# Meshgrids for evaluating functions on a grid
x = np.linspace(-2, 2, 5)
y = np.linspace(-2, 2, 5)
X, Y = np.meshgrid(x, y)
Z = X**2 + Y**2 # 5x5 grid of values零、一与常量数组
zeros/ones/full 会预填充缓冲区;empty 跳过填充,所以当你无论如何都要覆盖每个元素时(例如作为输出缓冲区)它最快。eye(k=n) 构建单位矩阵;eye(k=1) 将对角线向上移动一 位。始终向 zeros/ones 传入 dtype——它们默认为 float64,如果你想要整数就会浪费内存。
import numpy as np
np.zeros((2, 3)) # 2x3 of float64 zeros
np.zeros((2, 3), dtype=int) # 2x3 of int zeros
np.ones((3, 3)) # 3x3 of ones
np.full((2, 2), 7) # 2x2 filled with 7
np.full((2, 2), np.pi) # fill with any constant
# Identity & eye
np.eye(3) # 3x3 identity matrix
np.eye(3, k=1) # identity shifted up by one diagonal
np.diag([1, 2, 3]) # diagonal matrix from a vector
# Empty: uninitialized memory (fastest, but garbage values)
np.empty((2, 2)) # allocate without filling随机数组
使用 np.random.default_rng(seed)——现代 Generator 更快、统计特性更好且可重现。传统的 np.random.rand/randn/seed API 使用全局状态,在新代码中不推荐。始终为实验和测试中的可重现性传入显式种子;没有种子,结果在不同运行间会不同。
import numpy as np
rng = np.random.default_rng(seed=42) # modern Generator (NumPy >= 1.17)
rng.random((2, 3)) # uniform in [0, 1), shape (2, 3)
rng.standard_normal((2, 3)) # standard normal (mean 0, std 1)
rng.normal(100, 15, size=5) # normal(mu=100, sigma=15)
rng.uniform(0, 10, size=5) # uniform in [low, high)
rng.integers(0, 10, size=5) # random ints in [low, high)
# Reproducibility: always pass a seed
rng2 = np.random.default_rng(42)
print(rng2.random(3)) # same every run
# Legacy API (still common but prefer default_rng)
# np.random.rand(2, 3) # uniform [0,1)
# np.random.randn(2, 3) # standard normal复制现有数组
切片和 .view() 创建共享内存的视图——修改一个会改变另一个。当你需要独立数组时使用 .copy()。reshape、ravel、transpose 在可能时也返回视图,所以如果你需要原始数组,在修改前总是 .copy()。忘记这一点是最常见的 NumPy bug 之一。
import numpy as np
a = np.array([1, 2, 3])
# View (shares memory!) - shape change only
v = a.view()
v[0] = 99
print(a) # [99 2 3] <- a is mutated!
# Deep copy (independent)
b = a.copy()
b[0] = 0
print(a) # [99 2 3] <- a unchanged
# np.array defaults to copying
c = np.array(a)
c is a # False
# Repeat / tile to expand arrays
np.repeat([1, 2, 3], 2) # [1 1 2 2 3 3]
np.tile([1, 2, 3], 2) # [1 2 3 1 2 3]索引与切片
基础切片
切片遵循 Python 的 [start:stop:step] 约定,stop 不包含在内。关键的是,NumPy 切片是视图——它们与源共享内存,所以修改切片会修改原始数组。这是有意为之(节省内存),但也是常见的 bug 来源。当你需要独立数组时使用 .copy()。
import numpy as np
a = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
a[0] # 0 (scalar)
a[-1] # 9 (last element)
a[2:5] # [2 3 4] start:stop (stop excluded)
a[:5] # [0 1 2 3 4]
a[5:] # [5 6 7 8 9]
a[::2] # [0 2 4 6 8] step=2
a[::-1] # [9 8 7 6 5 4 3 2 1 0] reversed
a[1:7:2] # [1 3 5] start:stop:step
# Slices return VIEWS (not copies) -> editing changes the original
b = a[2:5]
b[0] = 99
print(a[2]) # 99 <- a was modified!多维索引
使用 a[i, j] 而非 a[i][j]——逗号形式是一次操作且更快。a[:, j] 选取整列。在处理 3 维及以上数组时使用 ...(省略号)表示'所有剩余轴',避免编写多个冒号。每个切片都返回视图。
import numpy as np
a = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
a[1, 2] # 6 (row 1, col 2) - equivalent to a[1][2] but faster
a[0] # [1 2 3] (row 0)
a[0, :] # [1 2 3] (row 0, all cols)
a[:, 1] # [2 5 8] (all rows, col 1)
a[1:3, 0:2] # [[4 5]
# [7 8]] rows 1-2, cols 0-1
a[-1] # [7 8 9] last row
a[:, -1] # [3 6 9] last column
# ... (ellipsis) selects any remaining full axes
b = np.zeros((2, 3, 4))
b[..., 0] # shape (2, 3) - last axis indexed, others kept
b[1, ...] # shape (3, 4) - first axis indexed布尔/掩码索引
布尔索引总是返回副本,从不返回视图——可自由修改。用 & | ~(NOT and/or——它们对数组的真值操作并会报错)组合掩码。np.where 返回索引;np.where(cond, x, y) 是向量化的三元运算符。这是过滤和条件逻辑的主力工具。
import numpy as np
a = np.array([1, 2, 3, 4, 5, 6])
# Build a boolean mask
mask = a > 3
# [False False False True True True]
a[mask] # [4 5 6] (select elements where mask is True)
a[a > 3] # same, in one expression
a[(a > 2) & (a < 6)] # [3 4 5] combine with & (and), | (or), ~ (not)
# Modify via mask
a[a > 3] = 0 # set all matching elements
a[a == 0] = -1 # conditional assignment
# Count matching elements
(a > 3).sum() # 3 (True == 1)
np.count_nonzero(a > 3)
# np.where: positions where condition is True
np.where(a > 3) # (array([3, 4, 5]),) indices花式/整数数组索引
花式(整数数组)索引也返回副本,从不返回 视图。当用两个索引数组索引 2-D 数组 b[rows, cols] 时,数组是逐元素配对的(给出点),而非叉积。要获得叉积使用 np.ix_(rows, cols)。这个区别是常见的混淆来源。
import numpy as np
a = np.array([10, 20, 30, 40, 50])
# Index with a list/array of indices
a[[0, 2, 4]] # [10 30 50]
a[np.array([0, 2, 4])] # same
# Indices can repeat
a[[0, 0, 1]] # [10 10 20]
# Negative indices work
a[[-1, -2]] # [50 40]
# 2-D fancy indexing
b = np.arange(12).reshape(3, 4)
rows = np.array([0, 2])
cols = np.array([1, 3])
b[rows, cols] # [1 11] pairs (0,1) and (2,3)
# Mix a slice and an index array
b[1:, [0, 2]] # rows 1-2, cols 0 and 2
# np.take: same as fancy indexing, more flexible
np.take(a, [0, 2]) # [10 30]索引赋值与 np.where
np.where(cond, x, y) 是向量化的 if-else——极其常用于基于条件构建数组。带单个参数时它返回条件为 True 的索引。np.select 处理多个互斥条件,np.clip 是无需编写显式掩码即可限定值范围的简洁方式。
import numpy as np
a = np.array([1, 2, 3, 4, 5, 6])
# Conditional assignment with a mask
a[a % 2 == 0] = -1 # even -> -1: [1 -1 3 -1 5 -1]
# np.where(cond, x, y): vectorized ternary
b = np.where(a > 3, "big", "small")
# ['small' 'small' 'small' 'big' 'big' 'big']
# np.where with one argument -> indices of True
idx = np.where(a == -1)
# (array([1, 3, 5]),)
# np.select: multiple conditions, multiple choices
conds = [a < 3, a > 3, a == 3]
choices = ["low", "high", "mid"]
np.select(conds, choices, default="ok")
# np.clip: clamp values to a range
np.clip(a, 0, 4) # values < 0 -> 0, > 4 -> 4重塑与形状操作
reshape 与 -1 推断
reshape 改变形状但保持相同的总大小。对一个维度使用 -1,NumPy 会从大小推断它——只允许一个 -1。reshape 通常返回视图(无数据复制),所以修改重塑后的数组会修改原始数组。元素总数必须保持不变,否则会引发 ValueError。
import numpy as np
a = np.arange(12) # [0 1 2 ... 11], shape (12,)
# reshape to (3, 4)
b = a.reshape(3, 4)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
# Use -1 to infer one dimension
a.reshape(2, -1) # (2, 6) - -1 means "figure it out"
a.reshape(-1, 3) # (4, 3)
a.reshape(2, -1, 2) # (2, 3, 2) only one -1 allowed
# Total size must match -> ValueError otherwise
# a.reshape(5, 3) # 5*3 = 15 != 12
# reshape returns a view when possible (no copy)
b[0, 0] = 99
print(a[0]) # 99 <- a was modifiedflatten 与 ravel
ravel() 返回视图(快速,共享内存),而 flatten() 总是返回副本(安全但较慢且使用 2 倍内存)。当你只需迭代时使用 ravel,当你需要修改结果而不影响原始数组时使用 flatten。order 参数在与 Fortran 代码或 MATLAB 互操作时很重要。
import numpy as np
a = np.array([[1, 2, 3],
[4, 5, 6]])
# ravel: returns a VIEW (C order by default) - efficient
a.ravel() # [1 2 3 4 5 6]
a.ravel(order='F') # [1 4 2 5 3 6] Fortran (column) order
# flatten: always returns a COPY - safe to mutate
flat = a.flatten()
flat[0] = 99
print(a[0, 0]) # 1 <- a unchanged
# Order conventions:
# 'C' (default): last axis varies fastest (row-major)
# 'F': first axis varies fastest (column-major)
# 'A': Fortran order if array is F-contiguous, else C
# 'K': preserve memory order转置与交换轴
转置只是 一个重新排序内存读取方式的视图——不复制数据,这使它很廉价,但意味着结果可能不是连续的。对于 2-D 数组 .T 就够了;对于 3 维及以上数组使用 transpose(axis_order)、swapaxes 或 moveaxis 来精确控制哪个轴去哪里。对非连续数组的操作可能较慢,所以 np.ascontiguousarray 在热循环中可能有帮助。
import numpy as np
a = np.arange(12).reshape(3, 4)
a.T # shape (4, 3) transpose (2-D)
a.transpose() # same
# For 3+ dimensions, specify axis order
b = np.arange(24).reshape(2, 3, 4)
b.transpose(1, 0, 2) # shape (3, 2, 4) axes reordered
b.swapaxes(0, 1) # shape (3, 2, 4) swap two axes
b.T # shape (4, 3, 2) reverse all axes
# Move axes to new positions
np.moveaxis(b, 0, -1) # shape (3, 4, 2)
# All of these return VIEWS (no data copy)堆叠数组
stack 添加一个新轴(将两个 1-D 数组变成 2-D);concatenate 沿现有轴连接。vstack/hstack/dstack 是 1-D 和 2-D 数组的便捷包装器。对于复杂布局,np.block 接受嵌套的数组列表,就像从子块构建矩阵一样。所有堆叠函数都创建新数组(副本)。
import numpy as np
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
# Stack along a NEW axis
np.stack([a, b]) # shape (2, 3) - default axis=0
np.stack([a, b], axis=1) # shape (3, 2) column stack
# Concatenate along an EXISTING axis
np.concatenate([a, b]) # [1 2 3 4 5 6]
np.vstack([a, b]) # shape (2, 3) vertical
np.hstack([a, b]) # [1 2 3 4 5 6] horizontal
np.dstack([a, b]) # shape (1, 3, 2) depth
# 2-D arrays
A = np.ones((2, 2))
B = np.zeros((2, 2))
np.hstack([A, B]) # shape (2, 4)
np.vstack([A, B]) # shape (4, 2)
# np.block: build from nested blocks
np.block([[A, B], [B, A]]) # shape (4, 4)分割数组
split 要求等大小的块(否则引发 ValueError),而 array_split 允许不均匀分割——当 N 不能均匀整除时很方便。vsplit/hsplit/dsplit 是轴特定的辅助函数,等同于 np.split(arr, n, axis=...)。所有这些都返回原始数组的视图列表,因此内存高效。
import numpy as np
a = np.arange(12)
# Split into equal parts (must divide evenly)
np.split(a, 3) # [array([0,1,2,3]), array([4,5,6,7]), array([8,9,10,11])]
# Split at specific indices
np.split(a, [3, 5]) # [array([0,1,2]), array([3,4]), array([5,6,7,8,9,10,11])]
# 2-D
b = np.arange(16).reshape(4, 4)
np.vsplit(b, 2) # split along rows -> 2 arrays of shape (2, 4)
np.hsplit(b, 2) # split along cols -> 2 arrays of shape (4, 2)
# array_split: allows uneven division
np.array_split(np.arange(10), 3) # sizes [4, 3, 3] - no error
# dsplit: split along depth (axis 2)
c = np.arange(24).reshape(2, 2, 6)
np.dsplit(c, 3) # 3 arrays of shape (2, 2, 2)广播
广播规则
广播通过虚拟扩展大小为 1 的轴让你组合不同形状的数组——实际上没有复制数据。规则:从右向左比较形状;每个轴必须相等或其中一个为 1(或不存在)。这就是让 NumPy 代码简洁(无显式循环)的原因,但它也是静默形状 bug 的常见来源——当看起来不对时总是检查形状。
import numpy as np
# Broadcasting: operate on arrays of different shapes without copying.
# Two shapes are compatible if, for each trailing axis, they are
# equal OR one of them is 1.
# Scalar broadcast to any array
a = np.array([1, 2, 3])
a + 10 # [11 12 13] 10 treated as shape ()
# 1-D with 2-D
A = np.ones((3, 4))
row = np.array([10, 20, 30, 40])
A + row # row (4,) broadcast to (3, 4) -> added to each row
col = np.array([[100], [200], [300]]) # shape (3, 1)
A + col # col broadcast across columns -> added to each column
# Incompatible shapes raise ValueError
# A + np.array([1, 2, 3]) # (3,4) vs (3,) -> error为广播添加轴
np.newaxis(None 的别名)插入一个大小为 1 的轴,是重塑数组使其按你想要的方式广播的标准技巧。添加列轴 (a[:, None]) 和行轴 (a[None, :]) 让你无需编写循环即可计算外积、成对差分等。np.expand_dims 是用于可读性的命名函数等价物。
import numpy as np
a = np.array([1, 2, 3]) # shape (3,)
b = np.array([10, 20, 30, 40]) # shape (4,)
# a + b would fail (3,) vs (4,)
# Insert a new axis so shapes become (3, 1) and (4,) -> broadcasts to (3, 4)
a_col = a[:, np.newaxis] # shape (3, 1) - same as a.reshape(-1, 1)
b_row = b[np.newaxis, :] # shape (1, 4)
outer = a_col + b_row # shape (3, 4) outer sum
# [[11 21 31 41]
# [12 22 32 42]
# [13 23 33 43]]
# np.newaxis / None are equivalent
a[:, None] # same as a[:, np.newaxis]
# np.expand_dims is the explicit function form
np.expand_dims(a, axis=1) # shape (3, 1)外积与成对运算
添加新轴将 1-D 运算转换为 2-D 外积式计算是向量化 NumPy 的关键惯用法。a[:, None] - b[None, :] 无需循环即可计算所有成对差分——比嵌套 Python 循环快得多。此模式驱动距离矩阵、核计算和网格运算。
import numpy as np
a = np.array([1, 2, 3])
b = np.array([10, 20, 30, 40])
# Outer product (3,) x (4,) -> (3, 4)
np.outer(a, b)
# [[10 20 30 40]
# [20 40 60 80]
# [30 60 90 120]]
# Manual outer via broadcasting
a[:, None] * b[None, :] # same as np.outer(a, b)
# Pairwise difference: (4,) - (3,) -> (4, 3) or (3, 4)?
diff = b[:, None] - a[None, :] # shape (4, 3): diff[i, j] = b[i] - a[j]
# Pairwise equality (used in set ops, distance)
x = np.array([1, 2, 3, 4])
y = np.array([2, 4, 6])
x[:, None] == y[None, :] # shape (4, 3) boolean matrix
# Add a constant vector to each row of a matrix
M = np.zeros((5, 3))
v = np.array([1, 2, 3])
M + v # broadcasts v to every row常见广播陷阱
形状为 (N,) 的 1-D 数组针对 2-D 数组广播为行——它不是列向量。要使其成为列,你必须添加一个轴:a[:, None]。添加新轴时要注意意外的广播——两个 (1000, 1000) 数组可能爆炸成 (1000, 1000, 1000) 中间结果(8 GB)并使程序崩溃。在运行大型计算前总是用 .shape 验证形状。
import numpy as np
# Pitfall 1: (N,) is NOT a row or column vector - it has no axis direction
a = np.array([1, 2, 3])
M = np.ones((3, 3))
M + a # OK: (3,3) + (3,) -> a broadcast as a ROW
M + a[:, None] # OK: a as a COLUMN
# Pitfall 2: accidentally broadcasting when you wanted element-wise
x = np.array([1, 2, 3])
y = np.array([10, 20, 30])
x + y # [11 22 33] element-wise (both (3,))
x[:, None] + y # (3,3) matrix - probably not what you wanted!
# Pitfall 3: shape (1,) vs () - both broadcast but mean different things
np.array([5]).shape # (1,)
np.array(5).shape # () - 0-D, broadcasts to ANYTHING
# Pitfall 4: memory blow-up from over-broadcasting
# Big arrays broadcast together can produce enormous intermediates
A = np.ones((1000, 1000))
B = np.ones((1000, 1000))
(A * B).sum() # OK - intermediate is 8MB
# A[:, None, :] * B[None, :, :] # (1000,1000,1000) = 8GB! crash使用 np.broadcast_to 广播
np.broadcast_to 让你显式创建广播视图——当 API 需要特定形状但你想要避免完整副本的内存成本时很有用。结果是只读的,因为底层数据比形状建议的要小。np.broadcast_arrays 在你想像多个数组具有相同形状一样迭代它们时很方便。
import numpy as np
# np.broadcast_to: explicit view of an array broadcast to a new shape
a = np.array([1, 2, 3]) # shape (3,)
b = np.broadcast_to(a, (4, 3)) # shape (4, 3) - VIEW, no copy
# [[1 2 3]
# [1 2 3]
# [1 2 3]
# [1 2 3]]
b[0, 0] = 99 # ERROR: broadcast_to returns read-only view
# np.broadcast_arrays: broadcast several arrays together (as views)
x = np.array([1, 2, 3]) # (3,)
y = np.array([[10], [20]]) # (2, 1)
bx, by = np.broadcast_arrays(x, y)
bx.shape # (2, 3)
by.shape # (2, 3)
# np.broadcast: low-level iterator object
b = np.broadcast(x, y)
b.shape # (2, 3)
b.index # current position in the iteration
b.iters # list of iterators over each input数学运算
逐元素算术
NumPy 运算符(+、*、**)映射到 ufunc(np.add、np.multiply、np.power)并逐元素操作。* 运算符是逐元素的,不是矩阵乘法——矩阵乘法使用 @ 或 np.dot。就地运算符(+=、*=)通过重用数组缓冲区节省内存;它们需要匹配的 dtype 和形状(广播后)。
import numpy as np
a = np.array([1, 2, 3, 4])
b = np.array([10, 20, 30, 40])
a + b # [11 22 33 44]
a - b # [-9 -18 -27 -36]
a * b # [10 40 90 160] element-wise (NOT matrix product)
a / b # [0.1 0.1 0.1 0.1] always float
a // b # [0 0 0 0] floor division
a ** 2 # [1 4 9 16] power
a % 2 # [1 0 1 0] modulo
-a # [-1 -2 -3 -4] negation
# In-place ops (modify a, save memory)
a += 10
a *= 2
# Scalar ops broadcast
a + 100 # [102 104 106 108]
# Universal functions (ufuncs) - same operations as function form
np.add(a, b)
np.multiply(a, b)
np.power(a, 2)通用函数(ufunc)
ufunc 是逐元素操作的向量化函数,在编译的 C 中运行——比在循环中应用 Python 的 math 模块快得多。np.maximum(两个数组的逐元素最大值)不同于 np.max(一个数组的最大值)。使用 np.fmax 在比较中忽略 NaN。所有 ufunc 都支持 out=,用于写入预分配缓冲区以实现零分配热循环。
import numpy as np
a = np.array([-1.5, 0.0, 1.5, 2.5])
# Math ufuncs
np.abs(a) # [1.5 0. 1.5 2.5]
np.sqrt(np.abs(a))
np.exp(a) # e^a
np.log(np.abs(a) + 1)
np.log2(a + 5)
np.log10(a + 5)
# Rounding
np.round(a) # [-2. 0. 2. 2.]
np.floor(a) # [-2. 0. 1. 2.]
np.ceil(a) # [-1. 0. 2. 3.]
np.trunc(a) # [-1. 0. 1. 2.] truncate toward zero
# Trigonometry (radians)
np.sin(np.pi/2) # 1.0
np.cos(0) # 1.0
np.degrees(np.pi) # 180.0
np.radians(180) # 3.14159...
# Comparison ufuncs
np.greater(a, 0) # [False False True True]
np.maximum(a, 0) # clip negatives to 0 (element-wise max)
np.minimum(a, 0)归约运算
归约(sum、mean、max、min、std)默认对整个数组操作,或用 axis= 沿单个轴。axis=0 归约行(向下折叠),axis=1 归约列(横向折叠)。使用 keepdims=True 保持归约轴为大小 1——对于将结果广播回去至关重要。当求和可能溢出的整数时始终传入 dtype=np.float64。
import numpy as np
a = np.array([[1, 2, 3],
[4, 5, 6]])
# Global reductions
a.sum() # 21
a.prod() # 720
a.min(), a.max()
a.mean() # 3.5
a.std(), a.var()
a.argmin(), a.argmax() # indices of min/max (flattened)
# Axis-wise reductions
a.sum(axis=0) # [5 7 9] sum down each column
a.sum(axis=1) # [6 15] sum across each row
a.min(axis=1) # [1 4] min of each row
a.cumsum(axis=0) # cumulative sum down columns
a.cumprod(axis=1) # cumulative product across rows
# Keep dimensions
a.sum(axis=0, keepdims=True) # shape (1, 3) - keeps the axis
# Specify dtype for accumulation (avoid overflow!)
a.sum(dtype=np.float64)
# NaN-aware versions
b = np.array([1.0, np.nan, 3.0])
np.nansum(b) # 4.0 treats NaN as 0累积与扫描运算
cumsum / cumprod 保持与输入相同的形状并返回运行总计。diff 是离散导数,是 cumsum 的逆运算。注意 cumsum 中的整数溢出——累加 int32 值可能静默环绕;提升到 int64 或 float 以确保安全。任何 ufunc 上的 .accumulate 方法(np.maximum.accumulate)给出运行归约。
import numpy as np
a = np.array([1, 2, 3, 4, 5])
# Cumulative sum and product
np.cumsum(a) # [ 1 3 6 10 15]
np.cumprod(a) # [ 1 2 6 24 120]
# 2-D: specify axis
b = np.array([[1, 2], [3, 4]])
np.cumsum(b, axis=0) # [[1 2]
# [4 6]]
np.cumsum(b, axis=1) # [[1 3]
# [3 7]]
# Differences (inverse of cumulative sum)
np.diff(a) # [1 1 1 1] a[i+1] - a[i]
np.diff(a, n=2) # second-order diff [0 0 0]
# Edge: cumsum on integers can overflow silently
big = np.array([2**30, 2**30, 2**30], dtype=np.int32)
np.cumsum(big) # wraps around - use int64 or float!
# Cumulative max / min
np.maximum.accumulate(a) # [1 2 3 4 5]
np.minimum.accumulate(a) # [1 1 1 1 1]比较与逻辑运算
在布尔数组上使用 & | ~(NOT and/or——那些需要单个布尔值)。np.any / np.all 将布尔数组归约为单个 True/False,在断言和条件中很有用。永远不要对浮点数使用 ==——np.isclose 或 np.allclose 配合适当的容差(atol/rtol)处理浮点舍入。allclose 是比较浮点数组的标准方法。
import numpy as np
a = np.array([1, 2, 3, 4, 5])
# Element-wise comparisons return boolean arrays
a > 3 # [False False False True True]
a == 3 # [False False True False False]
a != 3
np.greater(a, 3)
# Logical ops on boolean arrays
b = (a > 2)
c = (a < 5)
np.logical_and(b, c) # [False False True True False]
np.logical_or(b, c)
np.logical_not(b)
np.logical_xor(b, c)
# Array-level truth testing
np.any(a > 4) # True
np.all(a > 0) # True
np.all(a > 0, axis=0) # per-axis
(a == 3).any() # True
# np.isclose for float comparisons
np.isclose(0.1 + 0.2, 0.3) # True (handles float error)
np.allclose([0.1, 0.2], [0.1, 0.2 + 1e-9], atol=1e-8) # True统计运算
基础统计
var/std 默认为 ddof=0(总体),但 pandas 默认为 ddof=1(样本)——这是常见的失配来源。样本统计使用 ddof=1。np.percentile 接受 0-100 的值,np.quantile 接受 0.0-1.0。np.corrcoef 返回完整矩阵;索引 [0, 1] 获取两个数组之间的标量系数。
import numpy as np
a = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
a.mean() # 5.5 arithmetic mean
a.median() if hasattr(a, 'median') else np.median(a) # 5.5
a.var() # 8.25 variance (population, ddof=0)
a.std() # 2.87 standard deviation
a.min(), a.max() # 1, 10
a.ptp() # 9 peak-to-peak = max - min
# Sample vs population variance
a.var(ddof=1) # sample variance (divide by N-1, like pandas default)
# Percentiles & quantiles
np.percentile(a, 50) # 5.5 median
np.percentile(a, [25, 50, 75]) # [3.25 5.5 7.75] quartiles
np.quantile(a, 0.5) # 5.5 same as percentile / 100
# Correlation
x = np.array([1, 2, 3, 4])
y = np.array([2, 4, 5, 4])
np.corrcoef(x, y) # 2x2 correlation matrix直方图与分箱
np.histogram 返回计数和边界(边界比 bin 数多一个元素)。使用 density=True 获取概率密度(面积 = 1)而非原始计数。np.histogram2d 构建用于热力图和联合分布的 2-D 直方图。np.digitize 将值映射到 bin 索引;np.bincount 是计数整数出现的快速方法。
import numpy as np
data = np.random.default_rng(42).standard_normal(1000)
# 1-D histogram
counts, edges = np.histogram(data, bins=10)
# counts: array of element counts per bin
# edges: array of bin edge positions (length = bins + 1)
# Custom bin edges
np.histogram(data, bins=[-3, -1, 0, 1, 3])
# Density (integrates to 1) vs counts
np.histogram(data, bins=20, density=True)
# 2-D histogram (for heatmaps)
x = np.random.default_rng(1).standard_normal(1000)
y = np.random.default_rng(2).standard_normal(1000)
H, xedges, yedges = np.histogram2d(x, y, bins=20)
# Digitize: which bin does each value fall into?
bins = [-2, -1, 0, 1, 2]
indices = np.digitize(data, bins) # array of bin indices (1-based)
# Bincount: count occurrences of each non-negative int
np.bincount(np.array([0, 1, 1, 3, 3, 3])) # [1 2 0 3]排序与顺序统计
argsort 是排序的关键:它返回会排序数组的索引,让你以相同方式重新排序另一个数组。np.partition 当你只需要 top-k 元素时比完整排序快得多(O(n) vs O(n log n))。np.unique 返回排序的唯一值;传入 return_counts=True 获取类似直方图的摘要。
import numpy as np
a = np.array([3, 1, 4, 1, 5, 9, 2, 6])
# Sorting
np.sort(a) # [1 1 2 3 4 5 6 9] returns a copy
a.sort() # in-place sort (modifies a)
np.sort(a)[::-1] # descending
np.argsort(a) # indices that would sort a (no copy)
# 2-D sorting along axis
b = np.array([[3, 1, 2], [6, 5, 4]])
np.sort(b, axis=0) # sort down each column
np.sort(b, axis=1) # sort across each row (default)
# Partial sorting: top-k smallest (no full sort)
np.partition(a, 3) # 3 smallest first (unordered), rest after
np.argpartition(a, 3) # indices of partition
# k-th smallest element (selection algorithm)
np.partition(a, 2)[2] # 3rd smallest (index 2)
# unique values (sorted)
np.unique([1, 2, 1, 3, 2]) # [1 2 3]
np.unique(a, return_counts=True) # values and counts协方差与相关性
np.cov 默认将每行视为一个变量(rowvar=True)——与使用列的 pandas 相反。要匹配 pandas 行为,传入 rowvar=False 或转置数据。np.corrcoef 返回完整矩阵;索引 [0, 1] 获取两个变量之间的标量系数。协方差矩阵的对角线是每个变量的方差。
import numpy as np
# Sample data: 3 variables, 5 observations
data = np.array([[1, 2, 3, 4, 5],
[2, 4, 1, 3, 5],
[5, 4, 3, 2, 1]], dtype=float)
# Covariance matrix (3x3)
# Each ROW is a variable, so set rowvar=False to treat COLUMNS as variables
cov = np.cov(data, rowvar=True) # 3x3 covariance between rows
# Diagonal = variance of each variable
# Off-diagonal = covariance between pairs
# Correlation matrix
corr = np.corrcoef(data) # 3x3, values in [-1, 1]
# Correlation between two specific variables
np.corrcoef(data[0], data[1])[0, 1] # scalar
# Weighted covariance: compute manually
weights = np.array([1, 1, 1, 2, 2])
mean = np.average(data, axis=1, weights=weights, keepdims=True)
centered = data - mean
weighted_cov = (centered * weights) @ centered.T / weights.sum()
# Pearson correlation coefficient (manual)
def pearson(x, y):
xm, ym = x - x.mean(), y - y.mean()
return np.dot(xm, ym) / (np.sqrt((xm**2).sum()) * np.sqrt((ym**2).sum()))NaN 感知统计
标准归约会传播 NaN(任何 NaN 会毒化结果),这很少是你想要的。使用 nan* 变体(nanmean、nansum、nanstd)忽略 NaN 值。nan_to_num 用有限值替换 NaN 和无穷大——非常适合在计算前清理数据。对于大量 NaN 处理,pandas 通常比原始 NumPy 更符合人体工程学。
import numpy as np
a = np.array([1.0, 2.0, np.nan, 4.0, 5.0])
# Standard functions propagate NaN
a.mean() # nan
a.sum() # nan
# NaN-aware equivalents
np.nanmean(a) # 3.0 ignores NaN
np.nansum(a) # 12.0
np.nanstd(a)
np.nanmedian(a)
np.nanvar(a)
np.nanmin(a), np.nanmax(a)
np.nanpercentile(a, 50)
# Count non-NaN elements
np.count_nonzero(~np.isnan(a)) # 4
# Find NaN positions
np.isnan(a) # [False False True False False]
np.where(np.isnan(a)) # (array([2]),)
# Replace NaN with a value (imputation)
np.nan_to_num(a, nan=0.0) # replace NaN with 0
np.where(np.isnan(a), 0, a) # same, more explicit
a_filled = np.copy(a)
a_filled[np.isnan(a)] = np.nanmean(a) # fill with mean
# Check if any NaN
np.isnan(a).any() # True线性代数(numpy.linalg)
矩阵乘法
使用 @ 进行矩阵乘法——它是现代运算符(Python 3.5+),比 .dot() 更清晰,且适用于批量(N-D)运算。* 运算符是逐元素的(Hadamard),这是来自 MATLAB 的人常见的混淆来源。np.matmul(@) 在 1-D 处理和 N-D 数组的堆叠行为上与 np.dot 不同。
import numpy as np
A = np.array([[1, 2],
[3, 4]])
B = np.array([[5, 6],
[7, 8]])
# Matrix product (2-D)
A @ B # [[19 22]
# [43 50]] preferred operator (Py 3.5+)
A.dot(B) # same, older method
np.dot(A, B) # same for 2-D
# 1-D: dot is inner product
v = np.array([1, 2, 3])
w = np.array([4, 5, 6])
v @ w # 32 (1*4 + 2*5 + 3*6)
np.dot(v, w) # 32
# Mixed 1-D and 2-D
v = np.array([1, 2])
A @ v # [5 11] matrix-vector product
# Batched matmul (3-D)
X = np.random.rand(10, 3, 4) # 10 matrices of shape (3, 4)
Y = np.random.rand(10, 4, 2)
(X @ Y).shape # (10, 3, 2)
# Element-wise product (Hadamard) - NOT matrix product
A * B # [[5 12]
# [21 32]]分解(LU、QR、SVD)
SVD 是最稳定的分解,适用于任何矩阵——不确定时使用它。特征分解只适用于方阵,且对亏损矩阵可能数值不稳定。Cholesky 对于对称正定矩阵最快(常见于协方差和优化)。QR 适用于最小二乘和正交化。
import numpy as np
A = np.array([[4, 3],
[6, 3]])
# SVD: A = U @ diag(s) @ Vt
U, s, Vt = np.linalg.svd(A)
# U: (2,2) left singular vectors
# s: (2,) singular values (sorted descending)
# Vt: (2,2) right singular vectors (already transposed)
# Reconstruct A from SVD
A_reconstructed = U @ np.diag(s) @ Vt
np.allclose(A, A_reconstructed) # True
# QR decomposition: A = Q @ R
Q, R = np.linalg.qr(A)
# Q: orthonormal columns
# R: upper triangular
# Cholesky (for symmetric positive-definite matrices)
S = A @ A.T # symmetric positive-definite
L = np.linalg.cholesky(S)
np.allclose(S, L @ L.T) # True
# Eigen-decomposition (square matrices)
w, V = np.linalg.eig(A)
# w: eigenvalues
# V: eigenvectors as columns
# Eigenvalues only (faster)
np.linalg.eigvals(A)求解线性方程组与逆矩阵
始终优先使用 np.linalg.solve 而非计算逆矩阵——它更快、数值更稳定,避免了近奇异矩阵的陷阱。实践中很少需要逆矩阵;如果你发现自己在写 inv(A) @ b,用 solve(A, b) 替换它。pinv 通过 SVD 处理矩形和奇异矩阵。检查 cond(A)——大的条件数(>1e10)意味着你的系统数值上病态。
import numpy as np
A = np.array([[3, 2],
[1, 4]])
b = np.array([7, 6])
# Solve Ax = b (preferred - no explicit inverse)
x = np.linalg.solve(A, b) # [1.6 1.1]
np.allclose(A @ x, b) # True
# Multiple right-hand sides
B = np.array([[7, 1], [6, 2]]) # 2x2
X = np.linalg.solve(A, B) # 2x2 solution matrix
# Inverse (avoid if you only need to solve Ax=b)
A_inv = np.linalg.inv(A)
A_inv @ b # same result as solve, but slower & less stable
# Pseudo-inverse (works for non-square / singular matrices)
A_pinv = np.linalg.pinv(A) # Moore-Penrose pseudo-inverse
# Determinant
np.linalg.det(A) # 10.0
# Matrix rank
np.linalg.matrix_rank(A) # 2
# Condition number (large = ill-conditioned)
np.linalg.cond(A) # ~1.9范数与向量运算
norm 对向量默认为 L2,对矩阵默认为 Frobenius——在生产代码中显式指定 ord 以提高清晰度。使用 ord=1 获取曼哈顿(出租车)距离,ord=np.inf 获取切比雪夫(最大绝对值)距离。叉积只适用于 3-D 向量。对于多点之间的成对距离,scipy.spatial.distance.cdist 比在 norm 上循环 Python 更快。