清晰易懂的Numpy入門教程

視學演算法發表於2020-04-06

清晰易懂的Numpy入門教程

翻譯 | 石頭

來源 | Machine Learning Plus

Numpy是python語言中最基礎和最強大的科學計算和資料處理的工具包,如資料分析工具pandas也是基於numpy構建的,機器學習包scikit-learn也大量使用了numpy方法。本文介紹了Numpy的n維陣列在資料處理和分析的所有核心應用。

目錄


1. 如何構建numpy陣列

2. 如何觀察陣列屬性的大小和形狀(shape)

3. 如何從陣列提取特定的項

4. 如何從現有的陣列定義新陣列

5. 多維陣列的重構(reshaping)和扁平(flattening)

6. 如何通過numpy生成序列數(sequences),重複數(repetitions)和隨機數(random)

7. 小結

1. 如何構建numpy陣列

構建numpy陣列的方法很多,比較常用的方法是用np.array函式對列表進行轉化。

# 通過列表建立一維陣列
import numpy as np
list1 = [0,1,2,3,4]
arr1d = np.array(list1)

#列印陣列和型別
print(type(arr1d))
arr1d
<type 'numpy.ndarray'>
[0 1 2 3 4]

陣列和列表最關鍵的區別是:陣列是基於向量化操作的,列表不是,我們在實際專案中處理的資料一般是矩陣結構,對該資料以行向量或列向量的形式進行計算,向量計算是基於陣列實現的,因此陣列比列表的應用更廣。

函式可以應用到陣列的每一項,列表不行。

比如,不可以對列表的每一項資料都加2,這是錯誤的。

list1 + 2  # 錯誤

可以對陣列的某一項資料都加2

# Add 2 to each element of arr1d
arr1d + 2

#> array([2, 3, 4, 5, 6])

另一個區別是已經定義的numpy陣列不可以增加陣列大小,只能通過定義另一個陣列來實現,但是列表可以增加大小。

然而,numpy有更多的優勢,讓我們一起來發現。

numpy可以通過列表中的列表來構建二維陣列。

# Create a 2d array from a list of lists
list2 = [[0,1,2], [3,4,5], [6,7,8]]
arr2d = np.array(list2)
arr2d

#> array([[0, 1, 2],
#>        [3, 4, 5],
#>        [6, 7, 8]])

你也可以通過dtype引數指定陣列的型別,一些最常用的numpy型別是:'float','int','bool','str'和'object'。

# Create a float 2d array
arr2d_f = np.array(list2, dtype='float')
arr2d_f

#> array([[ 0.,  1.,  2.],
#>        [ 3.,  4.,  5.],
#>        [ 6.,  7.,  8.]])

輸出結果的小數點表示float型別,你也可以通過 astype方法轉換成不同的型別。

# 轉換成‘int’型別
arr2d_f.astype('int')

#> array([[0, 1, 2],
#>        [3, 4, 5],
#>        [6, 7, 8]])
# 先轉換‘int’型別,再轉換‘str’型別
arr2d_f.astype('int').astype('str')

#> array([['0', '1', '2'],
#>        ['3', '4', '5'],
#>        ['6', '7', '8']],
#>       dtype='U21')

另一個區別是陣列要求所有項是同一個型別,list沒有這個限制。如果你想要一個陣列包含不同型別,設定‘dtype’為'object'。

# 構建布林型別陣列
arr2d_b = np.array([1, 0, 10], dtype='bool')
arr2d_b

#> array([ True, False,  True], dtype=bool)
# 構建包含數值和字串的陣列
arr1d_obj = np.array([1, 'a'], dtype='object')
arr1d_obj

#> array([1, 'a'], dtype=object)

最終使用 tolist()函式使陣列轉化為列表。

# Convert an array back to a list
arr1d_obj.tolist()

#> [1, 'a']

總結陣列和列表主要的區別:

  1. 陣列支援向量化操作,列表不支援;

  2. 陣列不能改變長度,列表可以;

  3. 陣列的每一項都是同一型別,list可以有多種型別;

  4. 同樣長度的陣列所佔的空間小於列表;

2. 如何觀察陣列屬性的大小和形狀(shape)

一維陣列由列表構建,二維陣列arr2d由列表的列表構建,二維陣列有行和列,比如矩陣,三維陣列由嵌入了兩個列表的列表構建。

