系列文章
一次性搞定NumPy入门基础知识
NumPy之操控ndarray的形状
NumPy之浅拷贝和深拷贝
NumPy之索引技巧
1. 基本数据结构
NumPy最核心的数据结构就是所谓的多维数组(ndarray, n-dimensional array)。这里,所谓的“维度”,指的是数据嵌套的层数,每一层叫做一个axis。
例如:
[[1., 0., 0.],
[0., 1., 2.]]
这个ndarray嵌套了两层,所以这个ndarray有两个axis,第一个axis的length是2(因为这一个axis有两个元素,分别是[1., 0., 0.]
和[0., 1., 2.]
),第二个axis的length是3(因为[1., 0., 0.]
和[0., 1., 2.]
均分别有三个元素。
下面的例子显示了NumPy数据结构的几个重要属性:
>>> import numpy as np
>>> a = np.arange(15).reshape(3, 5)
>>>a
array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14]])
>>> a.shape
(3, 5)
>>> a.ndim
2
>>> a.dtype.name
'int64'
>>> a.itemsize
8
>>> a.size
15
>>> type(a)
<type 'numpy.ndarray'>
>>> b = np.array([6, 7, 8])
>>> b
array([6, 7, 8])
>>> type(b)
<type 'numpy.ndarray'>
1.1 ndarray.ndim
ndarray的维数,也就是axis的数量,上述例子中是2
1.2 ndarray.shape
这是一个tuple类型的数据,每一个元素代表了每一个维度的长度。上述例子是(3, 5)
1.3 ndarray.size
ndarray里元素的总个数,上述例子是15
1.4 ndarray.dtype
ndarray里元素的数据类型,上述例子是int64
1.5 ndarray.itemsize
ndarray里每个元素占据的字节数,上述例子是8
2. 创建ndarray
通过np.array函数,可以创建一个ndarray结构,入参可以有多种形式,如下面各节所述。
2.1 从Python的list或tuple数据结构创建
如下例所示,从Python的list创建ndarray结构,并且NumPy可以自动判断数据类型:
>>> 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')
要注意的是,传递的是一个list或tuple,而不是数字参数,例如a = np.array(2,3,4)
就是错误的。
通过传递嵌套的list结构,可以创建N维ndarray:
>>> b = np.array([(1.5,2,3), (4,5,6)])
>>> b
array([[ 1.5, 2. , 3. ],
[ 4. , 5. , 6. ]])
在创建ndarray时,可以显式地指定数据类型:
>>> c = np.array( [ [1,2], [3,4] ], dtype=complex )
>>> c
array([[ 1.+0.j, 2.+0.j],
[ 3.+0.j, 4.+0.j]])
2.2 通过内建函数创建
内建ndarray创建函数可以为一些特定的场景提供方便。
zeros()
函数创建一个全为0的ndarray,ones()
创建一个全为1的ndarray,empty()
创建一个内容随机的ndarray:
>>> 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的arange对比,NumPy提供的arange()
函数可以用浮点数来表示间隔:
#arange()函数的参数分别为起始值、终值、步长
#生成的序列包括起始值,不包括终值
>>> 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
#l inspcae参数分别为起始值、终值、序列长度
# 生成的序列默认包括起始值和终值
# 可以通过endpoint参数指定是否包含终值,默认值为True,即包含终值。
>>> np.linspace( 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)
3. 打印ndarray
ndarray的打印符合如下原则:
- 最后一个axis的内容从左到右打印
- 倒数第二个axis的内容从上到下打印
- 其他axis的内容也是从上到下打印,中间通过空行进行分隔
>>> a = np.arange(24).reshape(2,3,4)
>>> print(a)
[[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
[[12 13 14 15]
[16 17 18 19]
[20 21 22 23]]]
如果ndarray内容太多,NumPy会自动省略中间的内容,只保留边角的内容
>>> 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]]
如果想打印全部内容,可以通过下面的语句修改打印选项:
>>> np.set_printoptions(threshold=sys.maxsize) # 需要引入sys模块
4. ndarray的基本操作
数学运算符是元素级的,两个ndarray进行运算,会产生一个新的ndarray。
>>> 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里,*
是元素级的互相相乘。而矩阵乘法则需要使用@
运算符。或者dot
函数或方法。
>>> A = np.array( [[1,1],
... [0,1]] )
>>> B = np.array( [[2,0],
... [3,4]] )
>>> A * B # elementwise product
array([[2, 0],
[0, 4]])
>>> A @ B # matrix product
array([[5, 4],
[3, 4]])
>>> A.dot(B) # another matrix product
array([[5, 4],
[3, 4]])
类似于+=
,*=
这种运算符,可以直接修改现有的ndarray,而不是再新建一个。
>>> a = np.ones((2,3), dtype=int)
>>> b = np.random.random((2,3))
>>> a *= 3
>>> a
array([[3, 3, 3],
[3, 3, 3]])
>>> b += a
>>> b
array([[ 3.417022 , 3.72032449, 3.00011437],
[ 3.30233257, 3.14675589, 3.09233859]])
>>> a += b # 注意,b的精度更高,无法累加到精度较低的ndarray a上
Traceback (most recent call last):
...
TypeError: Cannot cast ufunc add output from dtype('float64') to dtype('int64') with casting rule 'same_kind'
如果两个不同精度的ndarray进行运算,得到的结果的类型与二者中精度更高的ndarray相同
>>> 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'
NumPy支持一些单目运算符,单目运算符通常作为ndarray类的内建方法来使用:
>>> a = np.random.random((2,3))
>>> a
array([[ 0.18626021, 0.34556073, 0.39676747],
[ 0.53881673, 0.41919451, 0.6852195 ]])
>>> a.sum()
2.5718191614547998
>>> a.min()
0.1862602113776709
>>> a.max()
0.6852195003967595
上述操作默认是针对ndarray中的所有元素的,并不考虑ndarray的形状。如果指定axis参数,可以指定沿着哪个axis来进行运算。也就是说,保持其他axis不变,在这个axis上进行运算。
可以这么来理解axis参数的用法:以二维ndarray为例,由于axis代表嵌套的层数,假设ndarray的shape是(3, 4),那么可以将ndarray排布如下:
(a[0]0], a[0][1], a[0][2], a[0][3]
a[1]0], a[1][1], a[1][2], a[1][3]
a[2]0], a[2][1], a[2][2], a[2][3])
所谓沿着axis=0的方向进行sum运算,就是指沿着第一个索引的方向,分别做如下运算(保持第二个索引不变):
(a[0]0] + a[1][0] + a[2][0], a[0]1] + a[1][1] + a[2][1], a[0]2] + a[1][2] + a[2][2], a[0][3] + a[1][3] + a[2][3], )
下面是代码示例:
>>> 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) # sum of each column
array([12, 15, 18, 21])
>>>
>>> b.min(axis=1) # min of each row
array([0, 4, 8])
>>>
>>> b.cumsum(axis=1) # cumulative sum along each row
array([[ 0, 1, 3, 6],
[ 4, 9, 15, 22],
[ 8, 17, 27, 38]])
5. 通用函数(Universal Functions)
NumPy支持一些通用的数学函数,例如sin
,cos
,exp
等等。这些函数叫做Universal Functions(ufunc)。这些函数都是元素级的,并会产生一个新的ndarray作为输出。
举例如下:
>>> 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.])
6.索引、切片与遍历
一维ndarray的相关操作和Python中的list数据结构相似:
>>> 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[:6:2] = -1000 # 在0-6号元素中,循环地将每个排序为2的元素设置为-1000
>>> a
array([-1000, 1, -1000, 27, -1000, 125, 216, 343, 512, 729])
>>> a[ : :-1] # 得到一个逆序的ndarray
array([ 729, 512, 343, 216, 125, -1000, 27, -1000, 1, -1000])
>>> for i in a:
... print(i**(1/3.))
...
nan
1.0
nan
3.0
nan
5.0
6.0
7.0
8.0
9.0
多维ndarray每个axis都有一个独立的索引,把这些独立索引合成到一个tuple里,就可以对多维ndarray进行索引了。
>>> 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] #相当于b[-1, :]
array([40, 41, 42, 43])
可以用...
来代替一系列的:
。
例如,假设x
是一个由5个axis的ndarray,那么
-
x[1, 2, ...]
等价于x[1, 2, :, : ,:]
-
x[..., 3]
等价于[:, :, :, :, 3]
-
x[4, .., 5, :]
等价于x[4, :, :, 5, :]
举例:
>>> c = np.array( [[[ 0, 1, 2],
... [ 10, 12, 13]],
... [[100,101,102],
... [110,112,113]]])
>>> c.shape
(2, 2, 3)
>>> c[1,...]
array([[100, 101, 102],
[110, 112, 113]])
>>> c[...,2]
array([[ 2, 13],
[102, 113]])
多维ndarray的遍历时沿着第一个axis进行的:
>>> 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]
```
如果要遍历ndarray里的所有元素,那么可以利用`flat`属性:
```python
>>> 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
```