1. 程式人生 > >NumPy ndarray:多維陣列物件

NumPy ndarray:多維陣列物件

1NumPy ndarray:多維陣列物件

NumPy的核心特徵之一就是N-維陣列物件——ndarray。
首先匯入NumPy,然後生成一個小的隨機陣列:

import numpy as np
data = np.random.randn(2,3)
print(data)
----------------------------------
[[-0.16662491  0.17114219 -0.44236483]
 [ 0.06577042  0.54026812  0.62346929]]

然後可以給data加上一個數學操作:

print(data * 10)
print(data + data)

第一個輸出結果中data中所有的值都同時乘了10,第二個輸出結果中data中的物件元素對應相加。
一個ndarray是一個通用的多維同類資料容器,它包含的每一個元素均為相同型別。每一個數組都有一個shape屬性,用來表徵陣列每一維度的數量,每一個數組都有一個dtype屬性,用來描述陣列的資料型別:

print(data.shape) #(2, 3)
print(data.dtype) #float64

1.1生成ndarray

生成陣列最簡單的方式就是使用array函式。array函式接收任意的序列型物件,生成一個新的包含傳遞資料的NumPy陣列。例如,列表的轉換:

data1 = [6,7.5,8,0,1]
arr1 = np.array(data1)
print(arr1) #[6.  7.5 8.  0.  1. ]

巢狀序列,例如同等長度的列表,將會自動轉換成多維陣列:

data2 = [[1,2,3,4],[5,6,7,8]]
arr2 = np.array(data2)
print(arr2) 
----------------------------------
[[1 2 3 4]
 [5 6 7 8]]

data2是一個包含列表的列表,所以NumPy陣列arr2形成了二維陣列。我們可以通過檢查ndim和shape屬性來確認這一點:

print(arr2.ndim) #2
print(arr2.shape) #(2, 4)

除了np.array,還有很多其他函式可以建立新陣列。例如,給定長度及形狀後,zeros可以一次性創造全0陣列,ones可以一次性創造全1陣列。empty則可以建立一個沒有初始化數值的陣列。想要建立高維陣列,則可以為shape傳遞一個元祖:

print(np.zeros(10)) 
-----------------------------
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]

print(np.zeros((3,6)))
-----------------------------
[[0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0.]]

print(np.empty((2,3,2)))
-----------------------------
[[[6.23042070e-307 4.67296746e-307]
  [1.69121096e-306 1.86921822e-306]
  [2.22518251e-306 1.60218491e-306]]

 [[1.37962320e-306 1.78019354e-306]
  [9.17714149e+170 1.29060558e-306]
  [1.24611741e-306 1.11261027e-306]]]

arrange是python內建函式range的陣列版:

print(np.arange(15)) #[ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14]

表1.1:陣列生成函式

函式名 描述
array 將輸入資料(可以是列表、元祖、陣列以及其他序列)轉換為ndarray
asarray 將輸入轉換為ndarray,但如果輸入已經是ndarray則不再複製
arange python內建函式range的陣列版,返回一個數組
ones 根據給定形狀和資料型別生成全1陣列
ones_like 根據所給的陣列生成一個形狀一樣的全1陣列
zeros 根據給定形狀和資料型別生成全0陣列
zeros_like 根據所給的陣列生成一個形狀一樣的全0陣列
empty 根據給定形狀生成一個沒有初始化數值的空陣列
empty_like 根據所給陣列生成一個形狀一樣但沒有初始化數值的空陣列
full 根據給定的形狀和資料型別生成指定數值的陣列
full_like 根據所給的陣列生成一個形狀一樣但內容是指定數值的陣列
eye,identity 生成一個N*N特徵矩陣(對角線位置都是1,其餘位置都是0)

1.2nparray的資料型別

資料型別,即dtype,是一個特殊的物件,它包含了ndarray需要為某一種型別資料所申明的記憶體塊資訊(即表示資料的資料):

arr1 = np.array([1,2,3],dtype = np.float64)
arr2 = np.array([1,2,3],dtype = np.int32)
print(arr1.dtype) #float64
print(arr2.dtype) #int32

我們可以使用astype方法顯示地轉換陣列的資料型別:

arr = np.array([1,2,3,4,5])
print(arr.dtype) #int32
float_arr = arr.astype(np.float64)
print(float_arr.dtype) #float64

上面的例子中,整數被轉換為了浮點數,如果把浮點數轉換為整數,那麼小數點後的部分將被消除:

arr = np.array([3.7,3.5,1,9.2])
print(arr.dtype) #float64
int_arr = arr.astype(np.int32)
print(int_arr) #[3 3 1 9]

如果有一個數組,裡面的元素都是表達數字含義的字串,也可以通過astype將字串轉換為數字:

numeric_strings = np.array(['1.25','-9.6','43'],dtype = np.string_)
print(numeric_strings.astype(float)) #[ 1.25 -9.6  43.  ]

我們也可以使用另一個數組的dtype屬性:

int_array = np.arange(10)
calibers = np.array([.22,.270,.22,.16,.18],dtype = np.float64)
print(int_array.astype(calibers.dtype)) #[0. 1. 2. 3. 4. 5. 6. 7. 8. 9.]

也可以使用型別程式碼來傳入資料:

empty_uint32 = np.empty(8,dtype = 'u4')
print(empty_uint32)
------------------------------------------------
[2128575739 1309500030 1661424176 1988385690 1324770695      12290
          0  805457654]

使用astype時總是生成一個新的陣列,即使傳入的dtype與之前的一樣。

1.3NumPy陣列算術

陣列之所以重要是因為它允許你進行批量操作而無須任何for迴圈。這種特性稱為向量化。任何在兩個等尺寸陣列之間的算術操作都應用了逐元素操作的方式:

arr = np.array([[1.,2.,3.],[4.,5.,6.]])
print(arr)
--------------------------------------------
[[1. 2. 3.]
 [4. 5. 6.]]
print(arr * arr)
--------------------------------------------
[[ 1.  4.  9.]
 [16. 25. 36.]]
print(arr - arr)
--------------------------------------------
[[0. 0. 0.]
 [0. 0. 0.]]

帶有標量計算的算術操作,也會把計算引數傳遞給陣列的每一個元素。
同尺寸陣列之間的比較,會產生一個布林值陣列:

arr = np.array([[1.,2.,3.],[4.,5.,6.]])
arr2 = np.array([[0.,4.,1.],[7.,2.,12.]])
print(arr2)
--------------------------------------------
[[ 0.  4.  1.]
 [ 7.  2. 12.]]
print(arr2 > arr)
--------------------------------------------
[[False  True False]
 [ True False  True]]

不同尺寸的陣列間的操作,將會用到廣播特性。

1.4基礎索引與切片

區別於python的內建列表,陣列的切片是原陣列的檢視。這意味著資料並不是被複制了,任何對於檢視的修改都會反映到原陣列上:

arr = np.arange(10)
arr[5:8] = 12
arr_slice = arr[5:8]
print(arr_slice) #[12 12 12]
arr_slice[1] = 22
print(arr) #[ 0  1  2  3  4 12 22 12  8  9]

不寫切片值的[:]將會引用陣列的所有值:

arr_slice[:] = 32
print(arr) #[ 0  1  2  3  4 32 32 32  8  9]

由於NumPy被設計成適合處理非常大的陣列,所以如果NumPy持續複製資料將會引起很多的記憶體問題。
如果還是想要一份陣列切片的拷貝而不是一份檢視的話,我們就必須顯示地複製這個陣列,例如:arr[5:8].copy()
在多維陣列中,可以省略後續索引值,返回的物件將會是降低一個維度的陣列:

arr3d = np.array([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]])
print(arr3d)
--------------------------------------------
[[[ 1  2  3]
  [ 4  5  6]]

 [[ 7  8  9]
  [10 11 12]]]
print(arr3d[0])
--------------------------------------------
[[1 2 3]
 [4 5 6]]

標量和陣列都可以傳遞給arr3d[0]:

old_values = arr3d[0].copy()
arr3d[0] = 42
print(arr3d)
--------------------------------------------
[[[42 42 42]
  [42 42 42]]

 [[ 7  8  9]
  [10 11 12]]]
arr3d[0] = old_values
print(arr3d)
--------------------------------------------
[[[ 1  2  3]
  [ 4  5  6]]

 [[ 7  8  9]
  [10 11 12]]]

對於二維陣列的切片有所不同:

arr2d = np.array([[1,2,3],[4,5,6],[7,8,9]])
print(arr2d[:2])
--------------------------------------------
[[1 2 3]
 [4 5 6]]

陣列沿著軸0進行了切片,表示式arrzd[:2]的含義為選擇arr2d的前兩行
我們也可以進行多組切片:

print(arr2d[:2,1:])
--------------------------------------------
[[2 3]
 [5 6]]

當對切片表示式賦值時,整個切片都會重新賦值:

arr2d[:2,1:] = 0
print(arr2d)
--------------------------------------------
[[1 0 0]
 [4 0 0]
 [7 8 9]]

1.5布林索引

我們經常會使用numpy.random中的randn函式來生成一些隨機正態分佈的資料:

names = np.array(['Bob','Joe','Will','Bob','Will','Joe','Joe'])
data = np.random.randn(7,4)
print(names)
--------------------------------------------
['Bob' 'Joe' 'Will' 'Bob' 'Will' 'Joe' 'Joe']
print(data)
--------------------------------------------
[[ 1.9739665   0.9897147  -0.78550736 -0.9275028 ]
 [-0.47926498 -0.83707164 -1.08190423 -2.06021371]
 [ 0.11140563  0.37300882 -0.19671083 -0.57878727]
 [-0.4813887   1.92794616 -0.54502391 -1.24390633]
 [ 0.02961092 -0.90273863  0.44457704  0.75884028]
 [ 0.25222733  1.36974475  0.23402606  0.76146159]
 [ 0.57749217 -2.65068358  0.06935796  1.00334315]]