假設給定一個陣列,我們怎麼去了解該陣列的屬性。

陣列的屬性包括:

陣列的維度(ndim)

陣列的形狀(shape)

陣列的型別(dtype)

陣列的大小(size)

陣列元素的表示(通過索引)

# 定義3行4列的二維陣列
list2 = [[1, 2, 3, 4],[3, 4, 5, 6], [5, 6, 7, 8]]
arr2 = np.array(list2, dtype='float')
arr2

#> array([[ 1.,  2.,  3.,  4.],
#>        [ 3.,  4.,  5.,  6.],
#>        [ 5.,  6.,  7.,  8.]])
# 形狀(shape)
print('Shape: ', arr2.shape)

# 陣列型別(dtype)
print('Datatype: ', arr2.dtype)

# 陣列大小(size)
print('Size: ', arr2.size)

# 陣列維度(ndim)
print('Num Dimensions: ', arr2.ndim)

# 取陣列第3行3列元素
print('items of 3 line 3 column: ', c[2,2])

#> Shape:  (3, 4)
#> Datatype:  float64
#> Size:  12
#> Num Dimensions:  2
#> items of 3 line 3 column:  7

3. 如何從陣列提取特定的項

陣列的索引是從0開始計數的,與list類似。numpy陣列通過方括號的引數以選擇特定的元素。

# 選擇矩陣的前兩行兩列
arr2[:2, :2]
list2[:2, :2]  # 錯誤

#> array([[ 1.,  2.],
#>        [ 3.,  4.]])

numpy陣列支援布林型別的索引,布林型索引陣列與過濾前(array-to-be-filtered)的陣列大小相等,布林型陣列只包含Ture和False變數,Ture變數對應的陣列索引位置保留了過濾前的值 。

arr2

#> array([[ 1.,  2.,  3.,  4.],
#>          [ 3.,  4.,  5.,  6.],
#>          [ 5.,  6.,  7.,  8.]])
# 對陣列每一個元素是否滿足某一條件,然後獲得布林型別的輸出
b = arr2 > 4
b

#> array([[False, False, False, False],
#>        [False, False,  True,  True],
#>        [ True,  True,  True,  True]], dtype=bool)
# 取布林型陣列保留的原始陣列的值
arr2[b]

#> array([ 5.,  6.,  5.,  6.,  7.,  8.])

3.1 如何反轉陣列

# 反轉陣列的行
arr2[::-1, ]

#> array([[ 5.,  6.,  7.,  8.],
#>        [ 3.,  4.,  5.,  6.],
#>        [ 1.,  2.,  3.,  4.]])
# Reverse the row and column positions
# 反轉陣列的行和列
arr2[::-1, ::-1]

#> array([[ 8.,  7.,  6.,  5.],
#>        [ 6.,  5.,  4.,  3.],
#>        [ 4.,  3.,  2.,  1.]])

3.2 如何處理陣列的缺失值(missing)和無窮大(infinite)值

缺失值可以用np.nan物件表示,np.inf表示無窮大值,下面用二維陣列舉例:

# 插入nan變數和inf變數
arr2[1,1] = np.nan  # not a number
arr2[1,2] = np.inf  # infinite
arr2

#> array([[  1.,   2.,   3.,   4.],
#>        [  3.,  nan,  inf,   6.],
#>        [  5.,   6.,   7.,   8.]])
# 用-1代替nan值和inf值
missing_bool = np.isnan(arr2) | np.isinf(arr2)
arr2[missing_bool] = -1  
arr2

#> array([[ 1.,  2.,  3.,  4.],
#>        [ 3., -1., -1.,  6.],
#>        [ 5.,  6.,  7.,  8.]])

3.3 如何計算n維陣列的平均值,最小值和最大值

# 平均值,最大值,最小值
print("Mean value is: ", arr2.mean())
print("Max value is: ", arr2.max())
print("Min value is: ", arr2.min())

#> Mean value is:  3.58333333333
#> Max value is:  8.0
#> Min value is:  -1.0

如果要求陣列的行或列的最小值,使用np.amin函式

# Row wise and column wise min
# 求陣列行和列的最小值
# axis=0表示列,1表示行
print("Column wise minimum: ", np.amin(arr2, axis=0))
print("Row wise minimum: ", np.amin(arr2, axis=1))

