第89天:NumPy 排序和篩選函數

by 閒歡

對於數據分析來說,排序和篩選數據是不可或缺的一部分內容。NumPy 也提供了多種排序和篩選函數,本文就來介紹一下 NumPy 常見的排序和篩選函數。

排序函數

NumPy 中提供了排序相關的函數。排序函數已經幫助我們實現了不同的排序算法,我們只需要拿來直接使用就行。每個排序算法的執行速度,時間複雜度,空間複雜度和算法的穩定性都不相同,我們來看看常見的幾種排序算法的比較。

排序算法 速度 時間複雜度 空間複雜度 穩定性
quicksort(快速排序) 1 o(n^2) 0
mergesort(歸併排序) 2 O(n*log(n)) ~n/2
heapsort(堆排序) 3 O(n*log(n)) 0

numpy.sort(a, axis, kind, order)

這個排序函數有4個參數,我們來看看參數的說明:

參數 說明
a 要排序的數組
axis 排序數組的軸,如果沒有數組會被展開,沿着最後的軸排序。axis=0 按列排序,axis=1 按行排序
kind 排序類型,有 quicksortmergesortheapsortstable 幾種,默認爲quicksort(快速排序)
order 排序的字段,針對包含字段的數組

我們來看看實例:

import numpy as np
import time

a = np.array([[3, 7, 12, 45], [9, 1, 0, 34]])
print("初始數組:")
print(a)
print('\n')

print(np.char.center('調用 sort() 函數,默認快速排序', 15, '*'))
print(np.sort(a))
print('\n')

print(np.char.center('按列排序', 15, '*'))
print(np.sort(a, axis=0))
print('\n')

b = np.random.randint(1, 1000, size=[10000, 10000])

print(np.char.center('快速排序時間', 15, '*'))
t1 = time.time()
np.sort(b)
t2 = time.time()
print(t2 - t1)
print('\n')

print(np.char.center('堆排序時間', 15, '*'))
t3 = time.time()
np.sort(b, -1, 'heapsort')
t4 = time.time()
print(t4 - t3)
print('\n')

print(np.char.center('歸併排序時間', 15, '*'))
t5 = time.time()
np.sort(b, -1, 'mergesort')
t6 = time.time()
print(t6 - t5)
print('\n')

# 根據字段排序
dt = np.dtype([('name', 'S10'), ('age', int)])
c = np.array([("raju", 21), ("anil", 25), ("ravi", 17), ("amar", 27)], dtype=dt)
print(np.char.center('根據字段排序的數組', 15, '*'))
print(c)
print('\n')

print(np.char.center('按 name 排序', 15, '*'))
print(np.sort(c, order='name'))

# 返回
初始數組:
[[ 3  7 12 45]
 [ 9  1  0 34]]
 
調用 sort() 函數,默認
[[ 3  7 12 45]
 [ 0  1  9 34]]
 
******按列排序*****
[[ 3  1  0 34]
 [ 9  7 12 45]]
 
*****快速排序時間****
5.470074892044067

*****堆排序時間*****
6.988600015640259

*****歸併排序時間****
5.784327983856201

***根據字段排序的數組***
[(b'raju', 21) (b'anil', 25) (b'ravi', 17) (b'amar', 27)]

***按 name 排序***
[(b'amar', 27) (b'anil', 25) (b'raju', 21) (b'ravi', 17)]

在例子中,我們首先使用了默認的按橫軸的快速排序算法,可以看到每個數組都是橫向排序的。

接下來,我們多加了一個排序的參數,表示按縱軸排序,我們可以從結果中看到,兩個數組中對應位置的元素都按照升序排列了。

接着我們隨機生成了一個數據量大的多維數組,然後使用三種排序方式,打印了它們排序的時間,從結果中我們可以看到快速排序最快,其次是歸併排序,最後是堆排序。需要注意一點的是,有些排序算法不穩定,可能會導致每次運行的結果不一樣。另外,數據量也可能會影響不同排序算法排序的效率。

最後我們創建了一個帶字段的數組,然後按照 name 字段排序。

numpy.argsort()

函數對輸入數組沿給定軸執行間接排序,並使用指定排序類型返回數據的索引數組。 這個索引數組用於構造排序後的數組。

我們來看實例:

import numpy as np

