Skip to content

Latest commit

 

History

History
1034 lines (685 loc) · 30.3 KB

File metadata and controls

1034 lines (685 loc) · 30.3 KB

五、使用矩阵和ufunc

本章介绍矩阵和通用函数(ufuncs)。 矩阵在数学上是众所周知的,在 NumPy 中也具有表示。 通用函数适用于数组,逐元素或标量。 ufuncs 期望一组标量作为输入,并产生一组标量作为输出。 通用函数通常可以映射到它们的数学对应物,例如加,减,除,乘等。 我们还将介绍三角函数,按位和比较通用函数。

在本章中,我们将介绍以下主题:

  • 矩阵创建
  • 矩阵运算
  • 基本函数
  • 三角函数
  • 按位函数
  • 比较函数

矩阵

NumPy 中的矩阵是ndarray的子类。 我们可以使用特殊的字符串格式创建矩阵。 就像在数学中一样,它们是二维的。 正如您期望的那样,矩阵乘法不同于正常的 NumPy 乘法。 幂运算符也是如此。 我们可以使用mat()matrix()bmat()函数创建矩阵。

实战时间 – 创建矩阵

如果输入已经是矩阵或ndarray,则mat()函数不会复制。 调用此函数等效于调用matrix(data, copy=False)。 我们还将演示转置和求逆矩阵。

  1. 行用分号分隔,值用空格分隔。 使用以下字符串调用mat()函数以创建矩阵:

    A = np.mat('1 2 3; 4 5 6; 7 8 9')
    print("Creation from string", A)

    矩阵输出应为以下矩阵:

    Creation from string [[1 2 3]
     [4 5 6]
     [7 8 9]]
  2. 如下将矩阵转换为具有T属性的矩阵:

    print("transpose A", A.T)

    以下是转置矩阵:

    transpose A [[1 4 7]
     [2 5 8]
     [3 6 9]]
  3. 可以使用I属性将矩阵反转,如下所示

    print("Inverse A", A.I)

    逆矩阵打印如下(请注意,这是O(n<sup class="calibre54">3</sup>)操作,这意味着需要平均三次时间):

    Inverse A [[ -4.50359963e+15   9.00719925e+15  -4.50359963e+15]
     [  9.00719925e+15  -1.80143985e+16   9.00719925e+15]
     [ -4.50359963e+15   9.00719925e+15  -4.50359963e+15]]
  4. 不使用字符串创建矩阵,而是使用数组:

    print("Creation from array", np.mat(np.arange(9).reshape(3, 3)))

    新创建的数组如下所示:

    Creation from array [[0 1 2]
     [3 4 5]
     [6 7 8]]

刚刚发生了什么?

我们使用mat()函数创建了矩阵。 我们将使用T属性将矩阵转置,并使用I属性将其反转(请参见matrixcreation.py):

from __future__ import print_function
import numpy as np

A = np.mat('1 2 3; 4 5 6; 7 8 9')
print("Creation from string", A)
print("transpose A", A.T)
print("Inverse A", A.I)
print("Check Inverse", A * A.I)

print("Creation from array", np.mat(np.arange(9).reshape(3, 3)))

从其他矩阵创建矩阵

有时,我们想由其他较小的矩阵创建矩阵。 我们可以通过bmat()函数来实现。 b在这里代表块矩阵。

实战时间 – 从其他矩阵创建矩阵

我们将从两个较小的矩阵创建一个矩阵,如下所示:

  1. 首先,创建一个2×2单位矩阵:

    A = np.eye(2)
    print("A", A)

    单位矩阵如下所示:

    A [[ 1\.  0.]
     [ 0\.  1.]]
  2. 创建另一个类似于A的矩阵,并将其乘以 2:

    B = 2 * A
    print("B", B)

    第二个矩阵如下:

    B [[ 2\.  0.]
     [ 0\.  2.]]
  3. 从字符串创建复合矩阵。 字符串使用与mat()函数相同的格式-使用矩阵而不是数字:

    print("Compound matrix\n", np.bmat("A B; A B"))

    复合矩阵如下所示:

    Compound matrix
    [[ 1\.  0\.  2\.  0.]
     [ 0\.  1\.  0\.  2.]
     [ 1\.  0\.  2\.  0.]
     [ 0\.  1\.  0\.  2.]]

刚刚发生了什么?

