numpy數組的運算
不用編寫循環就可以實現對數據批量運算,Numpy用戶稱爲:矢量化。大小相同的數組之間的任何算數運算都可以將運算應用到元素級:
import numpy as np
arr = np.array([[1,2,3],[3,4,5]])
arr
array([[1, 2, 3],
[3, 4, 5]])
# 執行算術運算
arr * arr
array([[ 1, 4, 9],
[ 9, 16, 25]])
arr ** arr
array([[ 1, 4, 27],
[ 27, 256, 3125]], dtype=int32)
# 大小相同的數組之間的比較會產生布爾值數組
arr1 = np.array([[2,3,4],[3,2,1]])
arr1 > arr
array([[ True, True, True],
[False, False, False]])
不同大小的數組之間的運算叫做廣播
索引和切片
跟列表最重要的區別在於,數組切⽚是原始數組的視圖。這意味着數據不會被複制,視圖上的任何修改都會直接反映到源數組上。
當你將⼀個標量值賦值給⼀個切⽚時(如arr[5:8]=12),該值會⾃動傳播(也就說後⾯將會講到的“⼴播”)到整個選區。
arr = np.arange(10)
arr
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
arr_slice = arr[5:8]
arr_slice
array([5, 6, 7])
arr_slice[:] = 64
arr
array([ 0, 1, 2, 3, 4, 64, 64, 64, 8, 9])
arr_slice
array([64, 64, 64])
由於NumPy
的設計⽬的是處理⼤數據,所以你可以想象⼀下,假如NumPy
堅持要將數據複製來複制去的話會產⽣何等的性能和內存問題。
對於⾼維度數組,能做的事情更多。在⼀個⼆維數組中,各索引位置上的元素不再是標量⽽是⼀維數組:
arr2 = np.array([[[1,2,3],
[2,3,4]],
[[3,4,5],
[4,5,6]]])
arr2.shape
(2, 2, 3)
# 可以對各個元素進行遞歸訪問,也可以使用索引列表來進行訪問
arr2[0][0][0]
1
arr2[0,0,0]
1
arr2[0]
array([[1, 2, 3],
[2, 3, 4]])
data = np.random.randn(5, 5)
data.mean()
0.3587944495686687
data.std()
0.8490873700187817
布爾型索引
names = np.array(['xiao','zeng','zhang','liu','lili'])
names
array(['xiao', 'zeng', 'zhang', 'liu', 'lili'], dtype='<U5')
names == 'xiao'
array([ True, False, False, False, False])
# 這個產生的布爾數組可以用於數組索引
data[names == 'xiao']
array([[ 0.70902 , 0.12835622, -0.10731375, 0.17201327, -1.12689436]])
布爾型數組的⻓度必須跟被索引的軸⻓度⼀致。此外,還可以將布爾型數組跟切⽚、整數混合使⽤:
# 選取names == 'xiao'的行,同時索引了列
data[names == 'xiao', 2:]
array([[-0.10731375, 0.17201327, -1.12689436]])
要選擇除"Bob"以外的其他值,既可以使⽤不等於符號(!=),也可以通過~對條件進⾏否定:
names != 'xiao'
array([False, True, True, True, True])
data[~(names != 'xiao')]
array([[ 0.70902 , 0.12835622, -0.10731375, 0.17201327, -1.12689436]])
通過布爾型索引選取數組中的數據,將總是創建數據的副本,即使返回⼀模⼀樣的數組也是如此。
通過布爾型數組設置值是⼀種經常⽤到的⼿段。爲了將data中的所有負值都設置爲0,我們只需:
data[data < 0] = 0
data
array([[0.70902 , 0.12835622, 0. , 0.17201327, 0. ],
[0.06219114, 1.23528266, 0. , 0.5344566 , 1.05603675],
[1.05532396, 1.99560409, 1.44444832, 0.44811772, 0. ],
[1.6811567 , 0. , 0.4331555 , 0. , 0.73461594],
[0. , 0.88400999, 0.10480376, 0.65949918, 0.44154703]])
花式索引
花式索引(Fancy indexing)是⼀個NumPy術語,它指的是利⽤整數數組進⾏索引。
arr = np.empty((8,4))
arr
array([[6.95248686e-310, 6.95248686e-310, 1.03320693e-311,
6.95248686e-310],
[1.03320693e-311, 1.03320693e-311, 6.95248686e-310,
1.03320693e-311],
[6.95248686e-310, 6.95248686e-310, 1.03320693e-311,
1.03320693e-311],
[1.03320693e-311, 6.95248686e-310, 1.03320693e-311,
6.95248686e-310],
[1.03320693e-311, 6.95248686e-310, 1.03320693e-311,
1.03320693e-311],
[6.95248686e-310, 6.95248686e-310, 6.95248686e-310,
1.03320693e-311],
[6.95248686e-310, 1.03320693e-311, 6.95248686e-310,
1.03320693e-311],
[6.95248686e-310, 1.03320693e-311, 1.03320693e-311,
1.03320693e-311]])
for i in range(8):
arr[i] = i
arr
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即可:
arr[[4, 3, 0, 6]]
array([[4., 4., 4., 4.],
[3., 3., 3., 3.],
[0., 0., 0., 0.],
[6., 6., 6., 6.]])
arr[[-3, -5, -7]]
array([[5., 5., 5., 5.],
[3., 3., 3., 3.],
[1., 1., 1., 1.]])
⼀次傳⼊多個索引數組會有⼀點特別。它返回的是⼀個⼀維數組,其中的元素對應各個索引元組
arr = np.arange(32).reshape(8,4)
arr
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]])
arr[[1, 5, 7, 2], [0, 3, 1, 2]]
array([ 4, 23, 29, 10])
最終選出來的元素是:(1, 0), (5, 3), (7, 1), (2, 2).
不論數組是多少維的, 花式索引總是一維的。
# 可能上述結果與自己想象中的不一樣,但實際上,可以通過如下方法,得到你要的
arr[[1, 5, 7, 2]][:,[0, 3, 1, 2]]
array([[ 4, 7, 5, 6],
[20, 23, 21, 22],
[28, 31, 29, 30],
[ 8, 11, 9, 10]])
數組的轉置操作
轉置操作是重塑的一種特殊形式, 他返回的是源數據的視圖(不會進行任何的複製操作)
數組不僅僅有transpose方法, 還有一個.T屬性
arr = np.arange(15).reshape(3, 5)
arr
array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14]])
# 轉置操作
arr.T
array([[ 0, 5, 10],
[ 1, 6, 11],
[ 2, 7, 12],
[ 3, 8, 13],
[ 4, 9, 14]])
# 再進行矩陣計算的時候, 會經常使用到這個操作。
# 計算內積
np.dot(arr.T, arr)
array([[125, 140, 155, 170, 185],
[140, 158, 176, 194, 212],
[155, 176, 197, 218, 239],
[170, 194, 218, 242, 266],
[185, 212, 239, 266, 293]])
高維數組轉置
對於⾼維數組,transpose需要得到⼀個由軸編號組成的元組才能對這些軸進⾏轉置(⽐較費腦⼦):
arr = np.arange(16).reshape((2, 2, 4))
arr
array([[[ 0, 1, 2, 3],
[ 4, 5, 6, 7]],
[[ 8, 9, 10, 11],
[12, 13, 14, 15]]])
arr.transpose((1, 0, 2))
array([[[ 0, 1, 2, 3],
[ 8, 9, 10, 11]],
[[ 4, 5, 6, 7],
[12, 13, 14, 15]]])
簡單的轉置可以使⽤.T,它其實就是進⾏軸對換⽽已。
# ndarray還有一個swapaxes方法,他需要一對軸編號
arr
array([[[ 0, 1, 2, 3],
[ 4, 5, 6, 7]],
[[ 8, 9, 10, 11],
[12, 13, 14, 15]]])
# swapaxes也是返回源數據的視圖(不會進⾏任何複製操作)
arr.swapaxes(1, 2)
array([[[ 0, 4],
[ 1, 5],
[ 2, 6],
[ 3, 7]],
[[ 8, 12],
[ 9, 13],
[10, 14],
[11, 15]]])
通用函數
通⽤函數(即ufunc)是⼀種對ndarray中的數據執⾏元素級運算的函數。
arr = np.arange(10)
arr
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
# 開方
np.sqrt(arr)
array([0. , 1. , 1.41421356, 1.73205081, 2. ,
2.23606798, 2.44948974, 2.64575131, 2.82842712, 3. ])
# 指數運算
np.exp(arr)
array([1.00000000e+00, 2.71828183e+00, 7.38905610e+00, 2.00855369e+01,
5.45981500e+01, 1.48413159e+02, 4.03428793e+02, 1.09663316e+03,
2.98095799e+03, 8.10308393e+03])
x = np.random.randn(8)
y = np.random.randn(8)
np.maximum(x, y)
array([ 1.03157835, -0.29230895, 2.77475606, 1.55604276, -0.91644659,
2.08410919, 0.81804035, 1.11546013])
numpy.maximum計算了x和y中元素級別最大的元素
np.square(2)
4
NumPy數組可以將許多種數據處理任務表述爲簡潔的數組表達式(否則需要編寫循環)。⽤數組表達式代替循環的做法,通常被稱爲⽮量化。⼀般來說,⽮量化數組運算要⽐等價的純Python⽅式快上⼀兩個數量級(甚⾄更多),尤其是各種數值計
算。
points = np.arange(-5, 5, 0.01) # 1000 equally spaced points
xs, ys = np.meshgrid(points, points)
ys
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]])
z = np.sqrt(xs ** 2 + ys ** 2)
z
array([[7.07106781, 7.06400028, 7.05693985, ..., 7.04988652, 7.05693985,
7.06400028],
[7.06400028, 7.05692568, 7.04985815, ..., 7.04279774, 7.04985815,
7.05692568],
[7.05693985, 7.04985815, 7.04278354, ..., 7.03571603, 7.04278354,
7.04985815],
...,
[7.04988652, 7.04279774, 7.03571603, ..., 7.0286414 , 7.03571603,
7.04279774],
[7.05693985, 7.04985815, 7.04278354, ..., 7.03571603, 7.04278354,
7.04985815],
[7.06400028, 7.05692568, 7.04985815, ..., 7.04279774, 7.04985815,
7.05692568]])
import matplotlib.pyplot as plt
plt.imshow(z, cmap=plt.cm.gray); plt.colorbar()
plt.title("Image plot of $\sqrt{x^2 + y^2}$ for a grid of values")
Text(0.5, 1.0, 'Image plot of $\\sqrt{x^2 + y^2}$ for a grid of values')
用於布爾數組的方法
arr = np.random.randn(100)
(arr > 0).sum() # 統計正數值的個數
47
any()和all()方法
any()用於檢測數組中是否存在一個或者多個True, all()用於檢測數組中所有的值是否都是True
bools = np.array([False, False, True, True, False])
bools.any()
True
bools.all()
False
排序
Numpy數組可以通過sort方法進行排序
print(np.random.randn(10).sort())
None
arr = np.random.randn(10)
arr.sort()
arr
array([-1.9227681 , -0.87893899, -0.69759408, -0.39209399, 0.75957655,
0.97596031, 1.00763634, 1.05315874, 1.09735182, 1.46630886])
多維度數組可以再任何一個軸上進行排序,將軸編號傳遞給sort
arr = np.random.randn(5, 3)
arr
array([[-0.05231919, -0.64989375, 0.72685241],
[-0.4338584 , -0.73782085, -0.30604027],
[ 0.45229578, 0.12069083, 0.65669421],
[-0.57799688, -0.93550434, -1.99844601],
[ 1.59897264, 0.5119599 , -1.93736217]])
arr.sort(1)
arr
array([[-0.64989375, -0.05231919, 0.72685241],
[-0.73782085, -0.4338584 , -0.30604027],
[ 0.12069083, 0.45229578, 0.65669421],
[-1.99844601, -0.93550434, -0.57799688],
[-1.93736217, 0.5119599 , 1.59897264]])
頂級⽅法np.sort返回的是數組的已排序副本,⽽就地排序則會修改數組本身。計算數組分位數最簡單的辦法是對其進⾏排序,然後選取特定位置的值:
唯一化
針對一維數組的基本集合運算,最長用的就是:np.unique,他用於找出數組中的唯一值,同時返回已經排序的結果
arr = np.array((1, 1, 3, 4, 5, 5, 5, 6, 6, 7))
np.unique(arr)
array([1, 3, 4, 5, 6, 7])
sorted(set(arr))
[1, 3, 4, 5, 6, 7]
函數np.in1d⽤於測試⼀個數組中的值在另⼀個數組中的成員資格,返回⼀個布爾型數組:
values = np.array([6, 0, 0, 3, 2, 5, 6])
np.in1d(values, [2, 3, 6])
array([ True, False, False, True, True, False, True])
用於數組的文件輸入輸出
NumPy能夠讀寫磁盤上的⽂本數據或⼆進制數據。np.save和np.load是讀寫磁盤數組數據的兩個主要函數。默認情
況下,數組是以未壓縮的原始⼆進制格式保存在擴展名爲.npy的⽂件中的:
arr = np.arange(10)
np.save('some_array', arr)
np.load('some_array.npy')
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
通過np.savez可以將多個數組保存到⼀個未壓縮⽂件中,將數組以關鍵字參數的形式傳⼊即可:
np.savez('array_archive.npz', a = arr, b = arr)
加載.npz⽂件時,你會得到⼀個類似字典的對象,該對象會對各
個數組進⾏延遲加載:
arch = np.load('array_archive.npz')
# 使用numpy.savez_compressed.npz()
np.savez_compressed('array_compressed.npz', arr)
線性代數
x.dot(y)相當於 np.dot(x, y)
numpy.linalg中有⼀組標準的矩陣分解運算以及諸如求逆和⾏列
式之類的東⻄。它們跟MATLAB和R等語⾔所使⽤的是相同的⾏
業標準線性代數庫,如BLAS、LAPACK、Intel MKL(Math
Kernel Library,可能有,取決於你的NumPy版本)等:
from numpy.linalg import inv, qr
x = np.random.randn(5, 5)
mat = x.T.dot(x)
inv(mat)
array([[ 3.78993038, -2.31907465, -1.89085455, -0.23658529, -0.79725393],
[-2.31907465, 2.38770406, 0.8332225 , 0.4303317 , 0.23327152],
[-1.89085455, 0.8332225 , 1.42981316, 0.15209649, 0.87887263],
[-0.23658529, 0.4303317 , 0.15209649, 0.40301234, 0.16143467],
[-0.79725393, 0.23327152, 0.87887263, 0.16143467, 0.83002176]])
mat.dot(inv(mat))
array([[ 1.00000000e+00, 1.65398246e-17, -8.02190954e-16,
-1.16581206e-16, -2.47483443e-16],
[ 4.25597955e-16, 1.00000000e+00, -2.73011620e-16,
-3.88963944e-17, 1.44400437e-16],
[ 4.35129850e-16, -4.92848896e-16, 1.00000000e+00,
1.01451209e-16, 3.11993913e-16],
[-3.68142027e-16, 2.95272796e-16, 2.50284243e-16,
1.00000000e+00, -2.98913253e-17],
[-8.74528484e-16, -4.73167621e-16, -2.22459730e-16,
-6.75494951e-17, 1.00000000e+00]])
# qr分解操作
q, r = qr(mat)
r
array([[ -5.69298997, -2.92024922, -10.50739866, 1.57730475,
6.53703433],
[ 0. , -1.82064519, 3.9333861 , 3.85676364,
-4.96368589],
[ 0. , 0. , -1.21360022, 0.73417752,
1.85954433],
[ 0. , 0. , 0. , -1.90606856,
0.54424417],
[ 0. , 0. , 0. , 0. ,
0.67768289]])
僞隨機數生成
numpy.random模塊對Python內置的random進⾏了補充,增加了
⼀些⽤於⾼效⽣成多種概率分佈的樣本值的函數。例如,你可以
⽤normal來得到⼀個標準正態分佈的4×4樣本數組:
samples = np.random.normal(size=(4,4))
samples
array([[-0.81886679, -0.61556353, 1.3763956 , -1.28563408],
[-0.68575698, -1.34565189, 1.66916661, -0.36784602],
[-0.19165034, 0.83448014, -1.52353481, 0.63099716],
[ 0.65169991, -1.10422473, 2.0830342 , 0.48158532]])
python的內置模塊一次產生一個樣本值, 下面進行效率比較
from random import normalvariate
N = 1000000
%timeit samples = [normalvariate(0, 1) for _ in range(N)]
%timeit np.random.normal(size=N)
687 ms ± 1.74 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
24.4 ms ± 141 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
這些都是僞隨機數,是因爲它們都是通過算法基於隨機數
⽣成器種⼦,在確定性的條件下⽣成的。可以⽤NumPy的
np.random.seed更改隨機數⽣成種⼦:
np.random.seed(1234)
numpy.random的數據生成使用了全局的隨機種子。要避免全局狀態,可以使用random.RandomState,創建一個與他隔離的隨機數生成器。
rng = np.random.RandomState(1234)
rng.randn(10)
array([-0.20264632, -0.65596934, 0.19342138, 0.55343891, 1.31815155,
-0.46930528, 0.67555409, -1.81702723, -0.18310854, 1.05896919])
隨機漫步
模擬隨機漫步來說明如何運⽤數組運算。先來看⼀個簡
單的隨機漫步的例⼦:從0開始,步⻓1和-1出現的概率相等。
# 通過內置的random模塊以純python的方式實現1000步的隨機漫步
import random
position = 0
walk = [position]
steps = 1000
for i in range(steps):
step = 1 if random.randint(0, 1) else -1
position += step
walk.append(position)
# 隨機漫步值生成的折線圖
plt.plot(walk[:])