Numpy(1)

《Python数据科学手册》读书笔记

理解Python中的数据类型

要实现高效的数据驱动科学和计算, 需要理解数据是如何被存储和操作
的。

Python 的用户往往被其易用性所吸引, 其中一个易用之处就在于动态输
入。 静态类型的语言(如 C 或 Java) 往往需要每一个变量都明确地声
明, 而动态类型的语言(例如 Python) 可以跳过这个特殊规定。 例如在
C 语言中, 你可能会按照如下方式指定一个特殊的操作:

/* C代码 */
int result = 0;
for(int i=0; i<100; i++){
result += i;
}


在 Python 中, 同等的操作可以按照如下方式实现:

Python代码

result = 0
for i in range(100):
result += i

注意这里最大的不同之处: 在 C 语言中, 每个变量的数据类型被明确地
声明; 而在 Python 中, 类型是动态推断的。 这意味着可以将任何类型
的数据指定给任何变量:

Python代码

x = 4
x = "four

这里已经将 x 变量的内容由整型转变成了字符串, 而同样的操作在 C
语言中将会导致(取决于编译器设置) 编译错误或其他未知的后果:

/* C代码 */
int x = 4;
x = "four"; // 编译失败

这种灵活性是使 Python 和其他动态类型的语言更易用的原因之一。但是这种类型灵活性也指出了一个事实: Python 变量不仅是它们的
值, 还包括了关于值的类型的一些额外信息。

Python整型不仅仅是一个整型

标准的 Python 实现是用 C 语言编写的。 这意味着每一个 Python 对象都
是一个聪明的伪 C 语言结构体, 该结构体不仅包含其值, 还有其他信
息。 例如, 当我们在 Python 中定义一个整型, 例如 x = 10000 时, x
并不是一个“原生”整型, 而是一个指针, 指向一个 C 语言的复合结构
体, 结构体里包含了一些值。查看 Python 3.4 的源代码, 可以发现整型
(长整型) 的定义, 如下所示 :

struct _longobject {
long ob_refcnt;
PyTypeObject *ob_type;
size_t ob_size;
long ob_digit[1];
};

Python 3.4 中的一个整型实际上包括 4 个部分。

  • ob_refcnt 是一个引用计数, 它帮助 Python 默默地处理内存的分
    配和回收。
  • ob_type 将变量的类型编码。
  • ob_size 指定接下来的数据成员的大小。
  • ob_digit 包含我们希望 Python 变量表示的实际整型值。

这意味着与 C 语言这样的编译语言中的整型相比, 在 Python 中存储一
个整型会有一些开销。

两者的差异在于, C 语言整型本质上是对应某个内存位置的标签, 里面
存储的字节会编码成整型。 而 Python 的整型其实是一个指针, 指向包
含这个 Python 对象所有信息的某个内存位置, 其中包括可以转换成整
型的字节。 由于 Python 的整型结构体里面还包含了大量额外的信息,
所以 Python 可以自由、 动态地编码。 但是, Python 类型中的这些额外
信息也会成为负担, 在多个对象组合的结构体中尤其明显。

Python列表不仅仅是一个列表

设想如果使用一个包含很多 Python 对象的 Python 数据结构, 会发生什
么? Python 中的标准可变多元素容器是列表。 可以用如下方式创建一
个整型值列表:

L = list(range(10))
L
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
 type(L[0])
int

或者创建一个字符串列表:

L2 = [str(c) for c in L]
L2
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
 type(L2[0])
str

因为 Python 的动态类型特性, 甚至可以创建一个异构的列表:

L3 = [True, "2", 3.0, 4]
[type(item) for item in L3]
[bool, str, float, int]

但是想拥有这种灵活性也是要付出一定代价的: 为了获得这些灵活的类
型, 列表中的每一项必须包含各自的类型信息、 引用计数和其他信息;
也就是说, 每一项都是一个完整的 Python 对象。 来看一个特殊的例
子, 如果列表中的所有变量都是同一类型的, 那么很多信息都会显得多
余——将数据存储在固定类型的数组中应该会更高效。

在实现层面, 数组基本上包含一个指向连续数据块的指针。 另一方面,
Python 列表包含一个指向指针块的指针, 这其中的每一个指针对应一个
完整的 Python 对象 。 另外, 列表的优势
是灵活, 因为每个列表元素是一个包含数据和类型信息的完整结构体,
而且列表可以用任意类型的数据填充。 固定类型的 NumPy 式数组缺乏
这种灵活性, 但是能更有效地存储和操作数据。

Python中的固定类型数组

Python 提供了几种将数据存储在有效的、 固定类型的数据缓存中的选
项。 内置的数组(array) 模块 可以用于创
建统一类型的密集数组:

