1. NumPy Basics: Arrays and Vectorized Computation

來自:http://pda.readthedocs.org/en/latest/chp4.html

NumPy 是 Numerical Python 的簡稱,是高性能計算和數據分析的基礎包。本書中幾乎所有高級工具都是建立在它的基礎之上,下面是它所能做的一些事情:

  • ndarray,快速和節省空間的多維數組,提供數組化的算術運算和高級的 廣播 功能。
  • 使用標準數學函數對整個數組的數據進行快速運算,而不需要編寫循環。
  • 讀取/寫入磁盤上的陣列數據和操作存儲器映像文件的工具。
  • 線性代數,隨機數生成,和傅里葉變換的能力。
  • 集成C,C++,Fortran代碼的工具。

從生態系統的角度看,最後一點是最爲重要的。因爲NumPy 提供了易用的C API,它可以很容易的將數據傳遞到使用低級語言編寫的外部庫,也可以使外部庫返回NumPy數組數據到Python。 這一特性使得Python成爲包裝傳統的C/C++/Fortran代碼庫,並給它們一個動態的、易於使用的接口的首選語言。

雖然NumPy本身並沒有提供非常高級的數據分析功能,但是瞭解NumPy的數組和麪向數組的計算將會幫助你高效的使用類似於pandas這樣的工具。 如果你是Python新手,並且只希望使用pandas來處理你手邊的數據,隨時可以略過這一章。 更多的NumPy的特性例如廣播,請見 第12章 。

對於大多數的數據分析應用來說,我關注的主要功能是:

  • 快速的矢量化數組操作:數據切割和清除,子集和過濾,轉化和任何其它類型的計算
  • 通用的數組算法,例如:sorting,unique和set操作
  • 有效的描述性統計和聚集/彙總數據
  • 數據對齊、關係數據的合併操作、異構數據的拼接操作
  • 使用數組表達式來表示條件邏輯,而不是用帶有 if-elif-else 分支的循環來表示
  • 組間數據的操作(聚合,轉換,功能應用)。關於這一點詳見 第5章

雖然NumPy提供了這些操作的計算功能,但你或許希望使用pandas作爲大多數數據分析的基礎(特別是結構化或表格數據),因爲它提供了一個豐富的,高級的接口使得常見的數據任務非常簡潔和簡單。 pandas也提供了更多的一些特定領域的功能,如時間數組操作,這是NumPy所沒有的。

在本章和全書,我始終使用 import numpy as np 。當然,你喜歡在代碼中使用 from numpy import * 來避免寫 np ,但我要提醒你反對這種習慣。

1.1. NumPy ndarray:多維數組對象

NumPy的一個關鍵特性是它的N維數組對象(ndarray),它在Python中是一個大型數據集的快速的,靈活的容器。 數組使你能夠在整個數據塊上進行數學運算,且與對應的純量元素間操作有相似的語法:

In [8]: data
Out[8]:
array([[ 0.9526, -0.246 , -0.8856],
       [ 0.5639, 0.2379, 0.9104]])
In [9]: data * 10                           In [10]: data + data
Out[9]:                                     Out[10]:
array([[ 9.5256, -2.4601, -8.8565],         array([[ 1.9051, -0.492 , -1.7713],
       [ 5.6385, 2.3794, 9.104 ]])                 [ 1.1277, 0.4759, 1.8208]])

ndarray是一個同種類數據的多維容器,也就是說,它的所有元素都是同類型的。每一個數組都有一個 shape(表示它每一維大小的元組)和 dtype (一個描述數組數據類型的對象):

In [11]: data.shape
Out[11]: (2, 3)
In [12]: data.dtype
Out[12]: dtype('float64')

本章將介紹ndarray的基礎知識,並足以應對本書剩下的部分。 雖然對於許多的數據分析應用來說不必要對NumPy有深入的理解,但是精通面向數組編程和思想是成爲一名科學的Python大師的關鍵一步。

每當你在正文中看見“array”, “NumPy array”, or “ndarray”,除了很少的列外之外,它們都指的是同一個東西:ndarray對象。

1.1.1. 創建ndarray

最簡單的創建數組的方式是使用 array 函數。它接受任何數組對象(包括其它數組),產生一個包含所傳遞的數據的新NumPy數組。例如,列表就是一個很好的用於轉換的候選:

In [13]: data1 = [6, 7.5, 8, 0, 1]
In [14]: arr1 = np.array(data1)
In [15]: arr1
Out[15]: array([ 6. , 7.5, 8. , 0. , 1. ])

嵌套序列,如等長列表的列表,將會轉化爲一個多維數組:

In [16]: data2 = [[1, 2, 3, 4], [5, 6, 7, 8]]
In [17]: arr2 = np.array(data2)
In [18]: arr2
Out[18]:
array([[1, 2, 3, 4],
[5, 6, 7, 8]])
In [19]: arr2.ndim
Out[19]: 2
In [20]: arr2.shape
Out[20]: (2, 4)

除非明確指定(在此以後會更多), np.array 試圖推斷一個好的數據類型給它所創建的數組。數據類型存儲在一個特定的 dtype 的對象中;例如,在上面的兩個例子中,我們有:

In [21]: arr1.dtype
Out[21]: dtype('float64')
In [22]: arr2.dtype
Out[22]: dtype('int64')

除 np.array 之外,還有許多函數來創建新的數組。例如, zeros 和 ones 使用給定的長度或形狀分別的創建0s 和 1s數組。 empty 會創建一個沒有使用特定值來初始化的數組。給這些方法傳遞一個元組作爲形狀來創建高維數組:

In [23]: np.zeros(10)
Out[23]: array([ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])
In [24]: np.zeros((3, 6))
Out[24]:
array([[ 0., 0., 0., 0., 0., 0.],
       [ 0., 0., 0., 0., 0., 0.],
       [ 0., 0., 0., 0., 0., 0.]])
In [25]: np.empty((2, 3, 2))
Out[25]:
array([[[ 4.94065646e-324, 4.94065646e-324],
        [ 3.87491056e-297, 2.46845796e-130],
        [ 4.94065646e-324, 4.94065646e-324]],
       [[ 1.90723115e+083, 5.73293533e-053],
        [ -2.33568637e+124, -6.70608105e-012],
        [ 4.42786966e+160, 1.27100354e+025]]])
假定 np.array 會返回一個全零的數組是不安全的。在許多情況下,如前所示,它將返回未初始化的垃圾值。

arange 是Python內建 range 函數的數組版本:

In [26]: np.arange(15)
Out[26]: array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14])

表格4-1 是一個用於構建數組的標準函數的清單。

