五、NumPy 基礎:數組和矢量計算
NumPy(Numerical Python的簡稱)是Python數值計算最重要的基礎包。大多數提供科學計算的包都是用NumPy的數組作爲構建基礎。
部分功能:多維數組 ndarray;標準數學函數(對整組數據快速運算不用 for 循環);讀寫磁盤工具;線性代數、隨機生成以及傅里葉變換功能;可集成 C、C++ 代碼的 API。
Numpy 本身沒有很高級的數據分析功能,理解 Numpy 數組和麪向數組的計算有助於高效使用 pandas 等工具。
1、n維數組對象 ndarray
n 維數組對象(即ndarray)是一個快速而靈活的大數據集容器,每個數組有 shape(一個表示各維度大小的元組) 和 dtype(一個用於說明數組數據類型的對象)
import numpy as np
data = np.random.randn(2, 3)
data * 10 # 矩陣的數乘
data + data # 矩陣的加法
data.shape # (2, 3)
data.dtype # dtype('float64')
創建
array 函數:接受一切序列型的對象(包括其他數組),產生一個新的含有傳入數據的 NumPy 數組。
import numpy as np
data = [1,2,3,4]
arr = np.array(data)
arr # array([1, 2, 3, 4])
data2 = [[1, 2, 3, 4], [5, 6, 7, 8]]
arr2 = np.array(data2)
arr2 # array([[1, 2, 3, 4], [5, 6, 7, 8]])
arr2.ndim # 2
arr2.shape # (2, 4)
arr2.dtype # dtype('int64')
一些數組創建函數,數據類型默認爲 float64(浮點數),arange是Python內置函數range的數組版。
dtype
dtype(數據類型)是一個特殊的對象,它含有ndarray將一塊內存解釋爲特定數據類型所需的信息。
Numpy數據類型
你可以通過 ndarray 的 astype 方法明確地將一個數組從一個 dtype 轉換成另一個 dtype:
arr = np.array([1, 2, 3, 4, 5])
arr.dtype # dtype('int32')
float_arr = arr.astype(np.float64)
float_arr.dtype # dtype('float64')
創建數組指定 dtype
int_array = np.arange(10)
calibers = np.array([.22, .270, .357, .380, .44, .50], dtype=np.float64)
int_array.astype(calibers.dtype) # int_array.astype(calibers.dtype)
可以用簡潔的類型代碼來表示 dtype
empty_uint32 = np.empty(8, dtype='u4')
empty_uint32
# array([ 0, 1075314688, 0, 1075707904, 0.1075838976, 0, 1072693248], dtype=uint32)
調用astype總會創建一個新的數組(一個數據的備份),即使新的dtype
與舊的dtype相同。
數組運算
不用編寫循環即可對數據執行批量運算,矢量化。
大小相等的數組之間的任何算術運算都會將運算應用到元素級;數組與標量的算術運算會將標量值傳播到各個元素;大小相同的數組之間的比較會生成布爾值數組:
arr = np.array([[1., 2., 3.], [4., 5., 6.]])
arr
array([[1., 2., 3.],
[4., 5., 6.]])
arr * arr
array([[ 1., 4., 9.],
[16., 25., 36.]])
3 * arr
array([[ 3., 6., 9.],
[12., 15., 18.]])
arr2 = np.array([[0., 4., 1.], [7., 2., 12.]])
arr2 > arr
array([[False, True, False],
[ True, False, True]], dtype=bool)
不同大小的數組之間的運算叫做廣播(broadcasting)。
基本的索引和切片
一維數組
一維數組的索引很簡單,看起來和 Python 列表的功能差不多。
arr = np.arange(10)
arr # array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
arr[5] # 5
arr[5:8] # array([5, 6, 7])
與列表不同,數組切片是原始數組的視圖,視圖上的任何修改都會直接反映到原數組上。
arr[5:8] = 12
arr # array([ 0, 1, 2, 3, 4, 12, 12, 12, 8, 9])
# 創建一個切片
arr_slice = arr[5 : 8]
arr_slice # array([12, 12, 12])
# 修改切片的值,變動也體現在原始數組中。
arr_slice[1] = 123456
arr # array([ 0, 1, 2, 3, 4, 12, 12345, 12, 8, 9])
# 切片[ : ]會給數組中的所有值賦值
arr_slice[:] = 64
arr # array([ 0, 1, 2, 3, 4, 64, 64, 64, 8, 9])
如果想得到 ndarray 切片的副本而不是視圖,可以用 arr[5:8].copy()。
二維數組
二維數組的索引位置的元素是一維數組。
arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
arr2d[2] # array([7, 8, 9])
# 訪問數組元素,下面兩者等價
arr2d[0][2] # 3
arr2d[0, 2] # 3
二維數組的切片,切片是沿着一個軸向選取元素。
arr2d[:2]
array([[1, 2, 3],
[4, 5, 6]])
arr2d[:2, 1:]
array([[2, 3],
[5, 6]])
arr2d[1, :2] # 第二行的前兩列
arr2d[:2, 2] # 第三列的前兩行
arr2d[:, :1] # 只有冒號表示選取整個軸
對切片表達式的賦值操作會被擴散到整個選區。
布爾型索引
布爾型索引就是把布爾型數組應用於 ndarray 的索引和切片。
假設每個名字對應 data 數組的一行,選出對應於名字“Bob”的所有行。
names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])
data = np.random.randn(7, 4)
首先對 names 和字符串"Bob"的比較運算將會產生一個布爾型數組:
names == Bob
# array([ True, False, False, True, False, False, False], dtype=bool)
這個布爾型數組可用於數組索引:
data[names == 'Bob']
# array([[ 0.0929, 0.2817, 0.769 , 1.2464], [ 1.669 , -0.4386, -0.5397, 0.477 ]])
布爾型數組的長度必須跟被索引的軸長度一致。
布爾型數組與索引切片混合使用:
data[names == 'Bob', 2:]
data[names == 'Bob', 3]
選擇除"Bob"以外的其他值,使用不等號 != 或~對條件進行否定:
names != 'Bob'
data[~(names == 'Bob')]
選取名字的組合需要布爾算數運算符,和爲 & 或爲 |:(Python關鍵字and和or在布爾型數組中無效)
mask = (names == 'Bob') | (names == 'Will')
data[mask]
通過布爾型索引選取數組中的數據,會創建數據的副本。
應用:通過布爾型數組設置值
# data 數組所有負值設爲0
data[data < 0] = 0
# 設置整行或整列的值
data[names != 'Joe'] = 7
花式索引
花式索引利用整數數組進行索引。
arr = np.empty((8, 4))
for i in range(8):
arr[i] = i
爲了以特定順序選取行子集,只需傳入一個用於指定順序的整數列表或ndarray即可:
arr[[4, 3, 0, 6]]
使用負數索引將會從末尾開始選取行:
arr[[-3, -5, -7]]
花式索引跟切片不一樣,它總是將數據複製到新數組中。
數組轉置和軸對換
數組不僅有transpose方法,還有一個特殊的T屬性:
在進行矩陣計算時,經常需要用到該操作,比如利用np.dot計算矩陣內積:
np.dot(arr.T, arr)
對於高維數組,transpose需要得到一個由軸編號組成的元組才能對這些軸進行轉置。
arr = np.arange(16).reshape((2, 2, 4))
arr.transpose((1, 0, 2))
這裏,第一個軸被換成了第二個,第二個軸被換成了第一個,最後一個軸不變。
簡單的轉置可以使用.T,它其實就是進行軸對換而已。
ndarray還有一個 swapaxes 方法,它需要接受一對軸編號。(返回源數據的視圖)
arr
array([[[ 0, 1, 2, 3],
[ 4, 5, 6, 7]],
[[ 8, 9, 10, 11],
[12, 13, 14, 15]]])
arr.swapaxes(1, 2)
array([[[ 0, 4],
[ 1, 5],
[ 2, 6],
[ 3, 7]],
[[ 8, 12],
[ 9, 13],
[10, 14],
[11, 15]]])
2、通用函數 ufunc
通用函數(即ufunc)是一種對ndarray中的數據執行元素級運算的函數,可看作簡單函數(接受一個或多個標量值,併產生一個或多個標量值)的矢量化包裝器。
一元 ufunc
二元 ufunc
np.sqrt(arr)
np.maximum(x, y)
3、利用數組進行數據處理
NumPy數組使你可以將許多種數據處理任務表述爲簡潔的數組表達式(否則需要編寫循環)。用數組表達式代替循環的做法,通常被稱爲矢量化。
將條件邏輯表述爲數組運算 np.where
numpy.where函數是三元表達式x if condition else y的矢量化版本。
例如:根據 cond 中的值選取 xarr 和 yarr 的值:當 cond 中的值爲 True 時,選取 xarr 的值,否則從 yarr 中選取。
# 列表推導式 缺點:大數組處理速度慢,無法用於多維數組
result = [(x if c else y) for x, y, c in zip(xarr, yarr, cond)]
# np.where
result = np.where(cond, xarr, yarr)
np.where的第二個和第三個參數不必是數組,它們都可以是標量值。在數據分析工作中,where 通常用於根據另一個數組而產生一個新的數組。
例如:把矩陣的所有正值替換爲 2,所有負值替換爲 -2
np.where(arr > 0, 2, -2)
也可以將標量和數組結合起來。
例如,用常數2替換arr中所有正的值:
np.where(arr > 0, 2, arr)
數學和統計方法 sum mean std
可以通過數組上的一組數學函數對整個數組或某個軸向的數據進行統計計算。
sum、mean以及標準差 std 既可以當做數組的實例方法調用,也可以當做頂級NumPy 函數使用。
arr = np.random.randn(5, 4)
arr.mean()
np.mean(arr)
arr.sum()
mean 和 sum 這類的函數可以接受一個 axis 選項參數,用於計算該軸向上的統計值,最終結果是一個少一維的數組:
arr.mean(axis=1)
arr.sum(axis=0)
基本數組統計方法:
用於布爾型數組的方法 sum any all
sum 用來對布爾型數組中的True值計數:
arr = np.random.randn(100)
(arr > 0).sum() # 42
any 用於測試數組中是否存在一個或多個 True,而 all 則檢查數組中所有值是否都是 True:
bools = np.array([False, False, True, False])
bools.any() # True
bools.all() # False
這兩個方法也能用於非布爾型數組,所有非0元素將會被當做True
排序 sort
arr = np.random.randn(6)
arr.sort()
多維數組可以在任何一個軸向上進行排序,只需將軸編號傳給 sort 即可:
arr = np.random.randn(5, 3)
arr.sort(1)
頂級方法np.sort返回的是數組的已排序副本,而就地排序則會修改數組本身。
例如:計算數組分位數
large_arr = np.random.randn(1000)
large_arr.sort()
large_arr[int(0.05 * len(large_arr))]
唯一化以及其它的集合邏輯 np.unique 等
np.unique 用於找出數組中的唯一值並返回已排序的結果:
ints = np.array([3, 3, 3, 2, 2, 1, 1, 4, 4])
np.unique(ints)
數組集合運算:
4、用於數組的文件輸入輸出
NumPy 能夠讀寫磁盤上的文本數據或二進制數據。這一小節只討論NumPy的內置二進制格式,因爲更多的用戶會使用 pandas 或其它工具加載文本或表格數據。
np.save 和 np.load
讀寫磁盤數據,數組是以未壓縮的原始二進制格式保存在擴展名爲 .npy 的文件中。如果文件路徑末尾沒有擴展名.npy,則該擴展名會被自動加上。
arr = np.arange(10)0
np.save('some_array', arr)
np.load('some_array.npy')
np.savez
np.savez 可以將多個數組保存到一個未壓縮文件 .npz 中,將數組以關鍵字參數的形式傳入。加載.npz文件時,你會得到一個類似字典的對象,該對象會對各個數組進行延遲加載:
np.savez('array_archive.npz', a=arr, b=arr)
arch = np.load('array_archive.npz')
arch['b'] # array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
numpy.savez_compressed
將數據壓縮
np.savez_compressed('arrays_compressed.npz', a=arr, b=arr)
5、線性代數 numpy.linalg
線性代數(如矩陣乘法、矩陣分解、行列式以及其他方陣數學等)是任何數組庫的重要組成部分。
NumPy 提供了一個用於矩陣乘法的 dot 函數(既是一個數組方法也是numpy命名空間中的一個函數):
x.dot(y)
np.dot(x, y)
numpy.linalg 中有一組標準的矩陣分解運算以及諸如求逆和行列式之類的函數:
from numpy.linalg import inv, qr
6、僞隨機數生成 numpy.random
numpy.random 模塊對 Python 內置的 random(一次生成一個樣本值
)進行了補充,增加了一些用於高效生成多種概率分佈的樣本值的函數。
生成的樣本值都是僞隨機數,可以用NumPy的np.random.seed更改隨機數生成種子。
samples = np.random.normal(size=(4, 4))
np.random.seed(1234)
部分 numpy.random 函數: