NumPy入门基础语法学习10

2.7 花哨的索引

在前面的小节中,我们看到了如何利用简单的索引值(如 arr[0] )、
切片(如 arr[:5] )和布尔掩码(如 arr[arr > 0] )获得并修改部分
数组。在这一节中,我们将介绍另外一种数组索引,也称作花哨的索引
fancy indexing )。花哨的索引和前面那些简单的索引非常类似,但是
传递的是索引数组,而不是单个标量。花哨的索引让我们能够快速获得
并修改复杂的数组值的子数据集。
 

2.7.1 探索花哨的索引

花哨的索引在概念上非常简单,它意味着传递一个索引数组来一次性获
得多个数组元素。例如以下数组:

假设我们希望获得三个不同的元素,可以用以下方式实现:

另外一种方法是通过传递索引的单个列表或数组来获得同样的结果:
 

利用花哨的索引,结果的形状与索引数组的形状一致,而不是与被索引
数组的形状一致:
花哨的索引也对多个维度适用。假设我们有以下数组:
 

和标准的索引方式一样,第一个索引指的是行,第二个索引指的是列:
 

这里需要注意,结果的第一个值是 X[0, 2] ,第二个值是 X[1, 1] ,第
三个值是 X[2, 3] 。在花哨的索引中,索引值的配对遵循 2.5 节介绍过
的广播的规则。因此当我们将一个列向量和一个行向量组合在一个索引
中时,会得到一个二维的结果:
 
X[row[:, np.newaxis], col]


array([[ 2,  1,  3],
       [ 6,  5,  7],
       [10,  9, 11]])
这里,每一行的值都与每一列的向量配对,正如我们看到的广播的算术
运算:
 
row[:, np.newaxis] * col

array([[0, 0, 0],
       [2, 1, 3],
       [4, 2, 6]])
这里特别需要记住的是,花哨的索引返回的值反映的是广播后的索引数
组的形状,而不是被索引的数组的形状。

2.7.2 组合索引

花哨的索引可以和其他索引方案结合起来形成更强大的索引操作:
 print(X)


 [[ 0 1 2 3] 
 [ 4 5 6 7] 
 [ 8 9 10 11]]
可以将花哨的索引和简单的索引组合使用:
X[2, [2, 0, 1]]

array([10,  8,  9])
也可以将花哨的索引和切片组合使用:
 

 X[1:, [2, 0, 1]]

 array([[ 6,  4,  5],
       [10,  8,  9]])
更可以将花哨的索引和掩码组合使用:
索引选项的组合可以实现非常灵活的获取和修改数组元素的操作。
 

2.7.3 示例:选择随机点

花哨的索引的一个常见用途是从一个矩阵中选择行的子集。例如我们有
一个 N × D 的矩阵,表示在 D 个维度的 N 个点。以下是一个二维正态分
布的点组成的数组:
 
 
可以用散点图将这些点可视化(如图2-7 所示):
 

                                                   图 2-7 :正态分布的点

我们将利用花哨的索引随机选取 20 个点——选择 20 个随机的、不重复

的索引值,并利用这些索引值选取到原始数组对应的值:
indices = np.random.choice(X.shape[0], 20, replace=False) 
indices 


 array([93, 45, 73, 81, 50, 10, 98, 94, 4, 64, 65, 89, 47, 84, 82, 80, 25, 90, 63, 20])
 selection = X[indices] # 花哨的索引
 selection.shape 

 (20, 2)
现在来看哪些点被选中了,将选中的点在图上用大圆圈标示出来(如图
2-8 所示):
 
                                           图 2-8 :随机选择的点

 

2.7.4 用花哨的索引修改值

正如花哨的索引可以被用于获取部分数组,它也可以被用于修改部分数
组。例如,假设我们有一个索引数组,并且希望设置数组中对应的值:

可以用任何的赋值操作来实现,例如:

不过需要注意,操作中重复的索引会导致一些出乎意料的结果产生,如
以下例子所示:
x = np.zeros(10) 
x[[0, 0]] = [4, 6] 
print(x)

[ 6. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
4 去哪里了呢?这个操作首先赋值 x[0] = 4 ,然后赋值 x[0] = 6 ,因
此当然 x[0] 的值为 6
以上还算合理,但是设想以下操作:
 
你可能期望 x[3] 的值为 2 x[4] 的值为 3 ,因为这是这些索引值重复的次数。但是为什么结果不同于我们的预想呢?从概念的角度理解,这
是因为 x[i] += 1 x[i] = x[i] + 1 的简写。 x[i] + 1 计算后,这个结果被赋值给了 x 相应的索引值。记住这个原理后,我们却发现数 组并没有发生多次累加,
而是发生了赋值,显然这不是我们希望的结 果。
因此,如果你希望累加,该怎么做呢?你可以借助通用函数中的 at() 方法(在 NumPy 1.8 以后的版本中可以使用)来实现。进行如下操作:
 
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() 函数,你可以在 NumPy 文档中找到关于
该函数的更多信息。

2.7.5 示例:数据区间划分

你可以用这些方法有效地将数据进行区间划分并手动创建直方图。例
如,假定我们有 1000 个值,希望快速统计分布在每个区间中的数据频
次,可以用 ufunc.at 来计算:
 
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)
# 画出结果 
plt.plot(bins, counts, linestyle='steps');
import warnings
warnings.simplefilter("error")

                         图 2-9 :手动计算的直方图 
 
当然,如果每次需要画直方图你都这么做的话,也是很不明智的。这就
是为什么 Matplotlib 提供了 plt.hist() 方法,该方法仅用一行代码就
实现了上述功能:
这个函数将生成一个和图 2-9 几乎一模一样的图。为了计算区间,
Matplotlib 将使用 np.histogram 函数,该函数的计算功能也和上面执
行的计算类似。接下来比较一下这两种方法:
 
可以看到,我们一行代码的算法比 NumPy 优化过的算法快好几倍!这
是如何做到的呢?如果你深入 np.histogram 源代码(可以在 IPython
中输入 np.histogram?? 查看源代码),就会看到它比我们前面用过的
简单的搜索和计数方法更复杂。这是由于 NumPy 的算法更灵活(需要
适应不同场景),因此在数据点比较大时更能显示出其良好性能:
 

以上比较表明,算法效率并不是一个简单的问题。一个对大数据集非常
有效的算法并不总是小数据集的最佳选择,反之同理(详情请参见
2.8.3 节)。但是自己编写这个算法的好处是可以理解这些基本方法。
你可以利用这些编写好的模块去扩展,以实现一些有意思的自定义操
作。将 Python 有效地用于数据密集型应用中的关键是,当应用场景合
适时知道使用 np.histogram 这样的现成函数,当需要执行更多指定的
操作时也知道如何利用更低级的功能来实现。
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值