NumPy - fromstring - fromfunction

NumPy - fromstring - fromfunction

标准安装的 Python 中用列表 (list) 保存一组值,可以用来当作数组使用,不过由于列表的元素可以是任何对象,因此列表中所保存的是对象的指针。为了保存一个简单的 [1, 2, 3],需要有 3 个指针和三个整数对象。对于数值运算来说这种结构显然比较浪费内存和 CPU 计算时间。

Python 还提供了一个 array 模块,array 对象和列表不同,它直接保存数值,和 C 语言的一维数组比较类似。但是由于它不支持多维,也没有各种运算函数,也不适合做数值运算。

NumPy 提供了两种基本的对象:ndarray (N-dimensional array object) 和 ufunc (universal function object)。ndarray 是存储单一数据类型的多维数组,而 ufunc 则是能够对数组进行处理的函数。

1. ndarray 对象

NumPy 函数库的导入

import numpy as np

1.1 创建

array 函数传递 Python 的序列对象创建数组。如果传递的是多层嵌套的序列,将创建多维数组。

(pt-1.4_py-3.6) yongqiang@yongqiang:~$ python
Python 3.6.10 |Anaconda, Inc.| (default, May  8 2020, 02:54:21)
[GCC 7.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy as np
>>>
>>> a = np.array([1, 2, 3, 4])
>>> b = np.array((5, 6, 7, 8))
>>> c = np.array([[1, 2, 3, 4],[4, 5, 6, 7], [7, 8, 9, 10]])
>>>
>>> a
array([1, 2, 3, 4])
>>> b
array([5, 6, 7, 8])
>>> c
array([[ 1,  2,  3,  4],
       [ 4,  5,  6,  7],
       [ 7,  8,  9, 10]])
>>> c.dtype
dtype('int64')
>>>

数组的大小可以通过其 shape 属性获得。

>>> a.shape
(4,)
>>>
>>> b.shape
(4,)
>>>
>>> c.shape
(3, 4)
>>>

数组 a 的 shape 只有一个元素,因此它是一维数组。而数组 c 的 shape 有两个元素,因此它是二维数组,其中第 0 轴的长度为 3,第 1 轴的长度为 4。

可以通过修改数组的 shape 属性,在保持数组元素个数不变的情况下,改变数组每个轴的长度。下面的例子将数组 c 的 shape 改为 (4, 3),注意从 (3, 4) 改为 (4, 3) 并不是对数组进行转置,而只是改变每个轴的大小,数组元素在内存中的位置并没有改变:

>>> c.shape
(3, 4)
>>>
>>> c
array([[ 1,  2,  3,  4],
       [ 4,  5,  6,  7],
       [ 7,  8,  9, 10]])
>>>
>>> c.shape = 4,3
>>>
>>> c
array([[ 1,  2,  3],
       [ 4,  4,  5],
       [ 6,  7,  7],
       [ 8,  9, 10]])
>>>

当某个轴的元素为 -1 时,将根据数组元素的个数自动计算此轴的长度,因此下面的程序将数组 c 的 shape 改为了 (2, 6)。

>>> c.shape
(4, 3)
>>>
>>> c
array([[ 1,  2,  3],
       [ 4,  4,  5],
       [ 6,  7,  7],
       [ 8,  9, 10]])
>>>
>>> c.shape = 2,-1
>>>
>>> c
array([[ 1,  2,  3,  4,  4,  5],
       [ 6,  7,  7,  8,  9, 10]])
>>>

使用数组的 reshape 方法,可以创建一个改变了尺寸的新数组,原数组的 shape 保持不变。

>>> a = np.array([1, 2, 3, 4])
>>> a
array([1, 2, 3, 4])
>>> a.shape
(4,)
>>>
>>> d = a.reshape((2, 2))
>>> d
array([[1, 2],
       [3, 4]])
>>> d.shape
(2, 2)
>>>
>>> a
array([1, 2, 3, 4])
>>>

数组 a 和 d 其实共享数据存储内存区域,因此修改其中任意一个数组的元素都会同时修改另外一个数组的内容。

>>> a[1] = 100 # 将数组 a 的第一个元素改为 100
>>> a
array([  1, 100,   3,   4])
>>> d # 注意数组 d 中的 2 也被改变了
array([[ 1, 100],
[ 3, 4]])

数组的元素类型可以通过 dtype 属性获得。上面例子中的参数序列的元素都是整数,因此所创建的数组的元素类型也是整数,并且是 64bit 的长整型。可以通过 dtype 参数在创建时指定元素类型:

>>> np.array([[1, 2, 3, 4],[4, 5, 6, 7], [7, 8, 9, 10]], dtype=np.float)
array([[ 1.,  2.,  3.,  4.],
       [ 4.,  5.,  6.,  7.],
       [ 7.,  8.,  9., 10.]])
>>>
>>> np.array([[1, 2, 3, 4],[4, 5, 6, 7], [7, 8, 9, 10]], dtype=np.complex)
array([[ 1.+0.j,  2.+0.j,  3.+0.j,  4.+0.j],
       [ 4.+0.j,  5.+0.j,  6.+0.j,  7.+0.j],
       [ 7.+0.j,  8.+0.j,  9.+0.j, 10.+0.j]])
>>>

上面的例子都是先创建一个 Python 序列,然后通过 array 函数将其转换为数组,这样做显然效率不高。因此NumPy 提供了很多专门用来创建数组的函数。

arrange 函数类似于 python 的 range 函数,通过指定开始值、终值和步长来创建一维数组,注意数组不包括终值。

>>> np.arange(0,1,0.1)
array([0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9])
>>>

linspace 函数通过指定开始值、终值和元素个数来创建一维数组,可以通过 endpoint 关键字指定是否包括终值,缺省设置是包括终值。

>>> np.linspace(0, 1, 12)
array([0.        , 0.09090909, 0.18181818, 0.27272727, 0.36363636,
       0.45454545, 0.54545455, 0.63636364, 0.72727273, 0.81818182,
       0.90909091, 1.        ])
>>>

logspace 函数和 linspace 类似,不过它创建等比数列,下面的例子产生 1 (10^0) 到 100 (10^2)、有 20 个元素的等比数列:

>>> np.logspace(0, 2, 20)
array([  1.        ,   1.27427499,   1.62377674,   2.06913808,
         2.6366509 ,   3.35981829,   4.2813324 ,   5.45559478,
         6.95192796,   8.8586679 ,  11.28837892,  14.38449888,
        18.32980711,  23.35721469,  29.76351442,  37.92690191,
        48.32930239,  61.58482111,  78.47599704, 100.        ])
>>>

使用 frombuffer, fromstring, fromfile 等函数可以从字节序列创建数组。

Python 的字符串实际上是字节序列,每个字符占一个字节,因此如果从字符串 s 创建一个 8bit 的整数数组的话,所得到的数组正好就是字符串中每个字符的 ASCII 编码。

>>> s = "abcdefgh"
>>> np.fromstring(s, dtype=np.int8)
array([ 97,  98,  99, 100, 101, 102, 103, 104], dtype=int8)
>>>

如果从字符串 s 创建 16bit 的整数数组,那么两个相邻的字节就表示一个整数,把字节 98 和字节 97 当作一个16 位的整数,它的值就是 98 * 256 + 97 = 25185。可以看出内存中是以 little endian (低位字节在前) 方式保存数据的。

>>> s = "abcdefgh"
>>> np.fromstring(s, dtype=np.int8)
array([ 97,  98,  99, 100, 101, 102, 103, 104], dtype=int8)
>>>
>>> np.fromstring(s, dtype=np.int16)
array([25185, 25699, 26213, 26727], dtype=int16)
>>>

如果把整个字符串转换为一个 64 位的双精度浮点数数组,那么它的值是:

>>> np.fromstring(s, dtype=np.float)
array([8.54088322e+194])

显然这个例子没有什么意义,但是可以想象如果我们用 C 语言的二进制方式写了一组 double 类型的数值到某个文件中,那们可以从此文件读取相应的数据,并通过 fromstring 函数将其转换为 float64 类型的数组。

我们可以写一个 Python 的函数,它将数组下标转换为数组中对应的值,然后使用此函数创建数组:

>>> def func(i):
...     return i % 4 + 1
...
>>> np.fromfunction(func, (10,))
array([1., 2., 3., 4., 1., 2., 3., 4., 1., 2.])
>>>

fromfunction 函数的第一个参数为计算每个数组元素的函数,第二个参数为数组的大小 (shape),因为它支持多维数组,所以第二个参数必须是一个序列,本例中用 (10,) 创建一个 10 元素的一维数组。

下面的例子创建一个二维数组表示九九乘法表,输出的数组 a 中的每个元素 a[i, j] 都等于 func2(i, j):

>>> def func2(i, j):
...     return (i+1) * (j+1)
...
>>> a = np.fromfunction(func2, (9,9))
>>> a
array([[ 1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9.],
       [ 2.,  4.,  6.,  8., 10., 12., 14., 16., 18.],
       [ 3.,  6.,  9., 12., 15., 18., 21., 24., 27.],
       [ 4.,  8., 12., 16., 20., 24., 28., 32., 36.],
       [ 5., 10., 15., 20., 25., 30., 35., 40., 45.],
       [ 6., 12., 18., 24., 30., 36., 42., 48., 54.],
       [ 7., 14., 21., 28., 35., 42., 49., 56., 63.],
       [ 8., 16., 24., 32., 40., 48., 56., 64., 72.],
       [ 9., 18., 27., 36., 45., 54., 63., 72., 81.]])
>>>

References

http://www.uml.org.cn/python/201811123.asp

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章