#> Column wise minimum:  [ 1. -1. -1.  4.]
#> Row wise minimum:  [ 1. -1.  5.]

對陣列的每個元素進行累加,得到一維陣列,一維陣列的大小與二維陣列相同。

# 累加
np.cumsum(arr2)

#> array([  1.,   3.,   6.,  10.,  13.,  12.,  11.,  17.,  22.,  28.,  35., 43.])

4. 如何從現有的陣列定義新陣列

如果使用賦值運算子從父陣列定義新陣列,新陣列與父陣列共佔同一個記憶體空間,如果改變新陣列的值,那麼父陣列也相應的改變。

為了讓新陣列與父陣列相互獨立,你需要使用copy()函式。所有父陣列都使用copy()方法構建新陣列。

# Assign portion of arr2 to arr2a. Doesn't really create a new array.
# 分配arr2陣列給新陣列arr2a,下面方法並沒有定新陣列
arr2a = arr2[:2,:2]  
arr2a[:1, :1] = 100  # arr2相應位置也改變了
arr2

#> array([[ 100.,    2.,    3.,    4.],
#>        [   3.,   -1.,   -1.,    6.],
#>        [   5.,    6.,    7.,    8.]])
# 賦值arr2陣列的一部分給新陣列arr2b
arr2b = arr2[:2, :2].copy()
arr2b[:1, :1] = 101  # arr2沒有改變
arr2

#> array([[ 100.,    2.,    3.,    4.],
#>        [   3.,   -1.,   -1.,    6.],
#>        [   5.,    6.,    7.,    8.]])

5. 多維陣列的重構(reshaping)和扁平(flattening)

重構(reshaping)是改變了陣列項的排列,即改變了陣列的形狀,未改變陣列的維數。

扁平(flattening)是對多維陣列轉化為一維陣列。

# 3x4陣列重構為4x3陣列
arr2.reshape(4, 3)

#> array([[ 100.,    2.,    3.],
#>        [   4.,    3.,   -1.],
#>        [  -1.,    6.,    5.],
#>        [   6.,    7.,    8.]])

5.1 flatten()和ravel()的區別

陣列的扁平化有兩種常用的方法,flatten()和ravel() 。flatten處理後的陣列是父陣列的引用,因此新陣列的任何變化也會改變父陣列,因其未用複製的方式構建陣列,記憶體使用效率高,ravel通過複製的方式構建新陣列。

# flatten方法
arr2.flatten()

#> array([ 100.,    2.,    3.,    4.,    3.,   -1.,   -1.,    6.,    5., 6.,    7.,    8.])
# flatten方法
b1 = arr2.flatten()  
b1[0] = 100  # 改變b1的值並未影響arr2
arr2

#> array([[ 100.,    2.,    3.,    4.],
#>        [   3.,   -1.,   -1.,    6.],
#>        [   5.,    6.,    7.,    8.]])
# ravel方法
b2 = arr2.ravel()  
b2[0] = 101  # 改變b2值,相應的改變了arr2值
arr2

#> array([[ 101.,    2.,    3.,    4.],
#>        [   3.,   -1.,   -1.,    6.],
#>        [   5.,    6.,    7.,    8.]])

6. 如何通過numpy生成序列數(sequences),重複數(repetitions)和隨機數(random)

np.arrange函式手動生成指定數目的序列數,與ndarray作用一樣。

# 預設下限為0
print(np.arange(5))  

# 0 to 9,預設步數為1
print(np.arange(0, 10))  

# 遞增步數2
print(np.arange(0, 10, 2))  

# 降序
print(np.arange(10, 0, -1))

#> [0 1 2 3 4]
#> [0 1 2 3 4 5 6 7 8 9]
#> [0 2 4 6 8]
#> [10  9  8  7  6  5  4  3  2  1]

上例是通過np.arrange設定初始位置和結束位置來生成序列數,如果我們設定陣列的元素個數,那麼可以自動計算陣列的遞增值。

如構建1到50的陣列,陣列有10個元素,使用np.linspace總動計算陣列的遞增值

# 起始位置和結束位置分別為1和50
np.linspace(start=1, stop=50, num=10, dtype=int)

