Python進階之NumPy快速入門(二)

  • 2019 年 10 月 11 日
  • 筆記

前言

NumPy是Python的一個擴展庫,負責數組和矩陣運行。相較於傳統Python,NumPy運行效率高,速度快,是利用Python處理數據必不可少的工具

這個NumPy快速入門系列分為四篇,包含了NumPy大部分基礎知識,每篇閱讀時間不長,但內容含量高。大家最好親自碼一遍程式碼,這樣可以更有收穫。

概要

學會數組的運算,輕鬆應對數學公式

學會數組的索引,瞬間定位數組位置

學會數組的迭代,快速遍曆數組元素

01

NumPy數組運算

基礎運算

NumPy數組的基本運算,即加減乘除。我們分成兩種情況:

  • 數組形狀相同時,即對對應元素進行運算,
  • 數組形狀不一致的時候有廣播機制來彌補

我們先看兩個形狀一樣的數組基礎運算:

程式碼:

import numpy as np    a = np.array([1, 2, 3])  b = np.arange(10,13)  print (a+b, a-b)  print (a*b, a/b)

講解:

我們建立了a,b兩個一維數組,分別採用直接創建和用arange函數創建的方法。對於同樣大小的數組之間的加減乘除運算,運算規則是對位元素一一對應。也就是說a的第一個元素和b的第一個元素進行運算,a的第二個元素和b的第二個元素進行運算,以此類推,所有對位的元素進行運算。

運行結果:

[11 13 15] [-9 -9 -9]

[10 22 36] [0.1 0.18181818 0.25]

廣播機制

如果a,b兩個數組的形狀(shape)並不一樣,那麼運算規則又是什麼樣子的呢?Numpy對於兩個不同形狀的數組的運算採用一種叫做廣播(broadcast)的機制負責運算:

程式碼:

a = np.array([[1, 2, 3],[4, 5, 6]])  b = np.arange(0,3)  print (a+b)

講解:

a是一個2*3的數組,而b的形狀是1*3,廣播機制會讓他們之間的加法得到一個相對合理的結果:

運行結果:

[[1 3 5]

[4 6 8]]

不難發現廣播讓a中第一個維度[1,2,3]加上b之後成為結果的第一個維度,讓a中的第二個維度[4,5,6]加上之後成為結果的第二個維度。廣播的規律總結起來有以下幾點:

  1. 讓所有輸入數組都向其中形狀最長的數組看齊,形狀中不足的部分都通過在前面加 1 補齊。
  2. 輸出數組的形狀是輸入數組形狀的各個維度上的最大值。
  3. 如果輸入數組的某個維度和輸出數組的對應維度的長度相同或者其長度為 1 時,這個數組能夠用來計算,否則出錯。
  4. 當輸入數組的某個維度的長度為 1 時,沿著此維度運算時都用此維度上的第一組值。

對於NumPy的廣播,我給大家的建議是會多少用多少,盡量不要超出自己知識範圍內使用。

高級運算:

  • 乘方:numpy.power,用法是numpy(a,n),其中a是NumPy數組,n是冪
  • 取余:numpy.mod(a,b),數組a對於數組b除法後取餘數。
  • 三角函數:numpy.sin(), numpy.cos()等

程式碼:

a = np.array([1, 2, 3])  b = np.arange(10,13)  print (a**2, np.power(a,2))  print (np.sin(a))  print (np.mod(b,a))

講解:

我們建立了a,b兩個數組,第一個運算是求a每個元素的平方,有兩種方法實現,二者結果相同。第二個運算,我們嘗試了一下三角函數中的正弦函數。最後,我們用數組b對於數組a取余運算,除了11對於2取余等於1之外,其餘都是0。

運行結果:

[1 4 9] [1 4 9]

[0.84147098 0.90929743 0.14112001]

[0 1 0]

02

NumPy索引

索引就是像是GPS導航,可以直接到數組中的特定位置的元素。我們把數組的索引按方式不同分成兩種,然後分別介紹:

  • 數字索引
  • 布爾(條件)索引

數字索引

數字索引,顧名思義,就是根據數字來定位數組中元素,這個十分好理解。我們將數字索引分成兩種方式:

  1. 單個數字索引
  2. 範圍數字索引

對於一維數組,單個數字索引和列表方法一樣。比如我們有一個數組A,那麼A[x]就是索引A數組中的第x個元素,這裡切記x從0開始計數,所以準確來講是索引第x+1個元素。

對於二維的NumPy數組,我們也可以用一維索引的方法,這時我們會索引出某一行。

程式碼:

import numpy as np  A = np.arange(0,12)  print (A[3])  A = A.reshape((2,6))  print (A[1])

講解:

我們首先建立了一個0到11的數組A,我們試圖索引它的第四個元素。接著我們利用了一個變形技術reshape把A轉換成一個二維數組,然後用一維索引得到變形後的第二行所有元素。

