文章目錄
簡介
NumPy是Python中科學計算的基礎包。它是一個Python庫,提供多維數組對象,各種派生對象(如掩碼數組和矩陣),以及用於數組快速操作的各種API,有包括數學、邏輯、形狀操作、排序、選擇、輸入輸出、離散傅立葉變換、基本線性代數,基本統計運算和隨機模擬等等。
使用
我們僅需要簡單的通過import numpy as np
就可以使用numpy了。
import numpy as np
np.eye(4)
#output
array([[1., 0., 0., 0.],
[0., 1., 0., 0.],
[0., 0., 1., 0.],
[0., 0., 0., 1.]])
爲什麼要用numpy?
如果我們希望兩個列表對應項相加,則我們需要這樣做,使用Python列表這樣的代碼是冗餘的,而使用numpy則大大減少了代碼的冗餘。
#使用Python列表
a = [1,2,3,4]
b = [4,5,6,7,8]
out = []
for i,j in zip(a,b):
out.append(i+j)
print(out)
#output
[ 5, 7, 9, 11]
#使用numpy
import numpy as np
a = np.array([1,2,3,4])
b = np.array([4,5,6,7])
print(a+b)
#output
array([ 5, 7, 9, 11])
而且Python自帶的list
在使用中也更加耗時。不難看出使用numpy將大大提高代碼的運行速度。
#使用Python自帶的數據類型
a = list(range(1000000))
%timeit sum(a)
#output
72.3 ms ± 2.54 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
#使用numpy
import numpy as np
a = list(range(1000000))
a_array = np.array(a)
%timeit np.sum(a_array)
#output
1.06 ms ± 33.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
爲什麼numpy運行速度會很快?
在Python list中,list存放的是每一項數據的索引,通過這個索引找到相應的內存地址,才能知道這個數值是多少。這就像是你想開一個party,但是你手裏只有給個人的地址,你需要進入他們的房間。但numpy不同的是把所有的元素都組織到一個屋子,也就是一個數據結構中,同一個緩存中。所以這些元素在內存中是相鄰的。他們都有相同的類型,numpy就能對這些數字進行非常快速的計算。因爲不用做類型檢查,它不需要申請獲取這些元素擁有所有對象。
Beware of type coercion(提防強制數據類型)
numpy對傳入的數據類型是有強管制的。這樣做的好處就是numpy的行爲比較可預測。缺點就是不靈活。因此如果你發現數據被改變時,不要驚訝
# 傳入浮點,整型,字符串
arr = np.array([1.9,2,'asd','a'])
# output
array(['1.9', '2', 'asd', 'a'], dtype='<U32')
# 傳入浮點,整型
arr = np.array([1,2.2,3,4.9])
# output
array([1. , 2.2, 3. , 4.9])
# 傳入整數、浮點、負數
arr = np.array([1,2.2,3,4.5+5j])
# output
array([1. +0.j, 2.2+0.j, 3. +0.j, 4.5+5.j])
基礎操作
現在讓我們一起探討一下numpy的基礎操作,爲了簡便,一下代碼涉及的一些數值,現已給出。
import numpy as np
a = np.array(['a','s','d','f'])
b = np.array([1,2,3,4])
c = np.array([4,5,6,7])
d = np.eye(4)
讀取、修改
# 讀取一維數組指定值
a[1]
# output
's'
#讀取二維數組指定值
d[1,1]
# output
1.0
# 讀取一行
d[1]
#output
array([0., 1., 0., 0.])
# 讀取一列(之所以不用d[,2]是因爲numpy是按行存儲的)
d[:,2]
#output
array([0., 0., 1., 0.])
#修改二維數組
d[2,3] = 5
# output
array([[1., 0., 0., 0.],
[0., 1., 0., 0.],
[0., 0., 1., 5.],
[0., 0., 0., 1.]])
加、減、乘、除、
b + c
#output
array([ 5, 7, 9, 11])
b - c
#output
array([-3, -3, -3, -3])
b * c
#output
array([ 4, 10, 18, 28])
b / c
#output
array([0.25 , 0.4 , 0.5 , 0.57142857])
b ** c
#output
array([ 1, 32, 729, 16384], dtype=int32)
shape(各維度長度)
shape返回一個元組,列出每個維度的數組長度。
a.shape
#output
(1,)
d.shape
#output
(4, 4)
ndim(維度)
a.ndim
#output
1
d.ndim
#output
2
dtype(類型)
我們可以通過dtype來查看numpy數組元素的數據類型。
a.dtype
#output
dtype('<U2')
d.dtype
#output
dtype('int32')
指定數據類型
由於numpy會強制數據類型,因此,如果想指定數據類型的話可以這樣操作。
arr = np.array([1, 2.2, 3, 4.9],dtype = 'int32')
# output
array([1, 2, 3, 4])
# 如果遇到無法轉換,則會報錯
arr = np.array([1. , 2.2, 3. , 'a'],dtype = 'int32')
ValueError: invalid literal for int() with base 10: 'a'
修改數據類型
numpy數據類型轉換需要調用方法astype()
,不能直接修改dtype
。調用astype
返回數據類型修改後的數據,但是源數據的類型不會變。
arr = np.array([1 , 2.2, 3, 4.9])
a = arr.astype(int)
# output
array([1, 2, 3, 4])
a = arr.astype(np.int64)
# output
array([1, 2, 3, 4], dtype=int64)
a = arr.astype(np.str)
# output
array(['1.0', '2.2', '3.0', '4.9'], dtype='<U32')
如果直接修改dtype則不會轉換,而是直接切分,因此會導致數據出錯。
arr = np.array([1 , 2.2, 3, 4.9])
arr.dtype
#oupput
dtype('float64')
#將原來每個64bits的浮點數 硬生生切成32bits的整型
arr.dtype = np.int32
#output
array([ 0, 1072693248, -1717986918, 1073846681, 0,
1074266112, -1717986918, 1075026329])
itemsize(最大元素的字節數)
a.itemsize
#output
4
b.itemsize
#output
4
nbytes(總元素字節數)
a.nbytes
# output
16
b.nbytes
# output
16
fill(填充)
以指定的元素填充數組。
a.fill('a')
#output
array(['a', 'a', 'a', 'a'], dtype='<U1')
reshape(重塑)
在不改變原數據的情況下,重新按指定形狀生成數組
>>> a = np.arange(1,26)
>>> a
array([ 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])
# 元素不匹配時,則會報錯
>>> a.reshape(5,6)
ValueError: cannot reshape array of size 25 into shape (5,6)
>>> a.reshape(5,5)
array([[ 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]])
sum
sxis
對於sum
中有一個axis
的參數,假設默認爲None
,現設a
是一個形狀爲(2,3,2)的數組。則
- axis=0(-3)時,則將a在第0維(倒數第3維)上求和,得到一個(3,2)的數組
- axis=1(-2)時,則將a在第1維(倒數第2維)上求和,得到一個(2,2)的數組
- axis=2(-1)時,則將a在第2維(倒數第1維)上求和,得到一個(2,3)的數組
a = np.arange(12).reshape(2,3,2)
# output
array([[[ 0, 1],
[ 2, 3],
[ 4, 5]],
[[ 6, 7],
[ 8, 9],
[10, 11]]])
a_0 = a.sum(axis = 0)
a_1 = a.sum(axis = 1)
a_2 = a.sum(axis = 2)
# output 分別爲a_0, a_1, a_2
array([[ 6, 8],
[10, 12],
[14, 16]])
array([[ 6, 9],
[24, 27]])
array([[ 1, 5, 9],
[13, 17, 21]])
keepdims
在sum
函數中,參數keepdims
默認是<<no value>>
的。如果想讓求和後的緯度保持不變,則可以通過設置keeodims
爲True
來實現。
b = a.sum(axis = 2,keepdims = True)
# output
array([[[ 1],
[ 5],
[ 9]],
[[13],
[17],
[21]]])
b.shape
# output
(2, 3, 1)
initinal
通過initial
可以設置初始值。
a.sum()
# output
66
a.sum(initial = 10)
# output
76
其他相似函數
函數名 | 含義 |
---|---|
prod | 返回數組的或給定軸上數組元素的乘積。(空數組的乘積爲1) |
min/max | 返回數組的最小(大)值或給定軸的最小(大)值。 |
argmin/argmin | 返回沿軸的最小(大)值的索引。(無initial和keepdims) |
max | 返回數組的最大值或給定軸的最大值。 |
ptp | 沿軸的值範圍(max-min, 無initial) |
mean | 返回數組的均值或給定軸的均值。 |
std/var | 計算沿指定軸的標準偏差(方差)。(其中自由度ddof=0爲總體標準差(總體方差),ddof=1爲樣本標準差(樣本方差)。無initial) |
any/all | any是元組元素只要有一個爲真才爲真,all是所有元素爲真才爲真。(無initial) |
flags(返回內存信息)
flags
返回ndarray
對象的內存信息,包含以下屬性:
屬性 | 描述 |
---|---|
C_CONTIGUOUS | 數據在一個單一的C風格的連續字段中 |
F_CONTIGUOUS | 數據在一個單一的Fortran風格的連續字段中 |
OWNDATA | 數據擁有它所使用的內存(True)或從另一個對象中借用它(False) |
WRITEABLE | 數據區域可以被寫入(True),設置爲False時爲只讀。 |
ALIGNED | 數據和所有元素都適當對其到硬件上(True爲對齊) |
WRITEBACKIFCOPY | UPDATEIFCOPY 已棄用,由 WRITEBACKIFCOPY 取代; |
UPDATEIFCOPY | 這個數組是其它數組的一個副本,當這個數組被釋放時,原數組的內容將被更新 |
C_CONTIGUOUS和F_CONTIGUOUS
C_CONTIGUOUS
和F_CONTIGUOUS
分別表示C風格和Fortran風格。這是兩種不同的編程語言,在C語言中數組的存儲風格是按行存儲,而在Fortran語言中則是按列存儲。==numpy數組是按行存儲。==既然如此那爲什麼又設計到了兩種不同風格的判別呢?
- 一維數組,
對於一維數組,不管按行存儲還是按列存儲內存中的順序都是不變的,因此既符合C_CONTIGUOUS
又符合F_CONTIGUOUS
。
數組a
的存儲方式如下圖所示:
a = np.array([1,2,3,4])
a.flags
# output
C_CONTIGUOUS : True
F_CONTIGUOUS : True
···
- 二維數組
當然,對於二維數組就不一樣了,我們可以看到數組b
在內存中是按C風格(行優先)存儲的。所以符合C_CONTIGUOUS
不符合F_CONTIGUOUS
。內存中的存儲方式如下圖左,現在我們將b
轉置賦給c
。如果爲c
單獨開闢一段內存是不划算的,numpy做的是給你一個相同內存緩衝區的一個視圖,儘量不創建一個副本。對於c
來說,c
的C風格相當於是b
的Fortran風格。因此只需要在b
上創建一個視圖,並標爲Fortran風格。便可作爲c
。
b = np.array([[1,2,3,4],[2,5,1,4]])
# output
C_CONTIGUOUS : True
F_CONTIGUOUS : False
···
c = b.T
# output
In [7]: c.flags
Out[7]:
C_CONTIGUOUS : False
F_CONTIGUOUS : True
OWNDATA
因爲b
是直接創建的,因此b
上的數據擁有所屬的內存,而c
相當於是b
上的一個視圖,因此數據沒有所屬的內存
b.flags
# outpit
···
OWNDATA : True
···
c.flags
# output
OWNDATA : False
類x數組
函數 | 解釋 |
---|---|
empty_like | 返回形狀和類型與給定數組相同的新數組。(值爲內存中的原垃圾值) |
ones_like | 返回形狀與類型與給定數組相同的數組。(1填充) |
zeros_like | 返回形狀與類型與給定數組相同的數組。(0填充) |
a = np.range(36).reshape(6,6)
np.empty_like(a)
np.ones_like(a)
np.zeros_like(a)
# output
array([[ 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0],
[-2, 0, -2, -1, 0, 0]])
array([[1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1]])
array([[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0]])
where
where(條件,[x,y])
返回根據條件從x或y中選擇的元素。
舉例
a = np.array([[1,2],[3,4]])
b = np.array([[9,8],[7,6]])
mask = np.array([[1,0],[0,1]],dtype = bool)
np.where(mask,a,b)
# output
array([[1, 8],
[7, 4]])
索引
現給出兩個數組,下面所有操作均在其上。
>>> import numpy as np
>>>#負索引 -6 -5 -4 -3 -2 -1
···#正索引 0, 1, 2, 3, 4, 5
··· a = np.numpy([10, 5, 7, 9, 8, 4])
>>> b = np.array([[ 1, 2, 9, 4, 3],
[ 0, 5, 3, 5, 1],
[ 8, 3, 2, 4, 7]])
切片
每當做切片時,numpy做的是給你一個相同內存緩衝區的一個試圖。所以numpy大多數情況下儘量不創建一個副本,它不賦值數據,只是指向內存中的同一個位置,所以這意味着對一個巨大數組做切片操作很廉價。除了一些關於形狀和維度數量的元數據外。
一維切片
numpy數組切片和python基本上是一樣的。
# 正索引從零開始,len(a)-1結束
# 負索引從-len(a)開始,-1結束
>>> a
array([10, 5, 7, 9, 8, 4])
>>> a[1:3]
array([5, 7])
>>> a[:3]
array([10, 5, 7])
>>> a[-4:]
array([7, 9, 8, 4])
>>> a[:-3]
array([10, 5, 7])
# 只能正向索引,否則返回爲空
# 無法從倒數第4個到正數第2個
>>> a[-4:2]
array([], dtype=int32)
# 可以從正數第2個到倒數第1個
>>> a[2:-1]
array([7, 9, 8])
#每n間隔n-1個取一個
>>> a[::3]
array([10, 9])
>>> a[::2]
array([10, 7, 8])
# 間隔反取
>>> a[::-2]
array([4, 9, 5])
# 區間跳轉 (開始:結束:間隔)
>>> b = np.arange(10)
>>> b
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> b[1:7:2]
array([1, 3, 5])
>>> b[7:1:-2]
array([7, 5, 3])
多維切片
以二維數組爲例,先在行切片,找到指定行後再列切片。你可以簡單理解爲在不同維度切片後重疊區域。
# 灰色區域
>>> b[1,3:5]
array([5, 1])
# 橙色區域
>>> b[0:2,0:2]
array([[1, 2],
[0, 5]])
# 藍色區域
>>> b[:,2]
array([9, 3, 2])
# 間隔選取
>>> b[::2,1::2]
array([[2, 4],
[3, 4]])
花式索引
雖然切片已經能夠滿足大多數的情況,但如果讓你不定長去索引numpy中的幾個數組呢?你會怎麼做?這裏將介紹花式索引。
通過位置索引
在numpy的數組可以通過列表批量傳入索引值來進行索引。
a = np.arange(8)
indices = [1,2,-2]
b = a[indices]
# output
array([1, 2, 6])
# 也可以直接傳入行和列(對應位置組成點)
a = np.arange(36).reshape([6,6])
# putout
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, 32, 33, 34, 35]])
b = a[[0,1,2,3,4],[1,2,3,4,5]]
# output
array([ 1, 8, 15, 22, 29])
布爾索引
我們也可以通過布爾的真假值來進行索引。
a = np.arange(10)
b = a < 5
# output 返回的是一個bool數組
array([True, True, True, True, True, False, False, False, False, False])
a[a < 5] = 0
print(a)
# output
array([0, 0, 0, 0, 0, 5, 6, 7, 8, 9])
# 注意這裏的bool個數應該和a對等
c = np.array([0,1,0,0,0,1,0,1,0,1],dtype = bool)
a[c]
# output
array([1, 5, 7, 9])
# 值得注意的是,只有0才爲False
In [1]: c = np.array([1,2,3,4,0,0,-5],dtype = bool)
Out[2]: array([ True, True, True, True, False, False, True])
- 想一下能不能用
a > 3 and a < 5
來找出數組a中小於5大於3的元素呢?我可以看一下
a > 3 and a < 5
# output
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
具有多個元素的數組的真值是不明確的。oh,原來數組a
元素中判斷的結果有真有假,而and
左右兩邊只能是確定的,要麼是真要麼是假,當兩邊都爲真的時候才爲真。那麼錯誤提示中的建議使用any()
和all()
又是什麼呢?
# any()意思是隻要有一個爲真,這個數組就爲真
(a < 5).any()
# output
True
#all()意思是所有爲真才爲真
(a < 9).all()
(a < 10).all()
#output
False True
嗯哼,好像並沒有解決問題。這裏我們引入一個幾種位操作。
操作符 | 含義 |
---|---|
& | and |
丨 | or |
~ | not |
^ | xor |
因此我們可以通過&
來實現對數組實現按位運算,這就像numpy中的“+”,“-”一樣。
# 之所以使用括號是因爲位運算符優先級高於比較運算符
(a > 3) & (a < 5)
# output
array([False, False, False, False, True, False, False, False, False, False])
# 因此這樣我們就可以進行bool索引
a[(a > 3) & (a < 5)]
# output
array([4])
- 如何得到滿足條件的索引值?
a = np.array([[1,2,3,4],[2,5,9,7],[5,2,3,4]])
np.nonzero(a < 3)
# output 既分別是(0,0),(0,1),(1,0),(2,1)四個點
(array([0, 0, 1, 2], dtype=int64), array([0, 1, 0, 1], dtype=int64))
#上面的輸出方式如果看不習慣我們可以反轉一下就比較清楚了
np.transpose(np.nonzero(a < 3))
# output
array([[0, 0],
[0, 1],
[1, 0],
[2, 1]], dtype=int64)
- 下面我們通過一個聯繫來進一步熟悉花式索引。
【1.】輸出下列表格中各顏色區域的元素。
a = np.arange(36).reshape([6,6])
# 黃色區域
y = a[[0,1,2,3,4],[1,2,3,4,5]]
# 紅色區域
mask = np.array([1,0,1,0,0,1],dtype = bool)
r = a[mask,2]
# 藍色區域
b = a[3:,[0,2,5]]
【2.】輸出上表中能被3整除的數
這裏需要說明的是,在通過以對應位置輸出時,由於引入Nan(表空缺,浮點型),所以原始數據不可避免的被強制改變成浮點型。
# 以一維數組形式輸出
mask = a % 3 ==0
a[mask]
# output
array([ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33])
# 以對應位置輸出
# 方法一,使用emoty_like函數
b = np.empty_like(a,dtype = np.float)
b.fill(np.nan)
b[mask] = a[mask]
#output
array([[ 0., nan, nan, 3., nan, nan],
[ 6., nan, nan, 9., nan, nan],
[12., nan, nan, 15., nan, nan],
[18., nan, nan, 21., nan, nan],
[24., nan, nan, 27., nan, nan],
[30., nan, nan, 33., nan, nan]])
# 方法二,使用where函數
mask = a % 3 == 0
np.where(mask,a,np.nan)
# output
array([[ 0., nan, nan, 3., nan, nan],
[ 6., nan, nan, 9., nan, nan],
[12., nan, nan, 15., nan, nan],
[18., nan, nan, 21., nan, nan],
[24., nan, nan, 27., nan, nan],
[30., nan, nan, 33., nan, nan]])
切片與花式索引
值得注意的是花式索引給出的是一個 副本 而切片給出的是一個 視圖
a = np.arange(10)
num1 = a[(a > 2) & (a < 8)]
num2 = a[3,8]
# output num1和num2均爲
array([3, 4, 5, 6, 7])
# 修改num1
num1[2] = 9
# output num1改變,a未改變
array([3, 4, 9, 6, 7])
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
# 修改num2
num2[2] = 9
# output num2和a均改變
array([3, 4, 9, 6, 7])
array([0, 1, 2, 3, 4, 9, 6, 7, 8, 9])
計算規則
- Rule 1:首先檢查多維數組之間的操作是否正確匹配形狀。
- Rule 2:數學運算符將元素逐個應用於值(+,-,*,/log,exp…)。
- Rule 3:歸約運算適用於整個數組,除非指定了座標軸(mean,std,skew,sum…)。
- Rule 4:除非明確忽略,否則缺失值會傳播(nanmean,nansum,…)。
這裏我們主要詳細講一下規則一
每次你在數組之間進行操作時,numpy做的第一件事就是檢查它們的形狀是否匹配,這稱爲“廣播規則”。廣播的初衷就是要節約時間和不必要的空間,沒有必要分配更多的內存,只需要重複相同的數據。
這裏舉幾個被認爲是匹配形狀的例子。
形狀完全一致
在最簡單的情況下,如下兩個形狀(3,2)
的數組,假設我們把它們加在一起,形狀顯然匹配,因爲它們是相同的形狀。得到的結果是數組元素對應位置逐個相加。
形狀不一致
另一種是兼容的數組,對於左邊a
形狀爲(3,2)
,右邊b
形狀(2,)
的數組,則numpy會認爲從 右側對齊。然後廣播數組b
依次與a
的每一行的元素進行操作。此時numpy並不需要額外的內存來複制b
將他們達到完全相同的形狀。
# (3,2)、 (2,) 右側對齊爲(2) 廣播3次
a = np.arange(1,7).reshape(3,2)
b = np.array([5,6])
# output a+b
array([[ 6, 8],
[ 8, 10],
[10, 12]])
# (2,3,2,2)、 (2,2) 右側對齊爲(2,2) 廣播2*3次
a = np.arange(24).reshape(2,3,2,2)
b = np.array([[1,1],[1,1]])
# output a+b
array([[[[ 1, 2],
[ 3, 4]],
[[ 5, 6],
[ 7, 8]],
[[ 9, 10],
[11, 12]]],
[[[13, 14],
[15, 16]],
[[17, 18],
[19, 20]],
[[21, 22],
[23, 24]]]])