a = np.array([3, 4, 2])
print("初始數組:")
print(a)
print('\n')

print(np.char.center('調用 argsort() 函數', 15, '*'))
b = np.argsort(a)
print(b)
print('\n')

print(np.char.center('以排序後的順序重構原數組', 15, '*'))
print(a[b])
print('\n')

# 返回
初始數組:
[3 4 2]

調用 argsort() 函數
[2 0 1]

**以排序後的順序重構原數組*
[2 3 4]

在上面例子中,我們調用 argsort() 函數後,返回了初始數組的排序後的索引。然後我們用排序後的索引數組重構原數組,得到排序後的數組。

numpy.lexsort()

函數使用鍵序列執行間接排序。 鍵可以看作是電子表格中的一列。 該函數返回一個索引數組,使用它可以獲得排序數據。 注意,最後一個鍵恰好是 sort 的主鍵。

對於這個函數,我們假設一種場景:現在有語文和數學考試成績以及總成績,我們需要對成績做個排序,排序原則爲總分優先,總分相同的語文高的排前面。

實現的代碼如下:

import numpy as np

print(np.char.center('lexsort() 函數', 15, '*'))
# 錄入了四位同學的成績
math = (10, 20, 50, 10)
chinese = (30, 50, 40, 60)
total = (40, 70, 90, 70)
# 將優先級高的項放在後面
ind = np.lexsort((math, chinese, total))

for i in ind:
    print(total[i], chinese[i], math[i])
    
# 返回
**lexsort() 函數*
40 30 10
70 50 20
70 60 10
90 40 50

例子中我們將參數由優先級從低到高傳入,優先級最高的放在最後。最後得到4個同學的成績排序。

numpy.msort()

數組按第一個軸排序,返回排序後的數組副本。

這個排序相當於 numpy.sort(a, axis=0)。很好理解。我們直接來看實例:

import numpy as np

print(np.char.center('msort() 函數', 20, '*'))
msa = np.array([[3, 7, 12, 45], [9, 1, 0, 34]])
print(np.msort(msa))

# 返回
*****msort() 函數*****
[[ 3  1  0 34]
 [ 9  7 12 45]]

numpy.partition()

指定一個數,對數組進行分區。

通俗點說,就是指定一個數,以這個數爲中心,將其他數分別放在這個數的兩邊。

我們來看實例:

import numpy as np

print(np.char.center('partition() 函數', 20, '*'))
pta = np.array([3, 7, 12, 45, 15, 0])
print(np.partition(pta, 2))
print('\n')
print(np.partition(pta, (2, 4)))
print('\n')

# 返回
***partition() 函數***
[ 0  3  7 45 15 12]
[ 0  3  7 12 15 45]

在第一次排序時,我們選中了索引爲2的數字7作爲中心,將小於7的數放在左邊,大於7的數放在右邊。在第二次排序時,我們選擇了索引爲2的數字7和索引爲4的數字45,將小於7的數放在左邊,大於7小於45的數放在中間,大於45的數放在右邊。

篩選函數

下面我們來看幾個常見的篩選函數,這些函數用於在數組中查找特定條件的元素。

numpy.argmax()

返回沿給定軸的最大值索引。

注意,索引的值是從0開始計算的。

我們來看實例:

import numpy as np

a = np.array([[30, 40, 70], [80, 20, 10], [50, 90, 60]])
print(np.char.center('初始數組', 20, '*'))
print(a)
print('\n')

print(np.char.center('調用 argmax() 函數', 20, '*'))
print(np.argmax(a))
print('\n')

print(np.char.center('展開數組', 20, '*'))
print(a.flatten())
print('\n')

print(np.char.center('沿0軸的最大索引', 20, '*'))
print(np.argmax(a, 0))
print('\n')

print(np.char.center('沿1軸的最大索引', 20, '*'))
print(np.argmax(a, 1))
print('\n')

# 返回
********初始數組********
[[30 40 70]
 [80 20 10]
 [50 90 60]]
***調用 argmax() 函數***
7
********展開數組********
[30 40 70 80 20 10 50 90 60]
******沿0軸的最大索引******
[1 2 0]
******沿1軸的最大索引******
[2 0 1]

numpy.argmin()

返回沿給定軸的最小值索引。

注意,索引的值是從0開始計算的。

我們來看實例:

import numpy as np

