数据分析之 Numpy 包

NumPy(Numerical Python) 是 Python 语言的一个扩展程序库,支持大量的维度数组矩阵运算,此外也针对数组运算提供大量的数学函数库

官方文档地址:https://numpy.org/doc/stable/index.html

下面简述一些 Numpy 库常用方法和属性。

 

1. NumPy 支持的数据类型

   尽管 Python 支持 int、float 等基础的数据类型,但是 NumPy 需要更多、更精确的数据类型支持科学计算以及内存分配的需要。

   下面列举了一些固定大小的类型别名:

np.bool          # 用一个字节存储的布尔类型(True或False), 可用字符 'b' 表示
np.int8          # 一个字节大小, -128 ~ 127, 可用字符串 'i1' 表示, 后面的 1 就表示 1 字节, 下面也一样
np.int16         # 整数, -32768 ~ 32767, 可用字符串 'i2' 表示
np.int32         # 整数, −2^31  ~ 2^32−1, 可用字符串 'i4' 表示
np.int64         # 整数, −2^63  ~ 2^63−1, 可用字符串 'i8' 表示
np.uint8         # 无符号整数, 0 ~ 255, 可用字符串 'u1' 表示
np.uint16        # 无符号整数, 0 ~ 65535, 可用字符串 'u2' 表示
np.uint32        # 无符号整数, 0 ~ 2^32−1, 可用字符串 'u4' 表示
np.uint64        # 无符号整数, 0 ~ 2^64−1, 可用字符串 'u8' 表示
np.float16       # 半精度浮点数: 16位,正负号1位, 指数5位, 精度10位, 可用字符串 'f2' 表示
np.float32       # 单精度浮点数: 32位,正负号1位, 指数8位, 精度23位, 可用字符串 'f4' 表示
np.float64       # 双精度浮点数: 64位,正负号1位, 指数11位, 精度52位, 可用字符串 'f8' 表示
np.complex64     # 复数, 分别用两个32位浮点数表示实部和虚部, 可用字符串 'c8' 表示
np.complex128    # 复数, 分别用两个64位浮点数表示实部和虚部, 可用字符串 'c16' 表示
np.object_       # python对象, 可用字符 'O' 表示
np.string_       # 字符串, 可用字符 S 表示(在S后面添加数字, 表示字符串长度, 比如 S3 表示长度为三的字符串, 不写则为最大长度)
np.unicode_      # unicode类型, 可用字符 'U' 表示

   这些类型在 NumPy 里属于元(原子性、不可分、最小单位)数据类型。

 

2. numpy.dtype() 方法

   这个方法返回一个数据类型对象,仅用来描述 ndarray 数组中每个元素对应的内存区域如何使用,即 ndarray 数组元素的数据类型。

   所以 ndarray 对象的属性中包含一个 numpy.dtype 类型的实例。方法原型如下:

"""
object: 可以转换为数据类型的对象, 为 None 的话就是 float64
align: 如果为 true,填充字段使其类似 C 的结构体。
copy: 对参数 object 是深拷贝还是浅拷贝, 
"""
numpy.dtype(object, align, copy)

   那么哪些对象可以转化为 dtype 类型呢?

   1)提供的 object 参数本身就是 dtype 类型。

   2)为 None,那就代表 float64。

   3)标量类型,包括 Python 内置的基础数据类型或者 numpy 内置的元数据类型。

dt = np.dtype(np.int16)
dt = np.dtype(float)

   4)python 的类类型

class test:
    def __init__(self, s):
        self.sstr = s

dt = np.dtype(test)

   5)字符串,可以是 numpy 元数据类型的组合

dt = np.dtype('<f4')  # 小端,单精度浮点数
dt = np.dtype('>i8')  # 大端,长整型

   6)元组列表,每个元组都具有以下形式:(字段名称、数据类型、形状),其中 Shape 是可选的

