NumPy基础

1.1 NumPy的特点与数据类型

  $NumPy$能提供类似于C的数组的结构ndarray,一维的常称为向量$vector$,多维的称之为矩阵$matrix$。$NumPy$的数组(向量、矩阵)的各类运算要比$Python$里类似结构类型list列表运算处理速度要快很多!$NumPy$提供大量的数学计算函数尤其是对向量、矩阵的运算函数相当的多和丰富。快速的基于内存映射和处理的向量、矩阵处理函数可以对数据进行处理和清洗,是大数据的首要任务,$NumPy$在这方面表现优异。

  $NumPy$的数组矩阵在内存里是连续存储的,和$Python$的其他类型的数据存储方式不一样,且操作函数是用C语言编写的,读写速度快、效率高。$NumPy$对矩阵进行复杂的操作效率高无须使用$Python$的for迭代结构,效果惊人!

先来测试一下是否正确安装:

1
2
3
>>>import numpy as np
>>>np.arange(10)
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
  • $NumPy$数值类型

  相较与$Pyhon$,$NumPy$提供了更多的数值类型,并且$NumPy$中的这些数值类型都是dtype(Data type objects)对象(类)的实例。numpy.dtype是$NumPy$中为了表示类型而定义的类,通过使用不同的属性去实例化这个类便得到了不同的数值类型。

序号 数据类型及描述
1. bool_ 存储为一个字节的布尔值(真或假)
2. int_ 默认整数,相当于 C 的long,通常为int32int64
3. intc 相当于 C 的int,通常为int32int64
4. intp 用于索引的整数,相当于 C 的size_t,通常为int32int64
5. int8 字节(-128 ~ 127)
6. int16 16 位整数(-32768 ~ 32767)
7. int32 32 位整数(-2147483648 ~ 2147483647)
8. int64 64 位整数(-9223372036854775808 ~ 9223372036854775807)
9. uint8 8 位无符号整数(0 ~ 255)
10. uint16 16 位无符号整数(0 ~ 65535)
11. uint32 32 位无符号整数(0 ~ 4294967295)
12. uint64 64 位无符号整数(0 ~ 18446744073709551615)
13. float_ float64的简写
14. float16 半精度浮点:符号位,5 位指数,10 位尾数
15. float32 单精度浮点:符号位,8 位指数,23 位尾数
16. float64 双精度浮点:符号位,11 位指数,52 位尾数
17. complex_ complex128的简写
18. complex64 复数,由两个 32 位浮点表示(实部和虚部)
19. complex128 复数,由两个 64 位浮点表示(实部和虚部)

  dtype类主要由以下几个属性来定义一个数值类型:

  • 数据类型(整数、浮点或者 Python 对象)
  • 数据大小
  • 字节序(小端或大端)
  • 在结构化类型的情况下,字段的名称,每个字段的数据类型,和每个字段占用的内存块部分。
  • 如果数据类型是子序列,它的形状和数据类型。

若要定义一个自己的数值类型,可通过下面语法初始化一个dtype实例

1
np.dtype(object, align=False, copy=False)

  其中,object表示被转化为数值类型对象的对象。align是bool类型参数,如果为True表示添加间隔,类似C的结构体。copy表示复制一个新的dtype副本,如果是False则只生成一个内建对象。

