本文是对《Python数据科学手册》第二章的整理。
理解Python中的数据类型
Python是一种动态输入的语言,区别于静态语言如C语言。
Python整形
标准的Python 实现是用C 语言编写的。这意味着每一个Python 对象都是一个聪明的伪C语言结构体,该结构体不仅包含其值,还有其他信息。当我们在Python 中定义一个整型,例如x = 10000 时,x 并不是一个“原生”整型,而是一个指针,指向一个C 语言的复合结构体,结构体里包含了一些值。
在Python3.4源代码中整形(长整型)用C语言定义如下:
struct _longobject {
long ob_refcnt;
PyTypeObject *ob_type;
size_t ob_size;
long ob_digit[1];
}
因此,Python 可以自由、动态地编码。但是,Python 类型中的这些额外信息也会成为负担,在多个对象组合的结构体中尤其明显。
Python列表
Python中的列表操作十分灵活,为了实现这种灵活性,列表中的每一项必须包含各自的类型信息、引用计数和其他信息;也就是说,每一项都是一个完整的Python 对象。列表可以用任意类型的数据填充。固定类型的NumPy 式数组缺乏这种灵活性,但是能更有效地存储和操作数据。
其区别如下图所示:
创建固定类型的数组
可使用内置的(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对象
常用的导入numpy包的语句为:
import numpy as np
从Python列表创建数组
np.array(list)
输出即为ndarray类型的数组。
不同于Python 列表,NumPy 要求数组必须包含同一类型的数据。如果类型不匹配,NumPy 将会向上转换(如果可行)。这里整型被转换为浮点型。
np.array([3.14, 4, 2, 3])
输出为array([ 3.14, 4. , 2. , 3. ])
如果希望设置数组的数据类型,可使用dtype关键字。
np.array([1, 2, 3, 4], 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]])
内层的列表被当作二维数组的行。
从头创建数组
# 创建一个长度为10的数组,数组的值都是0
np.zeros(10, dtype=int)
# 创建一个3×5的浮点型数组,数组的值都是1
np.ones((3, 5), dtype=float)
# 创建一个3×5的浮点型数组,数组的值都是3.14
np.full((3, 5), 3.14)
# 从0开始,到20结束,步长为2
# (它和内置的range()函数类似)
np.arange(0, 20, 2)
# 创建一个5个元素的数组,这5个数均匀地分配到0~1
np.linspace(0, 1, 5)
# 创建一个3×3的、在0~1均匀分布的随机数组成的数组
np.random.random((3, 3))
# 创建一个3×3的、均值为0、方差为1的
# 正态分布的随机数数组
np.random.normal(0, 1, (3, 3))
# 创建一个3×3的、[0, 10)区间的随机整型数组
np.random.randint(0, 10, (3, 3))
# 创建一个3×3的单位矩阵
np.eye(3)
# 创建一个由3个整型数组成的未初始化的数组
# 数组的值是内存空间中的任意值
np.empty(3)
Numpy标准数据类型
有如下两种指定数据类型的方式:
np.zeros(10, dtype='int16')
np.zeros(10, dtype=np.int16)
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
Numpy数组索引
一维数组的索引与Python列表相同,多维数组则在方括号中以逗号分隔 ,如x2[0, 0]
或x2[2, -1]
。
和Python 列表不同,NumPy 数组是固定类型的。这意味着当你试图将一个浮点值插入一个整型数组时,浮点值会被截短成整型。并且这种截短是自动完成的,不会给你提示或警告。
Numpy数组切片
可用
x[start:stop:step]
获得数组切片。
如果以上3 个参数都未指定,那么它们会被分别设置默认值start=0、stop= 维度的大小(size of dimension)和step=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, ::2]
的结果为:
array([[12, 2],
[ 7, 8],
[ 1, 7]])
一种常见的需求是获取数组的单行和单列。你可以将索引与切片组合起来实现这个功能,
用一个冒号(:)表示空切片:
print(x2[:, 0]) # x2的第一列
print(x2[0, :]) # x2的第一行
print(x2[0]) #等于x2[0, :]
数组切片返回的是数组数据的视图,而不是数值数据的副本。这一点也是NumPy 数组切片和Python 列表切片的不同之处:在Python 列表中,切片是值的副本。
x2_sub = x2[:2, :2]
print(x2_sub)
其结果为:
[[12 5]
[ 7 6]]
若执行以下语句:
x2_sub[0, 0] = 99
print(x2_sub)
结果为:
[[99 5]
[ 7 6]]
此时打印原数组:
[[99 5 2 4]
[ 7 6 8 8]
[ 1 6 7 7]]
也就是说如果修改子数组,原始数组也会被修改。
这种默认的处理方式实际上非常有用:它意味着在处理非常大的数据集时,可以获取或处
理这些数据集的片段,而不用复制底层的数据缓存。
若要获得原数组的副本,则可通过copy()语句array_copy=array.copy()
简单地实现。
Numpy数组的变形
若希望将希望将数字1~9 放入一个3×3 的矩阵中,可执行:
grid = np.arange(1, 10).reshape((3, 3))
如果希望该方法可行,那么原始数组的大小必须和变形后数组的大小一致。
也可通过np.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]])
Numpy数组的拼接与分裂
数组的拼接
np.concatenate([array1, array2])
拼接两个一维数组
np.concatenate([array1, array2])
(省略axis=0)对二维数组上下拼接
np.concatenate([array1, array2], axis=1)
对二维数组左右拼接
沿固定方向拼接数组:
np.vstack(array1, array2)
垂直栈数组,在垂直方向对数组进行拼接
np.hstack(array1, array2)
水平栈数组,在水平方向对数组进行拼接
与之类似,np.dstack 将沿着第三个维度拼接数组。
数组的分裂
分裂可以通过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]
定义grid为
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 将数组沿着第三个维度分裂
Numpy数组的计算:通用函数
CPython 在每次循环时必须做数据类型的检查和函数的调度,每次循环时,Python 首先检查对象的类型,并且动态查找可以使用该数据类型的正确函数,使其循环的效率较低,如果我们在编译代码时进行这样的操作,那么就能在代码执行之前知晓类型的声明,结果的计算也会更加有效率。
通用函数
NumPy 为很多类型的操作提供了非常方便的、静态类型的、可编译程序的接口,也被称作向量操作。你可以通过简单地对数组执行操作来实现,这里对数组的操作将会被用于数组中的每一个元素。这种向量方法被用于将循环推送至NumPy 之下的编译层,这样会取得更快的执行效率。
可将这种运算类比为matlab矩阵的点运算。
数组运算
NumPy 通用函数的使用方式非常自然,因为它用到了Python 原生的算术运算符,标准的加、减、乘、除都可以使用。
其中指数为**,地板除法(整除)为//。
所有这些算术运算符都是NumPy 内置函数的简单封装器,可以用np.add(),np.substract(),np.negative(),np.multiply(),np.divide(),np.floor_divide(),np.power()
表示,传入参数即可。
特殊运算
绝对值
NumPy 能理解Python 内置的运算操作,NumPy 也可以理解Python 内置的绝对值函数,可以用abs(array)
,np.absolute(array)
和np.abs(array)
表示同一种运算。
当np.abs()
处理复数数组时,返回其幅值。
三角函数
np.sin()
正弦
np.cos()
余弦
np.tan()
正切
np.arcsin()
反正弦
np.arccos()
反余弦
np.arctan()
反正切
传入ndarray
指数与对数
np.exp(array)
自然指数
np.exp2(array)
以二为底的指数
np.power(num, array)
指定底数的指数
np.log(array)
自然对数
np.log2(array)
以2为底的对数
np.log10(array)
以10为底的对数
np.expm1(array)
e
x
−
1
e^x-1
ex−1
np.log1p(array)
ln
(
1
+
x
)
\ln(1+x)
ln(1+x)
专用通用函数
除了以上介绍到的,NumPy 还提供了很多通用函数,包括双曲三角函数、比特位运算、比较运算符、弧度转化为角度的运算、取整和求余运算,可以通过浏览numpy文档查询。
还有一个更加专用,也更加晦涩的通用函数优异来源是子模块scipy.special。
高级通用函数特性
np.operation(array, out=y)
将结果存于y中
对于较大的数组,通过慎重使用out 参数将能够有效节约内存。
np.add.reduce(array)
返回数组中所有元素之和
np.multiply.reduce(array)
返回数组中所有元素之积
np.add.accumulate
返回累和中的所有中间值组成的数组,对multiply类似
np.multiply.outer(array1, array2)
两个向量相乘,拿第一个向量的元素分别与第二个向量所有元素相乘得到结果的一行
任何通用函数都可以用outer 方法获得两个不同输入数组所有元素对的函数运算结果。
聚合:最大值,最小值和其他值
对数组整体聚合
np.sum(array) array.sum()
数组值求和
np.min(array) array.min()
求最小值
np.max(array) array.max()
求最大值
对单一维度聚合
在括号中的参数填入axis=0 axis=1
等对应的维度
其他聚合函数
函数名称 | NaN安全版本(忽略NaN处的值) | 描述 |
---|---|---|
np.sum | np.nansum | 计算元素的和 |
np.prod | np.nanprod | 计算元素的积 |
np.mean | np.nanmean | 计算元素的平均值 |
np.std | np.nanstd | 计算元素的标准差 |
np.var | np.nanvar | 计算元素的方差 |
np.min | np.nanmin | 找出最小值 |
np.max | np.nanmax | 找出最大值 |
np.argmin | np.nanargmin | 找出最小值的索引 |
np.argmax | np.nanargmax | 找出最大值的索引 |
np.median | np.nanmedian | 计算元素的中位数 |
np.percentile | np.nanpercentile | 计算基于元素排序的统计值 |
np.any | N/A | 验证任何一个元素是否为真 |
np.all | N/A | 验证所有元素是否为真 |
数组的计算:广播
广播允许二进制计算作用于不同大小的数组。
数组与标量相加转换为数组与大小相同的由相同元素组成的数组相加。
数组与一维数组相加转换为与相同大小的在另一维度上对该维度进行复制的数组相加。
两个不同维度的一维数组相加补全到公共维度后再相加。
广播的规则
-
如果两个数组的维度数不相同,那么小维度数组的形状将会在最左边补1。
-
如果两个数组的形状在任何一个维度上都不匹配,那么数组的形状会沿着维度为1 的维度扩展以匹配另外一个数组的形状。
-
如果两个数组的形状在任何一个维度上都不匹配并且没有任何一个维度等于1,那么会引发异常。
示例1:
M = np.ones((2, 3))
a = np.arange(3)
其中M.shape = (2, 3) a.shape = (3,)
a的维度较小,在其左边补1得a.shape = (1,3)
,再按前文所述补成两行,相加即得结果。
array([[ 1., 2., 3.],
[ 1., 2., 3.]])
示例2:
a = np.arange(3).reshape((3, 1))
b = np.arange(3)
其结果为:
array([[0, 1, 2],
[1, 2, 3],
[2, 3, 4]])
示例3:
若将
M = np.ones((3, 2))
a = np.arange(3)
相加,则会产生异常。
这些广播规则对于任意二进制通用函数都是适用的,其规则类似与matlab。
广播的应用
数组的归一化
X = np.random.random((10, 3))
Xmean = X.mean(0) #沿着第一个维度聚合
X_centered = X - Xmean
这样就实现了数组的归一化
画一个二维函数
导入matplotlib.pyplot
x = np.linspace(0, 5, 50)
y = np.linspace(0, 5, 50)[:, np.newaxis]
z = np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x)
plt.imshow(z, origin='lower', extent=[0, 5, 0, 5], cmap='viridis')
plt.colorbar();
其结果为:
比较、掩码和布尔逻辑
比较操作
x = np.array([1, 2, 3, 4, 5])
x < 3 # 小于
array([ True, True, False, False, False], dtype=bool)
x > 3 # 大于
array([False, False, False, True, True], dtype=bool)
x <= 3 # 小于等于
array([ True, True, True, False, False], dtype=bool)
x >= 3 # 大于等于
array([False, False, True, True, True], dtype=bool)
x != 3 # 不等于
array([ True, True, False, True, True], dtype=bool)
x == 3 # 等于
array([False, False, True, False, False], dtype=bool)
(2 * x) == (x ** 2)
array([False, True, False, False, False], dtype=bool)
和算术运算符一样,比较运算操作在NumPy 中也是借助通用函数来实现的。
操作布尔数组
统计布尔数组中True 记录的个数
np.count_nonzero(x < 6)
或者可以使用
np.sum(x < 6)
sum()可以按行或按列执行
检验是否存在:
np.any(x < 6)
检验是否任意元素都符合条件:
np.all(x < 6)
np.all() 和np.any() 也可以用于沿着特定的坐标轴。
条件语句中可使用重载过的按位运算符:
&和 |或 ^异或 ~否
也可使用np.bit_...()
来得到同样的结果
将布尔数组作为掩码
我们可以使用布尔数组作为掩码,通过该掩码选择数据的子数据集。
x = np.array([[5, 0, 3, 3],[7, 9, 3, 5],[2, 4, 7, 6]])
print(x)
print(x<5)
print(x[x<5])
输出为:
[[5 0 3 3]
[7 9 3 5]
[2 4 7 6]]
[[False True True True]
[False False True False]
[ True True False False]]
[0 3 3 3 2 4]
返回的是一个一维数组,它包含了所有满足条件的值。换句话说,所有的这些值是掩码数组对应位置为True 的值。
花哨的索引
通过数组进行索引
花哨的索引(fancy indexing)和前面那些简单的索引非常类似,但是传递的是索引数组,而不是单个标量。花哨的索引让我们能够快速获得并修改复杂的数组值的子数据集。
rand = np.random.RandomState(42)
x = rand.randint(100, size=10)# x = [51 92 14 71 60 20 82 86 74 74]
ind = [3, 7, 4]
x[ind]
输出为:
array([71, 86, 60])
同样:
ind = np.array([[3, 7],
[4, 5]])
x[ind]
输出为:
array([[71, 86],
[60, 20]])
对多维数组:
X = np.arange(12).reshape((3, 4))
'''
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
'''
row = np.array([0, 1, 2])
col = np.array([2, 1, 3])
X[row, col]
输出结果为:
array([ 2, 5, 11])
组合索引
花哨的索引可以与其他索引方式联合使用。
索引值的配对遵循2.5 节介绍过的广播的规则。因此当我们将一个列向量和一个行向量组合在一个索引中时,会得到一个二维的结果。
可以将花哨的索引和简单的索引组合使用:
X[2, [2, 0, 1]]
输出的结果为:
array([10, 8, 9])
也可以将切片和花哨的索引联合使用:
X[1:, [2, 0, 1]]
输出的结果为:
array([[ 6, 4, 5],
[10, 8, 9]])
将花哨的索引和掩码联合使用:
mask = np.array([1, 0, 1, 0], dtype=bool)
X[row[:, np.newaxis], mask]
输出结果为:
array([[ 0, 2],
[ 4, 6],
[ 8, 10]])
选择随机点
我们可以用以下代码构建一个二维正态分布的数据集:
mean = [0, 0]
cov = [[1, 2],
[2, 5]]
X = np.random.multivariate_normal(mean, cov, 100)
X.shape
使用可视化的代码:
import matplotlib.pyplot as plt
import seaborn; seaborn.set() # 设置绘图风格
plt.scatter(X[:, 0], X[:, 1]);
绘制出的图形如下:
应用花哨的索引,选出20个随机的点,并将它们圈起来:
indices = np.random.choice(X.shape[0], 20, replace=False);
selection = X[indices];
plt.scatter(X[:, 0], X[:, 1], alpha=0.3)
plt.scatter(selection[:, 0], selection[:, 1],
facecolor='none', edgecolor='b', s=200);
用花哨的索引修改值
x = np.arange(10)
i = np.array([2, 1, 8, 4])
x[i] = 99
print(x)
其输出结果为:
[ 0 99 99 3 99 5 6 7 99 9]
对于
x[i] -= 10
其结果为:
[ 0 89 89 3 89 5 6 7 89 9]
需要注意,操作中重复的索引会导致一些出乎意料的结果产生:
x = np.zeros(10)
x[[0, 0]] = [4, 6]
print(x)
其结果为:
[ 6. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
这个操作首先赋值x[0] = 4,然后赋值x[0] = 6。
对以下操作:
i = [2, 3, 3, 4, 4, 4]
x[i] += 1
输出结果为:
array([ 6., 0., 1., 1., 1., 0., 0., 0., 0., 0.])
我们可以发现这种通过花哨的索引进行的批量修改不会重复加值,若想实现重复加值,可以使用通用函数中的at()方法:
x = np.zeros(10)
np.add.at(x, i, 1)
print(x)
所得结果为:
[ 0. 0. 1. 2. 3. 0. 0. 0. 0. 0.]
at() 函数在这里对给定的操作、给定的索引(这里是i)以及给定的值(这里是1)执行的是就地操作。另一个可以实现该功能的类似方法是通用函数中的reduceat() 函数。
数据区间划分
可以用下列方法有效地将数据进行区间划分并手动创建直方图。
np.random.seed(42)
x = np.random.randn(100)
# 手动计算直方图
bins = np.linspace(-5, 5, 20)
counts = np.zeros_like(bins)
# 为每个x找到合适的区间
i = np.searchsorted(bins, x)
# 为每个区间加上1
np.add.at(counts, i, 1)
计数数组counts 反映的是在每个区间中的点的个数,即直方图分布。
用
plt.plot(bins, counts, drawstyle='steps')
画出的结果如下:
使用Matplotlib的plt.hist() 方法可以在一行内实现上述功能
plt.hist(x, bins, histtype='step');
所作出的图与上图几乎一摸一样。
上述代码调用了np.histogram函数,适用于大数据集,而之前的方法则适用于小数据集。
数组的排序
快速排序
np.sort(array)
对数组进行快速排序
np.argsort(array)
返回原始数组排好序的索引
可以通过花哨的索引产生排序好的数组:
x = np.array([2, 1, 4, 3, 5])
i = np.argsort(x)
x[i]
其结果为:
array([1, 2, 3, 4, 5])
np.sort(array, axis=0)
按列进行快排
np.sort(array, axis=1)
按行进行快排
部分排序:分隔
np.partition(array, K)
返回最左侧为第k小的数而右侧随机排序的数组
np.argpartition
返回索引
K个最近邻
生成10个二维向量:X = np.random.rand(10, 2)
计算各向量之间的距离并生成距离矩阵:
dist_sq = np.sum((X[:,np.newaxis,:] - X[np.newaxis,:,:]) ** 2, axis=-1)
nearest = np.argsort(dist_sq, axis=1)
print(nearest)
可视化数据点如下图:
得到的近邻矩阵为:
[[0 2 6 4 7 5 9 1 8 3]
[1 9 7 5 3 8 4 6 2 0]
[2 0 6 4 5 7 9 1 3 8]
[3 8 1 9 7 5 4 6 2 0]
[4 6 9 7 0 2 1 8 5 3]
[5 7 1 9 3 2 6 4 8 0]
[6 0 2 4 7 9 1 5 8 3]
[7 9 1 5 3 8 4 6 2 0]
[8 3 1 9 7 4 5 6 2 0]
[9 7 1 5 3 8 4 6 2 0]]
如果我们仅仅关心k 个最近邻,那么唯一需要做的是分隔每一行,这样最小的k + 1 的平方距离将排在最前面:
K = 2
nearest_partition = np.argpartition(dist_sq, K + 1, axis=1)
为了将邻节点网络可视化,可以使用以下的代码:
plt.scatter(X[:, 0], X[:, 1], s=100)
# 将每个点与它的两个最近邻连接
K = 2
for i in range(X.shape[0]):
for j in nearest_partition[i, :K+1]:
# 画一条从X[i]到X[j]的线段
# 用zip方法实现:
plt.plot(*zip(X[j], X[i]), color='black')
邻节点网络如下图所示:
尽管本例中的广播和按行排序可能看起来不如循环直观,但是在实际运行中,Python 中这类数据的操作会更高效。
对较大的数据量来说,也可以使用sklearn中的KD-Tree。
结构化数组
结构化数组又称记录数组,它们为复合的、异构的数据提供了非常有效的存储。这些场景通常也可以用Pandas中的DataFrame实现。
# 使用复合数据结构的结构化数组
data = np.zeros(4, dtype={'names':('name', 'age', 'weight'),
'formats':('U10', 'i4', 'f8')})
print(data.dtype)
其结果为:
[('name', '<U10'), ('age', '<i4'), ('weight', '<f8')]
这里U10 表示“长度不超过10 的Unicode 字符串”,i4 表示“4 字节(即32 比特)整型”,f8 表示“8 字节(即64 比特)浮点型”。
name = ['Alice', 'Bob', 'Cathy', 'Doug']
age = [25, 45, 37, 19]
weight = [55.0, 85.5, 68.0, 61.5]
data['name'] = name
data['age'] = age
data['weight'] = weight
print(data)
其结果为:
[('Alice', 25, 55.0) ('Bob', 45, 85.5) ('Cathy', 37, 68.0)
('Doug', 19, 61.5)]
可以通过索引或名称查看相应的值:
获取所有名字
data['name']
array(['Alice', 'Bob', 'Cathy', 'Doug'],dtype='<U10')
# 获取数据第一行
data[0]
('Alice', 25, 55.0)
# 获取最后一行的名字
data[-1]['name']
'Doug'
# 利用布尔掩码
data[data['age'] < 30]['name']
array(['Alice', 'Doug'],
dtype='<U10')
数值数据类型可以用Python 类型或NumPy 的dtype 类型指定:
np.dtype({'names':('name', 'age', 'weight'),
'formats':((np.str_, 10), int, np.float32)})
dtype([('name', '<U10'), ('age', '<i8'), ('weight', '<f4')])
复合类型也可以是元组列表:
np.dtype([('name', 'S10'), ('age', 'i4'), ('weight', 'f8')])
dtype([('name', 'S10'), ('age', '<i4'), ('weight', '<f8')])
也可以使用字符串来指定:
np.dtype('S10,i4,f8')
dtype([('f0', 'S10'), ('f1', '<i4'), ('f2', '<f8')])
第一个字节的<和>表示低字节序和高字节序。
我们也可以创建一种类型,其中每个元素都包含一个数组或矩阵:
tp = np.dtype([('id', 'i8'), ('mat', 'f8', (3, 3))])
X = np.zeros(1, dtype=tp)
print(X[0])
print(X['mat'][0])
其结果为:
(0, [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]])
[[ 0. 0. 0.]
[ 0. 0. 0.]
[ 0. 0. 0.]]
NumPy 的dtype 直接映射到C 结构的定义,因此包含数组内容的缓存可以直接在C 程序中使用。如果你想写一个Python 接口与一个遗留的C 语言或Fortran 库交互,从而操作结构化数据,你将会发现结构化数组非常有用。
NumPy 还提供了np.recarray 类。它和前面介绍的结构化数组几乎相同,但是它有一个独特的特征:域可以像属性一样获取,而不是像字典的键那样获取。
前述获取年龄的代码如下:
data['age']
array([25, 45, 37, 19], dtype=int32)
如果将这些数据当作一个记录数组,则可以通过如下的代码获取:
data_rec = data.view(np.recarray)
data_rec.age
其结果为:
array([25, 45, 37, 19], dtype=int32)
记录数组的缺点是,即使使用同样的语法,在获取域时也会有一些额外的开销。
结构化数组在某些场景中很好用,特别是当你用C、Fortran 或其他语言将NumPy 数组映射为二进制数据格式时。但是如果经常需要使用结构化数据,那么Pandas 包是更好的选择,