用 NumPy 在 Python 中處理數(shù)字
這篇文章討論了安裝 NumPy,然后創(chuàng)建、讀取和排序 NumPy 數(shù)組。
NumPy(即 Numerical Python)是一個(gè)庫,它使得在 Python 中對(duì)線性數(shù)列和矩陣進(jìn)行統(tǒng)計(jì)和集合操作變得容易。我在 Python 數(shù)據(jù)類型的筆記中介紹過,它比 Python 的列表快幾個(gè)數(shù)量級(jí)。NumPy 在數(shù)據(jù)分析和科學(xué)計(jì)算中使用得相當(dāng)頻繁。
我將介紹安裝 NumPy,然后創(chuàng)建、讀取和排序 NumPy 數(shù)組。NumPy 數(shù)組也被稱為 ndarray,即 N 維數(shù)組的縮寫。
安裝 NumPy
使用 pip
安裝 NumPy 包非常簡(jiǎn)單,可以像安裝其他軟件包一樣進(jìn)行安裝:
pip install numpy
安裝了 NumPy 包后,只需將其導(dǎo)入你的 Python 文件中:
import numpy as np
將 numpy
以 np
之名導(dǎo)入是一個(gè)標(biāo)準(zhǔn)的慣例,但你可以不使用 np
,而是使用你想要的任何其他別名。
為什么使用 NumPy? 因?yàn)樗?Python 列表要快好幾個(gè)數(shù)量級(jí)
當(dāng)涉及到處理大量的數(shù)值時(shí),NumPy 比普通的 Python 列表快幾個(gè)數(shù)量級(jí)。為了看看它到底有多快,我首先測(cè)量在普通 Python 列表上進(jìn)行 min()
和 max()
操作的時(shí)間。
我將首先創(chuàng)建一個(gè)具有 999,999,999 項(xiàng)的 Python 列表:
>>> my_list = range(1, 1000000000)
>>> len(my_list)
999999999
現(xiàn)在我將測(cè)量在這個(gè)列表中找到最小值的時(shí)間:
>>> start = time.time()
>>> min(my_list)
1
>>> print('Time elapsed in milliseconds: ' + str((time.time() - start) * 1000))
Time elapsed in milliseconds: 27007.00879096985
這花了大約 27,007 毫秒,也就是大約 27 秒。這是個(gè)很長(zhǎng)的時(shí)間。現(xiàn)在我試著找出尋找最大值的時(shí)間:
>>> start = time.time()
>>> max(my_list)
999999999
>>> print('Time elapsed in milliseconds: ' + str((time.time() - start) * 1000))
Time elapsed in milliseconds: 28111.071348190308
這花了大約 28,111 毫秒,也就是大約 28 秒。
現(xiàn)在我試試用 NumPy 找到最小值和最大值的時(shí)間:
>>> my_list = np.arange(1, 1000000000)
>>> len(my_list)
999999999
>>> start = time.time()
>>> my_list.min()
1
>>> print('Time elapsed in milliseconds: ' + str((time.time() - start) * 1000))
Time elapsed in milliseconds: 1151.1778831481934
>>>
>>> start = time.time()
>>> my_list.max()
999999999
>>> print('Time elapsed in milliseconds: ' + str((time.time() - start) * 1000))
Time elapsed in milliseconds: 1114.8970127105713
找到最小值花了大約 1151 毫秒,找到最大值 1114 毫秒。這大約是 1 秒。
正如你所看到的,使用 NumPy 可以將尋找一個(gè)大約有 10 億個(gè)值的列表的最小值和最大值的時(shí)間 從大約 28 秒減少到 1 秒。這就是 NumPy 的強(qiáng)大之處。
使用 Python 列表創(chuàng)建 ndarray
有幾種方法可以在 NumPy 中創(chuàng)建 ndarray。
你可以通過使用元素列表來創(chuàng)建一個(gè) ndarray:
>>> my_ndarray = np.array([1, 2, 3, 4, 5])
>>> print(my_ndarray)
[1 2 3 4 5]
有了上面的 ndarray 定義,我將檢查幾件事。首先,上面定義的變量的類型是 numpy.ndarray
。這是所有 NumPy ndarray 的類型:
>>> type(my_ndarray)
<class 'numpy.ndarray'>
這里要注意的另一件事是 “形狀”。ndarray 的形狀是 ndarray 的每個(gè)維度的長(zhǎng)度。你可以看到,my_ndarray
的形狀是 (5,)
。這意味著 my_ndarray
包含一個(gè)有 5 個(gè)元素的維度(軸)。
>>> np.shape(my_ndarray)
(5,)
數(shù)組中的維數(shù)被稱為它的 “秩”。所以上面的 ndarray 的秩是 1。
我將定義另一個(gè) ndarray my_ndarray2
作為一個(gè)多維 ndarray。那么它的形狀會(huì)是什么呢?請(qǐng)看下面:
>>> my_ndarray2 = np.array([(1, 2, 3), (4, 5, 6)])
>>> np.shape(my_ndarray2)
(2, 3)
這是一個(gè)秩為 2 的 ndarray。另一個(gè)要檢查的屬性是 dtype
,也就是數(shù)據(jù)類型。檢查我們的 ndarray 的 dtype
可以得到以下結(jié)果:
>>> my_ndarray.dtype
dtype('int64')
int64
意味著我們的 ndarray 是由 64 位整數(shù)組成的。NumPy 不能創(chuàng)建混合類型的 ndarray,必須只包含一種類型的元素。如果你定義了一個(gè)包含混合元素類型的 ndarray,NumPy 會(huì)自動(dòng)將所有的元素類型轉(zhuǎn)換為可以包含所有元素的最高元素類型。
例如,創(chuàng)建一個(gè) int
和 float
的混合序列將創(chuàng)建一個(gè) float64
的 ndarray:
>>> my_ndarray2 = np.array([1, 2.0, 3])
>>> print(my_ndarray2)
[1. 2. 3.]
>>> my_ndarray2.dtype
dtype('float64')
另外,將其中一個(gè)元素設(shè)置為 string
將創(chuàng)建 dtype
等于 <U21
的字符串 ndarray,意味著我們的 ndarray 包含 unicode 字符串:
>>> my_ndarray2 = np.array([1, '2', 3])
>>> print(my_ndarray2)
['1' '2' '3']
>>> my_ndarray2.dtype
dtype('<U21')
size
屬性將顯示我們的 ndarray 中存在的元素總數(shù):
>>> my_ndarray = np.array([1, 2, 3, 4, 5])
>>> my_ndarray.size
5
使用 NumPy 方法創(chuàng)建 ndarray
如果你不想直接使用列表來創(chuàng)建 ndarray,還有幾種可以用來創(chuàng)建它的 NumPy 方法。
你可以使用 np.zeros()
來創(chuàng)建一個(gè)填滿 0 的 ndarray。它需要一個(gè)“形狀”作為參數(shù),這是一個(gè)包含行數(shù)和列數(shù)的列表。它還可以接受一個(gè)可選的 dtype
參數(shù),這是 ndarray 的數(shù)據(jù)類型:
>>> my_ndarray = np.zeros([2,3], dtype=int)
>>> print(my_ndarray)
[[0 0 0]
[0 0 0]]
你可以使用 np. ones()
來創(chuàng)建一個(gè)填滿 1
的 ndarray:
>>> my_ndarray = np.ones([2,3], dtype=int)
>>> print(my_ndarray)
[[1 1 1]
[1 1 1]]
你可以使用 np.full()
來給 ndarray 填充一個(gè)特定的值:
>>> my_ndarray = np.full([2,3], 10, dtype=int)
>>> print(my_ndarray)
[[10 10 10]
[10 10 10]]
你可以使用 np.eye()
來創(chuàng)建一個(gè)單位矩陣 / ndarray,這是一個(gè)沿主對(duì)角線都是 1
的正方形矩陣。正方形矩陣是一個(gè)行數(shù)和列數(shù)相同的矩陣:
>>> my_ndarray = np.eye(3, dtype=int)
>>> print(my_ndarray)
[[1 0 0]
[0 1 0]
[0 0 1]]
你可以使用 np.diag()
來創(chuàng)建一個(gè)沿對(duì)角線有指定數(shù)值的矩陣,而在矩陣的其他部分為 0
:
>>> my_ndarray = np.diag([10, 20, 30, 40, 50])
>>> print(my_ndarray)
[[10 0 0 0 0]
[ 0 20 0 0 0]
[ 0 0 30 0 0]
[ 0 0 0 40 0]
[ 0 0 0 0 50]]
你可以使用 np.range()
來創(chuàng)建一個(gè)具有特定數(shù)值范圍的 ndarray。它是通過指定一個(gè)整數(shù)的開始和結(jié)束(不包括)范圍以及一個(gè)步長(zhǎng)來創(chuàng)建的:
>>> my_ndarray = np.arange(1, 20, 3)
>>> print(my_ndarray)
[ 1 4 7 10 13 16 19]
讀取 ndarray
ndarray 的值可以使用索引、分片或布爾索引來讀取。
使用索引讀取 ndarray 的值
在索引中,你可以使用 ndarray 的元素的整數(shù)索引來讀取數(shù)值,就像你讀取 Python 列表一樣。就像 Python 列表一樣,索引從 0
開始。
例如,在定義如下的 ndarray 中:
>>> my_ndarray = np.arange(1, 20, 3)
第四個(gè)值將是 my_ndarray[3]
,即 10
。最后一個(gè)值是 my_ndarray[-1]
,即 19
:
>>> my_ndarray = np.arange(1, 20, 3)
>>> print(my_ndarray[0])
1
>>> print(my_ndarray[3])
10
>>> print(my_ndarray[-1])
19
>>> print(my_ndarray[5])
16
>>> print(my_ndarray[6])
19
使用分片讀取 ndarray
你也可以使用分片來讀取 ndarray 的塊。分片的工作方式是用冒號(hào)(:
)操作符指定一個(gè)開始索引和一個(gè)結(jié)束索引。然后,Python 將獲取該開始和結(jié)束索引之間的 ndarray 片斷:
>>> print(my_ndarray[:])
[ 1 4 7 10 13 16 19]
>>> print(my_ndarray[2:4])
[ 7 10]
>>> print(my_ndarray[5:6])
[16]
>>> print(my_ndarray[6:7])
[19]
>>> print(my_ndarray[:-1])
[ 1 4 7 10 13 16]
>>> print(my_ndarray[-1:])
[19]
分片創(chuàng)建了一個(gè) ndarray 的引用(或視圖)。這意味著,修改分片中的值也會(huì)改變?cè)?ndarray 的值。
比如說:
>>> my_ndarray[-1:] = 100
>>> print(my_ndarray)
[ 1 4 7 10 13 16 100]
對(duì)于秩超過 1 的 ndarray 的分片,可以使用 [行開始索引:行結(jié)束索引, 列開始索引:列結(jié)束索引]
語法:
>>> my_ndarray2 = np.array([(1, 2, 3), (4, 5, 6)])
>>> print(my_ndarray2)
[[1 2 3]
[4 5 6]]
>>> print(my_ndarray2[0:2,1:3])
[[2 3]
[5 6]]
使用布爾索引讀取 ndarray 的方法
讀取 ndarray 的另一種方法是使用布爾索引。在這種方法中,你在方括號(hào)內(nèi)指定一個(gè)過濾條件,然后返回符合該條件的 ndarray 的一個(gè)部分。
例如,為了獲得一個(gè) ndarray 中所有大于 5 的值,你可以指定布爾索引操作 my_ndarray[my_ndarray > 5]
。這個(gè)操作將返回一個(gè)包含所有大于 5 的值的 ndarray:
>>> my_ndarray = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
>>> my_ndarray2 = my_ndarray[my_ndarray > 5]
>>> print(my_ndarray2)
[ 6 7 8 9 10]
例如,為了獲得一個(gè) ndarray 中的所有偶數(shù)值,你可以使用如下的布爾索引操作:
>>> my_ndarray2 = my_ndarray[my_ndarray % 2 == 0]
>>> print(my_ndarray2)
[ 2 4 6 8 10]
而要得到所有的奇數(shù)值,你可以用這個(gè)方法:
>>> my_ndarray2 = my_ndarray[my_ndarray % 2 == 1]
>>> print(my_ndarray2)
[1 3 5 7 9]
ndarray 的矢量和標(biāo)量算術(shù)
NumPy 的 ndarray 允許進(jìn)行矢量和標(biāo)量算術(shù)操作。在矢量算術(shù)中,在兩個(gè) ndarray 之間進(jìn)行一個(gè)元素的算術(shù)操作。在標(biāo)量算術(shù)中,算術(shù)運(yùn)算是在一個(gè) ndarray 和一個(gè)常數(shù)標(biāo)量值之間進(jìn)行的。
如下的兩個(gè) ndarray:
>>> my_ndarray = np.array([1, 2, 3, 4, 5])
>>> my_ndarray2 = np.array([6, 7, 8, 9, 10])
如果你將上述兩個(gè) ndarray 相加,就會(huì)產(chǎn)生一個(gè)兩個(gè) ndarray 的元素相加的新的 ndarray。例如,產(chǎn)生的 ndarray 的第一個(gè)元素將是原始 ndarray 的第一個(gè)元素相加的結(jié)果,以此類推:
>>> print(my_ndarray2 + my_ndarray)
[ 7 9 11 13 15]
這里,7
是 1
和 6
的和,這是我相加的 ndarray 中的前兩個(gè)元素。同樣,15
是 5
和10
之和,是最后一個(gè)元素。
請(qǐng)看以下算術(shù)運(yùn)算:
>>> print(my_ndarray2 - my_ndarray)
[5 5 5 5 5]
>>>
>>> print(my_ndarray2 * my_ndarray)
[ 6 14 24 36 50]
>>>
>>> print(my_ndarray2 / my_ndarray)
[6. 3.5 2.66666667 2.25 2. ]
在 ndarray 中加一個(gè)標(biāo)量值也有類似的效果,標(biāo)量值被添加到 ndarray 的所有元素中。這被稱為“廣播”:
>>> print(my_ndarray + 10)
[11 12 13 14 15]
>>>
>>> print(my_ndarray - 10)
[-9 -8 -7 -6 -5]
>>>
>>> print(my_ndarray * 10)
[10 20 30 40 50]
>>>
>>> print(my_ndarray / 10)
[0.1 0.2 0.3 0.4 0.5]
ndarray 的排序
有兩種方法可以對(duì) ndarray 進(jìn)行原地或非原地排序。原地排序會(huì)對(duì)原始 ndarray 進(jìn)行排序和修改,而非原地排序會(huì)返回排序后的 ndarray,但不會(huì)修改原始 ndarray。我將嘗試這兩個(gè)例子:
>>> my_ndarray = np.array([3, 1, 2, 5, 4])
>>> my_ndarray.sort()
>>> print(my_ndarray)
[1 2 3 4 5]
正如你所看到的,sort()
方法對(duì) ndarray 進(jìn)行原地排序,并修改了原數(shù)組。
還有一個(gè)方法叫 np.sort()
,它對(duì)數(shù)組進(jìn)行非原地排序:
>>> my_ndarray = np.array([3, 1, 2, 5, 4])
>>> print(np.sort(my_ndarray))
[1 2 3 4 5]
>>> print(my_ndarray)
[3 1 2 5 4]
正如你所看到的,np.sort()
方法返回一個(gè)已排序的 ndarray,但沒有修改它。
總結(jié)
我已經(jīng)介紹了很多關(guān)于 NumPy 和 ndarray 的內(nèi)容。我談到了創(chuàng)建 ndarray,讀取它們的不同方法,基本的向量和標(biāo)量算術(shù),以及排序。NumPy 還有很多東西可以探索,包括像 union()
和 intersection()
這樣的集合操作,像 min()
和 max()
這樣的統(tǒng)計(jì)操作,等等。
我希望我上面演示的例子是有用的。祝你在探索 NumPy 時(shí)愉快。