Numpy 学习笔记
文章目录
一 Numpy 数据类型
1 Python 内部的整数
与C语言相比,Python 中的变量在赋值前无需声明变量类型,且可以重复赋给变量不同数据类型的数据,其原因在于 Python 中的数据结构与 C 语言不同。以长整型为例:
# Python 3.4 中长整型的 C 语言源码
struct _longobject {
long ob_refcnt; # 用于分配和释放内存的计数器
PyTypeObject *ob_type; # 存储数据类型
size_t ob_size; # 存储数据大小
long ob_digit[1]; # 存储 long 型数字
}
可见,Python 中的长整型与 C 语言相比有许多额外的属性,这造就了 Python 语言的便捷性,但也降低了效率。
2 Python 内部的列表(list)
Python 中的列表可以存储多种类型的元素,列表中可同时存在字符、整型、浮点型等类型。其原因在于 Python 的列表存储的是一个个独立的 Python 对象,且 list
中存储的是若干指针,每一个指针指向独立的对象(形如[Python中的整数](#1 Python 内部的整数)),而 C 语言中的数组表示为内存中一段连续的数据。Python 中的 list
和 C 中的数组分别称为动态类型数组(dynamic-type)和固定类型数组(fixed-type)
3 Python 内部的固定类型数组
显然的,在固定类型数组上进行运算虽然灵活性受阻,但更为高效。Python 中提供了内置的module以固定类型数组的形式来存储数据
import array
dynamic_list = list(range(10))
# 其中 'i' 表示数据为 integer 型
fixed_list = array.array('i', dynamic_list)
虽然 Python 已经提供了相应数组,但 NumPy
包中提供了类似的对象 ndarray
,于此同时,该包中提供了极为丰富的运算方法,我们将在下文逐一了解。
4 创建一个 ndarray 对象
根据列表进行创建
a1 = np.array([1, 4, 2, 5, 3])
Numpy 数组要求数据类型一致,若列表中数据类型不同,如果条件允许,会进行自动类型转换。
a2 = np.array([1, 2.5])
# a2 = array([1. 2.5])
支持自定义数据类型
a3 = np.array([1, 2, 3, 4], dtype='float32')
# a3 = array([1. 2. 3. 4.] dtype=float32)
支持多维数组
a4 = np.array([range(i, i+3) for i in [2, 4, 6]])
'''
a4 = array([2 3 4]
[4 5 6]
[6 7 8])
'''
使用内置函数创建
# 创建一个有 10 个 0 的一维数组
np.zeros(10, dtype=int)
# 创建 3*5 的二维数组,元素全为 1.0
np.ones((3, 5), dtype=float)
# 自定义填充内容
np.full((3, 5), 3.14)
# 和 range() 功能相同
np.arange(0, 10, 2)
# 创建 5 个均匀分布在 [0,1] 区间的值
# 输出 array([0. 0.25 0.5 0.75 1.])
np.linspace(0, 1, 5)
# 创建 3*3 的随机矩阵,元素大小在 [0,1] 之间
np.random.random((3, 3))
# 创建均值为 0,方差为 1 的随机矩阵
np.random.normal(0, 1, (3, 3))
# 创建区间 [0,10) 内的整型矩阵
np.random.randint(0, 10, (3, 3))
# 创建对角阵
np.eye(3)
# 创建未初始化矩阵,内容由内存决定
np.empty(3)
5 NumPy 标准数据类型
NumPy 中提供了标准的数据类型,初始化数组时我们可以指定数据类型,以下两种定义方式是等价的
np.zeros(10, dtype='int16')
np.zeros(10, dtype=np.int16)
更多的数据类型见下表
二 NumPy 数组基础知识
1 NumPy 数组的属性
数组 x
定义如下
x = np.random.randint(10, size=(3, 4, 5))
属性名 | 解释 | 属性调用 | 属性值 |
---|---|---|---|
ndim | 数组的维度 | print(x.ndim) | 3 |
shape | 数组各个维度大小组成的元组 | print(x.shape) | (3, 4, 5) |
size | 数组中元素的个数 | print(x.size) | 60 |
dtype | 数组中元素的数据类型 | print(x.dtype) | int64 |
itemsize | 每个元素所占字节 | print(x.itemsize) | 8 bytes |
nbytes | 整个数组所占字节 | print(x.nbytes) | 480 bytes |
2 数组索引
数组索引用于访问单个元素,其使用方法与 Python 中的列表类似
数组访问:
# 一维数组
x = np.array([1, 2, 3, 4, 5])
print(x[0])
print(x[-1])
''' 输出结果为
1
5
'''
# 二维数组
m = np.array([[1, 2, 3], [4, 5, 6]])
print(x[1, 2]) # 表示访问第1行第2列(从0开始计数)
print(x[-1, 2])
'''输出结果为
6
6
'''
于此同时,可以利用类似的方式修改数组中的元素
x = np.array([[1, 2, 3], [4, 5, 6]])
x[0, 0] = 0
3 数组切片
一维数组切片
x = np.arange(10)
print(x[:5])
print(x[5:])
print(x[4:7])
print(x[::2])
print(x[1::2])
print(x[::-1])
print(x[5:-1:-2])
二维数组切片
其中逗号前表示行的切片,逗号后表示列的切片
x = np.random.ranint(10, (3, 4))
print(x[:2, :3])
print(x[:3, ::2])
print(x[::-1, ::-1])
获取二维数组的某一行和某一列
将索引与切片相结合即可得到某一行和某一列
# 获取第 0 列
print(x[:, 0])
# 获取第 0 行
print(x[0, :])
值得注意的是,根据切片得到的子数组相当于原数组的引用(C++中的概念)。即,若我们改变索引子数组中的元素,原数组中的元素会对应的改变。
a = np.zeros((2, 2))
b = a[0, :]
b[0, 0] = 1
print(a)
'''输出结果
[[1 0]
[0 0]]
'''
如果我们想要获取原数组的复制而非引用,可以使用copy
函数
b = a[0, :].copy()
4 定义数组的形状
将一维数组转化成成 3 × 3 3\times3 3×3 的二维数组
x = np.arange(1, 10).reshape(3, 3)
将一个拥有 10 个元素的一维数组转化成 1 × 10 1\times10 1×10 和 10 × 1 10\times1 10×1 的二维数组
x = np.arange(10) # 创建一个一维数组
# 使用 reshape 方法
y1 = x.reshape(1, 10)
y2 = x.reshape(10, 1)
# 使用 newaxis 关键字
z1 = x[np.newaxis, :]
z2 = x[:, np.newaxis]
上述代码中,y1
与 z1
等价,y2
与 z2
等价
5 数组的合并与分解
数组的合并
数组的合并有三个方法,分别为 np.concatenate()
、np.vstack()
和 np.hstack()
np.concatenate()
以数组的元组或列表作为其参数
x = np.array([1, 2, 3])
y = np.array([3, 2, 1])
z = np.array([6, 6, 6])
print(np.concatenate([x, y]))
# 输出:[1 2 3 3 2 1]
print(np.concatenate([x, y, z]))
# 输出:[1 2 3 3 2 1 6 6 6]
对于二维数组,使用 concatenate()
方法需指定合并的方向
x = np.zeros((2, 2))
y = np.ones((1, 1))
xy_in_vertical = np.concatenate([x, y], axis=0)
xy_in_horizontal = np.concatenate([x, y], axis=1)
对于二维数组的合并,使用 vstack()
和 hstack()
会更为清晰
xy_in_vertical = np.vstack([x, y])
xy_in_horizontal = np.hstack([x, y])
数组的分解
对于 NumPy 中数组的分解,其提供了 np.split()
、np.vsplit()
和 np.hsplit()
三个函数。其参数为一个整型数组,数组中的每个数字代表了一个分离点
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 = np.arange(16).reshape(4, 4)
upper, lower = np.vsplit(grid, [2])
left, right = np.hsplit(gird, [2])
此外,NumPy 还提供了 np.dsplit()
来对第三维进行分解
三 NumPy 数组上的运算
NumPy 提供了极其丰富的函数支持,这些函数与 Python 内置的函数相比,可以大大提高运算速度。
考虑将数组 a 中的元素全部取倒数
-
常规方法
a = np.random.randint(1, 10, size=10000) for i in range(len(a)): a[i] = 1/a[i]
-
使用内置函数
a = np.random.randint(1, 10, size=10000) a = 1/a
第二种方法运算速度远远大于第一种方法,原因在于第一种方法使用 Python 自带的循环,每次运算均需要检测数据类型,而第二种方法采用 NumPy 自带的方法,在底层进行了优化,无需重复检测数据类型。
由此,在 Numpy 数组的运算中,尤其是大规模的数据处理,我们要尽可能的使用自带的函数来替代循环。
1 常见函数
运算符的重载
NumPy 数组支持直接使用运算符进行运算。例如:
x = np.arange(4)
print(x + 5)
# 输出 [5 6 7 8]
print((x + 5)//2)
# 输出 [2 3 3 4]
于此同时,每个运算符还有等价的函数形式,x+5
等价于 x.add(5)
。以下为运算符重载的表格:
运算符 | 等价函数 |
---|---|
+ | np.add() |
- | np.substract() |
- | np.negative()(取相反数) |
* | np.multiply() |
/ | np.divide() |
// | np.floor_divide() |
** | np.power() |
% | np.mod() |
绝对值
np.abs()
和 np.absolute()
两者等价,返回各项的绝对值,对于复数,返回复数的大小
三角函数
np.sin()
、np.cos()
、np.tan()
、np.arcsin()
、np.arccos()
和 np.arctan()
指数函数和对数函数
-
指数函数
np.exp()
、np.exp2()
、np.power()
,分别计算 e 的指数,2 的指数以及自定义的指数。 -
对数函数
np.log()
、np.log2()
、np.log10()
,分别计算以 e 为底的对数,以 2 为底的对数和以 10 为底的对数 -
若输入为微小值,可以使用如下两个函数
np.expm1(x)
:其输出为 e x − 1 e^x-1 ex−1np.log1p(x)
:其输出为 l o g ( 1 + x ) log(1+x) log(1+x)
2 较高级的函数特性
指定输出
使用函数中的 out
参数可以直接存储运算结果,对于常规运算如
x = np.arange(10)
y = np.empty(10)
y = np.multiply(x, 10)
其会首先将 np.multiply(x, 10)
的运算结果存储于临时数组中,再将临时数组的内容复制到 y
变量中,这样会带来额外的空间和时间的开销(尤其对于大规模的数据),可以采用如下方法:
x = np.arange(10)
y = np.empty(10)
np.multiply(x, 10, out=y)
其中 out
参数亦可指向某一个数组的索引
x = np.arange(5)
y = np.zeros(10)
np.power(2, x, out=y[::2])
print(y)
# 输出 [1 0 2 0 4 0 8 0 16 0]
agg 函数
-
reduce()
方法reduce()
方法可以加在任何的计算函数之后,该方法依次作用于数组中的元素,不断缩小数组的长度,直到数组中最后仅留下一个元素。x = np.arange(1, 6) print(np.add.reduce(x)) # 输出结果:1+2+3+4+5=15 print(np.multiply.reduce(x)) # 输出结果:1*2*3*4*5=120
-
accumulate()
方法accumulate()
方法可以将上边reduce()
运算过程中的中间结果存储在数组中在。x = np.arange(1, 6) print(np.add.accumulate(x)) # 输出结果:[1 3 6 10 15] print(np.multiply.accumulate(x)) # 输出结果:[1 2 6 24 120]
外积
使用 outer()
方法和其他计算函数的组合,可以计算两个不同数组之间的每一对元素之间的函数值
x = np.arange(1, 6)
print(np.multiply.outer(x, x))
''' 输出结果
[[1 2 3 4 5]
[2 4 6 8 10]
[3 6 9 12 15]
[4 8 12 16 20]
[5 10 15 20 25]]
'''
四 聚集函数
Python 中绝大部分聚集函数都提供有 NaN 安全版本,即可以忽略数组中的 NaN 值。
下表为部分 NumPy 中的聚集函数
函数名 | 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 | 若任意元素不为0,则结果为真 |
np.all() | N/A | 若所有元素不为0,则结果为假 |
二维数组同样可以应用上述函数,使用轴(axis)的概念即可,即通过指定轴来决定统计的方向(行,列),若不使用轴,则统计二维数组所有元素。
a = np.array([[1, 2, 3], [4, 5, 6]])
print(np.sum(a, axis=0))
# 输出:[5 7 9]
print(np.sum(a, axis=1))
# 输出:[6 15]
print(np.sum(a))
# 输出:21
附两个链接:
五 数组计算中的广播机制
当 NumPy 中两个不同大小的数组进行运算时,就需要用到广播机制来使得两个数组大小匹配,从而进行运算。广播机制源自 broadcasting
,但我认为称其扩充机制更为贴切。
1 广播机制引入
对于相同大小的数组,二元运算函数会对两个数组中相同位置的元素进行计算
a = np.array([0, 1, 2])
b = np.array([5, 5, 5])
print(a + b)
# 输出:[5 6 7]
对于不同大小的两个数组,我们同样可以进行类似计算
print(a + 5)
# 输出:[5 6 7]
在这里,我们可以想象元素 5
被扩展成了 [5, 5, 5]
。
类似的
c = np.ones((3, 3))
print(a + c)
'''输出:
[[1 2 3]
[1 2 3]
[1 2 3]]
'''
在这里,我们可以认为数组 a
在运算中被自动扩展成了
[[0, 1, 2],
[0, 1, 2],
[0, 1, 2]]
更为复杂的例子:
x = np.arange(3)
y = np.arange(3)[:, np.newaxis] # 还记得该用法吗?
print(a + b)
'''输出:
[[0 1 2]
[1 2 3]
[2 3 4]]
'''
在上边的例子中,可以发现得到结果的维度与 x
和 y
均不同。
在实际的计算中,内存内部并不存在类似的过程,但上述过程可以帮助我们更好的理解广播机制的应用。
2 广播机制的规则
- 如果两个数组的维度不同,维度较小的那一个数组的
shape
属性左边加 1,直到两个数组维度相同 - 若两个数组的
shape
属性某一个位置上的值不同,且有一方该位置上的数字为 1,则将此处的 1 延伸至与另一个数组相同 - 若执行了
1
和2
两个步骤后,大小仍不匹配,则产生错误
举例说明:
M = np.ones((2, 3))
a = np.arange(3)
- 初始时,
M.shape=(2,3)
,a.shape=(3,)
- 使用规则
1
,有M.shape=(2,3)
,a.shape=(1,3)
- 使用规则
2
,有M.shape=(2,3)
,a.shape=(2,3)
- 使用规则
3
,变化后两个数组大小匹配,不产生错误
a = np.arange(3).reshape((3, 1))
b = np.arange(3)
- 初始时,
a.shape=(3, 1)
,b.shape=(3,)
- 使用规则
1
,有a.shape=(3,1)
,b.shape=(1,3)
- 使用规则
2
,有a.shape=(3,3)
,b.shape=(3,3)
- 使用规则
3
,此时两个数组大小匹配,不产生错误
若
a = np.ones((3, 2))
b = np.arange(3)
则 a+b
运算无法满足广播规则,将产生报错。
3 广播机制的应用
中心化
X = np.random.random((10, 3)) # 产生十行数据,每一行表示一组观测值,一组观测值含三个数
Xmean = X.mean(axis=0)
X_centered -= X.mean # X_centered 的每一列都是中心化的数据
绘制二维函数图像
定义函数 z = f ( x , y ) z=f(x,y) z=f(x,y) ,广播机制有助于我们求该函数在不同坐标下的值。
假设定义 z = s i n 10 ( x ) + c o s ( 10 + x ⋅ y ) ⋅ c o s ( x ) z=sin^{10}(x)+cos(10+x\cdot y)\cdot cos(x) z=sin10(x)+cos(10+x⋅y)⋅cos(x),则可以通过下列运算计算其在各个坐标上的值
x = np.linspace(0, 5, 50)
y = np.linspace(0, 5, 50)[:, np.newaxis]
z = np.sin(x) + np.cos(10 + x * y) * np.cos(x)
我们可以进而绘制函数图像
import matplotlib.pyplot as plt
plt.imshow(z, origin='lower', extent=[0, 5, 0, 5], cmap='viridis')
plt.colorbar();
六 布尔掩码
1 比较运算符
NumPy 中除了提供通常的加减乘除等运算符,还提供了比较运算符,用于逐元素的比较两个数组中的元素,返回一个 Boolean 类型的数组。例如:
x = np.array([1, 2, 3, 4, 5])
print(x < 3)
# 输出:[True True False False False] dtype=bool
print(2 * x == x ** 2)
# 输出:[False True False False False] dtype=bool
常见的比较运算符以及对应的函数形式如下表:
比较运算符 | 通用函数形式 |
---|---|
== | np.equal() |
!= | np.not_equal() |
< | np.less() |
<= | np.less_equal() |
> | np.greater() |
>= | np.greater_equal() |
2 布尔数组的应用
对于数组之间比较生成的结果,可以大大方便我们对数据的处理。
首先有
x = [[5, 0, 3, 3], [7, 9, 3, 5], [2, 4, 7, 6]]
接着,我们可以对满足条件的元素个数进行统计:
np.count_nonzero(x < 6)
np.sum(x < 6)
上述两种方法是等价的,其目的都是统计 x
中小于 6 的元素的个数。而对于 sum
方法,可以进一步统计每一维的数据:
np.sum(x < 6, axis=1) # 统计每一行小于 6 的元素的个数
# 返回数组为 [4 2 2]
可以判断是否所有元素或某一个元素为真:
# 判断是否有大于 8 的元素
np.any(x > 8)
# 判断是否全都大于 8
np.all(x > 8)
我们可以使用布尔运算符来丰富我们的选择条件:
# 计算 x 中所有大于 2 且小于 8 的元素的个数
np.sum((x > 2) & (x < 8))
支持的布尔运算符及支持的函数形式:
运算符 | 函数形式 |
---|---|
& | np.bitwise_and() |
| | np.bitwise_or() |
^ | np.bitwise_xor() |
~ | np.bitwise_not() |
3 使用布尔数组作为掩码
如果我们想要知道数组中所有满足条件的元素有哪些,我们可以采用布尔数组作为掩码,例如:
x = [[1, 2, 3], [4, 5, 6]]
print(x < 5)
'''输出:
[[True True True]
[True False False]]
'''
print(x[x < 5])
# 输出:[1 2 3 4]
在前边,有索引和切片两种访问 NumPy 数组的方式,而这里我们可以通过布尔数组对 NumPy 数组进行访问。
七 花式索引
花式索引是索引的升级版,可以通过传入数组的方式同时访问到多个元素。
1 花式索引介绍
创建这样一个数组
x = np.arange(1, 10, 2)
print(x)
# 输出:[1 3 5 7 9]
如果想要同时访问三个不同的元素,最简单的方法可以这样
print([x[1], x[2], x[4]])
# 输出:[3 5 9]
但是通过花式索引我们可以更简洁的访问
ind = [1, 2, 4]
print(x[ind])
# 输出:[3 5 9]
我们通过向数组传入一个索引列表,进而可以得到各个位置的元素。很重要的一点是,通过花式索引输出的结果的形状,仅与索引数组的形状有关,而与被索引数组的形状无关。例如:
ind = array([[0, 1], [2, 3]])
print(x[ind])
'''输出结果:
[[1 3]
[5 7]]
'''
同样的,我们可以访问多维数组
x = np.arange([[1, 3, 7], [2, 4, 2]])
row = [0, 1, 1]
col = [0, 1, 2]
print(x[row, col])
'''输出结果:
[1 4 2]
'''
这里我们访问的位置是 (0, 0)、(1, 1)和(1, 2),即 row
和 col
中的元素一一匹配形成三个元组对 x
进行访问。row
和 col
的配对满足广播规则,例如:
x = np.array([[1, 3, 7], [2, 4, 2]])
row = np.array([0, 1, 1])
col = np.array([0, 1, 2])[:, np.newaxis]
print(x[row, col])
'''输出结果:
[[1 2 2]
[3 4 4]
[7 2 2]]
'''
2 混合索引
花式索引可以与普通索引方式、切片索引方式和掩码索引方式结合起来使用,例如:
x = np.arange(12).reshape(3, 4)
print(x)
'''
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
'''
# 普通索引与花式索引结合
print(x[2, [2, 0, 1]])
'''
[10 8 9]
'''
# 切片索引和花式索引结合
print(x[1:, [2, 0, 1]])
'''
[[ 6 4 5]
[10 8 9]]
'''
# 掩码索引与花式索引结合
mask = np.array([1, 0, 1, 0], dtype=bool)
row = np.array([0, 1, 2])
print(x[row[:, np.newaxis], mask])
'''
[[ 0 2]
[ 4 6]
[ 8 10]]
'''
3 通过花式索引改变数组的值
简单的改变数组的值
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 = np.zeros(10)
x[i] = i
print(x)
'''
[0. 1. 2. 0. 4. 0. 0. 0. 8. 0.]
'''
若索引数组中存在相同的数,则对该索引对应位置的元素重复赋值
x = np.zeros(10)
x[[0, 0]] = [1, 2]
print(x)
'''
[2. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
'''
同时可以对索引使用运算符进行计算,对于相同索引不会重复相加
x = np.zeros(10)
x[[0, 0]] += 1
print(x)
'''
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
'''
如果想要对索引位置进行独立重复操作,可以使用 .at
方法
x = np.zeros(10)
i = [0, 0, 1]
np.add.at(x, i, 1)
print(x)
'''
[2. 1. 0. 0. 0. 0. 0. 0. 0. 0.]
'''
八 数组排序
1 快排
使用 np.sort()
函数可以将排序后的结果存储在额外的数组中,而原数组不会发生改变
x = np.array([2, 1, 4, 3, 5])
np.sort(x)
使用数组的 sort()
方法,可以对原数组进行排序
x.sort()
使用 np.argsort()
函数,可以得到排序后的索引
ind = np.argsort(x)
print(x[ind])
'''
[1 2 3 4 5]
'''
同样的,可以单独对行排序或对列排序
rand = np.random.RandomState(42)
X = rand.randint(0, 10, (4, 6))
# 对每一列进行排序
np.sort(X, axis=0)
# 对每一行进行排序
np.sort(X, axis=1)
2 部分排序
借助 np.partition()
函数,我们可以得到数组中前 K 小的数字
X = np.array([7, 2, 3, 1, 6, 5, 4])
print(np.partition(X, 3))
'''
[2 1 3 4 6 5 7]
'''
此时,前 3 小的元素被放在前 3 个位置上,但是无论是前 3 个位置,还是后边的元素,都呈乱序排列。同样,部分排序也可以按列或按行排序。
九 NumPy 中的结构化数组
NumPy 中的数组同样支持复合数据类型的元素,可以按照如下方式进行定义
name = ['Alice', 'Bob', 'Cathy', 'Doug']
age = [25, 45, 37, 19]
weight = [55.0, 85.5, 68.0, 61.5]
data = np.zeros(4, dtype={'names':('name', 'age', 'weight'),
'formats':('U10', 'i4', 'f8')})
data['name'] = name
data['age'] = age
data['weight'] = weight
# 查看总体情况
print(data)
'''
[('Alice', 25, 55. ) ('Bob', 45, 85.5) ('Cathy', 37, 68. )
('Doug', 19, 61.5)]
'''
# 仅根据属性进行查看
print(data['name'])
'''
['Alice' 'Bob' 'Cathy' 'Doug']
'''
# 根据索引进行查看
print(data[0])
'''
('Alice', 25, 55.)
'''
# 进行更为复杂的索引
print(data[data['age']<30]['name'])
'''
['Alice' 'Doug']
'''
1 复合结构数组的创建
-
字典法
type1 = np.dtype({'names':('name', 'age', 'weight'), 'formats':('U10', 'i4', 'f8')}) type2 = np.dtype({'names':('name', 'age', 'weight'), 'formats':((np.str_, 10), int, np.float32)})
-
列表法
type3 = np.dtype([('name', 'U10'), ('age', 'i4'), ('weight', 'f8')])
-
元组法
若不需要属性名,则可以直接定义数据类型
type4 = np.dtype('S10,i4,f8')
其中的 'i4'
、'f8'
为数据类型的缩写,其完整格式为 [> or <]xn
,其中 >
或 <
可选,代表高位优先和地位优先,x
表示具体的数据类型,n
表示数据类型所占的字节数。
字符 | 数据类型 |
---|---|
b | 字节 |
i | 有符号整数 |
u | 无符号整数 |
f | 浮点类型 |
c | 浮点复数类型 |
S、a | 字符串类型 |
U | Unicode 字符串 |
V | 空类型 |
2 较为复杂的结构
tp = np.dtype[('id', 'i8'), ('mat', 'f8', (3, 3))]
上述数据结构包含两个元素,第一个元素为一个 64 位整数,第二个类型为一个 3 × 3 3\times3 3×3 的矩阵,元素类型为 64 位浮点数。
由于 NumPy 的 dtype
类型直接对应于 C 语言的结构体,因此在编写 Python 和 C 语言的接口程序时,dtype
数据类型极为有用。
3 记录数组
NumPy 中提供了 np.recarray
类,该类可以使得用户通过 .属性名
的方式访问属性,而非通过字典键索引的方式访问属性。
name = ['Alice', 'Bob', 'Cathy', 'Doug']
age = [25, 45, 37, 19]
weight = [55.0, 85.5, 68.0, 61.5]
data = np.zeros(4, dtype={'names':('name', 'age', 'weight'),
'formats':('U10', 'i4', 'f8')})
data['name'] = name
data['age'] = age
data['weight'] = weight
data_rec = data.view(np.recarray)
print(data_rec.name)
这样可以更方便的访问元素,但 recarray
类上的访问速度慢于 ndarray
上的访问速度。