01-Numpy初体验:数组创建与数据类型

1. 为什么会有 Numpy ?

简单来讲,Python 内置的若干种数据类型,无法高效地应对计算密集型场景,比如矩阵运算。因此 Numpy 随之应运而生,并被认为是高性能科学计算和数据分析的基础包。

本专栏所介绍到几乎所有的高级工具,都是基于 Numpy 开发的。因为 NumPy 的大部分代码都是用 C 语言写的,其底层算法在设计时就有着极优异的性能,所以使得 NumPy 比纯 Python 代码高效得多。作为基础工具,其实玩转 Numpy 很简单,只要掌握三个关键知识点,即:数据类型的创建、数据层的索引切片、数组运算。下面我们分不同的篇幅一一展开。

对于新入门的同学,尤其需要注意的是,虽然大多数的数据分析工作并不会直接操作 Numpy 对象,但是深谙面向数组的编程方式和逻辑能力是成为 Python 数据分析大牛的关键,切记磨刀不误砍柴工。

面向数组的编程方式,最大的特点就是用数组表达式完成数据操作任务,无需写大量循环。向量化的数组操作会比纯 Python 的等价实现快一到两个数量级。在后续的学习中,我们会有机会细细品味其中的差别和优势。

2. ndarray 对象与创建

什么是数组?

简单说就是有序的元素序列。比如列表 [1,2,3,4] ,这个是简单的一维数组,只有 4个元素,并且不能被拆分为其他的数组组合;复杂一点呢, [[1,2,3],[4,5,6]] 是一个二维数组,由两个一维数组组成。

Numpy 最重要的一个特点就是它可以快速地创建一个 N 维数组对象(即 ndarray ,本专栏 ndarray 对象和 数组 并不做概念上的区分),然后你可以利用 ndarray 这种数据结构非常高效地执行一些数学运算,并且语法风格和 Python 基本一致。

  • 通过 array 函数创建 ndarray 对象

创建 ndarray 最简单的方法就是使用 array 函数,它接受一个序列型对象(比如列表),并转化为 ndarray 对象:

In [1]: import numpy as np

In [2]: data0 = [2, 4, 6.5, 8]

In [3]: arr0 = np.array(data0)

In [4]: arr0
Out[4]: array([2. , 4. , 6.5, 8. ])

这里细心的朋友会发现一个有趣的现象,我们传入的列表中,存在 float 和 int 两种数据类型,但是在创建的 ndarray 对象中,默认转化为了 float 结构,这是因为 ndarray 是一个通用的同构数据多维容器,也就是说,其中的所有的元素必须是相同的类型, Numpy 会根据输入的实际情况进行转换。「也就是如果创建的时候,没有指定数据类型,那 Numpy 就会以数组中最小的数据类型为数据。」

In [5]: # 查看创建的arr0的变量类型
   ...: type(arr0)
Out[5]: numpy.ndarray

In [6]: # 可以用isinstance函数来判断是否是ndarray类型
   ...: isinstance(arr0, np.ndarray)
Out[6]: True

In [7]: # 创建多维数组
   ...: data1 = [[1, 2, 3, 4], [5, 6, 7, 8]]
   ...: arr1 = np.array(data1)
   ...: arr1
Out[7]:
array([[1, 2, 3, 4],
       [5, 6, 7, 8]])
  • 如何快速定义 ndarray 的数据类型

那我们是否可以在创建 ndarray 的过程中,直接显式的指定数据类型呢,答案是肯定的!

In [8]: # 利用 dtype 关键字,传入合适的数据类型,显式地定义
   ...: arr2 = np.array(data1, dtype=np.float32)
   ...: arr2
Out[8]:
array([[1., 2., 3., 4.],
       [5., 6., 7., 8.]], dtype=float32)

In [9]: # 查看 arr2 的数据类型,这里实际上只查看的arr2的组成元素的数据类型
   ...: arr2.dtype
Out[9]: dtype('float32')

In [10]: # 查看 arr2 的各维度的大小,其结果用tuple表示。tuple的长度,表示数组的维
    ...: 数;具体值表示数组的大小
    ...: arr2.shape
Out[10]: (2, 4) # 表示该数组是2×4大小

Python支持基础的数据类型有 int,float,bool,string,complex 等,Numpy 则在此基础上进行了拓展,不过你并不需要进行死记硬背,记住常用的即可,用到的时候可以查表。Numpy 支持常用的数据类型如下:

类型 说明
int8、uint8 分别表示有符号和无符号的8位整型,可表示的整数范围为 -128 ~ 127、0 ~ 255
int16、uint16 分别表示有符号和无符号的16位整型,可表示的整数范围为 -32768 ~ 32767、0 ~ 65535
int32、uint32 分别表示有符号和无符号的32位整型,可表示的整数范围为 -2147483648 ~ 2147483647、0 ~ 4294967295
int64、uint64 分别表示有符号和无符号的64位整型,可表示的整数范围为 -9223372036854775808 ~ 9223372036854775807、0 ~ 18446744073709551615
float16、float32、float64、float128 分别表示半精度浮点数、单精度浮点、双精度浮点、扩展精度浮点数
complex64、complex128、complex256 分别用两个32位、64位、128位的浮点数表示的复数
bool 存储 True 和 False 值的布尔类型「不为零的皆变成 True,为零的变成 False」
Object Python 对象类型
string_ 类型代号 S,固定长度的字符串类型,每个字符 1 个字节
unicode_ 类型代号 U,固定长度的 unicode 类型,跟字符串的定义方式一样,例如(U8)