假設每個人名都和data陣列中的一行對應,並且想要選中所有’Bob’對應的行。陣列的比較操作也是可以向量化的。因此,比較names陣列和字串’Bob’會產生一個布林值陣列:

print(names == 'Bob') #[ True False False  True False False False]

在索引陣列時可以傳入布林陣列值:

print(data[names == 'Bob'])
--------------------------------------------
[[ 1.9739665   0.9897147  -0.78550736 -0.9275028 ]
 [-0.4813887   1.92794616 -0.54502391 -1.24390633]]

為了選擇除了’Bob’以外的其他資料,我們可以選擇!=或在條件表示式前使用~對條件取反:

print(names != 'Bob') #[False  True  True False  True  True  True]

~符號可以在我們想要對一個通用條件進行取反時使用:

cond = names == 'Bob'
print(data[~cond])
--------------------------------------------
 [[-0.47926498 -0.83707164 -1.08190423 -2.06021371]
 [ 0.11140563  0.37300882 -0.19671083 -0.57878727]
  [ 0.02961092 -0.90273863  0.44457704  0.75884028]
 [ 0.25222733  1.36974475  0.23402606  0.76146159]
 [ 0.57749217 -2.65068358  0.06935796  1.00334315]]

當要選擇三個名字中的兩個時,我們可以對多個布林值條件進行聯合,需要使用數學操作符&(and)和|(or):

mask = (names == 'Bob') | (names == 'Will')
print(mask) #[ True False  True  True  True False False]

使用布林值索引選擇資料時,總是生成資料的拷貝,即使返回的陣列並沒有任何變化。
python的關鍵字and和or隊布林值陣列並沒有用,請使用&和|來代替。
基於常識來設定布林值陣列的值也是可行的。將data中所有的負值設定為0,我們只需要如下程式碼即可:

data[data < 0] = 0

利用一維布林陣列對每一行設定陣列也是非常簡單的:

data[names != 'Joe'] = 8

1.6神奇索引

神奇索引用於描述使用整數陣列進行資料索引。
假設有一個8*4的陣列:

arr = np.empty((8,4))
for i in range(8):
    arr[i] = i
print(arr)
--------------------------------------------
[[0. 0. 0. 0.]
 [1. 1. 1. 1.]
 [2. 2. 2. 2.]
 [3. 3. 3. 3.]
 [4. 4. 4. 4.]
 [5. 5. 5. 5.]
 [6. 6. 6. 6.]
 [7. 7. 7. 7.]]

為了選出一個符合特定順序的子集,我們可以簡單地通過傳遞一個包含指明所需順序的列表或者陣列來完成:

print(arr[[4,3,0,6]])
--------------------------------------------
[[4. 4. 4. 4.]
 [3. 3. 3. 3.]
 [0. 0. 0. 0.]
 [6. 6. 6. 6.]]

如果使用負的索引,將從尾部進行選擇:

print(arr[[-3,-5,-7]])
--------------------------------------------
[[5. 5. 5. 5.]
 [3. 3. 3. 3.]
 [1. 1. 1. 1.]]

傳遞多個索引陣列時情況有些許不同,這樣會根據每個索引元祖對應的元素選出一個一維陣列:

arr = np.arange(32).reshape((8,4))
print(arr)
--------------------------------------------
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]
 [16 17 18 19]
 [20 21 22 23]
 [24 25 26 27]
 [28 29 30 31]]
print(arr[[1,5,7,2],[0,3,1,2]])
--------------------------------------------
[ 4 23 29 10]

神奇索引的行為和一些使用者所設想的並不相同。通常情況下,我們所設想的結果是通過選擇矩陣中行列的子集所形成的矩形區域。下面是實現我們想法的一種方式:

print(arr[[1,5,7,2]][:,[0,3,1,2]])
--------------------------------------------
[[ 4  7  5  6]
 [20 23 21 22]
 [28 31 29 30]
 [ 8 11  9 10]]

1.7陣列轉置和換軸

轉置是一種特殊的資料重組方式,可以返回底層資料的檢視而不需要複製任何內容。陣列擁有transpose方法,也有特殊的T屬性。
當進行矩陣計算時,我們可能經常會進行一些特定操作,比如,當計算矩陣內積會使用np.dot:

arr = np.random.randn(6,3)
print(arr)
--------------------------------------------
[[ 0.05633563  0.47718954  1.23340357]
 [-0.07206776  0.46227127  0.21771718]
 [ 1.45472632  1.6935244  -0.06247615]
 [-0.46176249  0.90008442 -1.02197697]
 [-0.50517647  0.05549249  0.3537419 ]
 [ 1.36490178 -2.3503037  -0.67012919]]
print(np.dot(arr.T,arr))
--------------------------------------------
[[ 4.45598085 -1.19440998 -0.6585435 ]
 [-1.19440998  9.64658834  1.25817839]
 [-0.6585435   1.25817839  3.19123179]]

`