numpy——基礎篇

簡介

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>>的。如果想讓求和後的緯度保持不變,則可以通過設置keeodimsTrue來實現。

 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_CONTIGUOUSF_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]]]])
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章