数据分析(三)--numpy进阶part2

Numpy进阶part2

1. 花哨的索引

我们知道如何利用简单的索引值(如 arr[0])、切片(如 arr[:5])和布尔掩码(如 arr[arr > 0])获得并修改部分数组。

这里介绍的花哨的索引和前面那些简单的索引非常类似,但是传递的是索引数组,而不是单个标量。花哨的索引让我们能够快速获得并修改复杂的数组值的子数据集。

1.1 探索花哨的索引

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

In[1]: import numpy as np
    rand = np.random.RandomState(42)
    x = rand.randint(100, size=10)
    print(x)
[51 92 14 71 60 20 82 86 74 74]

#假设我们希望获得三个不同的元素,可以用以下方式实现:
In[2]: [x[3], x[7], x[2]]
Out[2]: [71, 86, 14]

#另外一种方法是通过传递索引的单个列表或数组来获得同样的结果:
In[3]: ind = [3, 7, 4]
    x[ind]
Out[3]: array([71, 86, 60])

利用花哨的索引,结果的形状与索引数组的形状一致,而不是与被索引数组的形状一致:

In[4]: ind = np.array([[3, 7],[4, 5]])
    x[ind]
Out[4]: array([[71, 86],[60, 20]])

# 花哨的索引也对多个维度适用。假设我们有以下数组:
In[5]: X = np.arange(12).reshape((3, 4))
      X
Out[5]: array([[ 0, 1, 2, 3],
              [ 4, 5, 6, 7],
              [ 8, 9, 10, 11]])

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

In[6]: row = np.array([0, 1, 2])
    col = np.array([2, 1, 3])
    X[row, col]
Out[6]: array([ 2, 5, 11])

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

In[7]: X[row[:, np.newaxis], col]
Out[7]: array([[ 2, 1, 3],
              [ 6, 5, 7],
              [10, 9, 11]])

# 这里,每一行的值都与每一列的向量配对,正如我们看到的广播的算术运算:
In[8]: row[:, np.newaxis] * col
Out[8]: array([[0, 0, 0],
              [2, 1, 3],
              [4, 2, 6]])

这里特别需要记住的是,花哨的索引返回的值反映的是广播后的索引数组的形状,而不是被索引的数组的形状。

1.2 组合索引

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

In[9]: print(X)
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]

# 可以将花哨的索引和简单的索引组合使用:
In[10]: X[2, [2, 0, 1]]
Out[10]: array([10, 8, 9])

# 也可以将花哨的索引和切片组合使用:
In[11]: X[1:, [2, 0, 1]]
Out[11]: array([[ 6, 4, 5],
               [10, 8, 9]])

# 更可以将花哨的索引和掩码组合使用:

In[12]: mask = np.array([1, 0, 1, 0], dtype=bool)
        X[row[:, np.newaxis], mask]
Out[12]: array([[ 0, 2],
               [ 4, 6],
               [ 8, 10]])

索引选项的组合可以实现非常灵活的获取和修改数组元素的操作。

1.3 用花哨的索引修改值

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

In[18]: 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]

# 可以用任何的赋值操作来实现,例如:
In[19]: x[i] -= 10
    print(x)
[ 0 89 89 3 89 5 6 7 89 9]

不过需要注意,操作中重复的索引会导致一些意外发生,如:

In[20]: 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。以上还算合理,但是设想以下操作:

In[21]: i = [2, 3, 3, 4, 4, 4]
    x[i] += 1
    x
Out[21]: array([ 6., 0., 1., 1., 1., 0., 0., 0., 0., 0.])

你可能期望 x[3] 的值为 2, x[4] 的值为 3,因为这是这些索引值重复的次数。但是为什么结果不同于我们的预想呢?从概念的角度理解,这是因为 x[i] += 1 是 x[i] = x[i] + 1 的简写。 x[i] + 1 计算后,这个结果被赋值给了 x 相应的索引值。记住这个原理后,我们却发现数组并没有发生多次累加,而是发生了赋值,显然这不是我们希望的结果。

因此,如果你希望累加,该怎么做呢?你可以借助通用函数中的 at() 方法(在 NumPy 1.8以后的版本中可以使用)来实现。进行如下操作:

In[22]: 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() 函数。

2. 数组的排序

如果你曾经学习过计算机相关的课程,你可能对插入排序、 选择排序、 归并排序、 快速排序、 冒泡排序等这些算法有所了解,但是这些都是为了实现一个类似的任务:对一个列表或数组进行排序。

2.1 NumPy中的快速排序

尽管 Python 有内置的 sort 和 sorted 函数可以对列表进行排序,但是这里不会介绍这两个函数,因为NumPy 的 np.sort 函数实际上效率更高。默认情况下, np.sort 的排序算法是快速排序,其算法复杂度为 [N log N ],另外也可以选择归并排序和堆排序。

对于大多数应用场景,默认的快速排序已经足够高效了。如果想在不修改原始输入数组的基础上返回一个排好序的数组,可以使用 np.sort

In[5]: x = np.array([2, 1, 4, 3, 5])
    np.sort(x)
Out[5]: array([1, 2, 3, 4, 5])

# 如果希望用排好序的数组替代原始数组,可以使用数组的 sort 方法:
In[6]: x.sort()
    print(x)
[1 2 3 4 5]

另外一个相关的函数是 argsort,该函数返回的是原始数组排好序的索引值:

In[7]: x = np.array([2, 1, 4, 3, 5])
    i = np.argsort(x)
    print(i)
[1 0 3 2 4]

以上结果的第一个元素是数组中最小元素的索引值,第二个值给出的是次小元素的索引值,以此类推。这些索引值可以被用于(通过花哨的索引)创建有序的数组:

In[8]: x[i]

Out[8]: array([1, 2, 3, 4, 5])

沿着行或列排序:NumPy 排序算法的一个有用的功能是通过 axis 参数,沿着多维数组的行或列进行排序,例如:

In[9]: rand = np.random.RandomState(42)
    X = rand.randint(0, 10, (4, 6))
    print(X)
[[6 3 7 4 6 9]
[2 6 7 4 3 7]
[7 2 5 4 1 7]
[5 1 4 0 9 5]]

In[10]: # 对X的每一列排序
    np.sort(X, axis=0)
Out[10]: array([[2, 1, 4, 0, 1, 5],
               [5, 2, 5, 4, 3, 7],
               [6, 3, 7, 4, 6, 7],
               [7, 6, 7, 4, 9, 9]])
In[11]: # 对X每一行排序
    np.sort(X, axis=1)
Out[11]: array([[3, 4, 6, 6, 7, 9],
               [2, 3, 4, 6, 7, 7],
               [1, 2, 4, 5, 7, 7],
               [0, 1, 4, 5, 5, 9]])

需要记住的是,这种处理方式是将行或列当作独立的数组,任何行或列的值之间的关系将会丢失!

2.2 部分排序:分隔

有时候我们不希望对整个数组进行排序,仅仅希望找到数组中第 K 小的值, NumPy 的np.partition 函数提供了该功能。 np.partition 函数的输入是数组和数字 K,输出结果是一个新数组,最左边是第 K 小的值,往右是任意顺序的其他值:

In[12]: x = np.array([7, 2, 3, 1, 6, 5, 4])
    np.partition(x, 3)
Out[12]: array([2, 1, 3, 4, 6, 5, 7])

请注意,结果数组中前三个值是数组中最小的三个值,剩下的位置是原始数组剩下的值。在这两个分隔区间中,元素都是任意排列的。与排序类似,也可以沿着多维数组任意的轴进行分隔:

In[13]: np.partition(X, 2, axis=1)
Out[13]: array([[3, 4, 6, 7, 6, 9],
               [2, 3, 4, 7, 6, 7],
               [1, 2, 4, 5, 7, 7],
               [0, 1, 4, 5, 9, 5]])

输出结果是一个数组,该数组每一行的前两个元素是该行最小的两个值,每行的其他值分布在剩下的位置。

最后NumPy的结构化数组就不介绍了,因为工作中用的很少,因为如果有大量的结构化数据要处理,那将使用接下来的另外一个模块:Pandas来处理。

猜你喜欢

转载自blog.csdn.net/u014793102/article/details/80848063
今日推荐