我们使用bmat()函数从两个较小的矩阵创建了一个块矩阵。 我们给该函数一个字符串,其中包含矩阵名称,而不是数字(请参见bmatcreation.py):

from __future__ import print_function
import numpy as np

A = np.eye(2)
print("A", A)
B = 2 * A
print("B", B)
print("Compound matrix\n", np.bmat("A B; A B"))

小测验 – 使用字符串定义矩阵

Q1. mat()bmat()函数接受的字符串中的行分隔符是什么?

  1. 分号
  2. 句号
  3. 逗号
  4. 空格

通用函数

通用函数ufuncs)期望一组标量作为输入并产生一组标量作为输出。 它们实际上是 Python 对象,它们封装了函数的行为。 通常,我们可以将ufunc映射到它们的数学对应项,例如加,减,除,乘等。 通常,通用函数由于其特殊的优化以及在本机级别上运行而更快。

实战时间 – 创建通用函数

我们可以使用 NumPy 和frompyfunc()函数从 Python 函数创建ufunc,如下所示:

  1. 定义一个 Python 函数,该函数回答关于宇宙,存在和其他问题的最终问题(来自《银河系漫游指南》 ,道格拉斯·亚当,Pan Books,如果您还没有阅读,可以安全地忽略这一点!):

    def ultimate_answer(a):

    到目前为止,没有什么特别的。 我们给函数命名为ultimate_answer()并定义了一个参数a

  2. 使用zeros_like()函数,创建一个形状与a相同的所有零组成的结果:

    result = np.zeros_like(a)
  3. 现在,将初始化数组的元素设置为答案42,然后返回结果。 完整的函数应显示在下面的代码片段中。 flat属性使我们可以访问平面迭代器,该迭代器允许我们设置数组的值。

    def ultimate_answer(a):
       result = np.zeros_like(a)
       result.flat = 42
       return result
  4. frompyfunc()创建一个ufunc;指定1作为输入参数的数量,然后指定1作为输出参数的数量:

    ufunc = np.frompyfunc(ultimate_answer, 1, 1)
    print("The answer", ufunc(np.arange(4)))

    一维数组的结果如下所示:

    The answer [42 42 42 42]

    使用以下代码对二维数组执行相同的操作:

    print("The answer", ufunc(np.arange(4).reshape(2, 2)))

    二维数组的输出如下所示:

    The answer [[42 42]
     [42 42]]

刚刚发生了什么?

我们定义了一个 Python 函数。 在此函数中,我们使用zeros_like()函数,根据输入参数的形状将数组的元素初始化为零。 然后,使用ndarrayflat属性,将数组元素设置为最终答案42(请参见answer42.py):

from __future__ import print_function
import numpy as np

def ultimate_answer(a):
   result = np.zeros_like(a)
   result.flat = 42
   return result

ufunc = np.frompyfunc(ultimate_answer, 1, 1)
print("The answer", ufunc(np.arange(4)))

print("The answer", ufunc(np.arange(4).reshape(2, 2)))

通用函数的方法

函数如何具有方法? 如前所述,通用函数不是函数,而是表示函数的 Python 对象。 通用函数具有五种重要方法,如下:

  1. ufunc.reduce(a[, axis, dtype, out, keepdims])
  2. ufunc.accumulate(array[, axis, dtype, out])
  3. ufunc.reduceat(a, indices[, axis, dtype, out])
  4. ufunc.outer(A, B)
  5. ufunc.at(a, indices[, b])])])

实战时间 – 将ufunc方法应用于add函数