这里我们分别举一个布尔类型和字符串类型的例子,熟悉一下定义的规则:

In [12]: # 通过整形1和0,定义布尔类型的数组
    ...: data3 = [[1, 0], [0, 1]]
    ...: arr3 = np.array(data3, dtype=np.bool)
    ...: arr3
Out[12]:
array([[ True, False],
       [False,  True]])

In [13]: # 查看 arr3 的数据类型
    ...: arr3.dtype
Out[13]: dtype('bool')

尝试传入其它的数据类型,来定义布尔类型的数组,看一看会得到什么神奇的结果:

In [15]: data4 = [["a", "b"], ["c", ""]]
    ...: arr4 = np.array(data4, dtype=np.bool)
    ...: arr4
Out[15]:
array([[ True,  True],
       [ True, False]])

这里的定义规则非常 Pythonic,传入一个非空字符串,则 True,否则 False

定义字符串类型的数组:

In [16]: arr5 = np.array(data4, dtype=np.string_)
    ...: arr5
Out[16]:
array([[b'a', b'b'],
       [b'c', b'']], dtype='|S1')   # 这里的 1 表示的是 arr5 中各个元素的固定长度为 1

In [17]: # 查看arr5的数据类型
    ...: arr5.dtype
Out[17]: dtype('S1')

3. 数据类型的更改与格式化输出

在 Python 中,灵活即强大,这是一个深入人心的优势,也是无法抗拒的诱惑,确实,Numpy 也完美地集成了这个优点。对于我们已经定义好的数组,你可以方便地更改其数据类型,前提是数据类型之间的转化不会报错。

  • 更改 ndarray 的数据类型

这里我们推荐用 astype 函数来对数组进行操作:

定义一个 np.float32 类型的数组

In [19]: data6 =[[1.230, 2.670], [1.450, 6.000]]
    ...: arr6 = np.array(data6, np.float32)
    ...: arr6
Out[19]:
array([[1.23, 2.67],
       [1.45, 6.  ]], dtype=float32)

变更数据类型为 np.float16

In [20]: arr6.astype(np.float16)
Out[20]:
array([[1.23, 2.67],
       [1.45, 6.  ]], dtype=float16)

变更数据类型为 np.int8

In [21]: arr6.astype(np.int8)
Out[21]:
array([[1, 2],
       [1, 6]], dtype=int8)

虽然上面的操作中我们改变了 arr6 的数据类型,但是打印一下 arr6 你会发现,arr6 原始的的数据类型并没有发生变化:

In [22]: arr6
Out[22]:
array([[1.23, 2.67],
       [1.45, 6.  ]], dtype=float32)

如果只是按照上述方式,显式地更改数据类型,被操作对象 arr6 并没有发生更改。这是因为 astype 函数会返回更改数据类型后的新的数组。因此如果你想要保存更改后的结果,你需要将 astype 函数结果赋值给一个新命名的变量。

dtype 只接受 Numpy 能够识别的数据类型,因此,在数据类型之前需要加上 np. ,当然传入Python 原生的数据格式,比如 intfloat ,也是可以的。

另外,在 np.floatnp.int 之间的互相转化中, float 的小数部分会被直接抹去,仅仅保留整数部分,并非是四舍五入的方式。需要引起注意。

  • 数组的格式化输出

在实际应用中,有这样一种场景:浮点数的小数位过多,我们希望格式化地 print 数组的结构,比如保留 3 位有效数字,但是并不想改变原数组的值。其实在 Numpy 中已经封装好了这种方法,一行代码就能搞定:

# precision: 默认保留8位位有效数字,后面不会补0;supress: 对很大/小的数不使用科学计数法 (True)
np.set_printoptions(precision=3, suppress=True)

举例如下:

In [23]: # 对浮点数数组,保留3位有效数字,并禁用科学计数法;小数位数不够,后面不会补 0
    ...: arr7 = np.array([[3.141592653], [9.8]], dtype=np.float16) # 定义一个 2 维数组
    ...: np.set_printoptions(precision=3, suppress=True)
    ...: arr7
Out[23]:
array([[3.14],
       [9.8 ]], dtype=float16)

当然了,你也可以利用 Python 的语法规范,灵活地自定义数据的 print 效果:

In [25]: # 对浮点数,保留小数点后3位
    ...: np.set_printoptions(formatter={
    
    'float': '{: 0.3f}'.format})
    ...: arr7
Out[25]:
array([[ 3.141],
       [ 9.797]], dtype=float16)