運行結果:

3

[ 6 7 8 9 10 11]

單個數字也可以擴展到二維甚至更高維度,例如對於二維數組索引方式一般可以寫成A[1,1]或者A[1][1]。

現在我們著重介紹一下用冒號進行範圍索引,因為我們有時候想要一段的數組,這時候範圍索引就顯得很方便實用。具體而言,有兩種方式:

  • a:b,從a位置出發到b位置結束。
  • a:b:c,a是起始值,b是終止值,c是步長。

程式碼:

A = np.eye(5)  print (A[2,2], A[2][2])  print (A[1][0:4:2])

講解:

我們首先用numpy.eye()函數建立了一個5乘以5的單位矩陣。先測試一下二維索引中單體索引,A[2,2]和A[2][2]兩種方式都是可以的。接著我們測試一下範圍索引,第一個[1]表示A矩陣的第二行:[0 1 0 0 0];後面的[0:4:2]其實只能索引出來兩個數字,就是0和3兩個位置上的數字。

運行結果:

1.0 1.0

[0. 0.]

布爾索引

這是一種通過布爾(邏輯)運算來獲得符合條件元素的索引方式。簡單來說,你可以通過給定一定的條件,篩選出滿足條件的元素。這種索引方式是我們日常使用Numpy數組較為常用和使用的方法。

程式碼:

A = np.arange(0,9).reshape(3,3)  B = np.array([1,  2+6j,  5,  3.5+5j])  print (A>5)  print (A[A>5])  print (B[np.iscomplex(B)])

講解:

我們先用兩行程式碼給大家展示一下布爾索引的運算過程,第18行程式碼其實才是完整的操作,列印出A數組中大於5的元素,以一個一維數組的形式數出來。第17程式碼其實給出布爾運算的一步,輸出結果為:大於5的位置是True而小於5的位置是False,接著通過真假關係帶入A數組,最終把真的元素挑出來。這就是布爾索引的運算過程。B是一個列印出複數元素的例子,原理是一樣的。

03

數組迭代

這一節課我們嘗試用循環的方式,遍曆數組中所有元素。考慮到常見的數組往往不止一個維度,因此單純用while和for循環寫起來很費事,所以我們有必要學習NumPy自帶的遍歷方法。

迭代數組 nditer

Numpy自帶一個數組迭代器,叫nditer,可以讓我們靈活訪問數組中元素:

程式碼:

import numpy as np  A = np.arange(0,12).reshape(3,4)  for n in np.nditer(A):   print (n, end=' ')

講解:

我們照例創建了一個形狀為(3,4)的二維數組A,利用nditer配合for循環的格式,依次迭代訪問數組A中的元素。注意到在print函數中,我們給參數end賦值了一個空格字元串,目的是讓列印出來的元素可以被空格間隔。

運行結果:

0 1 2 3 4 5 6 7 8 9 10 11

大家可以嘗試一下給end賦值別的字元串,例如逗號,換行等等。

控制順序

事實上,nditer有一個參數來控制遍歷順序。這個參數叫order,有兩個值可以選擇:

  • 如果order='C',那麼就會按行優先的順序訪問;
  • 如果order='F',那麼則會按列順序優先訪問。

我們來看個例子:

程式碼:

B = np.arange(0,9).reshape(3,3)  for n in np.nditer(B,order='C'):   print (n, end=' ')  print ('n')  for n in np.nditer(B, order='F'):   print (n, end=' ') 

講解:

正如我們上面所說,'C'和'F'分別代表行和列優先。值得一提的這裡的C,F並不是我們常見的row和column的縮寫,而是程式碼C語言標準格式和Fortran格式,二者都是一種程式語言。

運行結果:

0 1 2 3 4 5 6 7 8

0 3 6 1 4 7 2 5 8

修改元素

nditer在遍曆數組的時候,給我們提供了一個讀寫的選項,也就是說,我們根據這個讀寫開關可以改變數組的數值。這個參數叫做op_flags,默認值是只讀模式'read-only',此時不可以修改元素。但是我們可以讓op_flags賦值'readwrite' 或者 'writeonly':

程式碼:

C = np.arange(0,8).reshape(2,4)  for n in np.nditer( C,op_flags=['writeonly'] ):   n[...] = 2*n  print (C) 

講解:

我們利用'writeonly'將遍歷的讀寫模式變成只寫模式,大家也可以嘗試'readwrite'一下看看效果如何。對於每個元素,我們都讓它擴大兩倍。有一點,我們用了n[…]格式,讓乘以兩倍後的元素重新賦值回去,[…]不可或缺。

運行結果:

[[ 0 2 4 6]

[ 8 10 12 14]]

總結回顧

1

學習了基礎運算,以及當數組形狀不一致時候的廣播機制;高級運算。

2

學慣用數字和邏輯索引兩種基本數組索引方式。

3

學習了數組的迭代器,以及迭代順序的控制。