让我们在add()函数上调用前四个方法:

  1. 通用函数沿指定元素的连续轴递归地减少输入数组。 对于add()函数,约简的结果类似于计算数组的总和。 调用reduce()方法:

    a = np.arange(9)
    print("Reduce", np.add.reduce(a))

    精简数组应如下所示:

    Reduce 36
  2. accumulate()方法也递归地遍历输入数组。 但是,与reduce()方法相反,它将中间结果存储在一个数组中并返回它。 如果使用add()函数,则结果等同于调用cumsum()函数。 在add()函数上调用accumulate()方法:

    print("Accumulate", np.add.accumulate(a))

    累积的数组如下:

    Accumulate [ 0  1  3  6 10 15 21 28 36]
  3. reduceat()方法的解释有点复杂,因此让我们对其进行调用并逐步了解其算法。 reduceat()方法需要输入数组和索引列表作为参数:

    print("Reduceat", np.add.reduceat(a, [0, 5, 2, 7]))

    结果如下所示:

    Reduceat [10  5 20 15]

    第一步与索引05有关。 此步骤可减少索引05之间的数组元素的运算:

    print("Reduceat step I", np.add.reduce(a[0:5]))

    步骤 1 的输出如下:

    Reduceat step I 10

    第二步涉及索引52。 由于2小于5,因此返回索引为5的数组元素:

    print("Reduceat step II", a[5])

    第二步产生以下输出:

    Reduceat step II 5

    第三步涉及索引27。 此步骤可减少索引27之间的数组元素的运算:

    print("Reduceat step III", np.add.reduce(a[2:7]))

    第三步的结果如下所示:

    Reduceat step III 20

    第四步涉及索引7。 此步骤导致从索引7到数组末尾的数组元素减少操作:

    print("Reduceat step IV", np.add.reduce(a[7:]))

    第四步结果如下所示:

    Reduceat step IV 15
  4. outer()方法返回一个具有等级的数组,该等级是其两个输入数组的等级的总和。 该方法适用于所有可能的输入数组元素对。 在add()函数上调用outer()方法:

    print("Outer", np.add.outer(np.arange(3), a))

    外部总和的输出结果如下:

    Outer [[ 0  1  2  3  4  5  6  7  8]
     [ 1  2  3  4  5  6  7  8  9]
     [ 2  3  4  5  6  7  8  9 10]]

刚刚发生了什么?

我们将通用函数的前四种方法reduce()accumulate()reduceat()outer()应用于add()函数(请参见ufuncmethods.py):

from __future__ import print_function
import numpy as np

a = np.arange(9)

print("Reduce", np.add.reduce(a))
print("Accumulate", np.add.accumulate(a))
print("Reduceat", np.add.reduceat(a, [0, 5, 2, 7]))
print("Reduceat step I", np.add.reduce(a[0:5]))
print("Reduceat step II", a[5])
print("Reduceat step III", np.add.reduce(a[2:7]))
print("Reduceat step IV", np.add.reduce(a[7:]))
print("Outer", np.add.outer(np.arange(3), a))

算术函数

通用算术运算符+-*分别隐式链接到通用函数的加,减和乘。 这意味着在 NumPy 数组上使用这些运算符之一时,将调用相应的通用函数。 除法涉及一个稍微复杂的过程。 与数组分割有关的三个通用函数是divide()true_divide()floor_division()。 两个运算符对应于除法:///

实战时间 – 分割数组