可以使用dtype构造一个类似以前学C时构造的结构体,姓名-成绩

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> dt = np.dtype([('name', np.unicode_, 16), ('grades', np.float64, (2, ))])
>>> dt['name']
dtype('<U16')
>>> dt['grades']
dtype(('<f8', (2,)))
>>> ##使用自定义结构类型初始化一个变量
>>> x = np.array([('xiaoming',(100,99)), ('xiaohei', (59, 59))], dtype=dt)
>>> x[0]
('xiaoming',[100,99])
>>> x[0]['name']
'xiaoming'
>>> type(x[0]) ##注意指向这个结构的是void
numpy.void

  上面可以看到打印出来的dtype用的是简化的表达,这是内置数据类型的标识符,目前支持的有:

  • 'b':布尔值
  • 'i':符号整数
  • 'u':无符号整数
  • 'f':浮点
  • 'c':复数浮点
  • 'm':时间间隔
  • 'M':日期时间
  • 'O':Python 对象
  • 'S', 'a':字节串
  • 'U':Unicode
  • 'V':原始数据(void

<f8为例,其中<表示小端模式,f表示浮点类型,8表示占8个字节的内存大小。

1.2 数组Array

  数组是numpy的一个核心数据类型,是具有相同类型和大小的项目的(通常是固定大小的)多维容器。数组中的维和项的数量由其shape(形状)定义。

  $NumPy$数组的创建可通过一些初始函数创建,比如$zeros(), ones()$等。也可通过使用$np.arange()$生成一串$ndarray$类型的序列,再通过$reshape()$重塑其形状为多维数组。还可直接用$np.array()$指定数组的内容进行创建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> np.zeros(3)   #默认为浮点数
array([0., 0., 0.])
>>> np.zeros(3, dtype= np.int)
array([0, 0, 0])
>>> np.zeros((2,3), dtype = np.int)
array([[0, 0, 0],
[0, 0, 0]])

>>> a = np.arange(6)
>>> a.reshape(2,3)
array([[0, 1, 2],
[3, 4, 5]])

>>> np.array([[1,2,3],[4,5,6]])
array([[1, 2, 3],
[4, 5, 6]])

  $NumPy$数组的索引可以有两种方法,一种是$Cpp$中习惯的$array[i][j]$,还有一种是$array[i,\;j]$。第二种方式实际上是扩展的$Python$切片语法,逗号分割维度,具体的在后面介绍。

1.2.1 数组属性

数组属性反映数组本身固有的信息。通常,通过数组的属性访问它,你可以获取并设置数组的内部属性,而无需创建新的数组。公开的属性是数组的核心部分,只有其中一些属性可以在不创建新数组的情况下进行有意义的重置。每个属性的信息如下。

1. 内存相关的属性

以下属性包含有关数组内存的信息:

  • ndarray.flags 有关数组内存分布的信息。
  • ndarray.shape 数组维度的元组。
  • ndarray.strides 遍历数组时要在每个维度中执行的字节元组。
  • ndarray.ndim 数组维数。
  • ndarray.data 指向数组数据开始的Python缓冲区对象。
  • ndarray.size 数组中的元素数。
  • ndarray.itemsize 一个数组元素的长度(以字节为单位)。
  • ndarray.nbytes 数组元素消耗的总字节。
  • ndarray.base 如果内存来自其他对象,则为基本对象。

2. 数据类型

与数组关联的数据类型对象可以在 dtype 属性中找到:

  • ndarray.dtype数组元素的数据类型。

3. 其他属性

  • ndarray.T 与 self.transpose()相同,只是如果 self.ndim <2 则返回自己。
  • ndarray.real 数组的真实部分。
  • ndarray.imag 数组的虚部。
  • ndarray.flat 数组上的一维迭代器。
  • ndarray.ctypes 一个简化数组与ctypes模块交互的对象。

1.2.2 数组方法

  $ndarray$对象有许多方法以某种方式对数组进行操作或与数组一起操作,通常返回数组结果。 下面简要说明这些方法。(每个方法的文档都有更完整的描述。)

对于以下方法,numpy中有相应的函数:

all,any,argmax,argmin,argpartition,argsort,choose,clip,compress,copy,cumprod,cumsum,diagonal,imag,max,mean,min,nonzero,partition, prod,ptp,put,ravel,real,repeat,reshape,round,searchsorted,sort,squeeze,std,sum,swapaxes,take,trace,transpose,var。

1. 数组转换

  • ndarray.item(*args) 将数组元素复制到标准Python标量并返回它。
  • ndarray.tolist() 将数组作为(可能是嵌套的)列表返回。
  • ndarray.itemset(*args) 将标量插入数组(如果可能,将标量转换为数组的dtype)
  • ndarray.tostring([order]) 构造包含数组中原始数据字节的Python字节。
  • ndarray.tobytes([order]) 构造包含数组中原始数据字节的Python字节。
  • ndarray.tofile(fid[, sep, format]) 将数组作为文本或二进制写入文件(默认)。
  • ndarray.dump(file) 将数组的pickle转储到指定的文件。
  • ndarray.dumps() 以字符串形式返回数组的pickle。
  • ndarray.astype(dtype[, order, casting, …]) 数组的副本,强制转换为指定的类型。
  • ndarray.byteswap([inplace]) 交换数组元素的字节
  • ndarray.copy([order]) 返回数组的副本。
  • ndarray.view([dtype, type]) 具有相同数据的数组的新视图。
  • ndarray.getfield(dtype[, offset]) 返回给定数组的字段作为特定类型。
  • ndarray.setflags([write, align, uic]) 分别设置数组标志WRITEABLE,ALIGNED,(WRITEBACKIFCOPY和UPDATEIFCOPY)。
  • ndarray.fill(value) 使用标量值填充数组。

  除了这些方法,$ndarray$中有的方法可以指定维度$axis$,$axis$一般默认为$None$,这时$ndarray$被看作普通的一维数组。当维度不为$None$时,$ndarray$被看作数组沿着不同的维度进行操作。$axis$一般取值有$0,1,2$三种。例如一个求和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
>>> x
array([[[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8]],
[[ 9, 10, 11],
[12, 13, 14],
[15, 16, 17]],
[[18, 19, 20],
[21, 22, 23],
[24, 25, 26]]])
>>> x.sum(axis=0)
array([[27, 30, 33],
[36, 39, 42],
[45, 48, 51]])
>>> # for sum, axis is the first keyword, so we may omit it,
>>> # specifying only its value
>>> x.sum(0), x.sum(1), x.sum(2)
(array([[27, 30, 33],
[36, 39, 42],
[45, 48, 51]]),
array([[ 9, 12, 15],
[36, 39, 42],
[63, 66, 69]]),
array([[ 3, 12, 21],
[30, 39, 48],
[57, 66, 75]]))

  除此之外,$ndarray$还有许多运算方法,后面再进行总结。

1.2.3 $NumPy$索引和切片

  对于$ndarray$的索引,只是将$Python$切片的基本概念从一维扩展到N维。基本语法都是基于array[obj],其中obj是索引选择对象。当输入不同的索引组合obj时,会触发不同的索引,具体来说有三种:字段访问、基本切片、高级索引。

  其实多维索引与切片也很简单,一维语法中,切片由以下语法给出

$list[ i: j: k ]$ ,其中$i$是起始索引,$j$是停止索引,$k$是步长。

  这一切片会按$k$为步长,将$list$中从下标$i$开始到$j-1$结束范围内的元素提取出来。通过基本切片生成的$list$是原$list$的视图。开始和停止索引的负数索引、步长的正负方向、切得$list$为空等问题在$python$切片中总结过了,这里就不再写了。

  对切片来说,$ndarray$数组无非是将基本的一维推广到多维。其语法可如下给出

$array[i:j:k, \;m:n:s,\;\cdots]$ ,其中,$i,j,k$是一维切片参数,其含义和上面一维$list$中表达的完全相同,$m,n,s$是二维切片参数,含义也完全相同,只不过是针对二维的,有更多维的只需用逗号将维数分割再添加即可。

  在每个维度中进行切片时,参数使用与一维切片基本相同,每个维度可使用不同的默认参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
>>> a   #三维array
array([[[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]],

[[12, 13, 14, 15],
[16, 17, 18, 19],
[20, 21, 22, 23]]])
>>> a[0, 1]
array([4, 5, 6, 7])
>>> a[0, 1, 1] #触发字段访问
5
>>> a[0, 0:3:1]
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>> a[0, 0:3:1, 0:2]
array([[0, 1],
[4, 5],
[8, 9]])
>>> a[0:, 0:3:1, 0:2]
array([[[ 0, 1],
[ 4, 5],
[ 8, 9]],

[[12, 13],
[16, 17],
[20, 21]]])
>>> a[0, 0:1, 0:1]
array([[0]])
>>> a[0, 0, 0:1] #注意未使用冒号则会直接选择而不是切片
array([0])

  在一维$list$中,像$list[i]$这种仅给出一个确定索引,未添加冒号约束范围的,会直接返回一个字段访问结果,而不是$list$。多维中也是如此,比如二维数组中,$array[i,\;j]$会访问该位置的元素,也可沿用$C$中习惯,使用$array[i][j]$来访问。使用冒号给出范围之后,即使只有一个元素也会返回一个数组格式的数据。

1592893946560

1.3 高级索引

Advanced indexing is triggered when the selection object, obj, is a non-tuple sequence object, an ndarray(of data type integer or bool), or a tuple with at least one sequence object or ndarray (of data type integer or bool). There are two types of advanced indexing: integer and Boolean.

Advanced indexing always returns a copy of the data (contrast with basic slicing that returns a view

在索引不是一个元组或单纯由整数组成,使用其他序列如列表或多个元组时,会触发高级索引。分为整数数组索引和布尔数组索引,一维情况下,整数数组索引就是选出给出的索引数组在被索引数组对应位置上的数:(注意高级索引得到的是原数组的copy,而不是视图)

1
2
3
4
>>> r = np.array(range(4))
>>> #r[(1,2)] #会报错,这种情况普通索引是访问二维数组中元素的方法
>>> r[[1, 2]] #访问一维中位置为1, 2的数
array([1, 2])

高维的实际上都是这样方式的推广

1
2
3
>>> x = np.array([[1, 2], [3, 4], [5, 6]])
>>> x[[0, 1, 2], [0, 1, 0]]
array([1, 4, 5])

在这个索引中,第一个列表代表选取第一维中[0, 1, 2] 位置上的元素,第二个列表代表选择从刚刚第一维中选出的元素分别在[0, 1, 0]位置上的元素。这两个索引列表都是[1 3] 的维度,因此得到的便是[1 3 * 选出的元素的维度], 广播方式对高级索引同样适用:

1
2
3
>>> x[ [[0], [1]], [0, 1]]
array([[1, 2],
[3, 4]])

在这个索引中,第一个是[2 1]的数组,第二个是[1 2] 因此广播为[2 * 2] 的索引。

Reference:

[1] NumPy Reference

[2] NumPy User Guide

[3] NumPy 中文文档