數組構建函數
函數 描述
array 轉換輸入數據(列表,數組或其它序列類型)到一個ndarray,可以推斷一個dtype或明確的設置一個dtype。默認拷貝輸入數據。
asarray 轉換輸入爲一個ndarray,當輸入已經是一個ndarray時就不拷貝。
arange 同內建的range函數,但不返回列表而是一個ndarray
ones, ones_like 根據提供的shape和dtype產生一個全1的數組。ones_like使用另一歌數組爲入參,產生一個shape和dtype都相同的數組。
zeros, zeros_like 同ones和ones_like,但是生成全0的數組
empty, enpty_like 通過分配新內存來構造新的數組,但不同與ones 和 zeros,不初始任何值。
eye, identity 生成一個NxN的單位方陣(對角線上爲1,其它地方爲0)

1.1.2. ndarray的數據類型

數據類型或dtype是一個特別的對象,保存了ndarray如何解釋一塊內存爲特定類型數據的信息:

In [27]: arr1 = np.array([1, 2, 3], dtype=np.float64)
In [28]: arr2 = np.array([1, 2, 3], dtype=np.int32)
In [29]: arr1.dtype
Out[29]: dtype('float64')
In [30]: arr2.dtype
Out[30]: dtype('int32')

Dtypes是使NumPy如此強大和靈活的一部分。在大多數情況下,它們直接映射到底層的機器表示,這是的很容易地讀取和寫入二進制流到磁盤上,也能鏈接低級語言,如C 或Fortran編寫的代碼。數值表示的dtypes以相同的方式命名:一個類型名,如 folt 或 int ,後面跟着一個表示數字有多少位的數字。一個標準的雙精度浮點值(它是Python的 float 對象的底層表示)佔據8字節或64位。因此,這一類型在NumPy中被認爲是float64 。見 表格4-2 是一個NumPy支持的全部數據類型的清單。

不要爲了記憶NumPy的dtypes而煩惱,尤其你是一個新用戶。通常只需要關心 你所處理數據的普通類型(浮點、複數、整形、布爾型、字符竄或一般的Python對象)。 當你需要更多的控制數據如何存儲到內存和磁盤,特別是大的數據集,知道你所控制的存儲類型是很好的。
NumPy數據類型
類型 類型碼 描述
類型 類型碼 描述
int8, uint8 i1, u1 有符號和無符號8位(1字節)整數類型
int16, uint16 i2, u2 有符號和無符號16位整數類型
int32, uint32 i4, u4 有符號和無符號32位整數類型
int64, uint64 i8, u8 有符號和無符號64位整數類型
float16 f2 半精度浮點類型
float32 f4 or f 標準精度浮點。與C的 float 兼容
float64, float128 f8 or d 標準雙精度浮點。與C的 double 和Python 的 folat 對象兼容
float128 f16 or g 擴展精度浮點
complex64, complex128, complex256 c8, c16, c32 分別使用兩個32,64,128位浮點表示的複數
bool ? 布爾值,存儲 True 和 False
object O Python對象類型
string_ S 定長字符竄類型(每字符一字節)。例如,爲了生成長度爲10的字符竄,使用 ‘S10’
unicode_ f16 or g 擴展精度浮點(字節書依賴平臺)。同 string_ 有相同的語義規範(例如:``U10`` )

你可以使用ndarray的 astype 方法顯示的把一個數組的dtype轉換或 投射 到另外的類型:

In [31]: arr = np.array([1, 2, 3, 4, 5])
In [32]: arr.dtype
Out[32]: dtype('int64')
In [33]: float_arr = arr.astype(np.float64)
In [34]: float_arr.dtype
Out[34]: dtype('float64')

在這個例子中,整形被轉換到浮點型。如果把浮點數轉換到整形dtype,小數部分將會被截斷:

In [35]: arr = np.array([3.7, -1.2, -2.6, 0.5, 12.9, 10.1])
In [36]: arr
Out[36]: array([ 3.7, -1.2, -2.6, 0.5, 12.9, 10.1])
In [37]: arr.astype(np.int32)
Out[37]: array([ 3, -1, -2, 0, 12, 10], dtype=int32)

你可能有一個字符竄數組表示的數字,可以使用 astype 把它們轉換到數字的形式:

In [38]: numeric_strings = np.array(['1.25', '-9.6', '42'], dtype=np.string_)
In [39]: numeric_strings.astype(float)
Out[39]: array([ 1.25, -9.6 , 42. ])

如果因爲某些原因(如一個字符竄不能轉換到 float64 )轉換失敗了,將會引起一個 TypeError 。正如你所看見的,我有一些懶,使用 float 而不是 np.float64 ;NumPy會足夠聰明的把Python的類型對應到等價的dtypes。

你也可以使用dtype的另一個屬性:

In [40]: int_array = np.arange(10)
In [41]: calibers = np.array([.22, .270, .357, .380, .44, .50], dtype=np.float64)
In [42]: int_array.astype(calibers.dtype)
Out[42]: array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])

你也可以使用速記的類型碼字符竄來指定一個dtype:

In [43]: empty_uint32 = np.empty(8, dtype='u4')
In [44]: empty_uint32
Out[44]:
array([ 0, 0, 65904672, 0, 64856792, 0,
        39438163, 0], dtype=uint32)
調用 astype 總是會創建一個新的數組(原數據的拷貝),即使是新的dtype和原來的dtype相同。
值得牢記的是浮點數,如那些是 float64 和 float32 的數組,是唯一能夠接近分數的。在複雜的計算中,可能會產生 浮點錯誤 ,計較時到了一定的小數位數時纔有效。

1.1.3. 數組和純量間的操作

數組非常重要,因爲它們使你不使用循環就可以在數據上進行一系列操作。 這通常被叫做矢量化。相同大小的數組間的算術運算,其操作作用在對應的元素上:

In [45]: arr = np.array([[1., 2., 3.], [4., 5., 6.]])

In [46]: arr
Out[46]:
array([[ 1.,  2.,  3.],
       [ 4.,  5.,  6.]])

In [47]: arr * arr                 In [48]:arr - arr
Out[47]:                           Out[48]:
array([[  1.,   4.,   9.],        array([[ 0., 0., 0.],
       [ 16.,  25.,  36.]])              [ 0., 0., 0.]])

純量的算術操作正如你期望的一樣,把操作值作用於每一個元素:

In [49]: 1 / arr                        In [50]: arr ** 0.5
Out[49]:                                Out[50]:
array([[ 1. , 0.5 , 0.3333],            array([[ 1. , 1.4142, 1.7321],
       [ 0.25 , 0.2 , 0.1667]])                [ 2. , 2.2361, 2.4495]])

在不同大小的數組見的操作被叫做 broadcasting ,將在 第12章 詳細討論。深入的瞭解broadcasting在本書的多數地方是不必要的。

1.1.4. 基本的索引和切片

NumPy的索引是一個內容豐富的主題,因爲有許多方法可以使你在你的數據中選取一個子集或單個元素。一維的數組很簡單,表面上它們的行爲類似於Python的列表:

In [51]: arr = np.arange(10)
In [52]: arr
Out[52]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
In [53]: arr[5]
Out[53]: 5
In [54]: arr[5:8]
Out[54]: array([5, 6, 7])
In [55]: arr[5:8] = 12
In [56]: arr
Out[56]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

