詳解NumPy庫(kù),強(qiáng)大的Python科學(xué)計(jì)算包
哈嘍,大家好。
之前寫了幾篇 Python 基礎(chǔ)的文章,效果不錯(cuò)。為感謝大家的支持,月底搞一波抽獎(jiǎng)送書活動(dòng)。
閑話少敘,今天來(lái)詳解一個(gè) Python 庫(kù) —— NumPy。
NumPy是 Python 科學(xué)計(jì)算的基本包,幾乎所有用 Python 工作的科學(xué)家都利用了NumPy的強(qiáng)大功能。此外,它也廣泛應(yīng)用在開(kāi)源的項(xiàng)目中,如:Pandas、Seaborn、Matplotlib、scikit-learn等。

舉個(gè)栗子,直觀感下NumPy的強(qiáng)大。

上圖是計(jì)算均方差的公式,其中Y_prediction和Y是數(shù)組。
下面是用NumPy代碼,一行便可完成。

NumPy結(jié)合可視化庫(kù),可以用幾行代碼,繪制出下面的數(shù)學(xué)函數(shù)圖

記得學(xué)高中學(xué)數(shù)學(xué)的時(shí)候,畫函數(shù)圖都要自己在本上描點(diǎn),而現(xiàn)在用NumPy,幾行代碼就搞定,既快又準(zhǔn)確。
簡(jiǎn)單認(rèn)識(shí)NumPy后,下面進(jìn)入詳解
1. 與list的區(qū)別
NumPy和list都是數(shù)組結(jié)構(gòu),那它們之間有什么區(qū)別呢?
NumPy數(shù)組中所有元素的數(shù)據(jù)類型是相同的。NumPy底層經(jīng)過(guò)充分優(yōu)化的 C 語(yǔ)言代碼,計(jì)算性能比list高。NumPy提供了全面的數(shù)學(xué)函數(shù)可以直接應(yīng)用在NumPy數(shù)組上。
2. 創(chuàng)建數(shù)組
NumPy中定義的數(shù)組叫ndarray,n-dimensions-array 即:n維數(shù)組。
用np.array()函數(shù)可以創(chuàng)建NumPy數(shù)組
>>>?import?numpy?as?np
>>>?a?=?np.array([1,?2,?3])?#創(chuàng)建ndarray數(shù)組
>>>?a
array([1,?2,?3])
>>>?type(a)
<class?'numpy.ndarray'>
a就是NumPy數(shù)組,也是numpy.ndarray類對(duì)象,該類定義了幾個(gè)常用的屬性
ndarray.ndim:維度的數(shù)量,二位數(shù)組ndim是 2ndarray.shape:元組,每位代表該維度上元素個(gè)數(shù),元組長(zhǎng)度等于ndimndarray.size:數(shù)組中元素總數(shù)ndarray.dtype:數(shù)組中元素的數(shù)據(jù)類型ndarray.itemsize:數(shù)組中元素存儲(chǔ)大小(以字節(jié)為單位)
>>>?a?=?np.array([[1,2,3],?[4,5,6]])
>>>?a
array([[1,?2,?3],
???????[4,?5,?6]])
>>>?a.ndim
2
>>>?a.shape
(2,?3)
>>>?a.size
6
>>>?a.dtype
dtype('int64')
>>>?a.itemsize
8
除了np.array()創(chuàng)建數(shù)組外,還有下面的方式創(chuàng)建數(shù)組
>>>?np.zeros((2,3))?#?以0填充的二維數(shù)組
array([[0.,?0.,?0.],
???????[0.,?0.,?0.]])
>>>?np.ones((2,3))?#?以1填充的二維數(shù)組
array([[1.,?1.,?1.],
???????[1.,?1.,?1.]])
>>>?np.empty((2,3))?#?空的二維數(shù)組,內(nèi)容為當(dāng)時(shí)內(nèi)存中的值
array([[1.,?1.,?1.],
???????[1.,?1.,?1.]])
>>>?np.arange(6)?#?用法跟range函數(shù)一樣
array([0,?1,?2,?3,?4,?5])
>>>?np.linspace(0,?10,?num=5)?#?以指定的線性間隔為初值,創(chuàng)建數(shù)組
array([?0.?,??2.5,??5.?,??7.5,?10.?])
>>>?rng?=?np.random.default_rng(0)?#?以隨機(jī)數(shù)創(chuàng)建二維數(shù)組
>>>?rng.random((2,3))
array([[0.63696169,?0.26978671,?0.04097352],
???????[0.01652764,?0.81327024,?0.91275558]])
3. 訪問(wèn)數(shù)組
支持索引和切片。格式為:
arr[i, j, k, ...],i, j, k分別代表數(shù)組第0維、第1維、第2維
其中,i, j, k的格式為:
s1:s2:s3,分別代表開(kāi)始下標(biāo),結(jié)束下標(biāo)和步長(zhǎng)
步長(zhǎng)s3不填時(shí),第二個(gè)冒號(hào)可省略,步長(zhǎng)為1。
以一個(gè)3維數(shù)組為例
>>>?#?創(chuàng)建?5*4?二維數(shù)組(5行4列)
>>>?c?=?np.array([[?0,??1,??2,??3],?[10,?11,?12,?13],?[20,?21,?22,?23],?[30,?31,?32,?33],?[40,?41,?42,?43]])
>>>?c
array([[?0,??1,??2,??3],
???????[10,?11,?12,?13],
???????[20,?21,?22,?23],
???????[30,?31,?32,?33],
???????[40,?41,?42,?43]])
>>>?c.shape
(5,?4)
>>>?#?按照索引取?第1行,第2列的元素
>>>?c[1,?2]
12
>>>?#?切片,取?1~2?行,第2列的元素(數(shù)組)
>>>?c[1:3,?2]
array([12,?22])
>>>?#?切片,取?1~2?行,第2~3列元素(數(shù)組)
>>>?c[1:3,?2:4]
array([[12,?13],
???????[22,?23]])
>>>?#?步長(zhǎng)=2,取到第?1,?3?行,第2~3列元素
>>>?c[1:6:2,?2:4]
array([[12,?13],
???????[32,?33]])
如果取最后一維,下標(biāo)為2的元素,可以按照下面方式取
>>>?c[:,2]
array([?2,?12,?22,?32,?42])
如果維度比較多,需要寫很多:,NumPy提供...可以代表之前或之后的任意維度
>>>?c[...,2]
array([?2,?12,?22,?32,?42])
取第0維的寫法也是一樣的。
4. 運(yùn)算(四則運(yùn)算和函數(shù))
NumPy數(shù)組支持四則運(yùn)算,它會(huì)將兩個(gè)數(shù)組相同位置的數(shù)值進(jìn)行加減乘除,生成新的數(shù)組。
>>>?data?=?np.array([1,?2])
>>>?ones?=?np.ones(2,?dtype=int)
>>>?data?+?ones
array([2,?3])

