SciPy和NumPy之间的关系

280

SciPy似乎在自己的命名空间中提供了大部分(但不是全部[1])NumPy的函数。换句话说,如果有一个名为numpy.foo的函数,几乎肯定有一个scipy.foo。大多数情况下,这两个函数看起来完全相同,甚至经常指向同一个函数对象。

有时它们是不同的。举个最近遇到的例子:

  • numpy.log10是返回负参数的NaN的ufunc;
  • scipy.log10对于负参数返回复杂值,并且似乎不是ufunc。

对于loglog2logn也可以这样说,但对于log1p[2]则不是这样。

另一方面,numpy.expscipy.exp似乎是相同ufunc的不同名称。这也适用于scipy.log1pnumpy.log1p

另一个例子是numpy.linalg.solvescipy.linalg.solve。它们很相似,但后者在前者的基础上提供了一些附加功能。

为什么会出现这种显然的重复?如果这意味着将numpy整体导入scipy命名空间,那么为什么行为有微妙的差别并且缺少一些函数?是否有某些总体逻辑可以帮助澄清混淆?

[1] numpy.minnumpy.maxnumpy.abs和其他一些函数没有对应于scipy命名空间。

[2] 使用NumPy 1.5.1和SciPy 0.9.0rc2进行测试。


8
在回答中,我看到“所有这些功能都可以在不额外导入NumPy的情况下使用”,因为“意图是让用户不必知道scipy和numpy命名空间之间的区别”。现在我想知道,因为我有点关注NumPy和SciPy的帖子并且自己也使用它。我几乎总是看到NumPy被单独导入(作为np)。那么他们失败了吗? 根据回答所说,“所有这些功能都可以在不额外导入NumPy的情况下使用”的意图是为了让用户不必知道scipynumpy命名空间之间的区别。尽管如此,人们仍然可以选择单独导入numpy并给它一个别名(例如np)来方便地使用其功能。这并不代表他们失败了,只是提供了更多的选择和灵活性。 - joris
8
Scipy 和 Numpy 在快速傅里叶变换(FFT)方面存在一些差异。我曾经遇到过一个问题,最终追踪到是由于 Scipy 和 Numpy 的 rfft 定义不同所导致的。 - wim
1
SciPy和NumPy的FFT不同。SciPy使用Fortran库FFTPACK,因此命名为scipy.fftpack。NumPy使用称为fftpack_lite的C库;它具有较少的函数并且只支持NumPy中的双精度。Enthought公司已经修补了他们的numpy.fft,以使用Intel MKL来进行FFT,而不是fftpack_lite。 - Sturla Molden
9
NumPy 最初被命名为 scipy.core。NumPy 和 SciPy 是密切相关的项目。将两者分开的主要原因是确保数组库(NumPy)变得精简高效,因为 SciPy 的大部分功能并非总是需要的。此外,科学家们决定放弃数组包 numeric(MIT)和 numarray(NASA),转而采用 scipy.core,于是它就被命名为 NumPy。目前,NumPy 已经发布了1.8.1 版本,而 SciPy 还未达到 1.0 版本。虽然 NumPy 也有一些进行傅里叶变换和线性代数的工具,但不及 SciPy 的广泛。 - Sturla Molden
@SturlaMolden 很高兴了解到Enthought,你知道Anaconda是否优化了numpy和其他的吗? - dashesy
显示剩余2条评论
8个回答

154

上次我查看scipy的时候,__init__方法被执行了

from numpy import *

目的是在导入scipy模块时将整个numpy命名空间包含在其中。

你所描述的log10行为很有趣,因为两个版本都来自于numpy。一个是ufunc,另一个是numpy.lib函数。我不知道为什么scipy更喜欢使用库函数而不是ufunc


编辑:实际上,我可以回答log10问题。查看scipy的__init__方法,我看到:

# Import numpy symbols to scipy name space
import numpy as _num
from numpy import oldnumeric
from numpy import *
from numpy.random import rand, randn
from numpy.fft import fft, ifft
from numpy.lib.scimath import *

你在scipy中获取的log10函数来自numpy.lib.scimath。查看该代码,它说:

"""
Wrapper functions to more user-friendly calling of certain math functions
whose output data-type is different than the input data-type in certain
domains of the input.

For example, for functions like log() with branch cuts, the versions in this
module provide the mathematically valid answers in the complex plane:

>>> import math
>>> from numpy.lib import scimath
>>> scimath.log(-math.exp(1)) == (1+1j*math.pi)
True

Similarly, sqrt(), other base logarithms, power() and trig functions are
correctly handled.  See their respective docstrings for specific examples.
"""

看起来这个模块覆盖了基本的 numpy 通用函数 sqrtloglog2lognlog10powerarccosarcsinarctanh。这解释了您所看到的行为。为什么要这样设计的根本原因可能在某个邮件列表帖子中。


10
在使用这些包进行全职工作一段时间后,我对它们的感觉是:NumPy旨在成为一个数值数组库,可被任何需要在Python中使用此类对象的人使用。SciPy旨在成为科学家/工程师的库,因此它针对更为严谨的理论数学(因此包括log10等的复数版本)。主要混淆来自于NumPy保留了许多旧的子模块(本应该归入Scipy),这些子模块是在SciPy/NumPy的区分不像今天这么清晰的时候包含的。 - PhilMacKay
@PhilMacKay 嗨Phil,我读了你2013年关于这个numpy/scipy问题的帖子和其他帖子。我的问题是,你的意见是否仍然与你上面的评论一样?我看到发帖人说scipy中有一些非等效项,并列出了abs、max和min作为例子,但我知道abs只是numpy.absolute的别名,而且还有一个scipy.absolute、scipy.maximum和scipy.minimum。那么在你现在的经验中,如果你已经需要scipy,你是否曾经需要导入numpy? - Dan Boschen
@PhilMacKay 看起来普遍的共识是对于它们相关的用例使用SciPy的子模块库,然后对于核心NumPy操作特别导入NumPy(而不是顶层的SciPy,否则您需要导入)。由于其他人以及SciPy文档本身都将其作为更好的编码实践,因此我正在尝试理解为什么这很重要。我认为这是因为它是一种惯例,因此易读性更好。你目前的意见是什么? - Dan Boschen
截至2018年11月,我仍然坚持我的上述评论。当只需要NumPy时导入SciPy可能有些过度。另一方面,当加载SciPy时,会同时导入NumPy,因此不需要额外导入NumPy。当然,遵循文档有充分的理由,所以请根据自己的情况自由选择最相关的操作。 - PhilMacKay
@PhilMacKay 感谢您的建议。经过深思熟虑,我猜测为什么建议导入numpy(即使所有内容都可以在scipy中完成)是一种惯例,因此可以更轻松地阅读共享代码。如果所有与numpy相关的代码都专门绑定到numpy库上,那么它也可以更容易地从较大的scipy库中分离出来,该库包含许多可能并不总是需要的内容。话虽如此,我的想法(对于我自己的方法)是导入numpy,然后不导入顶级scipy,而只根据需要导入scipy子包。 - Dan Boschen
其中一个让我困扰的细节就是sqrt(-1),如果你想在numpy包中处理这种数学类型,你需要导入numpy.emath。 - Dan Boschen

58

来自SciPy参考指南:

... 所有的Numpy函数都被纳入到scipy命名空间中,因此所有这些函数都可以在不需要额外导入Numpy的情况下使用。

这意味着用户无需了解scipynumpy命名空间之间的区别,尽管显然您已经发现了一个例外。


56
SciPy FAQ可以看出,一些NumPy的函数仅因历史原因而存在于此,但它们应该只存在于SciPy中。
区分NumPy和SciPy之间的区别是什么?
在理想情况下,NumPy只包含数组数据类型和最基本的操作:索引、排序、重塑、基本元素函数等。所有数值代码都应该驻留在SciPy中。然而,NumPy的一个重要目标是兼容性,因此NumPy试图保留其前身支持的所有功能。因此,NumPy包含一些线性代数函数,即使这些函数更适合放在SciPy中。无论如何,SciPy包含更完整功能的线性代数模块,以及许多其他数值算法。如果您正在使用Python进行科学计算,您应该安装NumPy和SciPy。大多数新功能应该放在SciPy而不是NumPy中。
这就解释了为什么scipy.linalg.solve比numpy.linalg.solve提供了一些额外的功能。

我没有看到SethMMorton对相关问题的回答。


13

SciPy入门文档的结尾处有一则简短的评论:

另一个有用的命令是source。当将Python函数作为参数传递时,它会打印出该函数的源代码清单。这对于学习算法或理解函数如何处理其参数非常有帮助。此外,不要忘记Python命令dir,它可用于查看模块或包的命名空间。