dt = np.dtype([('x', 'f4'), ('y', np.float32), ('z', 'f4', (2, 2))])  # 最后一个是 2*2 的 float 数组
dt = np.dtype([('name', str, 40), ('num_items', np.int32), ('price', np.float32)])
dt = np.dtype(('S10', 1))

 

3. NumPy Ndarray 对象

   Numpy 库所操作的对象就是 ndarray,即多维数组,它是一系列同类型数据的集合。

   多维数组:数组的数组。每一个维度都是一个数组,区别在于元素是矢量还是标量,如果元素是矢量,意味着这个维度的数组本身还是一个多维数组,

   如果元素是标量,那么这个维度的数组就是一维数组。假设最外层的数组维度为 $1$,那么以 $3$ 维数组为例:

       1)第 $1$ 维数组的每个元素是 $2$ 维数组,即确定一个参数便可得到一个 $2$ 维数组。

       2)第 $2$ 维数组的每个元素是 $1$ 维数组,即确定两个参数便可得到一个 $1$ 维数组。

       3)第 $3$ 维数组的每个元素是标量,总共确定三个参数后便得到最终的元素值。

   维数:描述一个数学对象所需的参数个数。比如上面的 $3$ 维数组,取到最后的标量需要 $3$ 个参数。

   ndarray 对象由两大部分组成:

       1)原始数组数据(raw array data):也称为数据缓冲区,是包含固定大小数据项的连续(固定)内存块。

          将 ndarray 与 python 中的 list 对比一下,list 可以容纳不同类型的对象,像 stringinttuple 等都可以放在一个 list 里,

          所以 list 中存放的是对象的引用,再通过引用找到具体的对象,这些对象所在的物理地址并不是连续的。

       2)原始数组数据描述信息(metadata):这些信息可以包括如下内容,

          a. 基本数据元素的大小(以字节为单位)。

          b. 数据缓冲区中数据的起始位置。

          c. 每个维度的元素之间的分隔(跨度)。

          d. 数据的字节顺序。

          e. 缓冲区是否为只读。

          f. 有关基本数据元素解释的信息(通过 np.dtype 对象)。数据元素可以像 int 或 float 一样,也可以是复合对象(例如,类似于 struct 的对象)。

   ndarray 的设计思路是数据存储与其解释方式分离让尽可能多的操作发生在解释方式上,而尽量少操作实际存储数据的内存区域。

   $\bullet$ metadata 都包含哪些信息呢?或者说 ndarry 对象有哪些属性呢?

ndarray.shape      # 每个维度数组的元素数量-元组
ndarray.ndim       # 数组的维数
ndarray.size       # 数组中的标量元素数量,即最后一维数组的元素个数
ndarray.dtype      # 数组元素的类型,是 np.dtype 实例对象,指示了每个数据占用多少个字节,这几个字节怎么解释,比如int32、float32等
ndarray.itemsize   # 数组中元素的字节大小
ndarray.strides    # 每个维度数组的元素大小(间隔)-元组

   $\bullet$ 如何创建一个 ndarray 对象?

     1)numpy.array():该方法创建一个 ndarray 数组对象,方法原型如下:

"""
object: 可以转化为数组的对象, 即 array_like, 可以是列表、元组等
dtype : 可选, 数组元素的数据类型, 可以是任何可以转为数据类型(dtype)的对象
copy  : 可选, 为 True 表示生成的 ndarray 对象由参数 object 对象深拷贝而来, 为 False, 则是浅拷贝
order : 指定阵列的内存布局, 'C'为按行方向, 'F'为列方向
subok : 默认返回一个与基类类型一致的数组
ndmin : 指定生成数组的最小维度
"""
ndarrayObj = numpy.array(object, dtype = None, copy = True, order = None, subok = False, ndmin = 0)

     2)numpy.linspace():该方法用于创建一个一维数组,数组是一个等差数列构成的,原型如下:

"""
start: 序列的起始值
stop:  序列的终止值,如果 endpoint 为 true, 该值包含于数列中
num: 要生成的等步长的样本数量, 默认为50
endpoint: 该值为 true 时,数列中包含 stop 值, 反之不包含, 默认是True
retstep: 如果为 True 时, 生成的数组中会显示间距, 反之不显示
dtype:   ndarray 的数据类型, 可以是任何可以转为 dtype 的类型, 默认为 None, 转为 dtype 后就是 float64 类型
"""
ndarrayObj = numpy.linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None)

 

4. numpy 索引

   有多种方法可以对数组元素进行索引,下面一一介绍。阅读本部分之前,最好先去了解下 python 对内置类型的切片索引语法。

   1)数字索引:这种方式很简单,每个维度都可以指定一个数字来索引,每个维度索引结果的交集就是输出。举个例子:

                  

      观察上面左边这张图,红色框框是第一个维度的数字 $1$ 的索引结果,绿色框框是第二个维度的数字 $2$ 的索引结果,蓝色框框是第三个维度的数字 $3$

      的索引结果,三个索引结果的交集就是 $33$。除了从交集角度来理解索引结果外,还可以这样理解:下一个维度的索引都是在上一个维度索引结果的基

      础上进行的,也就是说,第一个维度确定了红色框框后,第二个维度的索引直接在 $arr[1]$ 上进行就可以了,第三个维度的索引在 $arr[1,2]$ 上进行。

      上面的右图就是按第二种理解画出来的,用来索引的数字如果是负数,代表从右往左数,$-1$ 就是代表最后一个元素。

   2)切片索引:python 对 numpy 的切片在语法上并没有什么特殊之处,和 python 对内置类型的切片语法一致,特殊之处在于:数组切片是原始数组视图,

      这就意味着,如果做任何修改,原始数组也会跟着更改。如果不想更改原始数组,就需要进行显式的复制,从而得到它的副本(.copy())。

import torch
import numpy as np


a = np.arange(10)
b = a[3:6]
print("a =", a)
print("b =", b)
b[2] = 99
print("a =", a)
print("b =", b)

a[:2] = 101
print("a =", a)

"""
a = [0 1 2 3 4 5 6 7 8 9]
b = [3 4 5]
a = [ 0  1  2  3  4 99  6  7  8  9]
b = [ 3  4 99]
a = [101 101   2   3   4  99   6   7   8   9]
"""

      下面举两个用切片索引多维数组的情况,理解方式和 1)中是一样的,即可以理解成是并行索引结果的交集或串行索引结果的递进。

                   

      在上面左边这张图中,第一个维度和第二个维度都索引都是 $:$,这个是代表该维度的数据全选,如果不理解可先去看切片索引的博客。

      最终的交集就是如上所示的二维矩阵。

   3)布尔索引:我们可以通过一个布尔数组(numpy 数组)来索引目标数组,以此找出与布尔数组中值为 True 的对应的目标数组中的数据。需要注意的是:

      布尔数组的长度必须与目标数组对应的轴的长度一致。

import torch
import numpy as np

arr = np.arange(6).reshape(1,2,3)
booling = np.array([[[True, False, True],
                     [False, True, True]]])
print(arr)
print(arr[booling])  # [0 2 4 5]

booling = np.array([[True, False]])
print(arr[booling])  # [[0 1 2]]

      由上面的例子可知,想索引到维度 $3$,那么 booling 数组的形状就得是 $(1,2,3)$,想索引到维度 $2$,那么 booling 数组的形状为 $(1,2)$,

      也就是:用来索引的布尔数组的形状必须和原数组的前缀形状相同,至于这个前缀形状是多大取决于索引到哪个维度。再看个例子:

import torch
import numpy as np

arr = (np.arange(36)).reshape(6,6)
print(arr)
print()

x = np.array([0, 1, 2, 1, 4, 5])
booling = x == 1 # 通过比较运算得到一个布尔数组
print(booling)
print(arr[booling])
print()

print(arr[booling,2:])

"""
[[ 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 27 28 29]
 [30 31 32 33 34 35]]

[False  True False  True False False]
[[ 6  7  8  9 10 11]
 [18 19 20 21 22 23]]

[[ 8  9 10 11]
 [20 21 22 23]]
"""

   4)花式索引:利用整数数组(这里的数组,可以是 numpy 的数组,也可以是 python 自带的list )进行索引,其意义是根据索引数组的值作为目标数组

      的某个轴的下标来取值。对于使用一维整型数组作为索引,如果目标是一维数组,那么索引的结果就是对应位置的元素;如果目标是二维数组,那么

      就是对应下标的行。

import torch
import numpy as np

arr = np.empty((8,4))

for i in range(8):
    arr[i] = i

print(arr)
print()
print(arr[[2,6,1,7]])
print()
print(arr[[-2,-6,-1]])

"""
[[0. 0. 0. 0.]
 [1. 1. 1. 1.]
 [2. 2. 2. 2.]
 [3. 3. 3. 3.]
 [4. 4. 4. 4.]
 [5. 5. 5. 5.]
 [6. 6. 6. 6.]
 [7. 7. 7. 7.]]

[[2. 2. 2. 2.]
 [6. 6. 6. 6.]
 [1. 1. 1. 1.]
 [7. 7. 7. 7.]]

[[6. 6. 6. 6.]
 [2. 2. 2. 2.]
 [7. 7. 7. 7.]]
"""

      我们可以看到花式索引的结果,以一个特定的顺序排列。而这个顺序,就是我们所传入的整数列表或者 ndarray。这也为我们以特定的顺序来选取

      数组子集,提供了思路。

      一次传入多个索引数组,会返回一个一维数组,其中的元素对应各个索引元组。

import torch
import numpy as np

arr = np.arange(35).reshape(5,7) 
print(arr)
print()
print(arr[[1,3,2,4],[2,0,6,5]])

"""
[[ 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 27]
 [28 29 30 31 32 33 34]]

[ 9 21 20 33]
"""

       经过对比可以发现,返回的一维数组中的元素,分别对应 $(1,2),(3,0)....$,我们传入来两个索引数组,相当于传入了一组平面坐标,从而进行了定位。

      照这样理解的话,那么对应一个 $N$ 维数组,如果传入 $N$ 个索引数组的话,就相当于传入了一个 $N$ 维坐标。

import torch
import numpy as np

arr = np.arange(27).reshape(3,3,3)
print(arr)
print()
print(arr[[1,2],[0,1],[2,2]])  # (1,0,2), (2,1,2)

"""
[[[ 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]]]

[11 23]
"""

      将花式索引和切片索引结合,如

      

      依然采用交集准则或者递推准则即可。

 

5. 一些常用的方法

   1)np.pad:是用来是数组进行填充数的,函数原型如下:

"""
array - 表示需要填充的数组
pad_width - 表示每个轴(axis)边缘需要填充的数值数目
mode - 为填补类型, 即怎样去填补
"""
ndarray = numpy.pad(array, pad_width, mode, **kwargs)

      上面这个原型解释的不是很清楚,直接来看个例子:

import numpy as np


A = np.arange(95,99).reshape(2,2)
A = np.pad(A, ((4,2),(2,5)), 'constant', constant_values = ((0,1),(4,3)))

print(A)

"""
[[ 4  4  0  0  3  3  3  3  3]
 [ 4  4  0  0  3  3  3  3  3]
 [ 4  4  0  0  3  3  3  3  3]
 [ 4  4  0  0  3  3  3  3  3]
 [ 4  4 95 96  3  3  3  3  3]
 [ 4  4 97 98  3  3  3  3  3]
 [ 4  4  1  1  3  3  3  3  3]
 [ 4  4  1  1  3  3  3  3  3]]
"""

 

posted @ 2020-10-05 16:36  _yanghh  阅读(436)  评论(0编辑  收藏  举报