如你所見,當你給一個切片賦一純量值,如 arr[5:8] = 12 所示,該值被傳送(或 傳播 )到整個選擇區域。與列表的第一個重要的區別是數組的切片在原來的數組上(不生成新的數組)。這意味着數據不會被拷貝,且對切片的任何修改都會影響源數組:

In [57]: arr_slice = arr[5:8]
In [58]: arr_slice[1] = 12345
In [59]: arr
Out[59]: array([ 0, 1, 2, 3, 4, 12, 12345, 12, 8, 9])
In [60]: arr_slice[:] = 64
In [61]: arr
Out[61]: array([ 0, 1, 2, 3, 4, 64, 64, 64, 8, 9])

如果你是使用NumPy的新手,這一點回事你感到驚訝,尤其當你使用過其它數組編程語言,它們非常熱衷於拷貝數據。請記住,NumPy是設計用來處理大數據的情況,你可以想象如果NumPy堅持使用拷貝數據將會出現的性能和內存問題。

如果你想有數組切片的一個拷貝,你需要明顯的拷貝數組;例如 arr[5:8].copy() 。

對於高維數組,你會有更多選項。在兩維的數組,每一個索引的元素將不再是一個純量,而是一個一維數組:

In [62]: arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
In [63]: arr2d[2]
Out[63]: array([7, 8, 9])

因此,單個元素可以遞歸的訪問,但是這會做多一點的工作。不過,你可以使用一個逗號分隔的索引列表來選擇單個元素。因此,下面的操作是等價的:

In [64]: arr2d[0][2]
Out[64]: 3
In [65]: arr2d[0, 2]
Out[65]: 3

見 NumPy數組的索引,是在二維數組上的索引圖例。

_images/index-2d.png

NumPy數組的索引

在多維數組中,如果你省略了後面的索引,返回的對象將會是一個較低維的ndarray,它包括較高維度的所有數據。因此,在 2*2*3 的數組 arr3d 中

In [66]: arr3d = np.array([[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]])
In [67]: arr3d
Out[67]:
array([[[ 1, 2, 3],
        [ 4, 5, 6]],
       [[ 7, 8, 9],
        [10, 11, 12]]])

arr3d[0] 是一個 2*3 的數組:

In [68]: arr3d[0]
Out[68]:
array([[1, 2, 3],
       [4, 5, 6]])

純量值和數組都可以給 arr3d[0] 賦值:

In [69]: old_values = arr3d[0].copy()
In [70]: arr3d[0] = 42

In [71]: arr3d
Out[71]:
array([[[42, 42, 42],
        [42, 42, 42]],
       [[ 7, 8, 9],
        [10, 11, 12]]])

In [72]: arr3d[0] = old_values

In [73]: arr3d
Out[73]:
array([[[ 1, 2, 3],
        [ 4, 5, 6]],
       [[ 7, 8, 9],
        [10, 11, 12]]])

類似的, arr3d[1, 0] 給你那些索引以 (1, 0) 開始的值,形成了一個1維數組:

In [74]: arr3d[1, 0]
Out[74]: array([7, 8, 9])

請注意,在所有的情況下,被選中的子節返回的數組總是數組視窗。

1.1.4.1. 帶切片的索引

如同一維對象,例如Python的列表,ndarrys可以使用熟悉的語法來切片:

In [75]: arr[1:6]
Out[75]: array([ 1, 2, 3, 4, 64])

較高維的對象給你更多的選擇,你可以切割一個或多個座標座標軸,並且可以混合整數。對上面的2維數組,arr2d ,對它的切片有些不同:

In [76]: arr2d              In [77]: arr2d[:2]
Out[76]:                    Out[77]:
array([[1, 2, 3],           array([[1, 2, 3],
       [4, 5, 6],                  [4, 5, 6]])
       [7, 8, 9]])

正如你所見,它沿着 0 座標座標軸(第一個座標座標軸)切片。因此,一個切片沿着一個座標座標軸向選擇一個範圍的元素。你可以傳遞多個切片,就像你傳遞多個索引一樣:

In [78]: arr2d[:2, 1:]
Out[78]:
array([[2, 3],
       [5, 6]])

象這樣切片時,你得到的總是相同維數的數組視窗。通過混合整形索引和切片,你可以得到較低維的切片:

In [79]: arr2d[1, :2]             In [80]: arr2d[2, :1]
Out[79]: array([4, 5])            Out[80]: array([7])

見 兩維數組切片 圖解。注意,一個單一的冒號意味着取整個座標/座標軸,因此,你可以只切割更高維的座標軸,做法如下:

In [81]: arr2d[:, :1]
Out[81]:
array([[1],
       [4],
       [7]])

當然,給一個切片表達式賦值會對整個選擇賦值:

In [82]: arr2d[:2, 1:] = 0
_images/slicing-2d.png

兩維數組切片

1.1.5. 布爾索引

讓我們來考慮一個例子,我們有一些數據在一個數組中和一個有重複名字的數組。我將會在這使用numpy.random 中的 randn 函數來產生一些隨機的正態分佈的數據:

In [83]: names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])
In [84]: data = randn(7, 4)
In [85]: names
Out[85]:
array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'],
      dtype='|S4')
In [86]: data
Out[86]:
array([[-0.048 , 0.5433, -0.2349, 1.2792],
       [-0.268 , 0.5465, 0.0939, -2.0445],
       [-0.047 , -2.026 , 0.7719, 0.3103],
       [ 2.1452, 0.8799, -0.0523, 0.0672],
       [-1.0023, -0.1698, 1.1503, 1.7289],
       [ 0.1913, 0.4544, 0.4519, 0.5535],
       [ 0.5994, 0.8174, -0.9297, -1.2564]])

假設每一個名字都和 data 數組中的一行對應。如果我們想要選擇與 ‘Bob’ 名字對應的所有行。象算術運算一樣,數組的比較操作(例如 == )也可以矢量化。因此, names 和 Bob 字符竄的比較會產生一個布爾數組:

In [87]: names == 'Bob'
Out[87]: array([ True, False, False, True, False, False, False], dtype=bool)

當索引數組時可以傳遞這一布爾數組:

In [88]: data[names == 'Bob']
Out[88]:
array([[-0.048 , 0.5433, -0.2349, 1.2792],
       [ 2.1452, 0.8799, -0.0523, 0.0672]])

布爾數組必須和它索引的座標軸的長度相同。你甚至可以把布爾數組和切片或整數(或者整數序列,關於這一點後面會更多介紹)混合和匹配起來:

In [89]: data[names == 'Bob', 2:]
Out[89]:
array([[-0.2349, 1.2792],
       [-0.0523, 0.0672]])
In [90]: data[names == 'Bob', 3]
Out[90]: array([ 1.2792, 0.0672])

爲了選擇除了 ‘Bob’ 之外的所有東西,你可以使用 != 或用 - 對條件表達式取反:

In [91]: names != 'Bob'
Out[91]: array([False, True, True, False, True, True, True], dtype=bool)

In [92]: data[-(names == 'Bob')]
Out[92]:
array([[-0.268 , 0.5465, 0.0939, -2.0445],
       [-0.047 , -2.026 , 0.7719, 0.3103],
       [-1.0023, -0.1698, 1.1503, 1.7289],
       [ 0.1913, 0.4544, 0.4519, 0.5535],
       [ 0.5994, 0.8174, -0.9297, -1.2564]])

使用布爾算術操作符如 & (and) 和 | (or)來結合多個布爾條件,下面是從三個名字中選取兩個的操作:

In [93]: mask = (names == 'Bob') | (names == 'Will')
In [94]: mask
Out[94]: array([True, False, True, True, True, False, False], dtype=bool)
In [95]: data[mask]
Out[95]:
array([[-0.048 , 0.5433, -0.2349, 1.2792],
       [-0.047 , -2.026 , 0.7719, 0.3103],
       [ 2.1452, 0.8799, -0.0523, 0.0672],
       [-1.0023, -0.1698, 1.1503, 1.7289]])

通過布爾索引從一個數組中選取數據 總是 會創建數據的一份拷貝,即使是返回的數組沒有改變。

Python的 and 和 or 關鍵字不能與布爾數組一起工作。

通過布爾數組設置值工作於一種種常識性的方式。爲了設置 data 中所有的負值爲0,我們只需要:

In [96]: data[data < 0] = 0
In [97]: data
Out[97]:
array([[ 0. , 0.5433, 0. , 1.2792],
       [ 0. , 0.5465, 0.0939, 0. ],
       [ 0. , 0. , 0.7719, 0.3103],
       [ 2.1452, 0.8799, 0. , 0.0672],
       [ 0. , 0. , 1.1503, 1.7289],
       [ 0.1913, 0.4544, 0.4519, 0.5535],
       [ 0.5994, 0.8174, 0. , 0. ]])

使用一維布爾數組設置整行或列也非常簡單:

In [98]: data[names != 'Joe'] = 7
In [99]: data
Out[99]:
array([[ 7. , 7. , 7. , 7. ],
       [ 0. , 0.5465, 0.0939, 0. ],
       [ 7. , 7. , 7. , 7. ],
       [ 7. , 7. , 7. , 7. ],
       [ 7. , 7. , 7. , 7. ],
       [ 0.1913, 0.4544, 0.4519, 0.5535],
       [ 0.5994, 0.8174, 0. , 0. ]])

1.1.6. Fancy索引

Fancy 索引 是一個術語,被NumPy用來描述使用整形數組索引。假如我們有一個 8*4 的數組:

In [100]: arr = np.empty((8, 4))
In [101]: for i in range(8):
   .....:     arr[i] = i
In [102]: arr
Out[102]:
array([[ 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.]])

爲了選出一個有特定順序行的子集,你可以傳遞一個列表或整形ndarray來指定想要的順序:

In [103]: arr[[4, 3, 0, 6]]
Out[103]:
array([[ 4., 4., 4., 4.],
       [ 3., 3., 3., 3.],
       [ 0., 0., 0., 0.],
       [ 6., 6., 6., 6.]])

很慶幸這個代碼做了你所期望的!使用負的索引從結尾選擇行:

In [104]: arr[[-3, -5, -7]]
Out[104]:
array([[ 5., 5., 5., 5.],
       [ 3., 3., 3., 3.],
       [ 1., 1., 1., 1.]])

傳遞多個索引數組有些微的不同;它選取一個一維數組,元素對應與索引的每一個元組:

# 關於reshape在第12章會跟多介紹
In [105]: arr = np.arange(32).reshape((8, 4))
In [106]: arr
Out[106]:
array([[ 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]])
In [107]: arr[[1, 5, 7, 2], [0, 3, 1, 2]]
Out[107]: array([ 4, 23, 29, 10])

花一點兒時間來看看剛剛發生了什麼:元素 (1, 0), (5, 3), (7, 1), 和(2, 2)被選擇了。 fancy索引的行爲與一些用戶(也包括我自己)可能期望的有所不同, 它因該是一個矩形區域,由選取的矩形的行和列組成。這裏有一個方法來得到它:

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

另一種方法是使用 np.ix_ 函數,將兩個以爲整形數組轉換爲位標,來選取一個正方形區域:

In [109]: arr[np.ix_([1, 5, 7, 2], [0, 3, 1, 2])]
Out[109]:
array([[ 4, 7, 5, 6],
       [20, 23, 21, 22],
       [28, 31, 29, 30],
       [ 8, 11, 9, 10]])

注意,fancy索引,不像切片,它總是拷貝數據到一個新的數組。

1.1.7. 轉置數組和交換座標軸

轉置是一種特殊形式的變形,類似的它會返回基礎數據的一個視窗,而不會拷貝任何東西。數組有transpose 方法和專門的 T 屬性:

In [110]: arr = np.arange(15).reshape((3, 5))
In [111]: arr                          In [112]: arr.T
Out[111]:                              Out[112]:
array([[ 0, 1, 2, 3, 4],               array([[ 0, 5, 10],
       [ 5, 6, 7, 8, 9],                      [ 1, 6, 11],
       [10, 11, 12, 13, 14]])                 [ 2, 7, 12],
                                              [ 3, 8, 13],
                                              [ 4, 9, 14]])