我认为,只要拥有足够了解所涉及到的所有软件包的知识,就可以分析出一些 scipy和numpy函数之间差异的确切内容(这对我完全没用,因为它没有帮助我回答关于log10的问题)。我肯定没有那个知识,但是source确实表明scipy.linalg.solvenumpy.linalg.solve以不同的方式与lapack进行交互;

Python 2.4.3 (#1, May  5 2011, 18:44:23) 
[GCC 4.1.2 20080704 (Red Hat 4.1.2-50)] on linux2
>>> import scipy
>>> import scipy.linalg
>>> import numpy
>>> scipy.source(scipy.linalg.solve)
In file: /usr/lib64/python2.4/site-packages/scipy/linalg/basic.py

def solve(a, b, sym_pos=0, lower=0, overwrite_a=0, overwrite_b=0,
          debug = 0):
    """ solve(a, b, sym_pos=0, lower=0, overwrite_a=0, overwrite_b=0) -> x

    Solve a linear system of equations a * x = b for x.

    Inputs:

      a -- An N x N matrix.
      b -- An N x nrhs matrix or N vector.
      sym_pos -- Assume a is symmetric and positive definite.
      lower -- Assume a is lower triangular, otherwise upper one.
               Only used if sym_pos is true.
      overwrite_y - Discard data in y, where y is a or b.

    Outputs:

      x -- The solution to the system a * x = b
    """
    a1, b1 = map(asarray_chkfinite,(a,b))
    if len(a1.shape) != 2 or a1.shape[0] != a1.shape[1]:
        raise ValueError, 'expected square matrix'
    if a1.shape[0] != b1.shape[0]:
        raise ValueError, 'incompatible dimensions'
    overwrite_a = overwrite_a or (a1 is not a and not hasattr(a,'__array__'))
    overwrite_b = overwrite_b or (b1 is not b and not hasattr(b,'__array__'))
    if debug:
        print 'solve:overwrite_a=',overwrite_a
        print 'solve:overwrite_b=',overwrite_b
    if sym_pos:
        posv, = get_lapack_funcs(('posv',),(a1,b1))
        c,x,info = posv(a1,b1,
                        lower = lower,
                        overwrite_a=overwrite_a,
                        overwrite_b=overwrite_b)
    else:
        gesv, = get_lapack_funcs(('gesv',),(a1,b1))
        lu,piv,x,info = gesv(a1,b1,
                             overwrite_a=overwrite_a,
                             overwrite_b=overwrite_b)

    if info==0:
        return x
    if info>0:
        raise LinAlgError, "singular matrix"
    raise ValueError,\
          'illegal value in %-th argument of internal gesv|posv'%(-info)

>>> scipy.source(numpy.linalg.solve)
In file: /usr/lib64/python2.4/site-packages/numpy/linalg/linalg.py

def solve(a, b):
    """
    Solve the equation ``a x = b`` for ``x``.

    Parameters
    ----------
    a : array_like, shape (M, M)
        Input equation coefficients.
    b : array_like, shape (M,)
        Equation target values.

    Returns
    -------
    x : array, shape (M,)

    Raises
    ------
    LinAlgError
        If `a` is singular or not square.

    Examples
    --------
    Solve the system of equations ``3 * x0 + x1 = 9`` and ``x0 + 2 * x1 = 8``:

    >>> a = np.array([[3,1], [1,2]])
    >>> b = np.array([9,8])
    >>> x = np.linalg.solve(a, b)
    >>> x
    array([ 2.,  3.])

    Check that the solution is correct:

    >>> (np.dot(a, x) == b).all()
    True

    """
    a, _ = _makearray(a)
    b, wrap = _makearray(b)
    one_eq = len(b.shape) == 1
    if one_eq:
        b = b[:, newaxis]
    _assertRank2(a, b)
    _assertSquareness(a)
    n_eq = a.shape[0]
    n_rhs = b.shape[1]
    if n_eq != b.shape[0]:
        raise LinAlgError, 'Incompatible dimensions'
    t, result_t = _commonType(a, b)
#    lapack_routine = _findLapackRoutine('gesv', t)
    if isComplexType(t):
        lapack_routine = lapack_lite.zgesv
    else:
        lapack_routine = lapack_lite.dgesv
    a, b = _fastCopyAndTranspose(t, a, b)
    pivots = zeros(n_eq, fortran_int)
    results = lapack_routine(n_eq, n_rhs, a, n_eq, pivots, b, n_eq, 0)
    if results['info'] > 0:
        raise LinAlgError, 'Singular matrix'
    if one_eq:
        return wrap(b.ravel().astype(result_t))
    else:
        return wrap(b.transpose().astype(result_t))

这也是我的第一篇文章,如果我需要在这里做出任何更改,请让我知道。


底层包装器非常不同。NumPy使用用C编写的薄层。SciPy使用由f2py自动生成的层。SciPy始终链接到外部LAPACK库。如果找不到外部LAPACK,则NumPy使用其自己的f2c'd lapack_lite。 - Sturla Molden

8
来自维基百科(http://en.wikipedia.org/wiki/NumPy#History):
Numeric代码被改进,使其更易于维护和灵活,足以实现Numarray的新功能。这个新项目是SciPy的一部分。为了避免安装整个软件包只为获取一个数组对象,这个新软件包被分离出来并称为NumPy。
Scipy依赖于NumPy,并将许多NumPy函数导入其命名空间以方便使用。

5

关于linalg包 - scipy函数将调用lapack和blas,在许多平台上都有高度优化的版本,并为在相当大的稠密矩阵上进行操作提供非常好的性能。另一方面,它们不是易于编译的库,需要Fortran编译器和许多特定于平台的调整才能获得完全的性能。因此,numpy提供了许多常见线性代数函数的简单实现,这些函数通常对许多目的来说已经足够好。


1
numpy 1.10有一个很好的模块dual:“如果您想在numpy和scipy中使用函数,如果可以使用numpy版本,但另外使用scipy版本,则应使用此模块。”用法-- from numpy.dual import fft, inv - denis

1
除了SciPy FAQ中描述的复制主要是为了向后兼容,NumPy文档进一步澄清说:

可选的SciPy加速例程(numpy.dual)

可能会通过Scipy加速的函数的别名。

SciPy可以构建为使用加速或其他改进的库进行FFT、线性代数和特殊函数计算。当SciPy可用时,此模块允许开发人员透明地支持这些加速函数,但仍然支持仅安装了NumPy的用户。

为简洁起见,这些是:
  • 线性代数
  • FFT
  • 第一类修正Bessel函数,阶0
此外,来自SciPy教程

SciPy的顶层还包含了来自NumPy和numpy.lib.scimath的函数。但是,最好直接从NumPy模块中使用它们。

因此,在新的应用程序中,您应该优先选择在SciPy顶层中重复的数组操作的NumPy版本。对于上述领域,您应该优先选择SciPy中的内容,并在必要时检查NumPy的向后兼容性。

根据我的个人经验,我使用的大多数数组函数都存在于NumPy的顶层(除了random)。然而,所有特定于领域的例程都存在于SciPy的子包中,所以我很少使用来自SciPy顶层的任何东西。


1

从'Quantitative Economics'讲座中

SciPy是一个包含各种工具的软件包,这些工具都是基于NumPy构建的,使用其数组数据类型和相关功能

实际上,当我们导入SciPy时,也会得到NumPy,这可以从SciPy初始化文件中看出

# Import numpy symbols to scipy name space
import numpy as _num
linalg = None
from numpy import *
from numpy.random import rand, randn
from numpy.fft import fft, ifft
from numpy.lib.scimath import *

__all__  = []
__all__ += _num.__all__
__all__ += ['randn', 'rand', 'fft', 'ifft']

del _num
# Remove the linalg imported from numpy so that the scipy.linalg package can be
# imported.
del linalg
__all__.remove('linalg')

然而,更常见和更好的做法是明确地使用NumPy功能。

import numpy as np

a = np.identity(3)

SciPy中有用的是其子包中的功能

  • scipy.optimize、scipy.integrate、scipy.stats等

1
我看到你的评论,说明直接使用NumPy功能更好,包括SciPy教程中也强调了这点,但为什么这样做更好呢?似乎没有人回答这个问题。如果已经导入了SciPy并且它包括NumPy功能,那么为什么仍然需要导入NumPy呢?是因为当我们在SciPy中导入子包时,并没有同时导入顶层包,因此,在不引入具体的SciPy模块的情况下,我们应该只导入Numpy来进行核心数组处理功能吗? - Dan Boschen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接