Numpy介绍(2)数组操作

Python 中的数据操作几乎等同于 NumPy 数组操作:甚至像 Pandas(第 3 章)这样的更新工具也是围绕 NumPy 数组构建的。本节将展示几个使用 NumPy 数组操作访问数据和子数组以及拆分、重塑和连接数组的示例。虽然这里展示的操作类型可能看起来有点枯燥和迂腐,但它们构成了本书中使用的许多其他示例的构建块。好好认识他们!

我们将在这里介绍几类基本数组操作:

  • 数组的属性:确定数组的大小、形状、内存消耗和数据类型
  • 数组索引:获取和设置单个数组元素的值
  • 数组切片:在较大的数组中获取和设置较小的子数组
  • 数组重塑:改变给定数组的形状
  • 数组的合并和拆分:将多个数组合并为一个数组,将一个数组拆分为多个数组

Numpy数组属性

首先让我们讨论一些有用的数组属性。我们将从定义三个随机数组开始,分别是一维、二维和三维数组。我们将使用 NumPy 的随机数生成器,并使用一个设定值作为种子,以确保每次运行此代码时生成相同的随机数组:

import numpy as np
np.random.seed(0)  # 设置重现的种子

x1 = np.random.randint(10, size=6)  # 1维数组
x2 = np.random.randint(10, size=(3, 4))  # 2维数组
x3 = np.random.randint(10, size=(3, 4, 5))  # 3维数组

每个数组都有属性ndim(维度数)、shape(每个维度的大小)、size(数组的总大小):

print("x3 ndim: ", x3.ndim)
print("x3 shape:", x3.shape)
print("x3 size: ", x3.size)
x3 ndim:  3
x3 shape: (3, 4, 5)
x3 size:  60

另一个有用的属性是 dtype,数组的数据类型(这在上一节有说过):

print("dtype:", x3.dtype)
dtype: int64

其他属性包括 itemsize,列出了每个数组元素的大小(以字节为单位),以及 nbytes,列出了数组的总大小(以字节为单位):

print("itemsize:", x3.itemsize, "bytes")
print("nbytes:", x3.nbytes, "bytes")
itemsize: 8 bytes
nbytes: 480 bytes

通常,我们期望 nbytes 等于 itemsize 乘以 size。

数组索引:访问单个元素

如果您熟悉 Python 的标准列表索引,那么您会觉得 NumPy 中的索引非常熟悉。在一维数组中,可以通过在方括号中指定所需的索引来访问 i^{th} 值(从零开始计数),就像 Python 列表一样:

x1
array([5, 0, 3, 3, 7, 9])
x1[0]
5
x1[4]
7

如果想从后往前索引,可以加负号:

x1[-1]
9
x1[-2]
7

在多维数组中,可以使用逗号分隔的索引元组访问项目:

x2
array([[3, 5, 2, 4],
       [7, 6, 8, 8],
       [1, 6, 7, 7]])
x2[0, 0]
3
x2[2, 0]
1
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.14159  # 这会被截短!
x1
array([3, 0, 3, 3, 7, 9])

数组切片:访问子数组

正如我们可以使用方括号访问单个数组元素一样,我们也可以使用它们访问带有切片符号的子数组,以冒号 (:) 字符标记。

x[start:stop:step]

如果其中任何一个未指定,它们默认为值 start=0,stop=size of dimension,step=1。我们将看看访问一维和多维的子数组。

一维子数组

x = np.arange(10)
x
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
x[:5]  # 前5个元素
array([0, 1, 2, 3, 4])
x[5:]  # 索引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]  # 每隔一个元素, 从索引1开始
array([1, 3, 5, 7, 9])

一个可能令人困惑的情况是步长值为负。在这种情况下,启动和停止的默认值被交换。这成为反转数组的便捷方式:

x[::-1]  # 所有元素反转
array([9, 8, 7, 6, 5, 4, 3, 2, 1, 0])
x[5::-2]  # 从索引5开始每隔一个元素反转
array([5, 3, 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]])

