
科学计算数据分析【免费下载链接】numpyThe fundamental package for scientific computing with Python.项目地址https://gitcode.com/gh_mirrors/nu/numpy点击查看免费下载本指南围绕 NumPy 官方参考文档 routines.functional.rst 展开深入剖析 NumPy 函数式编程Functional programming子模块中的五大工具apply_along_axis、apply_over_axes、vectorize、frompyfunc与piecewise。这些函数用于将自定义的 Python 函数批量作用于 ndarray 的切片、轴或逐元素之上是科学计算中“按轴规约”“逐元素映射”“分段函数求值”等高频场景的官方推荐手段。读完本文你将掌握每个函数的参数语义、返回形状规则、与原生循环及 ufunc 的性能差异并能在实际代码中正确选用它们。一、模块总览NumPy 函数式编程工具箱NumPy 的“Functional programming”模块在官方参考文档中占据独立小节集中提供了一组把普通 Python 函数以各种方式“适配”到多维数组上的工具函数定位核心用途源码位置apply_along_axis按轴切片应用将 1-D 函数沿指定轴逐条切片执行numpy/lib/_shape_base_impl.pyapply_over_axes多轴重复应用将func(a, axis)沿多个轴依次重复执行numpy/lib/_shape_base_impl.pyvectorize逐元素向量化仿照map但遵循 NumPy 广播规则批量执行标量函数numpy/lib/_function_base_impl.pyfrompyfunc构造 ufunc把任意 Python 函数编译为真正的 NumPy ufunc 对象见 numpy/_core/_add_newdocs.pyC 实现位于 multiarraymodule.cpiecewise分段函数求值依据一组布尔条件对输入分片调用不同函数numpy/lib/_function_base_impl.py它们都通过numpy/__init__.py导出到顶层命名空间可直接以np.apply_along_axis(...)形式调用。此外掩码数组子模块 numpy/ma/extras.py 提供了针对MaskedArray的apply_along_axis与apply_over_axes版本处理带掩码数据时可优先考虑。二、apply_along_axis沿指定轴逐条执行 1-D 函数2.1 函数签名与行为apply_along_axis(func1d, axis, arr, *args, **kwargs)apply_along_axis将func1d依次应用于arr沿axis轴的每个 1-D 切片等价于但更快于用ndindex与s_手写如下双层循环文档原意Ni, Nk a.shape[:axis], a.shape[axis1:] for ii in ndindex(Ni): for kk in ndindex(Nk): f func1d(arr[ii s_[:,] kk]) Nj f.shape for jj in ndindex(Nj): out[ii jj kk] f[jj]核心参数func1d接受 1-D 数组的函数签名为(M,) - (Nj...)axis沿哪个轴切片整数支持负数源码中通过normalize_axis_index(axis, nd)归一化arr输入数组形状记为(Ni..., M, Nk...)*args, **kwargs传递给func1d的额外位置/关键字参数。2.2 输出形状规则轴维度被“替换”而非“删除”返回数组out的形状规则是整个函数最关键的语义原axis维度被移除并由func1d返回值的形状替代。若func1d返回标量out比arr少一维若func1d返回 1-D 数组out维数与arr相同axis处被替换若func1d返回更高维数组这些新维度将插入在axis位置上。 import numpy as np # 标量返回结果少一维 def my_func(a): ... Average first and last element of a 1-D array ... return (a[0] a[-1]) * 0.5 b np.array([[1,2,3], [4,5,6], [7,8,9]]) np.apply_along_axis(my_func, 0, b) array([4., 5., 6.]) np.apply_along_axis(my_func, 1, b) array([2., 5., 8.]) # 1-D 返回轴被 1-D 结果替换 b np.array([[8,1,7], [4,3,9], [5,2,6]]) np.apply_along_axis(sorted, 1, b) array([[1, 7, 8], [3, 4, 9], [2, 5, 6]]) # 高维返回新维度插入原轴位置 b np.array([[1,2,3], [4,5,6], [7,8,9]]) np.apply_along_axis(np.diag, -1, b) array([[[1, 0, 0], [0, 2, 0], [0, 0, 3]], [[4, 0, 0], [0, 5, 0], [0, 0, 6]], [[7, 0, 0], [0, 8, 0], [0, 0, 9]]])2.3 源码实现要点从 numpy/lib/_shape_base_impl.py 的实现可以看到几个值得注意的细节先转置再迭代实现先将迭代轴转置到末尾in_dims[:axis] in_dims[axis1:] [axis]保证每次func1d调用读取的是连续内存切片零维度迭代的报错若任一迭代维度为 0会抛出ValueError: Cannot apply_along_axis when any iteration dimensions are 0结果缓冲先用第一次调用探测结果形状用zeros_like预分配连续缓冲buff随后用buff[ind] func1d(...)填充最后通过buff_permute转置回原始轴序——matrix子类会被降级为普通数组处理源码注释Matrices are nasty with reshapingtrailing ellipsis 技巧迭代索附加Ellipsis防止 0-D 结果衰减为标量对应 gh-8642 修复。2.4 典型应用按行/列计算自定义统计量 def rms(a): # 沿轴计算均方根 ... return np.sqrt(np.mean(a**2)) data np.array([[1., 2., 3.], ... [4., 5., 6.]]) np.apply_along_axis(rms, 0, data) # 对每一列 array([2.91547595, 3.80788655, 4.74341649]) np.apply_along_axis(rms, 1, data) # 对每一行 array([2.1602469, 5.06622805])三、apply_over_axes沿多个轴重复应用func(a, axis)apply_over_axes(func, a, axes)与apply_along_axis不同它要求func形如func(a, axis)并将该函数依次施加于axes中的每一个轴后一次调用以上一次的结果为首参。每次调用后若res维数与a相同直接作为下一次输入若res少一维则在axis之前插入一个维度expand_dims(res, axis)后继续若维数不符抛出ValueError(function is not returning an array of the correct shape)。 a np.arange(24).reshape(2,3,4) a array([[[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]], [[12, 13, 14, 15], [16, 17, 18, 19], [20, 21, 22, 23]]]) # 依次对轴 0 和轴 2 求和保持维数不变 np.apply_over_axes(np.sum, a, [0,2]) array([[[ 60], [ 92], [124]]])值得注意的是官方文档明确指出该函数等价于 ufunc 带keepdimsTrue的元组轴参数且 ufunc 的元组轴参数自 NumPy 1.7.0 起可用 np.sum(a, axis(0,2), keepdimsTrue) array([[[ 60], [ 92], [124]]])因此对于sum、mean、prod这类可重排轴的 ufunc直接使用元组轴 keepdimsTrue是更快的替代方案apply_over_axes的价值在于它可以作用于任意自定义的func(a, axis)。负轴在实现中通过axis N axis归一化源码。四、vectorize按广播规则逐元素执行标量函数4.1 签名与核心参数vectorize(pyfuncnp._NoValue, otypesNone, docNone, excludedNone, cacheFalse, signatureNone)vectorize返回一个行为类似pyfunc、但可接受数组输入的可调用对象。它“like the python map function, except it uses the broadcasting rules of numpy”——按 NumPy 广播规则对输入逐元素求值。参数语义如下参数类型作用pyfunccallable待向量化的 Python 函数/方法省略时返回一个支持关键字参数的装饰器otypesstr 或 dtype 列表输出数据类型字符串 typecode如f、i或每个输出一个 dtype 说明符不指定时通过调用首个元素推断docstr生成函数的 docstring默认继承pyfunc.__doc__excludedset不参与向量化的位置/关键字参数集合按索引或参数名这些参数原样传给pyfunccachebool当既未提供otypes也未提供signature时缓存首调确定的输出个数避免二次调用signaturestr广义 ufunc 签名gufunc signature如(m,n),(n)-(m)使pyfunc接收/返回固定长度的核心维度数组4.2 性能定位为便利而生本质是 for 循环官方文档在 Notes 中反复强调两点务必牢记vectorize主要为便利提供而非为性能——Thevectorizefunction is provided primarily for convenience, not for performance. The implementation is essentially a for loop.推断输出会带来二次调用开销若不指定otypes/signature首调会先用输入第一个元素调用一次以确定输出个数与类型开启cacheTrue可缓存该结果避免重复调用但缓存实现要求包装原函数反而拖慢后续调用因此仅当你的函数本身开销昂贵时才值得启用源码注释this wrapping can almost double the execution time. Hence we make it optional.。4.3 基础用法示例 def myfunc(a, b): ... Return a-b if ab, otherwise return ab ... if a b: ... return a - b ... else: ... return a b vfunc np.vectorize(myfunc) vfunc([1, 2, 3, 4], 2) # 广播2 与每个元素比较 array([3, 4, 1, 2]) # docstring 默认继承自原函数 vfunc.__doc__ Return a-b if ab, otherwise return ab vfunc np.vectorize(myfunc, docVectorized myfunc) # 输出类型默认按首元素推断 out vfunc([1, 2, 3, 4], 2) type(out[0]) class numpy.int64 vfunc np.vectorize(myfunc, otypes[float]) # 显式指定输出类型 out vfunc([1, 2, 3, 4], 2) type(out[0]) class numpy.float644.4 excluded跳过固定长度参数的向量化excluded非常适合“定长数组参数”场景例如多项式求值中的系数向量p——按位置0或关键字p排除均可 def mypolyval(p, x): ... _p list(p) ... res _p.pop(0) ... while _p: ... res res*x _p.pop(0) ... return res vpolyval np.vectorize(mypolyval, excluded{0, p}) vpolyval([1, 2, 3], x[0, 1]) array([3, 6]) vpolyval(p[1, 2, 3], x[0, 1]) array([3, 6])4.5 signature向量化非标量核心维运算signature让vectorize具备 gufunc 能力可对固定长度的数组片断core dimensions做“向量化的批量调用”。官方文档给出两个经典例子批量计算 Pearson 相关系数及其 p 值以及批量卷积 import scipy.stats pearsonr np.vectorize(scipy.stats.pearsonr, ... signature(n),(n)-(),()) pearsonr([[0, 1, 2, 3]], [[1, 2, 3, 4], [4, 3, 2, 1]]) (array([ 1., -1.]), array([ 0., 0.])) convolve np.vectorize(np.convolve, signature(n),(m)-(k)) convolve(np.eye(4), [1, 2, 1]) array([[1., 2., 1., 0., 0., 0.], [0., 1., 2., 1., 0., 0.], [0., 0., 1., 2., 1., 0.], [0., 0., 0., 1., 2., 1.]])签名语法与广义 ufuncgufunc一致如(n),(n)-(),()表示两个等长向量输入、两个标量输出。4.6 装饰器语法vectorize可直接或带关键字参数作为装饰器使用 np.vectorize ... def identity(x): ... return x identity([0, 1, 2]) array([0, 1, 2]) np.vectorize(otypes[float]) ... def as_float(x): ... return x as_float([0, 1, 2]) array([0., 1., 2.])从源码 numpy/lib/_function_base_impl.py 可见装饰器两阶段初始化由__init__与_init_stage_2配合完成当pyfunc为np._NoValue时首次调用即装饰目标函数传入触发第二阶段初始化。实现底层最终借助frompyfunc(func, nin, nout)构造临时 ufunc见_get_ufunc_and_otypes即下一节的主角。五、frompyfunc把 Python 函数变成真正的 ufunc5.1 签名与行为frompyfunc(func, /, nin, nout, **kwargs) frompyfunc(func, /, nin, nout, *[, identity])frompyfunc接受任意 Python 函数返回一个真正的 NumPy ufunc 对象文档。这意味着返回值自动获得 ufunc 的全部能力广播、类型循环分发、reduce/accumulate/outer方法、identity属性等。典型用途是给内置 Python 函数加上广播能力 import numpy as np oct_array np.frompyfunc(oct, 1, 1) # oct(x) - 0o... 字符串 oct_array(np.array((10, 30, 100))) array([0o12, 0o36, 0o144], dtypeobject) np.array((oct(10), oct(30), oct(100))) # 对照非向量化 array([0o12, 0o36, 0o144], dtypeU5)参数要点func任意 Python 函数对象nin/nout输入参数个数 / 返回对象个数identity可选设置结果 ufunc 的~numpy.ufunc.identity属性。指定时等价于把底层 C 字段设为PyUFunc_IdentityValue省略则置为PyUFunc_None。需特别注意的是设置identityNone并不等价于省略——显式None意味着该操作可重排reorderable可用于reduce等场景。5.2 与 vectorize 的关键区别维度vectorizefrompyfunc返回值普通可调用包装对象标准numpy.ufunc对象输出 dtype可推断或由otypes指定始终返回PyObjectobject dtype数组附加能力无 ufunc 方法具备reduce、accumulate、outer、at等 ufunc 方法广播遵循广播规则遵循广播规则性能本质 for 循环首调需探测输出直接构造 ufunc无推断开销“返回的 ufunc 始终产生 PyObject 数组”是文档明确注明的行为The returned ufunc always returns PyObject arrays如需具体数值类型需再经astype或显式构造。5.3 使用 identity 实现可 reduce 的 ufunc在测试代码 numpy/_core/tests/test_umath.py 中可以看到identity参数的完整用法例如构建一个可做累计归约的乘法 ufunc mul np.frompyfunc(lambda a, b: a * b, nin2, nout1, identity1) mul.reduce([1, 2, 3, 4]) # 因 identity1 可安全 reduce 24不传identity时 identity 为PyUFunc_None操作不可重排此时对空数组的reduce将无法工作——这正是文档强调省略与None差异的原因。5.4 实现位置与边界frompyfunc是编译进numpy._core的 C 函数注册于 multiarraymodule.c并在 numpy/_core/umath.py 的__all__中导出。测试 numpy/_core/tests/test_regression.py 覆盖了其边界行为当nin过大如 64时会抛ValueErrornout0函数无返回值也可构造 ufunctest_ufunc.py 验证了nout0时ufunc()返回None的行为。六、piecewise分段函数求值6.1 签名与语义piecewise(x, condlist, funclist, *args, **kw)piecewise根据一组布尔条件condlist对输入x的不同区域调用funclist中对应的函数实现分段定义函数piecewise-defined function的求值。其求值逻辑可概括为文档原文out funclist0 # 条件 0 为 True 的部分 | funclist1 # 条件 1 为 True 的部分 | ... | funclistn2 # 最后一个条件参数说明x输入域ndarray 或标量condlist布尔数组或布尔标量列表每个元素与funclist中函数一一对应形状应与x相同funclist可调用对象或标量列表。若是标量则视为常值函数lambda x: scalar*args, **kw额外传给各函数的参数如piecewise(..., 1, a)会以f(x, 1, a)调用piecewise(..., alpha1)会以f(x, alpha1)调用。6.2 默认分支规则多出一个函数即“否则”condlist与funclist长度必须匹配但存在一个特殊约定若len(funclist) len(condlist) 1则多出的最后一个函数作为默认值在所有条件均为 False 的位置生效。任何条件都未覆盖的位置输出默认为 0。 import numpy as np # 用标量列表实现符号函数 signumx0 为 -1x0 为 1 x np.linspace(-2.5, 2.5, 6) np.piecewise(x, [x 0, x 0], [-1, 1]) array([-1., -1., -1., 1., 1., 1.]) # 用 lambda 实现绝对值 np.piecewise(x, [x 0, x 0], [lambda x: -x, lambda x: x]) array([2.5, 1.5, 0.5, 0.5, 1.5, 2.5]) # 支持标量输入 y -2 np.piecewise(y, [y 0, y 0], [lambda x: -x, lambda x: x]) array(2)6.3 源码实现细节从 numpy/lib/_function_base_impl.py 的实现看单条件自动提升若condlist是标量或首元素不是 list/ndarray 且x.ndim ! 0会自动包装为单元素列表源码注释注明undocumented: single condition is promoted to a list of one condition默认分支自动补全当n n2 - 1时用condelse ~np.any(condlist, axis0, keepdimsTrue)计算“否则”条件并拼接长度不匹配时抛出带明确提示的ValueErrorwith {n} condition(s), either {n} or {n 1} functions are expected常值分支直写非可调用条目直接y[cond] func写入可调用条目仅在选中元素非空vals.size 0时才调用且传入的是选中元素的子集x[cond]而非整个x——这正是与where的差异所在输出约定返回数组形状、dtype 与x相同未被条件覆盖处为 0。6.4 与 choose / select / where 的选型piecewise的 See Also 指向choose、select、where。它们都做“条件选值”但差异在于where(cond, a, b)二元分支且a/b为已计算好的值select(condlist, choicelist, default0)多分支但同样基于已计算值选择piecewise多分支且惰性调用函数——每个函数只在其条件为 True 的元素上执行天然避免了where中“两个分支都被求值”的浪费适合分支内计算昂贵的场景。七、函数式编程工具的选型决策结合上述源码级分析实际编码时的选型建议如下能向量化的运算优先用原生 ufunc 表达式、np.where、ufunc 元组轴等它们有 C 级实现性能最优对轴做自定义规约/变换非 ufunc 可表达→ 首选apply_along_axis涉及多个轴且func(a, axis)可复用 →apply_over_axes对每个标量元素应用自定义 Python 函数→vectorize便捷或frompyfunc需要 ufunc 方法、接受 object 输出时输入形状不规整、依赖元素位置的映射或在固定长度子数组上做批量操作 →vectorize(..., signature...)分段定义、分支计算代价高→piecewise利用其“仅对命中元素调用函数”的惰性语义。最后提醒vectorize与frompyfunc均不提供真正的性能加速文档明确vectorize实现is essentially a for loop它们解决的是代码简洁性与可读性问题当性能成为瓶颈时应转向 Cython、Numba 或原生向量化表达式。八、测试与进一步探索如需验证本文所述行为或在本地跑通全部示例可查阅以下测试与源码apply_along_axis/apply_over_axes测试numpy/lib/tests/test_shape_base.py掩码数组版本见 numpy/ma/tests/test_extras.pyvectorize/piecewise测试numpy/lib/tests/test_function_base.pyfrompyfunc测试numpy/_core/tests/test_ufunc.py、numpy/_core/tests/test_umath.py、numpy/_core/tests/test_regression.py函数式编程模块官方参考页routines.functional.rst其生成的 API 文档还包含每个函数的完整示例与 Notes。这些工具共同构成了 NumPy 中“以 Python 函数操作数组”的标准接口层理解其形状语义与实现机制能帮助你在撰写自定义数值算法时做出更优的工程决策。赞分享科学计算数据分析【免费下载链接】numpyThe fundamental package for scientific computing with Python.项目地址https://gitcode.com/gh_mirrors/nu/numpy点击查看免费下载相关推荐CuPy 函数式编程指南apply_along_axis、apply_over_axes、vectorize 与 piecewise 的 GPU 实现详解CuPy 函数式编程指南apply_along_axis、apply_over_axes、vectorize 与 piecewise 的 GPU 实现详解 导科学计算高性能计算Faiss 1.11.0 升级指南RaBitQ 量化让向量检索提速 10 倍的完整教程Faiss 1.11.0 升级指南RaBitQ 量化让向量检索提速 10 倍的完整教程 向量库到千万、上亿条之后查询慢、内存大是绕不开的痛点。Faiss 1科学计算数据分析NumPy 窗函数完全指南bartlett、blackman、hamming、hanning 与 kaiser 的公式、实现与实战NumPy 窗函数完全指南bartlett、blackman、hamming、hanning 与 kaiser 的公式、实现与实战 本文以 NumPy 官方参科学计算数据分析上一篇Clipcat 项目教程下一篇告别邮件混乱awesome-macOS电子邮件工具效率指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考