a = np.array([[30, 40, 70], [80, 20, 10], [50, 90, 60]])
print(np.char.center('初始數組', 20, '*'))
print(a)
print('\n')

print(np.char.center('調用 argmin() 函數', 20, '*'))
print(np.argmin(a))
print('\n')

print(np.char.center('沿0軸的最小索引', 20, '*'))
print(np.argmin(a, 0))
print('\n')

print(np.char.center('沿1軸的最小索引', 20, '*'))
print(np.argmin(a, 1))
print('\n')

# 返回
********初始數組********
[[30 40 70]
 [80 20 10]
 [50 90 60]]
***調用 argmin() 函數***
5
******沿0軸的最小索引******
[0 1 1]
******沿1軸的最小索引******
[0 2 0]

numpy.nonzero()

返回輸入數組中非零元素的索引。

  • 只有a中非零元素纔會有索引值,那些零值元素沒有索引值;
  • 返回的索引值數組是一個2維tuple數組,該tuple數組中包含一維的array數組。其中,一維array向量的個數與a的維數是一致的。
  • 索引值數組的每一個array均是從一個維度上來描述其索引值。比如,如果a是一個二維數組,則索引值數組有兩個array,第一個array從行維度來描述索引值;第二個array從列維度來描述索引值。
  • 該np.transpose(np.nonzero(x))
    函數能夠描述出每一個非零元素在不同維度的索引值。

我們來看實例:

import numpy as np

b = np.array([[30, 40, 0], [0, 20, 10], [50, 0, 60]])
print(np.char.center('我們的數組是', 20, '*'))
print(b)
print(np.char.center('調用 nonzero() 函數', 20, '*'))
c = np.nonzero(b)
print(c)
print(np.transpose(np.nonzero(b)))

# 返回
*******我們的數組是*******
[[30 40  0]
 [ 0 20 10]
 [50  0 60]]
**調用 nonzero() 函數***
(array([0, 0, 1, 1, 2, 2]), array([0, 1, 1, 2, 0, 2]))
[[0 0]
 [0 1]
 [1 1]
 [1 2]
 [2 0]
 [2 2]]

我們通過 np.transpose() 方法轉換後看起來比較直觀,注意這裏的索引是從0開始算的。

numpy.where()

返回輸入數組中滿足給定條件的元素的索引。

我們來看實例:

import numpy as np

b = np.array([[30, 40, 0], [0, 20, 10], [50, 0, 60]])

print(np.char.center('調用 where() 函數', 20, '*'))
print(np.where(b > 20))
print(np.transpose(np.where(b > 20)))

# 返回
***調用 where() 函數****
(array([0, 0, 2, 2]), array([0, 1, 0, 2]))
[[0 0]
 [0 1]
 [2 0]
 [2 2]]

這裏面我們輸入的條件是大於20,數組中大於20的數的索引都被查找出來了。

numpy.extract()

根據某個條件從數組中抽取元素,返回滿條件的元素。

我們來看實例:

import numpy as np

x = np.arange(9.).reshape(3,  3)
print(np.char.center('我們的數組是', 20, '*'))
print(x)
# 定義條件, 選擇偶數元素
condition = np.mod(x, 2) == 0
print(np.char.center('按元素的條件值', 20, '*'))
print(condition)
print(np.char.center('使用條件提取元素', 20, '*'))
print(np.extract(condition, x))

# 返回
*******我們的數組是*******
[[0. 1. 2.]
 [3. 4. 5.]
 [6. 7. 8.]]
******按元素的條件值*******
[[ True False  True]
 [False  True False]
 [ True False  True]]
******使用條件提取元素******
[0. 2. 4. 6. 8.]

例子中,我們先定義了一個條件,就是選擇偶數。然後我們可以打印這個數組每個元素是否滿足條件。最後我們調用 extract() 方法返回滿足條件的元素。注意這裏返回的是元素,而不是元素的索引。

總結

本文向大家介紹了 NumPy 的排序與篩選函數,熟練掌握和運用這些函數可以很輕鬆地幫助我們達到特定的目標,而不用自己去重複造輪子。大家在後續的代碼中遇到類似的情況應該要優先想到這些函數。

參考

https://numpy.org/devdocs/reference/routines.sort.html

文中示例代碼:python-100-days

關注公衆號:python技術,回覆"python"一起學習交流

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章