最后,子数组维度甚至可以一起反转:

x2[::-1, ::-1]
array([[ 7,  7,  6,  1],
       [ 8,  8,  6,  7],
       [ 4,  2,  5, 12]])

访问数组行和列

一个通常需要的例程是访问数组的单个行或列。这可以通过结合索引和切片来完成,使用一个由单个冒号标记的空切片:

print(x2[:, 0])  # x2的第1列
[12  7  1]
print(x2[0, :])  # x2的第1行
[12  5  2  4]

在行访问的情况下,可以省略空切片以获得更紧凑的语法:

print(x2[0])  # 和x2[0, :]一样
[12  5  2  4]

子数组作为非副本视图

关于数组切片需要了解的一件重要且非常有用的事情是它们返回视图(即原数据)而不是数组数据的副本。这是 NumPy 数组切片不同于 Python 列表切片的一个方面:在列表中,切片将是副本。考虑我们之前的二维数组:

print(x2)
[[12  5  2  4]
 [ 7  6  8  8]
 [ 1  6  7  7]]

让我们提取一个2×2的子数组:

x2_sub = x2[:2, :2]
print(x2_sub)
[[12  5]
 [ 7  6]]

现在如果我们修改这个子数组,我们会看到原来的数组被改变了!
观察:

x2_sub[0, 0] = 99
print(x2_sub)
[[99  5]
 [ 7  6]]
print(x2)
[[99  5  2  4]
 [ 7  6  8  8]
 [ 1  6  7  7]]

这种默认行为实际上非常有用:这意味着当我们处理大型数据集时,我们可以访问和处理这些数据集的片段,而无需复制底层数据缓冲区。

创建数组副本

尽管数组视图有很好的特性,但有时也需要在数组或子数组中显式复制数据。使用 copy() 方法可以最轻松地完成此操作:

x2_sub_copy = x2[:2, :2].copy()
print(x2_sub_copy)
[[99  5]
 [ 7  6]]

如果我们现在修改这个子数组,原始数组不会被改变:

x2_sub_copy[0, 0] = 42
print(x2_sub_copy)
[[42  5]
 [ 7  6]]
print(x2)
[[99  5  2  4]
 [ 7  6  8  8]
 [ 1  6  7  7]]

重塑数组

另一种有用的操作类型是数组的整形。最灵活的方法是使用重塑方法。例如,如果要将数字 1 到 9 放在 3×3 网格,您可以执行以下操作:

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

请注意,要使其正常工作,初始数组的大小必须与重塑数组的大小相匹配。在可能的情况下,reshape 方法将使用初始数组的副本视图,但对于非连续的内存缓冲区,情况并非总是如此:
另一种常见的重塑模式是将一维数组转换为二维行或列矩阵。这可以使用 reshape 方法来完成,或者更容易地通过在切片操作中使用 newaxis 关键字来完成:

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

# 用reshape重塑行向量
x.reshape((1, 3))
array([[1, 2, 3]])
# 用newaxis重塑行向量
x[np.newaxis, :]
array([[1, 2, 3]])
# 用reshape重塑列向量
x.reshape((3, 1))
array([[1],
       [2],
       [3]])
# 用newaxis重塑列向量
x[:, np.newaxis]
array([[1],
       [2],
       [3]])

我们将在本书的其余部分经常看到这种类型的转换。

创建数组和分列

前面的所有例子都适用于单个数组。也可以将多个数组合并为一个,并反过来将单个数组拆分为多个数组。我们将在这里查看这些操作。

创建数组

在 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])

也可以一次压缩多于2个的数组:

z = [99, 99, 99]
print(np.concatenate([x, y, z]))
[ 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]])
# 沿着第二轴压缩 (zero-indexed)
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 将沿第三轴堆叠数组。

分裂数组

连接的反面是拆分,它由函数 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会沿第三轴分裂数组。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352

推荐阅读更多精彩内容