import array
L = list(range(10))
A = array.array('i', L)
A
array('i', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

这里的 ‘i’ 是一个数据类型码, 表示数据为整型。更实用的是 NumPy 包中的 ndarray 对象。 Python 的数组对象提供了数
组型数据的有效存储, 而 NumPy 为该数据加上了高效的操作。 稍后将
介绍这些操作, 这里先展示几种创建 NumPy 数组的方法。

import numpy as np
# 查看numpy的版本
np.__version__
'1.15.1'
# 获取numpy教程
np?

从Python列表创建数组创建数组

首先, 可以用 np.array 从 Python 列表创建数组:

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

numpy要求数组必须包含同一类型的数据,如果类型不匹配将会向上转换

np.array([1.2,2,3,4,5])
array([1.2, 2. , 3. , 4. , 5. ])

可以使用dtype改变数组的数据类型

np.array([1,2,3,4,5],dtype='float32')
array([1., 2., 3., 4., 5.], dtype=float32)

不同于 Python 列表, NumPy 数组可以被指定为多维的。 以下是
用列表的列表初始化多维数组的一种方法:

np.array([range(i,i+3) for i in [2,4,6]])
array([[2, 3, 4],
       [4, 5, 6],
       [6, 7, 8]])

内层的列表被当作二维数组的行。

从头创建数组

面对大型数组的时候, 用 NumPy 内置的方法从头创建数组是一种更高
效的方法。 以下是几个示例:

# 创建一个所有值为0,长度为10的数组
np.zeros(10,dtype=int)
array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
# 创建一个3行5列所有值为1的浮点数组
np.ones((3,5),dtype=float)
array([[1., 1., 1., 1., 1.],
       [1., 1., 1., 1., 1.],
       [1., 1., 1., 1., 1.]])
# 创建一个3行5列的浮点数组,数组的值都是3.14
np.full((3,5),3.14)
array([[3.14, 3.14, 3.14, 3.14, 3.14],
       [3.14, 3.14, 3.14, 3.14, 3.14],
       [3.14, 3.14, 3.14, 3.14, 3.14]])
# 创建一个数组从0开始到20结束,步长为2
np.arange(0, 20, 2)
array([ 0,  2,  4,  6,  8, 10, 12, 14, 16, 18])
# 创建一个有5个元素的数组,5个数均匀分配到0~1之间
np.linspace(0, 1, 5)
array([0.  , 0.25, 0.5 , 0.75, 1.  ])
# 创建一个3*3的数组,个元素服从0~1上的均匀分布
np.random.random((3, 3))
array([[0.69872258, 0.0299473 , 0.99635603],
       [0.25231312, 0.90566931, 0.30884748],
       [0.54470709, 0.64877725, 0.48333009]])
# 创建一个3*3的数组,个元素服从均值为0,标准差为1
np.random.normal(0, 1, (3, 3))
array([[ 0.59378053, -0.82101116,  0.28096713],
       [ 0.72447884, -0.29206219,  0.86922837],
       [-0.82404414,  1.26396153,  0.66129599]])
# 创建一个3*3的整数数组,个元素服从0~10上的均匀分布
np.random.randint(0, 10, (3, 3))
array([[1, 5, 0],
       [9, 6, 5],
       [1, 5, 4]])
# 创建一个3*3的单位矩阵
np.eye(3)
array([[1., 0., 0.],
       [0., 1., 0.],
       [0., 0., 1.]])

NumPy标准数据类型

NumPy 数组包含同一类型的值, 因此详细了解这些数据类型及其限制
是非常重要的。

数据类型 描述
bool_ Boolean (True or False) stored as a byte
int_ Default integer type (same as C long; normally either int64 or int32)
intc Identical to C int (normally int32 or int64)
intp Integer used for indexing (same as C ssize_t; normally either int32 or int64)
int8 Byte (-128 to 127)
int16 Integer (-32768 to 32767)
int32 Integer (-2147483648 to 2147483647)
int64 Integer (-9223372036854775808 to 9223372036854775807)
uint8 Unsigned integer (0 to 255)
uint16 Unsigned integer (0 to 65535)
uint32 Unsigned integer (0 to 4294967295)
uint64 Unsigned integer (0 to 18446744073709551615)
float_ Shorthand for float64.
float16 Half precision float: sign bit, 5 bits exponent, 10 bits mantissa
float32 Single precision float: sign bit, 8 bits exponent, 23 bits mantissa
float64 Double precision float: sign bit, 11 bits exponent, 52 bits mantissa
complex_ Shorthand for complex128.
complex64 Complex number, represented by two 32-bit floats
complex128 Complex number, represented by two 64-bit floats

numpy数组的基本操作

  • 数组的属性

确定数组的大小、 形状、 存储大小、 数据类型。

  • 数组的索引

获取和设置数组各个元素的值。

  • 数组的切分

在大的数组中获取或设置更小的子数组。

  • 数组的变形

改变给定数组的形状。

  • 数组的拼接和分裂

将多个数组合并为一个, 以及将一个数组分裂成多个

NumPy数组的属性

首先介绍一些有用的数组属性。 定义三个随机的数组: 一个一维数组、
一个二维数组和一个三维数组。 将用 NumPy 的随机数生成器设置
一组种子值, 以确保每次程序执行时都可以生成同样的随机数组:

import numpy as np
np.random.seed(0)  

x1 = np.random.randint(10, size=6)  
x2 = np.random.randint(10, size=(3, 4))  
x3 = np.random.randint(10, size=(3, 4, 5))
print("x3 ndim: ", x3.ndim) #数组维度
print("x3 shape:", x3.shape) #数组每个维度的大小
print("x3 size: ", x3.size) #数组的总大小
print("dtype:", x3.dtype) #数组的数据类型
print("itemsize:", x3.itemsize, "bytes") #每个元素的字节大小
print("nbytes:", x3.nbytes, "bytes") #数组总字节大小
x3 ndim:  3
x3 shape: (3, 4, 5)
x3 size:  60
dtype: int32
itemsize: 4 bytes
nbytes: 240 bytes

数组索引: 获取单个元素

x1
array([5, 0, 3, 3, 7, 9])
x1[0]
5
x1[-1]
9
x2
array([[3, 5, 2, 4],
       [7, 6, 8, 8],
       [1, 6, 7, 7]])
x2[0,0]
3
x2[2,-1]
7
# 修改元素
x2[0,0] = 12
x2
array([[12,  5,  2,  4],
       [ 7,  6,  8,  8],
       [ 1,  6,  7,  7]])

请注意, 和 Python 列表不同, NumPy 数组是固定类型的。 这意味着当
你试图将一个浮点值插入一个整型数组时, 浮点值会被截短成整型。 并
且这种截短是自动完成的, 不会给你提示或警告, 所以需要特别注意这
一点!

x1[0] = 3.14
x1
array([3, 0, 3, 3, 7, 9])

数组切片: 获取子数组

  • x[start:stop:step]
  1. 一维子数组
x = np.arange(10)
x
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
# 前5个元素
x[:5]
array([0, 1, 2, 3, 4])
# 索引5之后的元素,包括索引5
x[5:]
array([5, 6, 7, 8, 9])
x[4:7]
array([4, 5, 6])
x[::2]
array([0, 2, 4, 6, 8])
x[1::2]
array([1, 3, 5, 7, 9])
#将所有元素逆序
x[::-1]
array([9, 8, 7, 6, 5, 4, 3, 2, 1, 0])
x[5::-2]
array([5, 3, 1])
  1. 多维子数组
x2
array([[12,  5,  2,  4],
       [ 7,  6,  8,  8],
       [ 1,  6,  7,  7]])
# 两行,三列
x2[:2, :3]
array([[12,  5,  2],
       [ 7,  6,  8]])
# 所有行,前三列
x2[:, :3]
array([[12,  5,  2],
       [ 7,  6,  8],
       [ 1,  6,  7]])
x2[::-1, ::-1]
array([[ 7,  7,  6,  1],
       [ 8,  8,  6,  7],
       [ 4,  2,  5, 12]])
  1. 获取数组的行和列
# x2的第一列
x2[:,0]
array([12,  7,  1])
# x2的第一行
x2[0,:]
array([12,  5,  2,  4])
# 等价于print(x2[0,:])
x2[0]
array([12,  5,  2,  4])

非副本视图的子数组

关于数组切片有一点很重要也非常有用, 那就是数组切片返回的是
数组数据的视图, 而不是数值数据的副本。 这一点也是 NumPy 数
组切片和 Python 列表切片的不同之处: 在 Python 列表中, 切片是
值的副本。 例如此前示例中的那个二维数组:

x2
array([[12,  5,  2,  4],
       [ 7,  6,  8,  8],
       [ 1,  6,  7,  7]])
x2_sub = x2[:2,:2]
x2_sub
array([[12,  5],
       [ 7,  6]])
# 修改子数组
x2_sub[0, 0] = 3.14
x2_sub
array([[3, 5],
       [7, 6]])
# 可以发现原始数组也被修改了!
x2
array([[3, 5, 2, 4],
       [7, 6, 8, 8],
       [1, 6, 7, 7]])

这种默认的处理方式实际上非常有用: 它意味着在处理非常大的数据集时, 可以获取或处理这些数据集的片段, 而不用复制底层的数
据缓存。

创建数组的副本

尽管数组视图有一些非常好的特性, 但是在有些时候明确地复制数
组里的数据或子数组也是非常有用的。 可以很简单地通过 copy()
方法实现:

x2_sub_copy = x2[:2, :2].copy()
x2_sub_copy
array([[3, 5],
       [7, 6]])
x2_sub_copy[0, 0] = 5.2
x2_sub_copy
array([[5, 5],
       [7, 6]])
# 修改子数组,但原始数组没有发生改变
x2
array([[3, 5, 2, 4],
       [7, 6, 8, 8],
       [1, 6, 7, 7]])

数组的变形

另一个有用的操作类型是数组的变形。 数组变形最灵活的实现方式是通
过 reshape() 函数来实现。 例如, 如果你希望将数字 1~9 放入一个
3×3 的矩阵中, 可以采用如下方法:

grid = np.arange(1, 10).reshape((3, 3))
grid
array([[1, 2, 3],
       [4, 5, 6],
       [7, 8, 9]])

请注意, 如果希望该方法可行, 那么原始数组的大小必须和变形后数组
的大小一致。 如果满足这个条件, reshape 方法将会用到原始数组的一
个非副本视图。 但实际情况是, 在非连续的数据缓存的情况下, 返回非
副本视图往往不可能实现。

另外一个常见的变形模式是将一个一维数组转变为二维的行或列的矩
阵。 你也可以通过 reshape 方法来实现, 或者更简单地在一个切片操
作中利用 newaxis 关键字:

x = np.array([1, 2, 3])

# 通过变形获得行向量
x.reshape((1, 3))
array([[1, 2, 3]])
# 通过newaxis获得行向量
x[np.newaxis, :]
array([[1, 2, 3]])
# 通过变形获得行向量
x.reshape((3, 1))
array([[1],
       [2],
       [3]])
# 通过newaxis获得行向量
x[:, np.newaxis]
array([[1],
       [2],
       [3]])

数组拼接和分裂

以上所有的操作都是针对单一数组的, 但有时也需要将多个数组合并为
一个, 或将一个数组分裂成多个。 接下来将详细介绍这些操作。

  1. 数组的拼接

拼接或连接 NumPy 中的两个数组主要由
np.concatenate、 np.vstack 和 np.hstack 例程实
现。 np.concatenate 将数组元组或数组列表作为第一个参数, 如
下所示:

x = np.array([1, 2, 3])
y = np.array([3, 2, 1])
np.concatenate([x, y])
array([1, 2, 3, 3, 2, 1])
z = [99, 99, 99]
np.concatenate([x, y, z])
array([ 1,  2,  3,  3,  2,  1, 99, 99, 99])
grid = np.array([[1, 2, 3],
                 [4, 5, 6]])
# 沿着第一个轴拼接
np.concatenate([grid, grid])
array([[1, 2, 3],
       [4, 5, 6],
       [1, 2, 3],
       [4, 5, 6]])
# 沿着第二个轴拼接
np.concatenate([grid, grid], axis=1)
array([[1, 2, 3, 1, 2, 3],
       [4, 5, 6, 4, 5, 6]])

沿着固定维度处理数组时, 使用 np.vstack(垂直栈) 和
np.hstack(水平栈) 函数会更简洁:

x = np.array([1, 2, 3])
grid = np.array([[9, 8, 7],
                 [6, 5, 4]])

# 垂直栈数组
np.vstack([x, grid])
array([[1, 2, 3],
       [9, 8, 7],
       [6, 5, 4]])
# 水平栈数组
y = np.array([[99],
              [99]])
np.hstack([grid, y])
array([[ 9,  8,  7, 99],
       [ 6,  5,  4, 99]])

与之类似, np.dstack 将沿着第三个维度拼接数组。

  1. 数组的分裂

与拼接相反的过程是分裂。 分裂可以通过 np.split、 np.hsplit
和 np.vsplit 函数来实现。 可以向以上函数传递一个索引列表作
为参数, 索引列表记录的是分裂点位置:

x = [1, 2, 3, 99, 99, 3, 2, 1]
x1, x2, x3 = np.split(x, [3, 5])# 用索引列表记录分裂点的位置
print(x1, x2, x3)
[1 2 3] [99 99] [3 2 1]

值得注意的是, N 分裂点会得到 N + 1 个子数组。 相关的
np.hsplit 和 np.vsplit 的用法也类似:

grid = np.arange(16).reshape((4, 4))
grid
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11],
       [12, 13, 14, 15]])
upper, lower = np.vsplit(grid, [2])
print(upper)
print(lower)
[[0 1 2 3]
 [4 5 6 7]]
[[ 8  9 10 11]
 [12 13 14 15]]
left, right = np.hsplit(grid, [2])
print(left)
print(right)
[[ 0  1]
 [ 4  5]
 [ 8  9]
 [12 13]]
[[ 2  3]
 [ 6  7]
 [10 11]
 [14 15]]

np.dsplit将沿数组第三个维度进行分裂

发布了57 篇原创文章 · 获赞 63 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/weixin_41503009/article/details/104183260
今日推荐