Numpy, 數組和矢量計算包
前幾年前想學數據分析,于是就去學習Python的Numpy。然而看完《利用Python進行數據分析》后,也對它沒有多大印象的。但是學了一段時間R語言,并且將R語言和Python進行一些比較,再次學習Numpy就特別輕松了。
由于學過R語言,我可以簡單認為Numpy提供的多維數據對象ndarray就是Python版本的R語言的vector
, matrix
和array
。幾乎沒有特殊說明,這兩者的任何操作都是一致的。
Numpy的部分功能如下:
- ndarray, 一個具有矢量算術運算和復雜廣播能力的快速且節省空間的多維數組
- 用于對整組數據進行快速運算的標準數學函數(無需編寫循環函數)
- 用于讀寫磁盤數據的工具以及用于操作內存映射文件的工具
- 線性代數, 隨機數生成以及傅里葉變換功能
- 用于繼承由C, C++, Fortran等語言編寫的代碼的工具
約定俗成:
import numpy as np
也就說不會特意去聲明導入numpy
。
創建多維數組
多維數組有多種創建方法,其中最簡單的就是使用arrary
函數。以一切序列類型的對象作為輸入
# 一維數組,也就是R語言的最基本元素,vector
In [1]: import numpy as np
In [2]: data1 = [1,2,3,4,5]
In [3]: arr1 = np.array(data1)
In [4]: ?arr1 # 內省下arr1對象
# 二維數組,也就是R語言的matrix
In [5]: data2 = [[1,2,3],[4,5,6]]
In [6]: arr2 = np.array(data2)
In [7]: ?arr2
# 在R語言中用dim(), nrows, ncols查看數據維度
# 在Python中,這些可以用對象的方法查看。
In [10]: arr2.ndim
Out[10]: 2
In [11]: arr2.shape
Out[11]: (2, 3)
# 和R語言一樣,ndarray的對象不允許存在多種數據類型
# ndarray會自動根據輸入選擇最合適的數據類型
In [17]: data3 = [1,"string", True]
In [18]: arr3 = np.array(data3)
In [19]: arr3
Out[19]:
array(['1', 'string', 'True'],
dtype='<U11')
In [20]: arr3.dtype
Out[20]: dtype('<U11')
除了用array
轉換序列型數據輸入以外,還可以用arange
(類似于內置的ranges), asarray
(類似于array)。后面的方法和線性代數密切相關,建議查看相應的說明: ones ones_like
, zeros zeros_like
empty empty_like
,eye identity
。
關于數據類型, 一般情況下我們沒必要對它太過于關注。但是對于大數據集,則需要自己主動聲明。因為數據類型(dtype)負責將一塊內存解釋為特定數據類型,即直接映射到相應的機器表示。在R語言中有一類類型轉換函數(例如as.numeric)對數組內的數據類型進行轉換,在Numpy則通過dtype.
數組運算
R語言的一大特點就是矢量化運算,能用來檢查你是否理解R語言。簡單理解,就是不用循環就能對數據批量運算。
個人愚見:矢量化運算是Numpy用C語言編寫,在C語言層面是也是循環。這也是為什么一個數組內的數據類型要一致。
# R
> arr1
[1] 1 2 3 4 5
> arr1 + 1
[1] 2 3 4 5 6
# Python
In [29]: arr1 + 1
Out[29]: array([2, 3, 4, 5, 6])
In [35]: arr2 * 3
Out[35]: array([[ 3, 6, 9],
[12, 15, 18]])
In [36]: arr2 * arr2
Out[36]: array([[ 1, 4, 9],
[16, 25, 36]])
我曾經在 Python和R的異同(一)里談到原生Python要想實現R語言的矢量化就要使用列表推導式, 而目前可以用numpy帶來的矢量化運算屬性了。
索引和切片
切片
在R語言和Numpy,包括原生的Python都有切片的功能, 所謂的切片(slicing) 就是從已有的數組中返回選定的元素,而索引(index)提供指向存儲在數組指定位置的數據值的指針。
# R
arr <- 0:9
arr_sub <- arr[1:5]
# Python default list
data = [i for i in range(10)]
data_sub = data[0:5]
# Python Numpy
arr = np.arange(10)
arr_sub = arr[0:5]
上面的結果都是一致的,都是提取前5個元素。只不過要注意R語言的索引從1開始(5-1+1),而Python從0開始(5-0)。表面看起來是相同的,但其實Numpy切片得到只是原始數據的視圖(view),也就是淺復制,即你對Numpy切片后的數據進行操作,會影響到原始數據。
# Python Numpy
In [60]: arr_sub[1] = 100
In [61]: arr
Out[61]: array([ 0, 100, 2, 3, 4, 5, 6, 7, 8, 9])
# Python default list
In [65]: data_sub[1] = 100
In [66]: data_sub
Out[66]: [0, 100, 2, 3, 4]
In [67]: data
Out[67]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# R 同Python默認的列表
原因就是Numpy的目的是處理大數據,對大規模的數據進行實際復制會消耗不必要的性能和內存。
numpy的索引操作和R語言幾乎一模一樣,分為切片索引,布爾值索引,花式索引。這些都在《R語言實戰》基本數據管理章節中的數據集選取子集里面提及。
切片索引:
In [79]: arr = np.eye(9,9)
## 類似于R的操作
In [80]: arr[1,1]
Out[80]: 1.0
In [82]: arr[:,:]
## Python原來是通過遞歸對元素進行訪問
In [81]: arr[1][1]
Out[81]: 1.0
布爾值索引, 也就是先產生一個True, False的數組,然后根據這個數組提取數據
In [87]: arr[arr == 1]
Out[87]: array([ 1., 1., 1., 1., 1., 1., 1., 1., 1.])
花式索引, 就是提供指定順序的整數型列表
In [94]: arr = np.empty((8,4))
In [95]: for i in range(8):
...: arr[i] = i
...:
## 選取第4,3,1,6行數據
In [99]: arr[[3,2,0,5]]
Out[99]: array([ 3., 2., 0., 5.])
## 在R里面就是
## arr[c(4,3,1,6)]
注意,如果一次性傳入多個索引數據,Numpy會返回一維數組,但是R依舊會返回多維。這是目前第一個與R不太一樣,當然和預想的結果也不同。
# R
> mdata <- matrix(1:32,nrow=8, ncol=4)
> mdata[c(2,6,8,2),c(1,4,2,3)]
[,1] [,2] [,3] [,4]
[1,] 2 26 10 18
[2,] 6 30 14 22
[3,] 8 32 16 24
[4,] 2 26 10 18
# Numpy
In [110]: arr = np.arange(1,33).reshape(8,4)
In [111]: arr[[1,5,7,2],[0,3,1,2]]
Out[111]: array([ 5, 24, 30, 11])
## 為了解決這個問題,有兩種方法
In [112]: arr[[1,5,7,2]][:,[0,3,1,2]]
Out[112]:
array([[ 5, 8, 6, 7],
[21, 24, 22, 23],
[29, 32, 30, 31],
[ 9, 12, 10, 11]])
In [113]: arr[np.ix_([1,5,7,2],[0,3,1,2])]
Out[113]:
array([[ 5, 8, 6, 7],
[21, 24, 22, 23],
[29, 32, 30, 31],
[ 9, 12, 10, 11]])
注: 花式索引以及布爾值索引和切片索引不同, 前者將數據復制到新的數組中,而后者是原始數據的視圖。 可能原因是前兩者的得到數據在原始數據中位置不是整塊存放。
數據轉置和軸對換
轉置(transpose)是數據重塑的一種特殊形式,返回的是原始數據的視圖(這一點和R不同)。數組不僅有transpose方法,還有一個T屬性, 這兩者在二維數組上是相同的。
arr = np.arange(1,33).reshape(8,4)
arr.T
np.transpose(arr)
# 線性代數的矩陣內積
np.dot(arr.T, arr)
但是在更高維度上,T屬性依舊還是軸對換,transpose方法還需要提供軸編號組成的元組,這個真的是非常難以理解。
通用函數:快速的元素級數組函數
我曾經寫過一篇文章,叫做R語言的數據管理里面提到了基石函數,來源于《R語言實戰》的高級數學管理。在numpy,這類函數叫做通用函數(UNIVERSAL FUNCTIONS, UFUNC),能夠進行矢量化運算的函數。按照官方文檔的劃分,大致分為
- 數學運算
- 三角函數
- 位運算函數
- 比較函數
- 浮點函數
按照《利用Pyton進行數據分析》可以分為一元函數和二元函數。
對于一些自定義的函數,R語言采用的apply家族函數進行矢量化操作,避免循環。而在Numpy則是frompyfunc
。不過這已經比較高級了。
Numpy更多是Python進行科學計算的基礎包,因此數據分析部分的內容就交給pandas吧。