除了基本的四則運(yùn)算符,還支持+=、-=、*=和/=增量賦值運(yùn)算符,可修改原數(shù)組的值。
除了運(yùn)算符NumPy中還提供了一些函數(shù)用于快速計(jì)算數(shù)組中的值。如sum、min、max和mean等。
>>>?a?=?np.array([[1,2,3],[4,5,6]])
>>>?np.sum(a)
21
上面例子中np.sum()函數(shù)對(duì)二維數(shù)組中所有元素求和。
這類函數(shù)不光可以對(duì)所有元素做計(jì)算,還支持按照指定維度計(jì)算。如:
>>>?#?按照第0維相加(行相加)
>>>?a.sum(axis=0)
array([5,?7,?9])
>>>?#?按照第1維相加(列相加)
>>>?a.sum(axis=1)
array([?6,?15])
參數(shù)axis指定對(duì)第幾維做計(jì)算,在NumPy經(jīng)常會(huì)用到這個(gè)參數(shù)。
很多教程,包括官網(wǎng)文檔,直接告訴讀者axis=0代表按行計(jì)算,axis=1代表按列計(jì)算。
我覺(jué)得這樣說(shuō)有局限性,一來(lái)容易記混,二來(lái)如果是三維或者更高維誰(shuí)是行,誰(shuí)又是列呢。
所以,我覺(jué)得干脆不要記行、列,只要記住axis的取值就是第幾維就好了。
比如,在三維數(shù)組的各維度運(yùn)用np.sum()
>>>?a?=?np.array([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]])
>>>?a
array([[[?1,??2,??3],
????????[?4,??5,??6]],
???????[[?7,??8,??9],
????????[10,?11,?12]]])
>>>?a.shape
(2,?2,?3)
按第0維相加
>>>?a.sum(axis=0)
array([[?8,?10,?12],
???????[14,?16,?18]])
第0維里面有兩個(gè)元素,每個(gè)元素都是二維數(shù)組,按照第0維相加就是將這倆二維數(shù)組相加,即:[[ 1, 2, 3],[ 4, 5, 6]] + [ 7, 8, 9],[10, 11, 12]]。上面說(shuō)了,NumPy數(shù)組相加,相同位置的數(shù)值直接相加即可,得到就是上面的結(jié)果。
按第1維相加
>>>?a.sum(axis=1)
array([[?5,??7,??9],
???????[17,?19,?21]])
第1維共有4個(gè)一維數(shù)組,但由于處在兩個(gè)第0維元素中,所以要分別計(jì)算,即:[ 1, 2, 3] + [ 4, 5, 6] 和 [ 7, 8, 9] + [10, 11, 12],最終返回兩個(gè)一維數(shù)組。
按第2維相加
>>>?a.sum(axis=2)
array([[?6,?15],
???????[24,?33]])
第2維是最內(nèi)層的數(shù)字,直接將數(shù)字相加即可。得到 4 個(gè)數(shù)字。
按照這種方式去推導(dǎo)每一維的計(jì)算邏輯才是最容易理解的,而不是教條的去記是行或者列。
5. 廣播
上面的運(yùn)算中,運(yùn)算符兩邊的數(shù)組都是相同維度的。
而實(shí)際中,可能會(huì)有不同維度的數(shù)組相加減,這時(shí)候NumPy會(huì)自動(dòng)將兩邊的數(shù)組維度調(diào)整相同后,再做計(jì)算,這個(gè)過(guò)程就叫廣播。
>>>?data?=?np.array([1.0,?2.0])
>>>?data?*?1.6
array([1.6,?3.2])