當進行矩陣運算時,你常常會這樣做,像下面的例子一樣,使用 np.dot 計算內部矩陣來產生 XTX` :

In [113]: arr = np.random.randn(6, 3)
In [114]: np.dot(arr.T, arr)
Out[114]:
array([[ 2.584 , 1.8753, 0.8888],
       [ 1.8753, 6.6636, 0.3884],
       [ 0.8888, 0.3884, 3.9781]])

對於更高維的數組, transpose 接受用於轉置的座標軸的號碼的一個元組(for extra mind bending):

In [115]: arr = np.arange(16).reshape((2, 2, 4))
In [116]: arr
Out[116]:
array([[[ 0, 1, 2, 3],
        [ 4, 5, 6, 7]],
       [[ 8, 9, 10, 11],
        [12, 13, 14, 15]]])

In [117]: arr.transpose((1, 0, 2))
Out[117]:
array([[[ 0, 1, 2, 3],
        [ 8, 9, 10, 11]],
       [[ 4, 5, 6, 7],
        [12, 13, 14, 15]]])

使用 .T 的轉置,僅僅是交換座標軸的一個特殊的情況:

In [118]: arr                             In [119]: arr.swapaxes(1, 2)
Out[118]:                                 Out[119]:
array([[[ 0, 1, 2, 3],                    array([[[ 0, 4],
        [ 4, 5, 6, 7]],                           [ 1, 5],
                                                  [ 2, 6],
       [[ 8, 9, 10, 11],                          [ 3, 7]],
        [12, 13, 14, 15]]])
                                                 [[ 8, 12],
                                                 [ 9, 13],
                                                 [10, 14],
                                                 [11, 15]]])

類似的 swapaxes 返回在數據上的一個視窗,而不進行拷貝。

1.2. 通用函數:快速的基於元素的數組函數

一個通用的函數,或者 ufunc ,是一個在ndarrays的數據上進行基於元素的操作的函數。你可以認爲它們是對簡單函數的一個快速矢量化封裝,它們接受一個或多個標量值併產生一個或多個標量值。

許多 ufuncs 都是基於元素的簡單變換,像 sqrt 或 exp :

In [120]: arr = np.arange(10)
In [121]: np.sqrt(arr)
Out[121]:
array([ 0. , 1. , 1.4142, 1.7321, 2. , 2.2361, 2.4495,
        2.6458, 2.8284, 3. ])
In [122]: np.exp(arr)
Out[122]:
array([ 1. , 2.7183, 7.3891, 20.0855, 54.5982,
       148.4132, 403.4288, 1096.6332, 2980.958 , 8103.0839])

這些歸諸於 unary ufuncs。其它的,例如 add 或 maximum ,接受兩個數組(因此,叫做 binary ufuncs)且返回一個數組:

In [123]: x = randn(8)
In [124]: y = randn(8)
In [125]: x
Out[125]:
array([ 0.0749, 0.0974, 0.2002, -0.2551, 0.4655, 0.9222, 0.446 ,
       -0.9337])
In [126]: y
Out[126]:
array([ 0.267 , -1.1131, -0.3361, 0.6117, -1.2323, 0.4788, 0.4315,
       -0.7147])
In [127]: np.maximum(x, y) # element-wise maximum
Out[127]:
array([ 0.267 , 0.0974, 0.2002, 0.6117, 0.4655, 0.9222, 0.446 ,
       -0.7147])

雖然不常見,一個ufunc可以返回多個數組。 nodf 就是一個例子,它是Python內建 divmod 的矢量化的版本:它返回一個副點數數組的分數和整數部分:

In [128]: arr = randn(7) * 5
In [129]: np.modf(arr)
Out[129]:
(array([-0.6808, 0.0636, -0.386 , 0.1393, -0.8806, 0.9363, -0.883 ]),
 array([-2., 4., -3., 5., -3., 3., -6.]))

見 表格4-3 和 表格4-4 是可用的ufuncs的清單。

Unary ufuncs
函數 描述
abs, fabs 計算基於元素的整形,浮點或複數的絕對值。fabs對於沒有複數數據的快速版本
sqrt 計算每個元素的平方根。等價於 arr ** 0.5
square 計算每個元素的平方。等價於 arr ** 2
exp 計算每個元素的指數。
log, log10, log2, log1p 自然對數(基於e),基於10的對數,基於2的對數和 log(1 + x)
sign 計算每個元素的符號:1(positive),0(zero), -1(negative)
ceil 計算每個元素的天花板,即大於或等於每個元素的最小值
floor 計算每個元素的地板,即小於或等於每個元素的最大值
rint 圓整每個元素到最近的整數,保留dtype
modf 分別返回分數和整數部分的數組
isnan 返回布爾數組標識哪些元素是 NaN (不是一個數)
isfinite, isinf 分別返回布爾數組標識哪些元素是有限的(non-inf, non-NaN)或無限的
cos, cosh, sin sinh, tan, tanh regular 和 hyperbolic 三角函數
arccos, arccosh, arcsin, arcsinh, arctan, arctanh 反三角函數
logical_not 計算基於元素的非x的真值。等價於 -arr
Binary universal funcitons
函數 描述
add 在數組中添加相應的元素
substract 在第一個數組中減去第二個數組
multiply 對數組元素相乘
divide, floor_divide 除和地板除(去掉餘數)
power 使用第二個數組作爲指數提升第一個數組中的元素
maximum, fmax 基於元素的最大值。 fmax 忽略 NaN
minimum, fmin 基於元素的最小值。 fmin 忽略 NaN
mod 基於元素的模(取餘)
copysign 拷貝第二個參數的符號到第一個參數
greater, greater_equal, less, less_equal, not_equal 基於元素的比較,產生布爾數組。等價於中綴操作符 >, >=, <, <=,==, !=
logical_and, logical_or, logical_xor 計算各個元素邏輯操作的真值。等價於中綴操作符 &, |, ^

1.3. 使用數組進行數據處理

使用NumPy可以是你能夠使用簡明的數組表達式而不是編寫循環表達許多種類的數據處理任務。這種使用數組表達式代替顯示循環通常被成爲“矢量化”。在一般情況下,矢量化數組操作比與之等價的純Python操作數度快一到兩(或更多)個等級,這對任何種類的數值計算有最大的影響。稍後,在chp12index中,我會講解broadcasting ,一個矢量化計算的強大方法。

作爲一個簡單示例,假如我們希望研究函數 sqrt(x\ :sup:`^`\ 2 + \ :sup:`^`\ 2) 穿過一個網格數據。np.meshgrid 函數接受兩個一維數組併產生兩個二維矩陣,其值對於兩個數組的所有 (x, y) 對:

In [130]: points = np.arange(-5, 5, 0.01) # 1000個等間隔點
In [131]: xs, ys = np.meshgrid(points, points)
In [132]: ys
Out[132]:
array([[-5. , -5. , -5. , ..., -5. , -5. , -5. ],
       [-4.99, -4.99, -4.99, ..., -4.99, -4.99, -4.99],
       [-4.98, -4.98, -4.98, ..., -4.98, -4.98, -4.98],
       ...,
       [ 4.97, 4.97, 4.97, ..., 4.97, 4.97, 4.97],
       [ 4.98, 4.98, 4.98, ..., 4.98, 4.98, 4.98],
       [ 4.99, 4.99, 4.99, ..., 4.99, 4.99, 4.99]])

現在,研究這個函數是一個簡單的事情,編寫與你可能寫過的相同的表達式:

In [134]: import matplotlib.pyplot as plt
In [135]: z = np.sqrt(xs ** 2 + ys ** 2)
In [136]: z
Out[136]:
array([[ 7.0711, 7.064 , 7.0569, ..., 7.0499, 7.0569, 7.064 ],
       [ 7.064 , 7.0569, 7.0499, ..., 7.0428, 7.0499, 7.0569],
       [ 7.0569, 7.0499, 7.0428, ..., 7.0357, 7.0428, 7.0499],
       ...,
       [ 7.0499, 7.0428, 7.0357, ..., 7.0286, 7.0357, 7.0428],
       [ 7.0569, 7.0499, 7.0428, ..., 7.0357, 7.0428, 7.0499],
       [ 7.064 , 7.0569, 7.0499, ..., 7.0428, 7.0499, 7.0569]])
In [137]: plt.imshow(z, cmap=plt.cm.gray); plt.colorbar()
Out[137]: <matplotlib.colorbar.Colorbar instance at 0x4e46d40>
In [138]: plt.title("Image plot of $\sqrt{x^2 + y^2}$ for a grid of values")
Out[138]: <matplotlib.text.Text at 0x4565790>

見 繪製在網格上的函數,我使用 matplotlib 函數 imshow 創建一個了一個圖像,數據來源於上面的函數生成的二維數組。

_images/plot-grid.png

繪製在網格上的函數

1.3.1. 用數組操作來表達條件邏輯

函數 numpy.where 是三元表達式 x if condition else y 的矢量化版本。假如我們有一個布爾數組和兩個值數組:

In [140]: xarr = np.array([1.1, 1.2, 1.3, 1.4, 1.5])
In [141]: yarr = np.array([2.1, 2.2, 2.3, 2.4, 2.5])
In [142]: cond = np.array([True, False, True, True, False])

假如我們想要當對應的 cond 值爲 True 時,從 xarr 中獲取一個值,否則從 yarr 中獲取值。使用列表推到來做這件事,可能會像這樣:

In [143]: result = [(x if c else y)
   .....: for x, y, c in zip(xarr, yarr, cond)]
In [144]: result
Out[144]: [1.1000000000000001, 2.2000000000000002, 1.3, 1.3999999999999999, 2.5]

這樣做會有許多問題。首先,對於大的數組,它不會很快(因爲所有的工作都是有純Python來做的)。其次,對於多維數組,它不能工作。使用 np.where 你可以像這樣非常簡潔的編寫:

In [145]: result = np.where(cond, xarr, yarr)
In [146]: result
Out[146]: array([ 1.1, 2.2, 1.3, 1.4, 2.5])

np.where 的第一個和第二個參數不需要是數組;它們中的一個或兩個可以是純量。 在數據分析中 where的典型使用是生成一個新的數組,其值基於另一個數組。假如你有一個矩陣,其數據是隨機生成的,你想要把其中的正值替換爲2,負值替換爲-2,使用 np.where 非常容易:

In [147]: arr = randn(4, 4)
In [148]: arr
Out[148]:
array([[ 0.6372, 2.2043, 1.7904, 0.0752],
       [-1.5926, -1.1536, 0.4413, 0.3483],
       [-0.1798, 0.3299, 0.7827, -0.7585],
       [ 0.5857, 0.1619, 1.3583, -1.3865]])
In [149]: np.where(arr > 0, 2, -2)
Out[149]:
array([[ 2, 2, 2, 2],
       [-2, -2, 2, 2],
       [-2, 2, 2, -2],
       [ 2, 2, 2, -2]])

In [150]: np.where(arr > 0, 2, arr) # 僅設置正值爲 2
Out[150]:
array([[ 2. , 2. , 2. , 2. ],
       [-1.5926, -1.1536, 2. , 2. ],
       [-0.1798, 2. , 2. , -0.7585],
       [ 2. , 2. , 2. , -1.3865]])

傳遞到 where 的數組不僅僅只是大小相等的數組或純量。

使用一些小聰明,你可以使用 where 來表達更復雜的邏輯;考慮這個例子,我有兩個布爾數組, cond1 和cond2 ,並想根據4種布爾值來賦值:

result = []
for i in range(n):
    if cond1[i] and cond2[i]:
        result.append(0)
    elif cond1[i]:
        result.append(1)
    elif cond2[i]:
        result.append(2)
    else:
        result.append(3)

也許可能不會很明顯,這個 for 循環可以轉換成一個嵌套的 where 表達式:

np.where(cond1 & cond2, 0,
         np.where(cond1, 1,
                  np.where(cond2, 2, 3)))

在這個特殊的例子中,我們還可以利用布爾表達式在計算中被當作0或1這一事實,因此可以使用算術運算來表達:

result = 1 * cond1 + 2 * cond2 + 3 * -(cond1 | cond2)

1.3.2. 數學和統計方法

一組數學函數,計算整個數組或一個軸向上數據的統計,和數組函數一樣是容易訪問的。聚合(通常被稱爲reductions ),如 sun , mean ,標準偏差 std 可以使用數組實例的方法,也可以使用頂層NumPy的函數:

In [151]: arr = np.random.randn(5, 4) # 正態分佈數據
In [152]: arr.mean()
Out[152]: 0.062814911084854597
In [153]: np.mean(arr)
Out[153]: 0.062814911084854597
In [154]: arr.sum()
Out[154]: 1.2562982216970919

像 mean 和 sun 函數可以有一個可選的 axis 參數,它對給定座標軸進行統計,結果數組將會減少一個維度:

In [155]: arr.mean(axis=1)
Out[155]: array([-1.2833, 0.2844, 0.6574, 0.6743, -0.0187])
In [156]: arr.sum(0)
Out[156]: array([-3.1003, -1.6189, 1.4044, 4.5712])

像 cumsum 和 cumprod 這些函數並不聚集,而是產生一個 intermediate results 的數組:

In [157]: arr = np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]])
In [158]: arr.cumsum(0)         In [159]: arr.cumprod(1)
Out[158]:                       Out[159]:
array([[ 0, 1, 2],              array([[ 0, 0, 0],
[ 3, 5, 7],                            [ 3, 12, 60],
[ 9, 12, 15]])                         [ 6, 42, 336]])

表格4-5 是一個完整的清單。我們將在稍後的章節中看見關於這些函數的大量例子。

數組構建函數
方法 描述
sum 對數組的所有或一個軸向上的元素求和。零長度的數組的和爲靈。
mean 算術平均值。靈長度的數組的均值爲NaN。
std, var 標準差和方差,有可選的調整自由度(默認值爲n)。
min, max 最大值和最小值
argmin, argmax 索引最小和最大元素。
cumsum 從0元素開始的累計和。
cumprod 從1元素開始的累計乘。

1.3.3. 布爾數組的方法

在上面的方法中布爾值被強制爲1( True )和0a( False )。因此, sum 經常被用來作爲對一個布爾數組中的True 計數的手段:

In [160]: arr = randn(100)
In [161]: (arr > 0).sum() # 正值的個數
Out[161]: 44

有兩個額外的方法, any 和 all ,對布爾數組尤其有用。 any 用來測試一個數組中是否有一個或更多的True ,而 all 用來測試所有的值是否爲 True :

In [162]: bools = np.array([False, False, True, False])
In [163]: bools.any()
Out[163]: True
In [164]: bools.all()
Out[164]: False

這些方法這些方法也可以工作在非不而數組上,非零元素作爲 True 。

1.3.4. 排序

像Python的內建列表一樣,NumPy數組也可以使用 sort 方法就地排序:

In [165]: arr = randn(8)
In [166]: arr
Out[166]:
array([ 0.6903, 0.4678, 0.0968, -0.1349, 0.9879, 0.0185, -1.3147,
       -0.5425])
In [167]: arr.sort()
In [168]: arr
Out[168]:
array([-1.3147, -0.5425, -0.1349, 0.0185, 0.0968, 0.4678, 0.6903,
        0.9879])

多維數組可以通過傳遞一個座標軸數到 sort ,對一維截面上的數據進行就地排序:

In [169]: arr = randn(5, 3)
In [170]: arr
Out[170]:
array([[-0.7139, -1.6331, -0.4959],
       [ 0.8236, -1.3132, -0.1935],
       [-1.6748, 3.0336, -0.863 ],
       [-0.3161, 0.5362, -2.468 ],
       [ 0.9058, 1.1184, -1.0516]])
In [171]: arr.sort(1)
In [172]: arr
Out[172]:
array([[-1.6331, -0.7139, -0.4959],
       [-1.3132, -0.1935, 0.8236],
       [-1.6748, -0.863 , 3.0336],
       [-2.468 , -0.3161, 0.5362],
       [-1.0516, 0.9058, 1.1184]])

頂層的 np.sort 函數返回一個經過排序後的數組拷貝,而不是就地修改。一個快速和骯髒的計算一個數組的位數是對它排序並選擇一個特定階層值:

In [173]: large_arr = randn(1000)
In [174]: large_arr.sort()
In [175]: large_arr[int(0.05 * len(large_arr))] # 5% quantile
Out[175]: -1.5791023260896004

關於使用NumPy的排序方法和更高級的技術,如間接排序,請見第12章。其它幾種有關排序的數據操作(例如,通過一列或多列對數據表排序)也會在 pandas 中找到。

1.3.5. Unique 和其它集合邏輯

Numpy有一些基本的針對一維ndarrays的集合操作。最常使用的一個可能是 np.unique ,它返回一個數組的經過排序的 unique 值:

In [176]: names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])
In [177]: np.unique(names)
Out[177]:
array(['Bob', 'Joe', 'Will'],
      dtype='|S4')
In [178]: ints = np.array([3, 3, 3, 2, 2, 1, 1, 4, 4])
In [179]: np.unique(ints)
Out[179]: array([1, 2, 3, 4])

np.unique 與純Python版本比較:

In [180]: sorted(set(names))
Out[180]: ['Bob', 'Joe', 'Will']

另一個函數 np.in1d ,測試一個數組的值和另一個的關係,返回一個布爾數組:

In [181]: values = np.array([6, 0, 0, 3, 2, 5, 6])
In [182]: np.in1d(values, [2, 3, 6])
Out[182]: array([ True, False, False, True, True, False, True], dtype=bool)

見 表格4-6 是關於集合函數的清單。

數組集合操作
unique(x) 計算x單一的元素,並對結果排序
intersect1d(x, y) 計算x和y相同的元素,並對結果排序
union1d 結合x和y的元素,並對結果排序
in1d(x, y) 得到一個布爾數組指示x中的每個元素是否在y中
setdiff1d(x, y) 差集,在x中但不再y中的集合
setxor1d(x, y) 對稱差集,不同時在兩個數組中的元素

1.4. 關於數組的文件輸入和輸出

NumPy能夠保存數據到磁盤和從磁盤加載數據,不論數據是文本或二進制的。在後面的章節你可以學到使用pandas提供的工具來加載表格化的數據到內存。

1.4.1. 對磁盤上的二進制格式數組排序

np.save 和 np.load 是兩個主力功能,有效的保存和加載磁盤數據。數組默認保存爲未經過壓縮的原始二進制數據,文件擴展名爲 .npy :

In [183]: arr = np.arange(10)
In [184]: np.save('some_array', arr)

如果文件路進並不是以 .npy 結尾,擴展名將會被自動加上。在磁盤上的數組可以使用 np.load 加載:

In [185]: np.load('some_array.npy')
Out[185]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

你可以使用 np.savez 並以關鍵字參數傳遞數組來保存多個數組到一個zip的歸檔文件中:

In [186]: np.savez('array_archive.npz', a=arr, b=arr)

當你加載一個 .npz 文件時,會得到一個字典對象,它懶洋洋的加載單個數組:

In [187]: arch = np.load('array_archive.npz')
In [188]: arch['b']
Out[188]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

1.4.2. 保存和加載文本文件

從文件加載文本是一個相當標準的任務。對一個新人來說,Python的文件加讀取和寫入函數的景象可能有一點兒混亂,因此我將主要集中在pandas的 read_csv 和 read_table 函數上。有時使用 np.loadtxt 或更專門的 np.genfromtxt 對於加載數據到 vanilla NumPy 數組是很有用的。

這些函數有許多選項,允許你指定不同的分割副,特定列的轉換函數,跳過某些行,和其它的事情。以這樣一個逗號分割文件(CSV)作爲一個簡單的例子:

In [191]: !cat array_ex.txt
0.580052,0.186730,1.040717,1.134411
0.194163,-0.636917,-0.938659,0.124094
-0.126410,0.268607,-0.695724,0.047428
-1.484413,0.004176,-0.744203,0.005487
2.302869,0.200131,1.670238,-1.881090
-0.193230,1.047233,0.482803,0.960334

它可以像這樣被加載到一個二維數組:

In [192]: arr = np.loadtxt('array_ex.txt', delimiter=',')
In [193]: arr
Out[193]:
array([[ 0.5801, 0.1867, 1.0407, 1.1344],
[ 0.1942, -0.6369, -0.9387, 0.1241],
[-0.1264, 0.2686, -0.6957, 0.0474],
[-1.4844, 0.0042, -0.7442, 0.0055],
[ 2.3029, 0.2001, 1.6702, -1.8811],
[-0.1932, 1.0472, 0.4828, 0.9603]])

np.savatxt 執行相反的操作:寫入數組到一個界定文本文件中。 genfromtxt 與 loadtxt 相似,但是她是面向結構數組和缺失數據處理的;更多關於結構數組請見第12章 。

更多有關讀取和寫入,特別是表格式的或類電子表格的數據,見後面涉及到pandas和DataFrame對象的章節。

1.5. 線性代數

線性代數,如矩陣乘法,分解,行列式和其它的方陣數學,對任何一個數組庫來說都是重要的部分。不像一些語言,如 MATLAB ,使用 * 來乘兩個二維數組是基於元素的乘法,而不是矩陣點積。因此,有一個 dot 函數,是數組的一個方法和 numpy 命名空間中的一個函數,用來進行矩陣乘法運算:

In [194]: x = np.array([[1., 2., 3.], [4., 5., 6.]])
In [195]: y = np.array([[6., 23.], [-1, 7], [8, 9]])
In [196]: x                In [197]: y
Out[196]:                  Out[197]:
array([[ 1., 2., 3.],      array([[ 6., 23.],
       [ 4., 5., 6.]])            [ -1., 7.],
                                  [ 8., 9.]])
In [198]: x.dot(y) # equivalently np.dot(x, y)
Out[198]:
array([[ 28., 64.],
       [ 67., 181.]])

在一個二維數組和合適大小的一維數組間的矩陣乘積的結果是一個一維數組:

In [199]: np.dot(x, np.ones(3))
Out[199]: array([ 6., 15.])

numpy.linalg 有一個關於矩陣分解和像轉置和行列式等的一個標準集合。它們和其它語言(如: MATLAB和 R )一樣都是基於行業標準的 Fortran 庫,如 BLSA , LAPACK ,或可能的 Intel MKL (依賴於你的NumPy的編譯)實現的:

In [201]: from numpy.linalg import inv, qr
In [202]: X = randn(5, 5)
In [203]: mat = X.T.dot(X)
In [204]: inv(mat)
Out[204]:
array([[ 3.0361, -0.1808, -0.6878, -2.8285, -1.1911],
       [-0.1808, 0.5035, 0.1215, 0.6702, 0.0956],
       [-0.6878, 0.1215, 0.2904, 0.8081, 0.3049],
       [-2.8285, 0.6702, 0.8081, 3.4152, 1.1557],
       [-1.1911, 0.0956, 0.3049, 1.1557, 0.6051]])
In [205]: mat.dot(inv(mat))
Out[205]:
array([[ 1., 0., 0., 0., -0.],
       [ 0., 1., -0., 0., 0.],
       [ 0., -0., 1., 0., 0.],
       [ 0., -0., -0., 1., -0.],
       [ 0., 0., 0., 0., 1.]])
In [206]: q, r = qr(mat)
In [207]: r
Out[207]:
array([[ -6.9271, 7.389 , 6.1227, -7.1163, -4.9215],
       [ 0. , -3.9735, -0.8671, 2.9747, -5.7402],
       [ 0. , 0. , -10.2681, 1.8909, 1.6079],
       [ 0. , 0. , 0. , -1.2996, 3.3577],
       [ 0. , 0. , 0. , 0. , 0.5571]])

表格4-7 是一些常用的線性代數常用的函數清單。

科學Python社區希望有一天可以實現矩陣乘法的中綴操作符,提供一個語法上更好的使用 np.dot 的替代。但是現在只能這樣做。
常用 numpy.linglg 函數
函數 描述
diag 返回一個方陣的對角線(或非對角線)元素爲一個一維數組,或者轉換一個一維數組到一個方陣(非對角線元素爲零)
dot 矩陣乘積
trace 計算對角線上元素的和
det 計算矩陣行列式
eig 計算方陣的特徵值和特徵向量
inv 計算方陣轉置
pinv 計算方陣 Moore-Penrose pseudo-inverse 的轉置
qr 計算 QR 分解
svd 計算奇異值分解(SVD)
solve 求解線性系統方程 Ax = b 的x,其中A是一個方陣
lstsq 計算 y = Xb 的最小二乘解

1.6. 示例:隨機遊走

這是一個利用數組操作來模擬隨機遊走的示例程序。讓我們先來看一個簡單的隨機遊走的例子,從0開始,步長爲1和-1,且以相等的概率出現。一個純Python方式來實現一個單一的有1000步的隨機遊走的方式是使用內建的 random 模塊:

import random
position = 0
walk = [position]
steps = 1000
for i in xrange(steps):
    step = 1 if random.randint(0, 1) else -1
    position += step
    walk.append(position)

一個簡單的隨機遊走是使用這些隨機遊走的前100個值的例圖。

_images/random-walk.png

一個簡單的隨機遊走

你可能會發現 walk 簡單的把隨機步長累積起來並且可以可以使用一個數組表達式來計算。因此,我用np.random 模塊去1000次硬幣翻轉,設置它們爲1和-1,並計算累計和:

In [215]: nsteps = 1000
In [216]: draws = np.random.randint(0, 2, size=nsteps)
In [217]: steps = np.where(draws > 0, 1, -1)
In [218]: walk = steps.cumsum()

從這,我們可以開始沿着遊走軌跡來提取如最小或做大值的統計信息:

In [219]: walk.min() In [220]: walk.max()
Out[219]: -3 Out[220]: 31

一個更復雜的統計數據是第一交叉時間,隨機遊走達到一個特定值的步值。這裏,我們可能想要知道過了多長時間的隨機遊走,從任一個方向到達距離原點0至少10步之遙。 ** np.ads(walk) >= 10 ** 會給我們一個布爾數組指示在哪兒遊走到達了或超過了10,但是我需要的是第一個10或-10的索引。可以使用 argmax 來計算,它返回布爾數組(最大值爲 True)中第一個最大值的索引:

In [221]: (np.abs(walk) >= 10).argmax()
Out[221]: 37

注意在這使用 ragmax 並不是總是高效的,因爲它總是對數組做全掃描。在這一特殊情況下,一旦一個 True出現了,我們就知道它是一個最大值。

1.6.1. 一次模擬許多隨機遊走

如果你的目標是模擬許多隨機遊走,如5000個,你可以對上面的代碼稍作修改來生成所有的隨機遊動。numpy.random 函數,如果通過一個2元組,將產生一個二維數組繪製,我們可以跨越行一次計算5000個隨機遊動的累計和:

In [222]: nwalks = 5000
In [223]: nsteps = 1000
In [224]: draws = np.random.randint(0, 2, size=(nwalks, nsteps)) # 0 or 1
In [225]: steps = np.where(draws > 0, 1, -1)
In [226]: walks = steps.cumsum(1)
In [227]: walks
Out[227]:
array([[ 1, 0, 1, ..., 8, 7, 8],
       [ 1, 0, -1, ..., 34, 33, 32],
       [ 1, 0, -1, ..., 4, 5, 4],
       ...,
       [ 1, 2, 1, ..., 24, 25, 26],
       [ 1, 2, 3, ..., 14, 13, 14],
       [ -1, -2, -3, ..., -24, -23, -22]])

現在,我們可以獲得所有遊走的最大和最小值:

In [228]: walks.max() In [229]: walks.min()
Out[228]: 138 Out[229]: -133

在這些遊走中,讓我們來計算到達30或-30的最短時間。這有一點兒狡猾,因爲不是所有的5000個遊走都能到達30。我們可以使用 any 方法來檢測:

In [230]: hits30 = (np.abs(walks) >= 30).any(1)
In [231]: hits30
Out[231]: array([False, True, False, ..., False, True, False], dtype=bool)
In [232]: hits30.sum() # 30或-30的個數
Out[232]: 3410

我們可以使用這個布爾數組來選擇這些遊走中跨過絕對30的行,並調用 argmax 來取得座標軸1的交叉時間:

In [233]: crossing_times = (np.abs(walks[hits30]) >= 30).argmax(1)
In [234]: crossing_times.mean()
Out[234]: 498.88973607038122

可以大膽的試驗其它的分佈的步長,而不是相等大小的硬幣翻轉。你只需要使用一個不同的隨機數生成函數,如 normal 來產生相同均值和標準偏差的正態分佈:

In [235]: steps = np.random.normal(loc=0, scale=0.25,
   .....: size=(nwalks, nsteps))

發佈了24 篇原創文章 · 獲贊 17 · 訪問量 31萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章