让我们来看一下数组分割的作用:

  1. divide()函数将截断整数除法和普通浮点除法:

    a = np.array([2, 6, 5])
    b = np.array([1, 2, 3])
    print("Divide", np.divide(a, b), np.divide(b, a))

    divide()函数的结果如下所示:

    Divide [2 3 1] [0 0 0]

    如您所见,截断发生了。

  2. 函数true_divide()更接近于除法的数学定义。 整数除法返回浮点结果,并且不会发生截断:

    print("True Divide", np.true_divide(a, b), np.true_divide(b, a))

    true_divide()函数的结果如下:

    True Divide [ 2\.          3\.          1.66666667] [ 0.5         0.33333333  0.6       ]
  3. floor_divide()函数总是返回整数结果。 等效于调用divide()函数之后调用floor()函数。 floor()函数丢弃浮点数的小数部分并返回一个整数:

    print("Floor Divide", np.floor_divide(a, b), np.floor_divide(b, a))
    c = 3.14 * b
    print("Floor Divide 2", np.floor_divide(c, b), np.floor_divide(b, c))

    floor_divide()函数调用导致:

    Floor Divide [2 3 1] [0 0 0]
    Floor Divide 2 [ 3\.  3\.  3.] [ 0\.  0\.  0.]
  4. 默认情况下,/运算符等效于调用divide()函数:

    from __future__ import division

    但是,如果在 Python 程序的开头找到此行,则将调用true_divide()函数。 因此,此代码将如下所示:

    print("/ operator", a/b, b/a)

    The result is shown as follows:

    / operator [ 2\.          3\.          1.66666667] [ 0.5         0.33333333  0.6       ]
  5. //运算符等效于调用floor_divide()函数。 例如,查看以下代码片段:

    print("// operator", a//b, b//a)
    print("// operator 2", c//b, b//c)

    //运算符结果如下所示:

    // operator [2 3 1] [0 0 0]
    // operator 2 [ 3\.  3\.  3.] [ 0\.  0\.  0.]

刚刚发生了什么?

divide()函数会执行截断整数除法和常规浮点除法。 true_divide()函数始终返回浮点结果而没有任何截断。 floor_divide()函数始终返回整数结果; 结果与通过连续调用divide()floor()函数(请参见dividing.py)获得的相同:

from __future__ import print_function
from __future__ import division
import numpy as np

a = np.array([2, 6, 5])
b = np.array([1, 2, 3])

print("Divide", np.divide(a, b), np.divide(b, a))
print("True Divide", np.true_divide(a, b), np.true_divide(b, a))
print("Floor Divide", np.floor_divide(a, b), np.floor_divide(b, a))
c = 3.14 * b
print("Floor Divide 2", np.floor_divide(c, b), np.floor_divide(b, c))
print("/ operator", a/b, b/a)
print("// operator", a//b, b//a)
print("// operator 2", c//b, b//c)

勇往直前 – 尝试__future__.division

通过实验确认导入__future__.division的影响。

模运算

我们可以使用 NumPy mod()remainder()fmod()函数来计算模或余数。 同样,我们可以使用%运算符。 这些函数之间的主要区别在于它们如何处理负数。 该组中的奇数是fmod()函数。

实战时间 – 计算模数

让我们调用前面提到的函数:

  1. remainder()函数以元素为单位返回两个数组的其余部分。 如果第二个数字为 0,则返回 0:

    a = np.arange(-4, 4)
    print("Remainder", np.remainder(a, 2))

    remainder()函数的结果如下所示:

    Remainder [0 1 0 1 0 1 0 1]
  2. mod()函数的功能与remainder()函数的功能完全相同:

    print("Mod", np.mod(a, 2))

    mod()函数的结果如下所示:

    Mod [0 1 0 1 0 1 0 1]
  3. %运算符只是remainder()函数的简写:

    print("% operator", a % 2)

    %运算符的结果如下所示:

    % operator [0 1 0 1 0 1 0 1]
  4. fmod()函数处理负数的方式与mod()%的处理方式不同。 余数的符号是被除数的符号,除数的符号对结果没有影响:

    print("Fmod", np.fmod(a, 2))

    fmod()结果打印如下:

    Fmod [ 0 -1  0 -1  0  1  0  1]

刚刚发生了什么?

我们向 NumPy 演示了mod()remainder()fmod()函数,它们计算模或余数(请参见modulo.py):

from __future__ import print_function
import numpy as np

a = np.arange(-4, 4)

print("Remainder", np.remainder(a, 2))
print("Mod", np.mod(a, 2))
print("% operator", a % 2)
print("Fmod", np.fmod(a, 2))

斐波那契数

斐波那契数 基于递归关系:

Fibonacci numbers

用 NumPy 代码直接表达这种关系是困难的。 但是,我们可以用矩阵形式表示这种关系,也可以按照黄金比例公式:

Fibonacci numbers

Fibonacci numbers

这将介绍matrix()rint()函数。 matrix()函数创建矩阵, 数字四舍五入到最接近的整数,但结果不是整数。

实战时间 – 计算斐波纳契数

矩阵可以表示斐波那契递归关系。 我们可以将斐波纳契数的计算表示为重复的矩阵乘法:

  1. 如下创建斐波那契矩阵:

    F = np.matrix([[1, 1], [1, 0]])
    print("F", F)

    斐波那契矩阵如下所示:

    F [[1 1]
     [1 0]]
  2. 通过从 8 减去 1 并取矩阵的幂,计算出第 8 个斐波那契数(忽略 0)。 斐波那契数然后出现在对角线上:

    print("8th Fibonacci", (F ** 7)[0, 0])

    斐波那契数如下:

    8th Fibonacci 21
  3. “黄金比例”公式(又称为“Binet”公式)使我们能够计算斐波纳契数,并在最后进行四舍五入。 计算前八个斐波那契数:

    n = np.arange(1, 9)
    sqrt5 = np.sqrt(5)
    phi = (1 + sqrt5)/2
    fibonacci = np.rint((phi**n - (-1/phi)**n)/sqrt5)
    print("Fibonacci", fibonacci)

    前八个斐波那契数如下:

    Fibonacci [  1\.   1\.   2\.   3\.   5\.   8\.  13\.  21.]

刚刚发生了什么?

我们用两种方法计算了斐波那契数。 在此过程中,我们了解了用于创建矩阵的matrix()函数。 我们还了解了rint()函数,该函数将数字四舍五入到最接近的整数,但不将类型更改为整数(请参见fibonacci.py):

from __future__ import print_function
import numpy as np

F = np.matrix([[1, 1], [1, 0]])
print("F", F)
print("8th Fibonacci", (F ** 7)[0, 0])
n = np.arange(1, 9)

sqrt5 = np.sqrt(5)
phi = (1 + sqrt5)/2
fibonacci = np.rint((phi**n - (-1/phi)**n)/sqrt5)
print("Fibonacci", fibonacci)

勇往直前 – 时间计算

您可能想知道哪种方法更快,因此请继续并确定时间。 用frompyfunc()创建通用的斐波那契函数,并对其计时。

利萨如曲线

所有标准的三角函数,例如sincostan等,都由 NumPy 中的通用函数表示)。利萨如曲线是使用三角函数的一种有趣方式。 我记得在物理实验室的示波器上制作了李沙育的数字。 两个参数方程式描述了这些图形:

x = A sin(at + π/2)
y = B sin(bt)

实战时间 – 绘制利萨如曲线

利萨如的数字是由ABab四个参数确定的 。 为了简单起见,我们将AB设置为1

  1. 将具有linspace()函数的t-pi初始化为具有201点的pi

    a = 9
    b = 8
    t = np.linspace(-np.pi, np.pi, 201)
  2. 使用sin()函数和np.pi计算x

    x = np.sin(a * t + np.pi/2)
  3. 使用sin()函数计算y

    y = np.sin(b * t)
  4. 如下图所示:

    plt.plot(x, y)
    plt.title('Lissajous curves')
    plt.grid()
    plt.show()

    a = 9b = 8的结果如下:

    Time for action – drawing Lissajous curves

刚刚发生了什么?

我们用上述参数方程式绘制了利萨如曲线,其中A=B=1a=9b=8。 我们使用了sin()linspace()函数,以及 NumPy pi常量(请参见lissajous.py):

import numpy as np
import matplotlib.pyplot as plt

a = 9
b = 8
t = np.linspace(-np.pi, np.pi, 201)
x = np.sin(a * t + np.pi/2)
y = np.sin(b * t)
plt.plot(x, y)
plt.title('Lissajous curves')
plt.grid()
plt.show()

方波

方波也是您可以在示波器上查看的那些整洁的东西之一。 正弦波可以很好地将其近似为 。 毕竟,方波是可以用无限傅立叶级数表示的信号。

注意

傅立叶级数是以著名数学家让·巴蒂斯特·傅立叶(Jean-Baptiste Fourier)命名的,一系列正弦和余弦项之和

代表方波的该特定系列的公式如下:

Square waves

实战时间 – 绘制方波

就像上一节一样,我们将初始化t。 我们需要总结一些术语。 术语数量越多,结果越准确; k = 99应该足够。 为了绘制方波,请按照下列步骤操作:

  1. 我们将从初始化tk开始。 将该函数的初始值设置为0

    t = np.linspace(-np.pi, np.pi, 201)
    k = np.arange(1, 99)
    k = 2 * k - 1
    f = np.zeros_like(t)
  2. 使用sin()sum()函数计算函数值:

    for i, ti in enumerate(t):
       f[i] = np.sum(np.sin(k * ti)/k)
    
    f = (4 / np.pi) * f
  3. 要绘制的代码与上一节中的代码几乎相同:

    plt.plot(t, f)
    plt.title('Square wave')
    plt.grid()
    plt.show()

    k = 99生成的所得方波如下:

    Time for action – drawing a square wave

刚刚发生了什么?

我们使用 sin()函数生成了方波,或者至少是它的近似值。 输入值通过 linspace()函数进行组装,而k值通过arange()函数进行组装(请参见squarewave.py):

import numpy as np
import matplotlib.pyplot as plt

t = np.linspace(-np.pi, np.pi, 201)
k = np.arange(1, 99)
k = 2 * k - 1
f = np.zeros_like(t)

for i, ti in enumerate(t):
   f[i] = np.sum(np.sin(k * ti)/k)

f = (4 / np.pi) * f

plt.plot(t, f)
plt.title('Square wave')
plt.grid()
plt.show()

勇往直前 – 摆脱循环

您可能已经注意到代码中存在一个循环。 使用 NumPy 函数摆脱它,并确保性能也得到改善。

锯齿波和三角波

锯齿波和三角波也是在示波器上容易看到的现象。 就像方波一样,我们可以定义无限傅立叶级数。 三角波可以通过获取锯齿波的绝对值来找到。 表示一系列锯齿波的公式如下:

Sawtooth and triangle waves

实战时间 – 绘制锯齿波和三角波

就像上一节一样,我们初始化t。 同样,k = 99应该足够。 为了绘制锯齿波和三角波,请按照下列步骤操作:

  1. 将该函数的初始值设置为zero

    t = np.linspace(-np.pi, np.pi, 201)
    k = np.arange(1, 99)
    f = np.zeros_like(t)
  2. 使用sin()sum()函数计算函数值:

    for i, ti in enumerate(t):
       f[i] = np.sum(np.sin(2 * np.pi * k * ti)/k)
    
    f = (-2 / np.pi) * f
  3. 绘制锯齿波和三角波很容易,因为三角波的值应等于锯齿波的绝对值。 绘制波形,如下所示:

    plt.plot(t, f, lw=1.0, label='Sawtooth')
    plt.plot(t, np.abs(f), '--', lw=2.0, label='Triangle')
    plt.title('Triangle and sawtooth waves')
    plt.grid()
    plt.legend(loc='best')
    plt.show()

    在下图中,三角形波是带有虚线的波:

    Time for action – drawing sawtooth and triangle waves

刚刚发生了什么?

我们使用sin()函数绘制了锯齿波 。 我们将输入值与linspace()函数组合在一起,并将k值与arange()函数组合在一起。 通过取绝对值从锯齿波中产生一个三角波(请参见sawtooth.py):

import numpy as np
import matplotlib.pyplot as plt

t = np.linspace(-np.pi, np.pi, 201)
k = np.arange(1, 99)
f = np.zeros_like(t)

for i, ti in enumerate(t):
   f[i] = np.sum(np.sin(2 * np.pi * k * ti)/k)

f = (-2 / np.pi) * f
plt.plot(t, f, lw=1.0, label='Sawtooth')
plt.plot(t, np.abs(f), '--', lw=2.0, label='Triangle')
plt.title('Triangle and sawtooth waves')
plt.grid()
plt.legend(loc='best')
plt.show()

勇往直前 – 摆脱循环

如果您选择接受 ,那么您面临的挑战是摆脱程序中的循环。 它应该可以与 NumPy 函数一起使用,并且性能应该得到改善。

按位和比较函数

按位函数是整数或整数数组的位,因为它们是通用函数。 运算符^&|<<>>等具有其 NumPy 对应物。 比较运算符,例如<>==等也是如此。 这些运算符使您可以做一些巧妙的技巧,从而提高性能; 但是,它们会使您的代码难以理解,因此请谨慎使用。

实战时间 – 翻转位

现在,我们将介绍三个技巧:检查整数的符号是​​否不同,检查数字是否为2的幂,以及计算作为2的幂的数字的模数。 我们将展示一个仅用于运算符的符号,以及一个使用相应的 NumPy 函数的符号:

  1. 第一个技巧取决于XOR^运算符。 XOR运算符也称为不等式运算符; 因此,如果两个操作数的符号位不同,XOR运算将导致负数

    下面的真值表说明了XOR运算符:

    输入 1 输入 2 异或
    True True False
    False True True
    True False True
    False False False

    ^运算符对应于bitwise_xor()函数,<运算符对应于less()函数:

    x = np.arange(-9, 9)
    y = -x
    print("Sign different?", (x ^ y) < 0)
    print("Sign different?", np.less(np.bitwise_xor(x, y), 0))

    结果为 ,如下所示:

    Sign different? [ True  True  True  True  True  True  True  True  True False  True  True
     True  True  True  True  True  True]
    Sign different? [ True  True  True  True  True  True  True  True  True False  True  True
     True  True  True  True  True  True]

    正如预期的那样,除零以外,所有符号均不同。

  2. 2 的幂由 1 表示,后跟一系列二进制表示的尾随零。 例如,101001000。 比 2 的幂小 1 的数字将由一排二进制 1 表示。 例如,111111111(或十进制中的3715)。 现在,如果我们对 2 的幂,和比它小 1 的整数进行“与”运算,则应该得到 0。

    AND运算符的真值表如下所示:

    输入 1 输入 2 AND
    True True True
    False True False
    True False False
    False False False

    &的 NumPy 对应项是bitwise_and()==的对应项是equal()通用函数:

    print("Power of 2?\n", x, "\n", (x & (x - 1)) == 0)
    print("Power of 2?\n", x, "\n", np.equal(np.bitwise_and(x,  (x - 1)), 0))

    The result is shown as follows:

    Power of 2?
    **[-9 -8 -7 -6 -5 -4 -3 -2 -1  0  1  2  3  4  5  6  7  8]** 
    [False False False False False False False False False  True  True  True
     False  True False False False  True]
    Power of 2?
    **[-9 -8 -7 -6 -5 -4 -3 -2 -1  0  1  2  3  4  5  6  7  8]** 
    [False False False False False False False False False  True  True  True
     False  True False False False  True]
  3. 当计算整数的 2 的幂的模数时,例如 4、8、16 等,计算 4 的模数的技巧实际上起作用。 左移导致值加倍。我们在上一步中看到,从 2 的幂中减去 1 会导致二进制表示形式的数字带有一行诸如 11、111 或 1111 之类的数字。 这基本上给了我们一个掩码。 用这样的数字按位与,得到的余数为 2。 NumPy 的<<的等价物是left_shift()通用函数:

    print("Modulus 4\n", x, "\n", x & ((1 << 2) - 1))
    print("Modulus 4\n", x, "\n", np.bitwise_and(x, np.left_shift(1, 2) - 1))

    The result is shown as follows:

    Modulus 4
    **[-9 -8 -7 -6 -5 -4 -3 -2 -1  0  1  2  3  4  5  6  7  8]** 
    [3 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3 0]
    Modulus 4
    **[-9 -8 -7 -6 -5 -4 -3 -2 -1  0  1  2  3  4  5  6  7  8]** 
    [3 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3 0]

刚刚发生了什么?

我们介绍了三点技巧:检查整数的符号是​​否不同,检查数字是否为2的幂,并计算数字的模数为2的幂。 我们看到了运算符^&<<<的 NumPy 对应项(请参见bittwidling.py):

from __future__ import print_function
import numpy as np

x = np.arange(-9, 9)
y = -x
print("Sign different?", (x ^ y) < 0)
print("Sign different?", np.less(np.bitwise_xor(x, y), 0))
print("Power of 2?\n", x, "\n", (x & (x - 1)) == 0)
print("Power of 2?\n", x, "\n", np.equal(np.bitwise_and(x,  (x - 1)), 0))
print("Modulus 4\n", x, "\n", x & ((1 << 2) - 1))
print("Modulus 4\n", x, "\n", np.bitwise_and(x, np.left_shift(1, 2) - 1))

花式索引

at()方法是在 NumPy 1.8 中添加的 。 此方法允许原地建立花式索引。 花式索引是不涉及整数或切片的索引 ,这是正常的索引。 原地意味着将对我们操作的数组进行修改。

at()方法的签名为ufunc.at(a, indices[, b]) indices数组指定要操作的元素。 我们仅需要b数组用于具有两个操作数的通用函数。 以下“实战时间”部分给出了at()方法的示例 。

实战时间 – 使用at()方法为 ufuncs 原地建立索引

要演示方法的工作方式,请启动 Python 或 IPython shell 并导入 NumPy。 您现在应该知道如何执行此操作。

  1. 创建一个由七个随机整数组成的数组,该整数从-33,种子为42

    >>> a = np.random.random_integers(-3, 3, 7)
    >>> a
    array([ 1,  0, -1,  2,  1, -2,  0])

    当我们在编程中谈论随机数字时,我们通常会谈论伪随机数。 这些数字看起来是随机的,但实际上是使用种子来计算的。

  2. sign()通用函数的at()方法应用于第四和第六个数组元素:

    >>> np.sign.at(a, [3, 5])
    >>> a
    array([ 1, 0, -1,  1,  1, -1,  0])

刚刚发生了什么?

我们使用at()方法来选择数组元素,并执行原地操作-确定符号。 我们还学习了如何创建随机整数。

总结

在本章中,您学习了关于矩阵和通用函数的知识。 我们介绍了如何创建矩阵,并研究了通用函数如何工作。 您简要介绍了算术,三角函数,按位和比较通用函数。

在下一章中,您将介绍 NumPy 模块。