在此,NumPy將數(shù)字1.6廣播成與data維度相同的一維數(shù)組,并用1.6填充,這樣就變成了兩個(gè)一維數(shù)組相乘。
當(dāng)然,并不是任何情況都能廣播成功,規(guī)則是:從兩個(gè)數(shù)組最右側(cè)維度開(kāi)始,依次向左判斷是否滿足以下兩個(gè)條件:
它們是相等的 其中一個(gè)為1
滿足一個(gè)條件即可,如果都不滿足,則拋ValueError: operands could not be broadcast together錯(cuò)誤。
舉個(gè)栗子:
A??????(4維數(shù)組):??8?x?1?x?6?x?1
B??????(3維數(shù)組):??????7?x?1?x?5
廣播后??(4維數(shù)組):??8?x?7?x?6?x?5
從右往左,要么A維度是1,要么B維度是1,滿足規(guī)則,可以廣播。
如果改成
A??????(4維數(shù)組):??8?x?1?x?6?x?2
B??????(3維數(shù)組):??????7?x?1?x?5
就會(huì)報(bào)錯(cuò),最右邊兩個(gè)維度,既不相等,也不是1。
再看一個(gè)計(jì)算的例子:
x?=?np.array([[1],[2],[3],[4]])
y?=?np.array([1,2,3,4])
>>>?x?+?y
array([[2,?3,?4,?5],
???????[3,?4,?5,?6],
???????[4,?5,?6,?7],
???????[5,?6,?7,?8]])
x會(huì)被廣播成4*4的數(shù)組
[[1,?1,?1,?1],
?[2,?2,?2,?2],
?[3,?3,?3,?4],
?[4,?4,?4,?4]]
y也會(huì)被廣播成4*4的數(shù)組
[[1,?2,?3,?4],
?[1,?2,?3,?4],
?[1,?2,?3,?4],
?[1,?2,?3,?4]]
二者按照數(shù)組規(guī)則直接相加即可。
6. 重塑數(shù)組
NumPy提供了很多函數(shù)可以更改數(shù)組的形狀(維度)。
reshape函數(shù)
>>>?a?=?np.arange(10)
>>>?a.reshape(5,2)
array([[0,?1],
???????[2,?3],
???????[4,?5],
???????[6,?7],
???????[8,?9]])
reshape()函數(shù)可以修改數(shù)組的維度,本例中將一個(gè)一維數(shù)組修改成5行2列的二維數(shù)組。
transpose函數(shù)
>>>?a?=?np.array([[1,2],[3,4],?[5,6]])
>>>?a.transpose()
array([[1,?3,?5],
???????[2,?4,?6]])
transpose()函數(shù)可以轉(zhuǎn)置數(shù)組,實(shí)現(xiàn)線性代數(shù)里矩陣轉(zhuǎn)置的效果。

