Numpy 中文文档 快速入门
Contents
预备条件
您需要了解Python、如有必要,请参考Python tutorial复习下.
为了后续例子运行, 您需要在numpy之外安装’matplotlib'
目的简介
这里是Numpy中数组的快速概览。展示了n-维($n>=2$)数组的表示和操作方法。 特别的、如何不熟悉n维数组的的通用函数应用(不使用python的for循环),或者想进一步了解n维数组的axis和shape属性,本文应该有些帮助 学习目标
学习本文之后、您应该掌握:
- 理解numpy中一维、二维和n-维数组的不同;
- 理解如何在不使用for-循环时对n维数组应用一些线性代数操作;
- 理解n维数组的维axis和形状shape属性.
基础概念
Numpy的主要对象是同构的多维数组。它是元素(通常是数字)的表、都是同一类型、以非零整数的元组为索引。 在Numpy中维被称为 axes(轴)
比如、在3D空间的一个点的坐标的的数组,[1, 2, 1]
, 具有一个轴。这个轴内有3个元素,我们称其长度为3.
在下面的例子中,数组具有2个轴。第一个轴具有长度2,第二个轴的长度为3.
|
|
NumPy的数组类称为 ndarray
. 它还有另外一个别名array
. 请注意 numpy.array
和Python标准库的类array.array
不一样, 后者仅仅处理一维数组并且提供很少的共功能。
ndarray
对象的很重要的属性如下:
- ndarray.ndim
轴(维度)的数量
- ndarray.shape
维度的大小。数组在每个维度的数组大小的整数的元组。比如 n 行和 m 列的矩阵,其
shape
是(n,m)
。shape
元组的长度即是轴的数量ndim
- ndarray.size
数组中所有元素的数量, 和
shape
元组中元素乘积相等.- ndarray.dtype
数据类型、数组元素类型的描述对象。用户可以使用标准python类型创建或者声明数据类型。 另外Numpy提供了自己的一些类型。比如numpy.int32, numpy.int16, andnumpy.float64等
- ndarray.itemsize
数组内每个元素所占字节大小。比如、 元素类型为
float64
的数组、其itemsize
是8(=64/8), 而类型为complex32
的数组、对应的itemsize
是 4 (=32/8)。这个属性和ndarray.dtype.itemsize
属性相等.- ndarray.data
包含数组实际元素的内存缓冲区。一般情况下、我们不会使用这个属性,因为我们通过索引功能来访问数组包含的元素.
数组实例
|
|
数组创建
创建numpy数组的方式有多种:
比如、可以使用常规的python列表或者元组应用 array
函数来创建数组。
生成的数组的类型从python序列的元素类型推断而来
>>> import numpy as np
>>> a = np.array([2, 3, 4])
>>> a
array([2, 3, 4])
>>> a.dtype
dtype('int64')
>>> b = np.array([1.2, 3.5, 5.1])
>>> b.dtype
dtype('float64')
一个常见错误是调用array
函数时提供多个参数、
这里正确的做法应该是提供一个作为参数的python序列
>>> a = np.array(1, 2, 3, 4) # WRONG **错误**
Traceback (most recent call last):
...
TypeError: array() takes from 1 to 2 positional arguments but 4 were given
>>> a = np.array([1, 2, 3, 4]) # RIGHT **正确**
array
函数将序列的序列转换成二维数组,将序列的序列的序列转换成三维数组,以此类推。
>>> b = np.array([(1.5, 2, 3), (4, 5, 6)])
>>> b
array([[1.5, 2. , 3. ],
[4. , 5. , 6. ]])
也可以在创建数组时明确指定数组元素类型:
>>> c = np.array([[1, 2], [3, 4]], dtype=complex)
>>> c
array([[1.+0.j, 2.+0.j],
[3.+0.j, 4.+0.j]])
通常、数组的元素最初是未知的,但是它的大小已知。因此,numpy提供了几个函数来创建以初始占位符为内容的数组。 这样最小化了对数组增长的需求,实际上数组增长是一个昂贵的操作。
函数zeros
创建充满0的数组,函数 ones
创建充满1的数组,
函数empty
创建的数组依赖于内存状态、内容是随机的。默认情况下,创建的数组的dtype类型是 float64
,
但是,也可以通过关键字参数 dtype
来指定dtype.
>>> np.zeros((3, 4))
array([[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]])
>>> np.ones((2, 3, 4), dtype=np.int16)
array([[[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1]],
[[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1]]], dtype=int16)
>>> np.empty((2, 3))
array([[3.73603959e-262, 6.02658058e-154, 6.55490914e-260], # 随机的数值
[5.30498948e-313, 3.14673309e-307, 1.00000000e+000]])
Numpy提供了函数 arange
来创建数字序列,它与python的内置函数range
类似,但是返回的是数组
>>> np.arange(10, 30, 5)
array([10, 15, 20, 25])
>>> np.arange(0, 2, 0.3) # 接受浮点数作为参数
array([0. , 0.3, 0.6, 0.9, 1.2, 1.5, 1.8])
通常使用浮点数作为 arange
的参数,由于浮点数的有限精度的原因,不太可能预知获得的元素的数量。
由此,最好使用函数 linspace
,它接受一个表示我们想获得的元素数量(而不是步长)的参数:
>>> from numpy import pi
>>> np.linspace(0, 2, 9) # 获得从0到2的9个数字
array([0. , 0.25, 0.5 , 0.75, 1. , 1.25, 1.5 , 1.75, 2. ])
>>> x = np.linspace(0, 2 * pi, 100) # 在函数多点求值时很有用
>>> f = np.sin(x)
** 更多数组创建函数请参考NumPy数组创建函数 **
打印数组
打印数组时,Numpy展示其内容的方式与嵌套的python列表接近。不过,具有下面的布局特点:
- 最后的轴从左到右打印 ,
- 倒数第二个轴从上到下打印 ,
- 其他轴也从上到下打印,每个切块同尾随的切块之间用空行分割.
一维数组打印成行,二维数组打印成矩阵,三维数组打印成矩阵列表
>>> a = np.arange(6) # 1d array
>>> print(a)
[0 1 2 3 4 5]
>>>
>>> b = np.arange(12).reshape(4, 3) # 2d array
>>> print(b)
[[ 0 1 2]
[ 3 4 5]
[ 6 7 8]
[ 9 10 11]]
>>>
>>> c = np.arange(24).reshape(2, 3, 4) # 3d array
>>> print(c)
[[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
<BLANKLINE>
[[12 13 14 15]
[16 17 18 19]
[20 21 22 23]]]
参考下面的 reshape
获得 reshape
函数的更多信息.
如果数组内容太多无法全部打印出来,那么Numpy自动省略数组的中间部分,仅仅打印角落。
>>> print(np.arange(10000))
[ 0 1 2 ... 9997 9998 9999]
>>>
>>> print(np.arange(10000).reshape(100, 100))
[[ 0 1 2 ... 97 98 99]
[ 100 101 102 ... 197 198 199]
[ 200 201 202 ... 297 298 299]
...
[9700 9701 9702 ... 9797 9798 9799]
[9800 9801 9802 ... 9897 9898 9899]
[9900 9901 9902 ... 9997 9998 9999]]
如果想禁用打印省略功能,强制Numpy打印整个数组,可以设置参数 set_printoptions
来改变打印的设置.
>>> np.set_printoptions(threshold=sys.maxsize) # 模块sys应该已导入
基础操作
数学符号按照 按元素展开 的方式应用在数组各个元素上,会创建一个新的数组并填入作为结果的内容。
>>> a = np.array([20, 30, 40, 50])
>>> b = np.arange(4)
>>> b
array([0, 1, 2, 3])
>>> c = a - b
>>> c
array([20, 29, 38, 47])
>>> b**2
array([0, 1, 4, 9])
>>> 10 * np.sin(a)
array([ 9.12945251, -9.88031624, 7.4511316 , -2.62374854])
>>> a < 35
array([ True, True, False, False])
与其他矩阵语言不同,乘积符号 *
在Numpy数组中按元素展开进行操作。
矩阵乘积可以使用 @
符号来完成(python>=3.5),也可以使用dot
函数或方法:
>>> A = np.array([[1, 1],
... [0, 1]])
>>> B = np.array([[2, 0],
... [3, 4]])
>>> A * B # 按位展开的乘积
array([[2, 0],
[0, 4]])
>>> A @ B # 矩阵乘积
array([[5, 4],
[3, 4]])
>>> A.dot(B) # 另外一种矩阵乘积方式
array([[5, 4],
[3, 4]])
一些运算比如 +=
and *=
, 会操作修改现存数组而不是创建一个新数组。
>>> rg = np.random.default_rng(1) # 创建默认随机数生成器的的实例
>>> a = np.ones((2, 3), dtype=int)
>>> b = rg.random((2, 3))
>>> a *= 3
>>> a
array([[3, 3, 3],
[3, 3, 3]])
>>> b += a
>>> b
array([[3.51182162, 3.9504637 , 3.14415961],
[3.94864945, 3.31183145, 3.42332645]])
>>> a += b # ** b不会自动转换为整数类型 **
Traceback (most recent call last):
...
numpy.core._exceptions._UFuncOutputCastingError: Cannot cast ufunc 'add' output from dtype('float64') to dtype('int64') with casting rule 'same_kind'
当操作不同类型数组时,计算结果的数组类型对应于输入数组中更一般或者说更精确的类型(称为向上数据类型转换).
>>> a = np.ones(3, dtype=np.int32)
>>> b = np.linspace(0, pi, 3)
>>> b.dtype.name
'float64'
>>> c = a + b
>>> c
array([1. , 2.57079633, 4.14159265])
>>> c.dtype.name
'float64'
>>> d = np.exp(c * 1j)
>>> d
array([ 0.54030231+0.84147098j, -0.84147098+0.54030231j,
-0.54030231-0.84147098j])
>>> d.dtype.name
'complex128'
许多一元运算,比如计算数组内所有元素的和,通过 ndarray
类的方法来实现
>>> a = rg.random((2, 3))
>>> a
array([[0.82770259, 0.40919914, 0.54959369],
[0.02755911, 0.75351311, 0.53814331]])
>>> a.sum()
3.1057109529998157
>>> a.min()
0.027559113243068367
>>> a.max()
0.8277025938204418
默认情况下,这些操作应用于数组,操作方式就好像数字列表一样,和数组的shape形状无关。
不过,通过声明 axis
参数,可以沿着指定的轴展开运算:
>>> b = np.arange(12).reshape(3, 4)
>>> b
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>>
>>> b.sum(axis=0) # 每列的和
array([12, 15, 18, 21])
>>>
>>> b.min(axis=1) # 每行的最小值
array([0, 4, 8])
>>>
>>> b.cumsum(axis=1) # 沿着行方向的累计和
array([[ 0, 1, 3, 6],
[ 4, 9, 15, 22],
[ 8, 17, 27, 38]])
通用函数
Numpy提供了常见的数学函数,比如 sin、cos和exp。在Numpy中,这些函数被称为numpy的"universal functions" (ufunc
)通用函数, 这些函数以遍历数组元素的方式的操作,生成新的数组作为输出。
>>> B = np.arange(3)
>>> B
array([0, 1, 2])
>>> np.exp(B)
array([1. , 2.71828183, 7.3890561 ])
>>> np.sqrt(B)
array([0. , 1. , 1.41421356])
>>> C = np.array([2., -1., 4.])
>>> np.add(B, C)
array([2., 0., 6.])
** 更多基础函数通用函数例子请参考NumPy通用函数例子 **
索引、切片、和迭代
一维 数组可以索引、切片和迭代, 操作方式和 python 列表 以及其它python序列很相似.
>>> a = np.arange(10)**3 //立方
>>> a
array([ 0, 1, 8, 27, 64, 125, 216, 343, 512, 729])
>>> a[2]
8
>>> a[2:5]
array([ 8, 27, 64])
>>> # 与 a[0:6:2] = 1000 等效;
>>> # 从开始到第六个(不包含)位置,设置每第二个元素为1000
>>> a[:6:2] = 1000
>>> a
array([1000, 1, 1000, 27, 1000, 125, 216, 343, 512, 729])
>>> a[::-1] # 列表反转
array([ 729, 512, 343, 216, 125, 1000, 27, 1000, 1, 1000])
>>> for i in a:
... print(i**(1 / 3.)) #立方根
...
9.999999999999998
1.0
9.999999999999998
3.0
9.999999999999998
4.999999999999999
5.999999999999999
6.999999999999999
7.999999999999999
8.999999999999998
多维 数组可以每个轴拥有索引,这些索引以逗号分割的元组的形式展现:
>>> def f(x, y):
... return 10 * x + y
...
>>> b = np.fromfunction(f, (5, 4), dtype=int)
>>> b
array([[ 0, 1, 2, 3],
[10, 11, 12, 13],
[20, 21, 22, 23],
[30, 31, 32, 33],
[40, 41, 42, 43]])
>>> b[2, 3]
23
>>> b[0:5, 1] # 每一行的第二列
array([ 1, 11, 21, 31, 41])
>>> b[:, 1] # 同上
array([ 1, 11, 21, 31, 41])
>>> b[1:3, :] # 第二和第三行的每列
array([[10, 11, 12, 13],
[20, 21, 22, 23]])
当提供的索引数量少于轴数量时,缺失的索引当作全切片符号:
处理
>>> b[-1] # 最后一行,等价于 [-1, :]
array([40, 41, 42, 43])
在b[i]
中括号中的表达式被当作 i
后面跟随着很多:
,:
冒号的数量由表示剩下的轴的需要而定.
Numpy中还允许使用句号.,b[i, ...]
,
这里句号 (...
)表示构建完整索引元祖所需的那么多冒号。
假如 x
是一个具有5轴的数组,那么
x[1, 2, ...]
等价于x[1, 2, :, :, :]
,x[..., 3]
等于x[:, :, :, :, 3]
andx[4, ..., 5, :]
等于x[4, :, :, 5, :]
|
|
多维数组的的迭代是相对于第一个轴而展开的:
>>> for row in b:
... print(row)
...
[0 1 2 3]
[10 11 12 13]
[20 21 22 23]
[30 31 32 33]
[40 41 42 43]
如果想对数组中每一个元素进行某个操作,可以使用数组的flat
属性,这是一个遍历数组所有元素的iterator:
>>> for element in b.flat:
... print(element)
...
0
1
2
3
10
11
12
13
20
21
22
23
30
31
32
33
40
41
42
43
** 更多Numpy索引例子请参考NumPy索引例子 **
形状操作
改变数组的形状
数组的形状属性由数组沿每个轴的元素数量定义:
>>> a = np.floor(10 * rg.random((3, 4)))
>>> a
array([[3., 7., 3., 4.], # 3行4列
[1., 4., 2., 2.],
[7., 2., 4., 9.]])
>>> a.shape
(3, 4)
可以通过许多命令改变数组的形状。请注意下面的三个命令都会返回修改的数组、并不会改变原数组:
>>> a.ravel() # 返回扁平化flattened的数组
array([3., 7., 3., 4., 1., 4., 2., 2., 7., 2., 4., 9.])
>>> a.reshape(6, 2) # 返回形状改变的数组
array([[3., 7.],
[3., 4.],
[1., 4.],
[2., 2.],
[7., 2.],
[4., 9.]])
>>> a.T # 返回转置的数组
array([[3., 1., 7.],
[7., 4., 2.],
[3., 2., 4.],
[4., 2., 9.]])
>>> a.T.shape
(4, 3)
>>> a.shape
(3, 4)
数组 ravel
操作的返回ß结果数组中元素的顺序一般符合"C-style"C语言样式,
也就是说,最右边的索引“变化的最快”,因此在元素 a[0, 0]
之后是 a[0, 1]
。
如果数组重新改变形状成其他形状,结果数组同样具有C语言样式。
通常Numpy创建的数组的数据存储就符合这样顺序,因此ravel
通常并不需要copy它的参数数据,
不过如果数组由其他数组切片或者通过不常见的选项构建,那么可能就需要copy它的参数数据。
函数 ravel
和 reshape
可以使用可选参数来定制使用FORTRAN样式的数组, FORTRAN数组中变化最快的是左边索引。
reshape函数返回数组改变的形状,而[ndarray.resize]{.title-ref}方法修改数组本身:
>>> a
array([[3., 7., 3., 4.],
[1., 4., 2., 2.],
[7., 2., 4., 9.]])
>>> a.resize((2, 6))
>>> a
array([[3., 7., 3., 4., 1., 4.],
[2., 2., 7., 2., 4., 9.]])
If a dimension is given as -1
in a reshaping operation, the other
dimensions are automatically calculated:
如果reshape操作的形状参数中一个是 -1
,那么这个参数会自动(根据数组长度和其他形状参数)被计算出来:
>>> a.reshape(3, -1)
array([[3., 7., 3., 4.],
[1., 4., 2., 2.],
[7., 2., 4., 9.]])
** 更多Numpy形状修改例子请参考NumPy形状修改例子 **
堆砌不同数组
几个数组可以沿不同的轴堆砌起来:
>>> a = np.floor(10 * rg.random((2, 2)))
>>> a
array([[9., 7.],
[5., 2.]])
>>> b = np.floor(10 * rg.random((2, 2)))
>>> b
array([[1., 9.],
[5., 1.]])
>>> np.vstack((a, b)) #vstack vertical 垂直堆砌
array([[9., 7.],
[5., 2.],
[1., 9.],
[5., 1.]])
>>> np.hstack((a, b)) #hstack horizontal 水平堆砌
array([[9., 7., 1., 9.],
[5., 2., 5., 1.]])
将一维数组作为数据列,进而堆砌成二维数组。 仅仅对于二维数组而言它和[hstack]{.title-ref}等效。
>>> from numpy import newaxis
>>> np.column_stack((a, b)) # with 2D arrays
array([[9., 7., 1., 9.],
[5., 2., 5., 1.]])
>>> a = np.array([4., 2.])
>>> b = np.array([3., 8.])
>>> np.column_stack((a, b)) # returns a 2D array
array([[4., 3.],
[2., 8.]])
>>> np.hstack((a, b)) # the result is different
array([4., 2., 3., 8.])
>>> a[:, newaxis] # view `a` as a 2D column vector
array([[4.],
[2.]])
>>> np.column_stack((a[:, newaxis], b[:, newaxis]))
array([[4., 3.],
[2., 8.]])
>>> np.hstack((a[:, newaxis], b[:, newaxis])) # the result is the same
array([[4., 3.],
[2., 8.]])
相反,函数[row_stack]{.title-ref}对于任何输入数组等价于[vstack]{.title-ref}。
实际上,[row_stack]{.title-ref} 是 vstack
的别名:
»> np.column_stack is np.hstack
False
»> np.row_stack is np.vstack
True
一般来说,对于多于二维的数组 函数[hstack]{.title-ref} 沿着他们的第二个轴堆砌, 函数[vstack]{.title-ref} 沿着他们的第一个轴堆砌, 函数[concatenate]{.title-ref} 接受一个可选参数, 这个参数提供了几个轴,沿着这些轴展开连接数组操作
注意
对于一些复杂的用例,需要沿着轴堆砌数字时,函数[r_]{.title-ref} 和 [c_]{.title-ref}很有用。
它们接受使用冒号:
的区间范围字符串作为参数:
>>> np.r_[1:4, 0, 4]
array([1, 2, 3, 0, 4])
当使用数组参数时,[r_]{.title-ref} 和 [c_]{.title-ref} 的缺省行为与 [vstack]{.title-ref} 和[hstack]{.title-ref}相似,不过它们还可以接受一个可选参数, 给出连接操作的轴的数。
** 更多Numpy堆砌例子请参考NumPy堆砌例子 **
将数组分成小数组
使用[hsplit]{.title-ref}函数,可以将数组沿着水平轴分割, 指定返回的相同形状的小数组的数量,或者声明数据列,指定在其后发生数组分割的数据列。
>>> a = np.floor(10 * rg.random((2, 12))) #生成(2,12)数组,rg是随机数生成器
>>> a
array([[6., 7., 6., 9., 0., 5., 4., 0., 6., 8., 5., 2.],
[8., 5., 5., 7., 1., 8., 6., 7., 1., 8., 1., 0.]])
>>> # a 水平分割成3组
>>> np.hsplit(a, 3)
[array([[6., 7., 6., 9.],
[8., 5., 5., 7.]]), array([[0., 5., 4., 0.],
[1., 8., 6., 7.]]), array([[6., 8., 5., 2.],
[1., 8., 1., 0.]])]
>>> # 在第三列和第四列之后分割 `a`
>>> np.hsplit(a, (3, 4))
[array([[6., 7., 6.],
[8., 5., 5.]]), array([[9.],
[7.]]), array([[0., 5., 4., 0., 6., 8., 5., 2.],
[1., 8., 6., 7., 1., 8., 1., 0.]])]
函数[vsplit]{.title-ref} 在垂直轴向分割数组, 函数[array_split]{.title-ref} 允许指定分割的轴.
拷贝和视图
在数组上运算或者操作数组时,有时候需要copy数组数据到新数组,有的时候并不需要这样copy。 对于初学者来说,这很困惑。 主要有下面3种cppy用例:
完全不复制
简单的赋值运算并不复制对象或者他们的数据
>>> a = np.array([[ 0, 1, 2, 3],
... [ 4, 5, 6, 7],
... [ 8, 9, 10, 11]])
>>> b = a # no new object is created
>>> b is a # a and b are two names for the same ndarray object
True
Python传递可变对象的引用,所以函数调用不需要复制数据
>>> def f(x):
... print(id(x))
...
>>> id(a) # id is a unique identifier of an object #doctest: +SKIP
148293216 # may vary
>>> f(a) #doctest: +SKIP
148293216 # may vary
视图或者浅复制
不同的数组对象可以共享相同的数据。
这个view
视图方法可以创建新的数组对象,新数组和原数组指向的是相同的数据
»> c = a.view()
»> c is a
False
»> c.base is a # 数组 c 是拥有数据的 数组a 的视图
True
»> c.flags.owndata
False
»>
»> c = c.reshape((2, 6)) # 数组a的shape不变
»> a.shape
(3, 4)
»> c[0, 4] = 1234 # 数组a的数据也变化
»> a
array([[ 0, 1, 2, 3],
[1234, 5, 6, 7],
[ 8, 9, 10, 11]])
数组切片返回的是它的一个视图:
>>> s = a[:, 1:3]
>>> s[:] = 10 # s[:] is a view of s. Note the difference between s = 10 and s[:] = 10
>>> a
array([[ 0, 10, 10, 3],
[1234, 10, 10, 7],
[ 8, 10, 10, 11]])
深复制
函数copy
对数组和它的数据进行一次完全的copy
>>> d = a.copy() # 创建了新的具有新的数据的数组对象 a new array object with new data is created
>>> d is a
False
>>> d.base is a # d并不和a共享任何信息
False
>>> d[0, 0] = 9999
>>> a
array([[ 0, 10, 10, 3],
[1234, 10, 10, 7],
[ 8, 10, 10, 11]])
有时候 copy
可以在数组切片后并且并不需要原数组的时候调用。比如, 假如a
是一个巨大的中间结果数组,
最终结果b
仅仅包含a
的一小部分,那么在数组切片构造b
的时候可以调用深复制操作
>>> a = np.arange(int(1e8))
>>> b = a[:100].copy()
>>> del a # 在上面操作后, ``a`` 的内存可以被释放
如果上面使用的是b = a[:100]
,那么a
被 b
引用,
那么即使 del a
被执行, a
还是会储存在内存中.
函数和方法总揽
下面是一些有用的Numpy函数和方法
- Array Creation
- Conversions
- Manipulations
- Questions
- Ordering
- Operations
- Basic Statistics
- Basic Linear Algebra