4. 创建数组的其它方式

用 array 函数可以非常便利地自定义简单的数组,但是想定义一些维度较大的数组的时候,则是有点黔驴技穷的感觉。其实创建数组有很多方式,array 函数只是最基本的用法。这里我们介绍如何创建一些特殊数组:

  • zeros 函数,创建指定维度的全为 0 的数组
In [29]: # 创建一个大小为10的全0数组
    ...: np.zeros(10, dtype=np.int8)
Out[29]: array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0], dtype=int8)

In [30]: # 创建一个大小为2×3的全0数组
    ...: np.zeros((2,3), dtype=np.float16)
Out[30]:
array([[ 0.000,  0.000,  0.000],
       [ 0.000,  0.000,  0.000]], dtype=float16)

zeros 函数接受的第一个参数为待创建数组的维度,若传入一个整数,则创建一个一维数组;若传入 tuple 类型的参数(a,b,c),则创建一个大小为 a×b×c 的数组;dtype 指定数据类型,缺省时 Numpy 默认指定。

  • ones 函数,创建指定维度的全为 1 的数组

ones 函数的用法和 zeros 函数类似:

In [31]: # 创建一个大小为5的全1数组
    ...: np.ones(5, dtype=np.int8)
Out[31]: array([1, 1, 1, 1, 1], dtype=int8)

In [32]: # 创建一个大小为2×3的全1数组
    ...: np.ones((2,3), dtype=np.float16)
Out[32]:
array([[ 1.000,  1.000,  1.000],
       [ 1.000,  1.000,  1.000]], dtype=float16)
  • empty 函数,创建一个空数组,只分配内存空间,但是不填充任何值

empty 函数的用法也是类似:

In [33]: # empty 函数返回值为未经过初始化的垃圾值
    ...: np.empty((2,3), dtype=np.int8)
Out[33]:
array([[0, 0, 0],
       [0, 0, 0]], dtype=int8)
  • identity 函数,创建一个大小为 n×n 的单位矩阵(对角线为1,其余为0)
# identity函数原型如下:
np.identity(n, dtype=<typefloat>)
In [42]: # 创建一个大小为3×3的单位矩阵
    ...: np.identity(3, dtype=np.int8)
Out[42]:
array([[1, 0, 0],
       [0, 1, 0],
       [0, 0, 1]], dtype=int8)
  • eye 函数,identity 的升级版本
# eye函数的原型如下:
np.eye(N, M=None, k=0, dtype=<typefloat>)

如果仅仅指定 N,则输出大小为 N×N 的方阵,功能与 identity 函数一致;如果同时指定 N 和 M ,则输出大小为 N×M 的矩形矩阵。K 为调节值,调节为 1 的对角线的位置偏离度。这里可以通过具体例子来体会一下:

In [44]: # 创建3×3的方阵
    ...: np.eye(N=3, dtype=np.int8)
Out[44]:
array([[1, 0, 0],
       [0, 1, 0],
       [0, 0, 1]], dtype=int8)

In [45]: # 创建3×4的矩形矩阵
    ...: np.eye(N=3, M=4, dtype=np.int8)
Out[45]:
array([[1, 0, 0, 0],
       [0, 1, 0, 0],
       [0, 0, 1, 0]], dtype=int8)

In [46]: # 创建3×4的矩形矩阵,并且为1的对角线向右偏移1个单位。
    ...: np.eye(N=3, M=4, k=1, dtype=np.int8)
Out[46]:
array([[0, 1, 0, 0],
       [0, 0, 1, 0],
       [0, 0, 0, 1]], dtype=int8)

In [47]: # 创建3×4的矩形矩阵,并且为1的对角线向右偏移2个单位。
    ...: np.eye(N=3, M=4, k=2, dtype=np.int8)
Out[47]:
array([[0, 0, 1, 0],
       [0, 0, 0, 1],
       [0, 0, 0, 0]], dtype=int8)

需要注意的是,k 值可以为负数。比如 k=-2,则表示:为1的对角线向左偏移2个单位。

5. 总结

  • 如何创建一个简单的 ndarray 想必朋友们都学会了。其实对于一个 ndarray 对象,我们可以把它认为是线性代数里的矩阵去加深理解;
  • 数组数据类型的定义有2种方法,一个是在创建的时候用 dtype 直接定义,另一个是用 astype 方法进行变化,无论哪种方法,都是非常方便的。;
  • Numpy 支持的数据类型相较 Python ,进行了很大的扩充, Numpy 支持的整型有8中,浮点型有4种。朋友们在实际使用中,可以根据业务需求,灵活地选择,以尽可能地节约内存资源,提高运算效率。

本章节列举了几种特殊数组的定义方式,其实我们还有用其他的方式来定义。例如在深度神经网络中,我们需要初始化一个符合高斯分布的高维数组以激活深度学习模型,这种情况下该如何定义呢?请等待下一篇!

猜你喜欢

转载自blog.csdn.net/qq_33254766/article/details/108362458