該函數(shù)也可以用a.T來(lái)代替。
反轉(zhuǎn)數(shù)組
np.flip()函數(shù)可以反轉(zhuǎn)數(shù)組。
>>>?a?=?np.array([[1,?2,?3,?4],?[5,?6,?7,?8],?[9,?10,?11,?12]])
>>>?a
array([[?1,??2,??3,??4],
???????[?5,??6,??7,??8],
???????[?9,?10,?11,?12]])
>>>?np.flip(a)
array([[12,?11,?10,??9],
???????[?8,??7,??6,??5],
???????[?4,??3,??2,??1]])
np.flip函數(shù)默認(rèn)將所有元素從左至右、從上至下全部反轉(zhuǎn)。當(dāng)然,也可以按照某維反轉(zhuǎn)
>>>?#?按行反轉(zhuǎn)
>>>?np.flip(a,?axis=0)
array([[?9,?10,?11,?12],
???????[?5,??6,??7,??8],
???????[?1,??2,??3,??4]])
>>>?#?按列反轉(zhuǎn)
>>>?np.flip(a,?axis=1)
array([[?4,??3,??2,??1],
???????[?8,??7,??6,??5],
???????[12,?11,?10,??9]])
扁平化數(shù)組
flatten()和ravel()函數(shù)可以將多維數(shù)組拉平成一維數(shù)組,區(qū)別在于前者會(huì)返回新的數(shù)組,而后者只是創(chuàng)建了原數(shù)組的視圖。
>>>?a?=?np.array([[1?,?2,?3,?4],?[5,?6,?7,?8]])
>>>?a
array([[1,?2,?3,?4],
???????[5,?6,?7,?8]])?
>>>?b?=?a.flatten()
>>>?b
array([1,?2,?3,?4,?5,?6,?7,?8])
>>>?b[0]?=?10
>>>?b
array([10,??2,??3,??4,??5,??6,??7,??8])
>>>?a
array([[1,?2,?3,?4],
???????[5,?6,?7,?8]])
a是二維數(shù)組,經(jīng)過(guò)flatten函數(shù)拉平后變成一維數(shù)組b,修改b數(shù)組的值,不會(huì)影響數(shù)組a。
>>>?a?=?np.array([[1?,?2,?3,?4],?[5,?6,?7,?8]])
>>>?b?=?a.ravel()
>>>?b
array([1,?2,?3,?4,?5,?6,?7,?8])
>>>?b[0]?=?10
>>>?a
array([[10,??2,??3,??4],
???????[?5,??6,??7,??8]])
數(shù)組a經(jīng)過(guò)ravel函數(shù)拉平成一維數(shù)組b,修改b中值會(huì)影響數(shù)組a。
這里會(huì)發(fā)現(xiàn)一個(gè)現(xiàn)象,數(shù)組a的仍然是二維數(shù)組,說(shuō)明raval只是建立了a的視圖,并沒(méi)有改變a本身的存儲(chǔ)結(jié)構(gòu)。
如果想修改b而不影響a,可以調(diào)用copy()函數(shù)
>>>?c?=?b.copy()
>>>?c
array([10,??2,??3,??4,??5,??6,??7,??8])
>>>?c[0]=100
>>>?a
array([[10,??2,??3,??4],
???????[?5,??6,??7,??8]])
copy()函數(shù)會(huì)創(chuàng)建一個(gè)新數(shù)組,并用原數(shù)組的值填充,因此修改新數(shù)組不會(huì)影響原數(shù)組。這個(gè)過(guò)程也叫做深拷貝。
重塑數(shù)組的函數(shù)還有很多,如:
np.sort():排序np.hstack():橫向合并數(shù)組np.vstack():縱向和并數(shù)組np.concatenate():按維合并數(shù)組
等等等等。
用法上并不復(fù)雜,大家可以參考官方文檔學(xué)習(xí)一下。
7. 高級(jí)訪問(wèn)
7.1 索引數(shù)組
第3小節(jié)講解訪問(wèn)數(shù)組時(shí),都是通過(guò)數(shù)字來(lái)訪問(wèn)。NumPy支持按照數(shù)組格式訪問(wèn)數(shù)組。
>>>?a?=?np.arange(12)
>>>?i?=?np.array([1,?1,?3,?8,?5])
>>>?a[i]?
array([1,?1,?3,?8,?5])
數(shù)組i是一個(gè)索引數(shù)組,它里面的值都可以當(dāng)做a的下標(biāo)來(lái)訪問(wèn)。
也可以通過(guò)同樣的方式訪問(wèn)多維數(shù)組。
>>>?a?=?np.array([[0,?0,?0],?[255,?0,?0],?[0,?255,?0],?[0,?0,?255],?[255,?255,?255]])
>>>?a
array([[??0,???0,???0],
???????[255,???0,???0],
???????[??0,?255,???0],
???????[??0,???0,?255],
???????[255,?255,?255]])
>>>?i?=?np.array([[0,?1,?2,?0],?[0,?3,?4,?0]])
>>>?a[i]
array([[[??0,???0,???0],
????????[255,???0,???0],
????????[??0,?255,???0],
????????[??0,???0,???0]],
???????[[??0,???0,???0],
????????[??0,???0,?255],
????????[255,?255,?255],
????????[??0,???0,???0]]])
訪問(wèn)多維數(shù)組,索引數(shù)組i中的數(shù)值,都將作為數(shù)組a中的第0維的下標(biāo)。
當(dāng)然索引數(shù)組并非只能訪問(wèn)第0維,也能支持多個(gè)索引數(shù)組訪問(wèn)同一個(gè)數(shù)組多個(gè)維度。
>>>?a?=?np.arange(12).reshape(3,?4)
>>>?a
array([[?0,??1,??2,??3],
???????[?4,??5,??6,??7],
???????[?8,??9,?10,?11]])
>>>?i?=?np.array([[0,?1],?[1,?2]])
>>>?j?=?np.array([[2,?1],?[3,?3]])
>>>?a[i,?j]
array([[?2,??5],
???????[?7,?11]])
索引數(shù)組的維度必須相同,排在第一位的索引數(shù)組i訪問(wèn)第0維,排在第二位的索引數(shù)組j訪問(wèn)第1維,以此類推。i和j相同位置的數(shù)字正好對(duì)應(yīng)數(shù)組a中的某行某列的元素。
7.2 布爾數(shù)組
索引數(shù)組可以是個(gè)布爾類型的數(shù)組,True代表保留元素,False代表刪除元素。
>>>?a?=?np.arange(12).reshape(3,?4)
>>>?b?=?a?>?4
>>>?a[b]
array([?5,??6,??7,??8,??9,?10,?11])
>>>?a[a?>?4]
array([?5,??6,??7,??8,??9,?10,?11])
因?yàn)?code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">a和b形狀一樣,所以返回的結(jié)果是一維數(shù)組。
當(dāng)然也可以指定維度來(lái)篩選
>>>?a?=?np.arange(12).reshape(3,?4)
>>>?b1?=?np.array([False,?True,?True])
>>>?b2?=?np.array([True,?False,?True,?False])
>>>?a[b1,?:]
array([[?4,??5,??6,??7],
???????[?8,??9,?10,?11]])
>>>?a[:,?b2]
array([[?0,??2],
???????[?4,??6],
???????[?8,?10]])
>>>?a[b1,?b2]
array([?4,?10])
到這里,我們就把NumPy結(jié)構(gòu)、訪問(wèn)和操作都講解完了,涵蓋了NumPy大部分常用的功能。
有了這篇詳解,相信大家不管是直接用還是再看官方文檔都會(huì)很容易。
如果本文對(duì)你有用就點(diǎn)個(gè)?在看?鼓勵(lì)一下吧

相關(guān)閱讀:

