量化分析師的Python日記【第3天:一大波金融Library來襲之numpy篇】

 

接下來要給大家介紹的系列中包含了Python在量化金融中運用最廣泛的幾個Library:

numpy

scipy

pandas

matplotlib

會給初學者一一介紹

NumPy 簡介

 

一、NumPy是什麼?

量化分析的工作涉及到大量的數值運算,一個高效方便的科學計算工具是必不可少的。Python語言一開始並不是設計爲科學計算使用的語言,隨着越來越多的人發現Python的易用性,逐漸出現了關於Python的大量外部擴展,NumPy (Numeric Python)就是其中之一。NumPy提供了大量的數值編程工具,可以方便地處理向量、矩陣等運算,極大地便利了人們在科學計算方面的工作。另一方面,Python是免費,相比於花費高額的費用使用Matlab,NumPy的出現使Python得到了更多人的青睞。

我們可以簡單看一下如何開始使用NumPy:

In [1]:
import numpy
numpy.version.full_version
Out[1]:
'1.8.0'
 

我們使用了"import"命令導入了NumPy,並使用numpy.version.full_version查出了量化實驗室裏使用的NumPy版本爲1.8.0。在往後的介紹中,我們將大量使用NumPy中的函數,每次都添加numpy在函數前作爲前綴比較費勁,在之前的介紹中,我們提及了引入外部擴展模塊時的小技巧,可以使用"from numpy import *"解決這一問題。

那麼問題解決了?慢!Python的外部擴展成千上萬,在使用中很可能會import好幾個外部擴展模塊,如果某個模塊包含的屬性和方法與另一個模塊同名,就必須使用import module來避免名字的衝突。即所謂的名字空間(namespace)混淆了,所以這前綴最好還是帶上。

那有沒有簡單的辦法呢?有的,我們可以在import擴展模塊時添加模塊在程序中的別名,調用時就不必寫成全名了,例如,我們使用"np"作爲別名並調用version.full_version函數:

In [2]:
import numpy as np
np.version.full_version
Out[2]:
'1.8.0'
 

二、初窺NumPy對象:數組

NumPy中的基本對象是同類型的多維數組(homogeneous multidimensional array),這和C++中的數組是一致的,例如字符型和數值型就不可共存於同一個數組中。先上例子:

In [3]:
a = np.arange(20)
 

這裏我們生成了一個一維數組a,從0開始,步長爲1,長度爲20。Python中的計數是從0開始的,R和Matlab的使用者需要小心。可以使用print查看:

In [4]:
print a
 
[ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19]
 

我們可以通過"type"函數查看a的類型,這裏顯示a是一個array:

In [5]:
type(a)
Out[5]:
numpy.ndarray
 

通過函數"reshape",我們可以重新構造一下這個數組,例如,我們可以構造一個4*5的二維數組,其中"reshape"的參數表示各維度的大小,且按各維順序排列(兩維時就是按行排列,這和R中按列是不同的):

In [6]:
a = a.reshape(4, 5)
print a
 
[[ 0  1  2  3  4]
 [ 5  6  7  8  9]
 [10 11 12 13 14]
 [15 16 17 18 19]]
 

構造更高維的也沒問題:

In [7]:
a = a.reshape(2, 2, 5)
print a
 
[[[ 0  1  2  3  4]
  [ 5  6  7  8  9]]

 [[10 11 12 13 14]
  [15 16 17 18 19]]]
 

既然a是array,我們還可以調用array的函數進一步查看a的相關屬性:"ndim"查看維度;"shape"查看各維度的大小;"size"查看全部的元素個數,等於各維度大小的乘積;"dtype"可查看元素類型;"dsize"查看元素佔位(bytes)大小。

In [8]:
a.ndim
Out[8]:
3
In [9]:
a.shape
Out[9]:
(2, 2, 5)
In [10]:
a.size
Out[10]:
20
In [11]:
a.dtype
Out[11]:
dtype('int64')
 

三、創建數組

數組的創建可通過轉換列表實現,高維數組可通過轉換嵌套列表實現:

In [12]:
raw = [0,1,2,3,4]
a = np.array(raw)
a
Out[12]:
array([0, 1, 2, 3, 4])
In [13]:
raw = [[0,1,2,3,4], [5,6,7,8,9]]
b = np.array(raw)
b
Out[13]:
array([[0, 1, 2, 3, 4],
       [5, 6, 7, 8, 9]])
 

一些特殊的數組有特別定製的命令生成,如4*5的全零矩陣:

In [14]:
d = (4, 5)
np.zeros(d)
Out[14]:
array([[ 0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.]])
 

默認生成的類型是浮點型,可以通過指定類型改爲整型:

In [15]:
d = (4, 5)
np.ones(d, dtype=int)
Out[15]:
array([[1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1]])
 

[0, 1)區間的隨機數數組:

In [16]:
np.random.rand(5)
Out[16]:
array([ 0.29527786,  0.5790265 ,  0.80226103,  0.8242678 ,  0.4318122 ])
 

四、數組操作

簡單的四則運算已經重載過了,全部的'+','-','*','/'運算都是基於全部的數組元素的,以加法爲例:

In [17]:
a = np.array([[1.0, 2], [2, 4]])
print "a:"
print a
b = np.array([[3.2, 1.5], [2.5, 4]])
print "b:"
print b
print "a+b:"
print a+b
 
a:
[[ 1.  2.]
 [ 2.  4.]]
b:
[[ 3.2  1.5]
 [ 2.5  4. ]]
a+b:
[[ 4.2  3.5]
 [ 4.5  8. ]]
 

這裏可以發現,a中雖然僅有一個與元素是浮點數,其餘均爲整數,在處理中Python會自動將整數轉換爲浮點數(因爲數組是同質的),並且,兩個二維數組相加要求各維度大小相同。當然,NumPy裏這些運算符也可以對標量和數組操作,結果是數組的全部元素對應這個標量進行運算,還是一個數組:

In [18]:
print "3 * a:"
print 3 * a
print "b + 1.8:"
print b + 1.8
 
3 * a:
[[  3.   6.]
 [  6.  12.]]
b + 1.8:
[[ 5.   3.3]
 [ 4.3  5.8]]
 

類似C++,'+='、'-='、'*='、'/='操作符在NumPy中同樣支持:

In [19]:
a /= 2
print a
 
[[ 0.5  1. ]
 [ 1.   2. ]]
 

開根號求指數也很容易:

In [20]:
print "a:"
print a
print "np.exp(a):"
print np.exp(a)
print "np.sqrt(a):"
print np.sqrt(a)
print "np.square(a):"
print np.square(a)
print "np.power(a, 3):"
print np.power(a, 3)
 
a:
[[ 0.5  1. ]
 [ 1.   2. ]]
np.exp(a):
[[ 1.64872127  2.71828183]
 [ 2.71828183  7.3890561 ]]
np.sqrt(a):
[[ 0.70710678  1.        ]
 [ 1.          1.41421356]]
np.square(a):
[[ 0.25  1.  ]
 [ 1.    4.  ]]
np.power(a, 3):
[[ 0.125  1.   ]
 [ 1.     8.   ]]
 

需要知道二維數組的最大最小值怎麼辦?想計算全部元素的和、按行求和、按列求和怎麼辦?for循環嗎?不,NumPy的ndarray類已經做好函數了:

In [21]:
a = np.arange(20).reshape(4,5)
print "a:"
print a
print "sum of all elements in a: " + str(a.sum())
print "maximum element in a: " + str(a.max())
print "minimum element in a: " + str(a.min())
print "maximum element in each row of a: " + str(a.max(axis=1))
print "minimum element in each column of a: " + str(a.min(axis=0))
 
a:
[[ 0  1  2  3  4]
 [ 5  6  7  8  9]
 [10 11 12 13 14]
 [15 16 17 18 19]]
sum of all elements in a: 190
maximum element in a: 19
minimum element in a: 0
maximum element in each row of a: [ 4  9 14 19]
minimum element in each column of a: [0 1 2 3 4]
 

科學計算中大量使用到矩陣運算,除了數組,NumPy同時提供了矩陣對象(matrix)。矩陣對象和數組的主要有兩點差別:一是矩陣是二維的,而數組的可以是任意正整數維;二是矩陣的'*'操作符進行的是矩陣乘法,乘號左側的矩陣列和乘號右側的矩陣行要相等,而在數組中'*'操作符進行的是每一元素的對應相乘,乘號兩側的數組每一維大小需要一致。數組可以通過asmatrix或者mat轉換爲矩陣,或者直接生成也可以:

In [22]:
a = np.arange(20).reshape(4, 5)
a = np.asmatrix(a)
print type(a)

b = np.matrix('1.0 2.0; 3.0 4.0')
print type(b)
 
<class 'numpy.matrixlib.defmatrix.matrix'>
<class 'numpy.matrixlib.defmatrix.matrix'>
 

再來看一下矩陣的乘法,這使用arange生成另一個矩陣b,arange函數還可以通過arange(起始,終止,步長)的方式調用生成等差數列,注意含頭不含尾。

In [23]:
b = np.arange(2, 45, 3).reshape(5, 3)
b = np.mat(b)
print b
 
[[ 2  5  8]
 [11 14 17]
 [20 23 26]
 [29 32 35]
 [38 41 44]]
 

有人要問了,arange指定的是步長,如果想指定生成的一維數組的長度怎麼辦?好辦,"linspace"就可以做到:

In [24]:
np.linspace(0, 2, 9)
Out[24]:
array([ 0.  ,  0.25,  0.5 ,  0.75,  1.  ,  1.25,  1.5 ,  1.75,  2.  ])
 

回到我們的問題,矩陣a和b做矩陣乘法:

In [25]:
print "matrix a:"
print a
print "matrix b:"
print b
c = a * b
print "matrix c:"
print c
 
matrix a:
[[ 0  1  2  3  4]
 [ 5  6  7  8  9]
 [10 11 12 13 14]
 [15 16 17 18 19]]
matrix b:
[[ 2  5  8]
 [11 14 17]
 [20 23 26]
 [29 32 35]
 [38 41 44]]
matrix c:
[[ 290  320  350]
 [ 790  895 1000]
 [1290 1470 1650]
 [1790 2045 2300]]
 

五、數組元素訪問

數組和矩陣元素的訪問可通過下標進行,以下均以二維數組(或矩陣)爲例:

In [26]:
a = np.array([[3.2, 1.5], [2.5, 4]])
print a[0][1]
print a[0, 1]
 
1.5
1.5
 

可以通過下標訪問來修改數組元素的值:

In [27]:
b = a
a[0][1] = 2.0
print "a:"
print a
print "b:"
print b
 
a:
[[ 3.2  2. ]
 [ 2.5  4. ]]
b:
[[ 3.2  2. ]
 [ 2.5  4. ]]
 

現在問題來了,明明改的是a[0][1],怎麼連b[0][1]也跟着變了?這個陷阱在Python編程中很容易碰上,其原因在於Python不是真正將a複製一份給b,而是將b指到了a對應數據的內存地址上。想要真正的複製一份a給b,可以使用copy:

In [28]:
a = np.array([[3.2, 1.5], [2.5, 4]])
b = a.copy()
a[0][1] = 2.0
print "a:"
print a
print "b:"
print b
 
a:
[[ 3.2  2. ]
 [ 2.5  4. ]]
b:
[[ 3.2  1.5]
 [ 2.5  4. ]]
 

若對a重新賦值,即將a指到其他地址上,b仍在原來的地址上:

In [29]:
a = np.array([[3.2, 1.5], [2.5, 4]])
b = a
a = np.array([[2, 1], [9, 3]])
print "a:"
print a
print "b:"
print b
 
a:
[[2 1]
 [9 3]]
b:
[[ 3.2  1.5]
 [ 2.5  4. ]]
 

利用':'可以訪問到某一維的全部數據,例如取矩陣中的指定列:

In [30]:
a = np.arange(20).reshape(4, 5)
print "a:"
print a
print "the 2nd and 4th column of a:"
print a[:,[1,3]]
 
a:
[[ 0  1  2  3  4]
 [ 5  6  7  8  9]
 [10 11 12 13 14]
 [15 16 17 18 19]]
the 2nd and 4th column of a:
[[ 1  3]
 [ 6  8]
 [11 13]
 [16 18]]
 

稍微複雜一些,我們嘗試取出滿足某些條件的元素,這在數據的處理中十分常見,通常用在單行單列上。下面這個例子是將第一列大於5的元素(10和15)對應的第三列元素(12和17)取出來:

In [31]:
a[:, 2][a[:, 0] > 5]
Out[31]:
array([12, 17])
 

可使用where函數查找特定值在數組中的位置:

In [32]:
loc = numpy.where(a==11)
print loc
print a[loc[0][0], loc[1][0]]
 
(array([2]), array([1]))
11
 

六、數組操作

還是拿矩陣(或二維數組)作爲例子,首先來看矩陣轉置:

In [33]:
a = np.random.rand(2,4)
print "a:"
print a
a = np.transpose(a)
print "a is an array, by using transpose(a):"
print a
b = np.random.rand(2,4)
b = np.mat(b)
print "b:"
print b
print "b is a matrix, by using b.T:"
print b.T
 
a:
[[ 0.73405579  0.26554639  0.35149432  0.55072272]
 [ 0.24976217  0.1432836   0.02280897  0.96363208]]
a is an array, by using transpose(a):
[[ 0.73405579  0.24976217]
 [ 0.26554639  0.1432836 ]
 [ 0.35149432  0.02280897]
 [ 0.55072272  0.96363208]]
b:
[[ 0.50191358  0.43575713  0.21853438  0.35257304]
 [ 0.73577449  0.57833658  0.65631312  0.91946705]]
b is a matrix, by using b.T:
[[ 0.50191358  0.73577449]
 [ 0.43575713  0.57833658]
 [ 0.21853438  0.65631312]
 [ 0.35257304  0.91946705]]
 

矩陣求逆:

In [34]:
import numpy.linalg as nlg
a = np.random.rand(2,2)
a = np.mat(a)
print "a:"
print a
ia = nlg.inv(a)
print "inverse of a:"
print ia
print "a * inv(a)"
print a * ia
 
a:
[[ 0.24986812  0.64203429]
 [ 0.68509577  0.89203528]]
inverse of a:
[[-4.11144764  2.95917709]
 [ 3.15765019 -1.15165815]]
a * inv(a)
[[  1.00000000e+00  -1.11022302e-16]
 [  0.00000000e+00   1.00000000e+00]]
 

求特徵值和特徵向量

In [35]:
a = np.random.rand(3,3)
eig_value, eig_vector = nlg.eig(a)
print "eigen value:"
print eig_value
print "eigen vector:"
print eig_vector
 
eigen value:
[-0.32257267  1.51331428  0.71559965]
eigen vector:
[[-0.23964687  0.65384676 -0.42020057]
 [ 0.88862492  0.47277525 -0.1893484 ]
 [-0.39104365  0.59073512  0.88745629]]
 

按列拼接兩個向量成一個矩陣:

In [36]:
a = np.array((1,2,3))
b = np.array((2,3,4))
print np.column_stack((a,b))
 
[[1 2]
 [2 3]
 [3 4]]
 

在循環處理某些數據得到結果後,將結果拼接成一個矩陣是十分有用的,可以通過vstack和hstack完成:

In [37]:
a = np.random.rand(2,2)
b = np.random.rand(2,2)
print "a:"
print a
print "b:"
print a
c = np.hstack([a,b])
d = np.vstack([a,b])
print "horizontal stacking a and b:"
print c
print "vertical stacking a and b:"
print d
 
a:
[[ 0.35141277  0.54723877]
 [ 0.39645303  0.88763732]]
b:
[[ 0.35141277  0.54723877]
 [ 0.39645303  0.88763732]]
horizontal stacking a and b:
[[ 0.35141277  0.54723877  0.42007564  0.80002737]
 [ 0.39645303  0.88763732  0.3000728   0.61844297]]
vertical stacking a and b:
[[ 0.35141277  0.54723877]
 [ 0.39645303  0.88763732]
 [ 0.42007564  0.80002737]
 [ 0.3000728   0.61844297]]
 

七、缺失值

缺失值在分析中也是信息的一種,NumPy提供nan作爲缺失值的記錄,通過isnan判定。

In [38]:
a = np.random.rand(2,2)
a[0, 1] = np.nan
print np.isnan(a)
 
[[False  True]
 [False False]]
 

nan_to_num可用來將nan替換成0,在後面會介紹到的更高級的模塊pandas時,我們將看到pandas提供能指定nan替換值的函數。

In [39]:
print np.nan_to_num(a)
 
[[ 0.18218581  0.        ]
 [ 0.26662401  0.05607354]]
 

NumPy還有很多的函數,想詳細瞭解可參考鏈接http://wiki.scipy.org/Numpy_Example_List 和http://docs.scipy.org/doc/numpy

最後獻上NumPy SciPy Pandas Cheat Sheet

my picture

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章