#> array([ 1,  6, 11, 17, 22, 28, 33, 39, 44, 50])

我們注意到上面例子的遞增值並不相等,有5和6兩個值,原因是計算遞增值採用了四捨五入的演算法(rounding)。與np.linspace類似,np.logspace以對數尺度的方式增長。

# 設定陣列的精度為小數點後兩位
np.set_printoptions(precision=2)  

# 起點為 10^1 and 終點為 10^50,陣列元素個數10,以10為底數
np.logspace(start=1, stop=50, num=10, base=10) 

#> array([  1.00e+01,   2.78e+06,   7.74e+11,   2.15e+17,   5.99e+22,
#>          1.67e+28,   4.64e+33,   1.29e+39,   3.59e+44,   1.00e+50])

初始化陣列的元素全為1或全為0。

np.zeros([2,2])
#> array([[ 0.,  0.],
#>        [ 0.,  0.]])
np.ones([2,2])
#> array([[ 1.,  1.],
#>        [ 1.,  1.]])

7.1 如何構建重複的序列數

np.tile重複整個的陣列或列表n次,np.repeat重複陣列每一項n次。

a = [1,2,3] 

# 重複陣列a兩次
print('Tile:   ', np.tile(a, 2))

# 重複陣列a每項兩次
print('Repeat: ', np.repeat(a, 2))

#> Tile:    [1 2 3 1 2 3]
#> Repeat:  [1 1 2 2 3 3]

7.2 如何生存隨機數

random模組包含的函式可以生成任一陣列形狀的隨機數和統計分佈。

# 生成2行2列的[0,1)的隨機數
print(np.random.rand(2,2))

# 生成均值為0方差為1的2行2列的正態分佈值
print(np.random.randn(2,2))

# 生成[0,10)的2行2列的隨機整數
print(np.random.randint(0, 10, size=[2,2]))

# 生成一個[0,1)的隨機數
print(np.random.random())

# 生成[0,1)的2行2列的隨機數
print(np.random.random(size=[2,2]))

# 從給定的列表等概率抽樣10次
print(np.random.choice(['a', 'e', 'i', 'o', 'u'], size=10))  

# 從給定的列表和對應的概率分佈抽樣10次
print(np.random.choice(['a', 'e', 'i', 'o', 'u'], size=10, p=[0.3, .1, 0.1, 0.4, 0.1]))  # picks more o's

#> [[ 0.84  0.7 ]
#>  [ 0.52  0.8 ]]

#> [[-0.06 -1.55]
#>  [ 0.47 -0.04]]

#> [[4 0]
#>  [8 7]]

#> 0.08737272424956832

#> [[ 0.45  0.78]
#>  [ 0.03  0.74]]

#> ['i' 'a' 'e' 'e' 'a' 'u' 'o' 'e' 'i' 'u']
#> ['o' 'a' 'e' 'a' 'a' 'o' 'o' 'o' 'a' 'o']

7.3 如何得到陣列獨特(unique)的項和個數(counts)

np.unique函式去除陣列中重複的元素,設定return_counts引數為True,得到陣列每一項的個數。

# 定義範圍為[0,10),個數為10的隨機整數陣列
np.random.seed(100)
arr_rand = np.random.randint(0, 10, size=10)
print(arr_rand)

#> [8 8 3 7 7 0 4 2 5 2]
# 得到陣列獨特的項和相應的個數
uniqs, counts = np.unique(arr_rand, return_counts=True)
print("Unique items : ", uniqs)
print("Counts       : ", counts)

#> Unique items :  [0 2 3 4 5 7 8]
#> Counts       :  [1 2 1 1 1 2 2]

8 小結

本文比較全面的介紹了numpy的基本用法,希望對numpy還不熟悉的同學有所幫助

- END -

如果看到這裡,說明你喜歡這篇文章,請轉發、點贊掃描下方二維碼或者微信搜尋「perfect_iscas」,新增好友後即可獲得10套程式設計師全棧課程+1000套PPT和簡歷模板向我私聊「進群」二字即可進入高質量交流群。

掃描二維碼進群↓

清晰易懂的Numpy入門教程

清晰易懂的Numpy入門教程

清晰易懂的Numpy入門教程

在看 清晰易懂的Numpy入門教程

相關文章