Python - Numpy用法說明

Python 數(shù)據(jù)類型

Python 的用戶往往被其易用性所吸引,其中一個易用之處就在于動態(tài)輸入。靜態(tài)類型的語言(如 C 或 Java)往往需要明確地聲明每一個變量的數(shù)據(jù)類型,而動態(tài)類型的語言(例如 Python)可以跳過這個特殊規(guī)定,類型是動態(tài)推斷的,這意味著可以將任何類型的數(shù)據(jù)指定給任何變量。

標(biāo)準(zhǔn)的 Python 實現(xiàn)是用 C 語言編寫的。這意味著每一個 Python 對象都是一個聰明的偽 C 語言結(jié)構(gòu)體,該結(jié)構(gòu)體不僅包含其值,還有其他信息。這意味著與 C 語言這樣的編譯語言中的整型相比,在 Python 中存儲一個整型會有一些開銷。

兩者的差異在于,C 語言整型本質(zhì)上是對應(yīng)某個內(nèi)存位置的標(biāo)簽,里面存儲的字節(jié)會編碼成整型。而 Python 的整型其實是一個指針,指向包含這個 Python 對象所有信息的某個內(nèi)存位置,其中包括可以轉(zhuǎn)換成整型的字節(jié)。由于 Python 的整型結(jié)構(gòu)體里面還包含了大量額外的信息,所以 Python 可以自由、動態(tài)地編碼。但是,Python 類型中的這些額外信息也會成為負(fù)擔(dān),在多個對象組合的結(jié)構(gòu)體中尤其明顯。

# Python 列表
L1 = list(range(10)) # 數(shù)字列表
L2 = [str(c) for c in L1] # 對應(yīng)的字符串列表
L3 = [True, "2", 3.0, 4] # 異構(gòu)列表
[type(item) for item in L3] # 輸出異構(gòu)列表的類型 [bool, str, float, int]

但是想擁有這種靈活性也是要付出一定代價的:為了獲得這些靈活的類型,列表中的每一項必須包含各自的類型信息、引用計數(shù)和其他信息;也就是說,每一項都是一個完整的 Python 對象。來看一個特殊的例子,如果列表中的所有變量都是同一類型的,那么很多信息都會顯得多余——將數(shù)據(jù)存儲在固定類型的數(shù)組中應(yīng)該會更高效。動態(tài)類型的列表和固定類型的(NumPy 式)數(shù)組間的區(qū)別如圖 2-2 所示。

2-2

在實現(xiàn)層面,數(shù)組基本上包含一個指向連續(xù)數(shù)據(jù)塊的指針。另一方面,Python 列表包含一個指向指針塊的指針,這其中的每一個指針對應(yīng)一個完整的 Python 對象(如前面看到的 Python 整型)。另外,列表的優(yōu)勢是靈活,因為每個列表元素是一個包含數(shù)據(jù)和類型信息的完整結(jié)構(gòu)體,而且列表可以用任意類型的數(shù)據(jù)填充。固定類型的 NumPy 式數(shù)組缺乏這種靈活性,但是能更有效地存儲和操作數(shù)據(jù)。

創(chuàng)建數(shù)組
# 使用 Python 內(nèi)置的 array 創(chuàng)建統(tǒng)一類型的密集數(shù)組
# 輸出A:array('i', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
import array
L = list(range(10))
A = array.array('i', L) # 'i' 是一個數(shù)據(jù)類型碼,表示數(shù)據(jù)為整型

# 使用 numpy 從 Python 列表創(chuàng)建數(shù)組
import numpy as np
np.array([1, 4, 2, 5, 3]) # 創(chuàng)建整型數(shù)組

# 不同于 Python 列表,NumPy 要求數(shù)組必須包含同一類型的數(shù)據(jù)。如果類型不匹配,NumPy 將會向上轉(zhuǎn)換(如果可行)
np.array([3.14, 4, 2, 3]) # 這里整型被轉(zhuǎn)換為浮點型

# 如果希望明確設(shè)置數(shù)組的數(shù)據(jù)類型,可以用 dtype 關(guān)鍵字
np.array([1, 2, 3, 4], dtype='float32')

# 不同于 Python 列表,NumPy 數(shù)組可以被指定為多維的
# 輸出:array([[2, 3, 4], [4, 5, 6], [6, 7, 8]])
np.array([range(i, i + 3) for i in [2, 4, 6]]) # 嵌套列表構(gòu)成的多維數(shù)組,內(nèi)層的列表被當(dāng)作二維數(shù)組的行

面對大型數(shù)組的時候,用 NumPy 內(nèi)置的方法從頭創(chuàng)建數(shù)組是一種更高效的方法。

np.zeros(10, dtype=int) # 創(chuàng)建一個長度為10的數(shù)組,數(shù)組的值都是0
np.ones((3,5), dtype=float) # 創(chuàng)建一個3×5的浮點型數(shù)組,數(shù)組的值都是1
np.full((3,5), 3.14) # 創(chuàng)建一個3×5的浮點型數(shù)組,數(shù)組的值都是3.14
np.arange(0, 20, 2) # 創(chuàng)建一個3×5的浮點型數(shù)組,數(shù)組的值是一個線性序列(從0開始,到20結(jié)束,步長為2,和內(nèi)置的range()函數(shù)類似)
np.linspace(0, 1, 5) # 創(chuàng)建一個5個元素的數(shù)組,這5個數(shù)均勻地分配到0~1
np.random.random((3, 3)) # 創(chuàng)建一個3×3的、在0~1均勻分布的隨機(jī)數(shù)組成的數(shù)組
np.random.normal(0, 1, (3, 3)) # 創(chuàng)建一個3×3的、均值為0、方差為1的正態(tài)分布的隨機(jī)數(shù)數(shù)組
np.random.randint(0, 10, (3, 3)) # 創(chuàng)建一個3×3的、[0, 10)區(qū)間的隨機(jī)整型數(shù)組
np.eye(3) # 創(chuàng)建一個3×3的單位矩陣
np.empty(3) # 創(chuàng)建一個由3個整型數(shù)組成的未初始化的數(shù)組,數(shù)組的值是內(nèi)存空間中的任意值
Numpy數(shù)據(jù)類型
np.zeros(10, dtype='int16')
np.zeros(10, dtype=np.int16) # 使用numpy對象指定數(shù)據(jù)類型
數(shù)據(jù)類型 描述
bool_ 布爾值(真、True 或假、False),用一個字節(jié)存儲
int_ 默認(rèn)整型(類似于 C 語言中的 long,通常情況下是 int64 或 int32)
intc 同 C 語言的 int 相同(通常是 int32 或 int64)
intp 用作索引的整型(和 C 語言的 ssize_t 相同,通常情況下是 int32 或 int64)
int8 字節(jié)(byte,范圍從–128 到 127)
int16 整型(范圍從–32768 到 32767)
int32 整型(范圍從–2147483648 到 2147483647)
int64 整型(范圍從–9223372036854775808 到 9223372036854775807)
uint8 無符號整型(范圍從 0 到 255)
uint16 無符號整型(范圍從 0 到 65535)
uint32 無符號整型(范圍從 0 到 4294967295)
uint64 無符號整型(范圍從 0 到 18446744073709551615)
float_ float64 的簡化形式
float16 半精度浮點型:符號比特位,5 比特位指數(shù)(exponent),10 比特位尾數(shù)(mantissa)
float32 單精度浮點型:符號比特位,8 比特位指數(shù),23 比特位尾數(shù)
float64 雙精度浮點型:符號比特位,11 比特位指數(shù),52 比特位尾數(shù)
complex_ complex128 的簡化形式
complex64 復(fù)數(shù),由兩個 32 位浮點數(shù)表示
complex128 復(fù)數(shù),由兩個 64 位浮點數(shù)表示
NumPy數(shù)組基礎(chǔ)
import numpy as np
np.random.seed(0) # 設(shè)置隨機(jī)數(shù)種子,以確保每次程序執(zhí)行時都可以生成同樣的隨機(jī)數(shù)組
x1 = np.random.randint(10, size=6) # 一維數(shù)組
x2 = np.random.randint(10, size=(3, 4)) # 二維數(shù)組
x3 = np.random.randint(10, size=(3, 4, 5)) # 三維數(shù)組

# 數(shù)組屬性
print("x3 ndim: ", x3.ndim) # nidm(數(shù)組的維度)
print("x3 shape:", x3.shape) # shape(數(shù)組每個維度的大?。?print("x3 size: ", x3.size) # size(數(shù)組的總大?。?print("dtype:", x3.dtype) # dtype(數(shù)據(jù)類型)
print("itemsize:", x3.itemsize, "bytes") # itemsize(每個數(shù)組元素字節(jié)大?。?print("nbytes:", x3.nbytes, "bytes") # nbytes(數(shù)組總字節(jié)大小)

# 數(shù)組索引
print(x1) # array([5, 0, 3, 3, 7, 9])
print(x1[0]) # 5
print(x1[-1]) # 倒數(shù)第一個元素

print(x2)
# array([[3, 5, 2, 4],
#        [7, 6, 8, 8],
#        [1, 6, 7, 7]])

print(x2[0,0]) # 在多維數(shù)組中,可以用逗號分隔的索引元組獲取元素
# 和 Python 列表不同,NumPy 數(shù)組是固定類型的。這意味著當(dāng)你試圖將一個浮點值插入一個整型數(shù)組時,浮點值會被截短成整型
x1[0] = 3.14159 # 這將被截短
數(shù)組切片:獲取子數(shù)組
# 默認(rèn)值 start=0、stop= 維度的大?。╯ize of dimension)和 step=1
x[start:stop:step]

# 一維子數(shù)組
x = np.arange(10) # array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
x[:5] # 前五個元素
x[5:] # 索引五之后的元素
x[4:7] # 中間的子數(shù)組
x[::2] # 每隔一個元素
x[1::2] # 每隔一個元素,從索引1開始
x[::-1] # 所有元素,逆序的
x[5::-2] # 從索引5開始每隔一個元素逆序

# 多維子數(shù)組
print(x2)
# array([[12, 5, 2, 4],
#        [ 7, 6, 8, 8],
#        [ 1, 6, 7, 7]])

x2[:2, :3] # 兩行,三列
x2[:3, ::2] # 所有行,每隔一列
x2[::-1, ::-1] # 逆序

# 獲取數(shù)組的行和列
x2[:, 0] # x2的第一列
x2[0, :] # x2的第一行
x2[0] # 相當(dāng)于x2[0, :]

NumPy 數(shù)組切片和 Python 列表切片的不同之處:Numpy 數(shù)組切片返回的是數(shù)組數(shù)據(jù)的視圖,而不是數(shù)值數(shù)據(jù)的副本。在 Python 列表中,切片是值的副本。

它意味著在處理非常大的數(shù)據(jù)集時,可以獲取或處理這些數(shù)據(jù)集的片段,而不用復(fù)制底層的數(shù)據(jù)緩存。

# 非副本視圖的子數(shù)組
x2_sub = x2[:2, :2] 
# 現(xiàn)在如果修改這個子數(shù)組,將會看到原始數(shù)組也被修改了!
x2_sub[0, 0] = 99

# 創(chuàng)建數(shù)組的副本
x2_sub_copy = x2[:2, :2].copy()
# 如果修改這個子數(shù)組,原始的數(shù)組不會被改變
x2_sub_copy[0, 0] = 42
數(shù)組變形
# 將數(shù)字 1~9 放入一個 3×3 的矩陣中
# 原始數(shù)組的大小必須和變形后數(shù)組的大小一致
# reshape 方法將會用到原始數(shù)組的一個非副本視圖
grid = np.arange(1, 10).reshape((3, 3)) 

x = np.array([1, 2, 3])
# 通過變形獲得的行向量
x.reshape((1, 3)) 
# 通過 newaxis 獲得的行向量
x[np.newaxis, :]
# 以上變換結(jié)果均為 array([[1, 2, 3]])
數(shù)組拼接
# 一維數(shù)組拼接
x = np.array([1, 2, 3])
y = np.array([3, 2, 1])
z = [99, 99, 99]
np.concatenate([x, y,z]) # array([1, 2, 3, 3, 2, 1, 99, 99, 99])

# 二維數(shù)組拼接
grid = np.array([[1, 2, 3], 
                 [4, 5, 6]])
np.concatenate([grid, grid]) # 沿著第一個軸拼接
# array([[1, 2, 3],
#        [4, 5, 6],
#        [1, 2, 3],
#        [4, 5, 6]])

np.concatenate([grid, grid], axis=1) # 沿著第二個軸拼接(從0開始索引)
# array([[1, 2, 3, 1, 2, 3],
#        [4, 5, 6, 4, 5, 6]])

# 沿著固定維度處理數(shù)組時,使用 np.vstack(垂直棧)和 np.hstack(水平棧)函數(shù)會更簡潔
x = np.array([1, 2, 3])
y = np.array([[99],
              [99]])
grid = np.array([[9, 8, 7],
                 [6, 5, 4]])

np.vstack([x, grid]) # 垂直棧數(shù)組
# array([[1, 2, 3],
#        [9, 8, 7],
#        [6, 5, 4]])

np.hstack([grid, y]) # 水平棧數(shù)組
# array([[ 9, 8, 7, 99],
#        [ 6, 5, 4, 99]])
        
np.dstack # 沿著第三個維度拼接數(shù)組
數(shù)組分裂
# split
x = [1, 2, 3, 99, 99, 3, 2, 1]
x1, x2, x3 = np.split(x, [3, 5]) # 分裂點位置,N 分裂點會得到 N + 1 個子數(shù)組
print(x1, x2, x3) # [1 2 3] [99 99] [3 2 1]

# vsplit
grid = np.arange(16).reshape((4, 4))
# array([[ 0, 1, 2, 3],
#        [ 4, 5, 6, 7],
#        [ 8, 9, 10, 11],
#        [12, 13, 14, 15]])

upper, lower = np.vsplit(grid, [2])
print(upper)
# [[0 1 2 3]
#  [4 5 6 7]]
print(lower)
# [[ 8 9 10 11]
# [12 13 14 15]]

# hsplit
left, right = np.hsplit(grid, [2])
print(left)
# [[ 0 1]
#  [ 4 5]
#  [ 8 9]
#  [12 13]]
print(right)
# [[ 2 3]
#  [ 6 7]
#  [10 11]
#  [14 15]]

# dsplit 沿著第三個維度分裂數(shù)組
NumPy數(shù)組的計算:通用函數(shù)

NumPy 提供了一個簡單靈活的接口來優(yōu)化數(shù)據(jù)數(shù)組的計算。NumPy 數(shù)組的計算有時非???,有時也非常慢。使 NumPy 變快的關(guān)鍵是利用向量化操作,通常在 NumPy 的通用函數(shù)(ufunc)中實現(xiàn)。NumPy 通用函數(shù)可以提高數(shù)組元素的重復(fù)計算的效率。

NumPy 為很多類型的操作提供了非常方便的、靜態(tài)類型的、可編譯程序的接口,也被稱作向量操作。你可以通過簡單地對數(shù)組執(zhí)行操作來實現(xiàn),這里對數(shù)組的操作將會被用于數(shù)組中的每一個元素。這種向量方法被用于將循環(huán)推送至 NumPy 之下的編譯層,這樣會取得更快的執(zhí)行效率。

通過通用函數(shù)用向量的方式進(jìn)行計算幾乎總比用 Python 循環(huán)實現(xiàn)的計算更加有效,尤其是當(dāng)數(shù)組很大時。只要你看到 Python 腳本中有這樣的循環(huán),就應(yīng)該考慮能否用向量方式替換這個循環(huán)。

# 求倒數(shù)
import numpy as np
np.random.seed(0)
big_array = np.random.randint(1, 100, size=1000000)

def compute_reciprocals(values):
    output = np.empty(len(values))
    for i in range(len(values)):
        output[i] = 1.0 / values[i]
        return output

# 1、使用循環(huán),2.91s
print(compute_reciprocals(big_array))

# 2、使用通用函數(shù),4.6ms
print(1.0 / big_array) 

NumPy 中的向量操作是通過通用函數(shù)實現(xiàn)的。通用函數(shù)的主要目的是對 NumPy 數(shù)組中的值執(zhí)行更快的重復(fù)操作。通用函數(shù)也可以對數(shù)組進(jìn)行運(yùn)算。

np.arange(5) / np.arange(1, 6) # 一維數(shù)組
x = np.arange(9).reshape((3, 3))
2 ** x # 多維數(shù)組

通用函數(shù)有兩種存在形式:
一元通用函數(shù)(unary ufunc):對單個輸入操作
二元通用函數(shù)(binary ufunc):對兩個輸入操作

# 數(shù)組運(yùn)算
# 1、標(biāo)準(zhǔn)加減乘除
x = np.arange(4)
print("x =", x)
print("x + 5 =", x + 5)
print("x - 5 =", x - 5)
print("x * 2 =", x * 2)
print("x / 2 =", x / 2)
print("x // 2 =", x // 2) # 地板除法運(yùn)算

# 2、一元通用函數(shù)
print("-x = ", -x) # 邏輯非
print("x ** 2 = ", x ** 2) # 指數(shù)運(yùn)算
print("x % 2 = ", x % 2) # 模運(yùn)算

以下運(yùn)算符是借助對應(yīng)的通用函數(shù)實現(xiàn)的,直接使用運(yùn)算符即可。

運(yùn)算符 對應(yīng)的通用函數(shù) 描述
+ np.add 加法運(yùn)算(即 1 + 1 = 2)
- np.subtract 減法運(yùn)算(即 3 - 2 = 1)
- np.negative 負(fù)數(shù)運(yùn)算( 即 -2)
* np.multiply 乘法運(yùn)算(即 2 * 3 = 6)
/ np.divide 除法運(yùn)算(即 3 / 2 = 1.5)
// np.floor_divide 地板除法運(yùn)算(floor division,即 3 // 2 = 1)
** np.power 指數(shù)運(yùn)算(即 2 ** 3 = 8)
% np.mod 模 / 余數(shù)( 即 9 % 4 = 1)
# 絕對值
np.abs(x) # 或者 np.absolute(x)

# 三角函數(shù)
theta = np.linspace(0, np.pi, 3) # 定義一個角度數(shù)組
print("theta = ", theta)
print("sin(theta) = ", np.sin(theta))
print("cos(theta) = ", np.cos(theta))
print("tan(theta) = ", np.tan(theta))

# 逆三角函數(shù)
x = [-1, 0, 1]
print("x = ", x)
print("arcsin(x) = ", np.arcsin(x))
print("arccos(x) = ", np.arccos(x))
print("arctan(x) = ", np.arctan(x))

# 指數(shù)運(yùn)算
x = [1, 2, 3]
print("x =", x)
print("e^x =", np.exp(x))
print("2^x =", np.exp2(x))
print("3^x =", np.power(3, x))

# 對數(shù)運(yùn)算
print("x =", x)
print("ln(x) =", np.log(x))
print("log2(x) =", np.log2(x))
print("log10(x) =", np.log10(x))

# 提高精度
x = [0, 0.001, 0.01, 0.1]
print("exp(x) - 1 =", np.expm1(x))
print("log(1 + x) =", np.log1p(x))

更多函數(shù)請瀏覽 NumPy 文檔

通用函數(shù)高級特性
# 1、指定輸出:所有的通用函數(shù)都可以通過 out 參數(shù)來指定計算結(jié)果的存放位置,有效節(jié)約內(nèi)存
x = np.arange(5)
y = np.empty(5)
np.multiply(x, 10, out=y)

# 這個特性也可以被用作數(shù)組視圖,例如可以將計算結(jié)果寫入指定數(shù)組的每隔一個元素的位置
y = np.zeros(10)
np.power(2, x, out=y[::2])

# 對比:創(chuàng)建臨時數(shù)組,并將值復(fù)制到y(tǒng)數(shù)組中,計算量增大
y[::2] = 2 ** x 

# 2、聚合:多個值經(jīng)過運(yùn)算后返回一個值
x = np.arange(1, 6) # [ 0, 10, 20, 30, 40 ]
np.add.reduce(x) # 返回數(shù)組中所有元素的和
np.multiply.reduce(x) # 返回數(shù)組中所有元素的乘積

# 存儲每次計算的中間結(jié)果
np.add.accumulate(x) # array([ 1, 3, 6, 10, 15])
np.multiply.accumulate(x) # array([ 1, 2, 6, 24, 120])

# 3、外積:獲得兩個不同輸入數(shù)組所有元素對的函數(shù)運(yùn)算結(jié)果
x = np.arange(1, 6)
np.multiply.outer(x, x)
# array([[ 1, 2, 3, 4, 5],
#        [ 2, 4, 6, 8, 10],
#        [ 3, 6, 9, 12, 15],
#        [ 4, 8, 12, 16, 20],
#        [ 5, 10, 15, 20, 25]])
聚合:最小值、最大值和其他值

當(dāng)你面對大量的數(shù)據(jù)時,第一個步驟通常都是計算相關(guān)數(shù)據(jù)的概括統(tǒng)計值。最常用的概括統(tǒng)計值可能是均值和標(biāo)準(zhǔn)差,這兩個值能讓你分別概括出數(shù)據(jù)集中的“經(jīng)典”值,但是其他一些形式的聚合也是非常有用的(如求和、乘積、中位數(shù)、最小值和最大值、分位數(shù),等等)。

當(dāng)你操作 NumPy 數(shù)組時,確保你執(zhí)行的是 NumPy 版本的聚合。

# 單維度聚合
big_array = np.random.rand(1000000)
print(min(big_array), max(big_array), np.sum(big_array))
print(big_array.min(), big_array.max(), big_array.sum()) # 數(shù)組對象直接調(diào)用方法,效果一樣

# 多維度聚合
M = np.random.random((3, 4))
M.sum() # 對整個數(shù)組的聚合結(jié)果
# axis 指定數(shù)組將會被折疊的維度,二維數(shù)組的維度為x和y
M.min(axis=0) # 第一個軸被折疊,返回每一列的最小值
M.max(axis=1) # 第二個軸被折疊,返回每一行的最大值

大多數(shù)的聚合都有對 NaN 值的安全處理策略(NaNsafe),即計算時忽略所有的缺失值,這些缺失值即特殊的 IEEE 浮點型 NaN 值。

函數(shù)名稱 NaN安全版本 描述
np.sum np.nansum 計算元素的和
np.prod np.nanprod 計算元素的積
np.mean np.nanmean 計算元素的平均值
np.std np.nanstd 計算元素的標(biāo)準(zhǔn)差
np.var np.nanvar 計算元素的方差
np.min np.nanmin 找出最小值
np.max np.nanmax 找出最大值
np.argmin np.nanargmin 找出最小值的索引
np.argmax np.nanargmax 找出最大值的索引
np.median np.nanmedian 計算元素的中位數(shù)
np.percentile np.nanpercentile 計算基于元素排序的統(tǒng)計值
np.any N/A 驗證任何一個元素是否為真
np.all N/A 驗證所有元素是否為真
# 示例:美國總統(tǒng)的身高是多少
import pandas as pd
data = pd.read_csv('data/president_heights.csv')
heights = np.array(data['height(cm)'])
print(heights)

# 計算概括統(tǒng)計值
print("Mean height: ", heights.mean())
print("Standard deviation:", heights.std())
print("Minimum height: ", heights.min())
print("Maximum height: ", heights.max())

# 計算分位數(shù)
print("25th percentile: ", np.percentile(heights, 25))
print("Median: ", np.median(heights))
print("75th percentile: ", np.percentile(heights, 75))

# 可視化
%matplotlib inline
import matplotlib.pyplot as plt
import seaborn; seaborn.set() # 設(shè)置繪圖風(fēng)格
plt.hist(heights) # 直方圖
plt.title('Height Distribution of US Presidents')
plt.xlabel('height (cm)')
plt.ylabel('number');
數(shù)組的計算:廣播

前面介紹了 NumPy 如何通過通用函數(shù)的向量化操作來減少緩慢的 Python 循環(huán),另外一種向量化操作的方法是利用 NumPy 的廣播功能。廣播可以簡單理解為用于不同大小數(shù)組的二進(jìn)制通用函數(shù)(加、減、乘等)的一組規(guī)則。

a = np.array([0, 1, 2])
b = np.array([5, 5, 5])

# 對于同樣大小的數(shù)組,二進(jìn)制操作是對相應(yīng)元素逐個計算
print(a + b) # array([5, 6, 7])

# 廣播允許這些二進(jìn)制操作可以用于不同大小的數(shù)組,例如數(shù)組和標(biāo)量相加(相當(dāng)于一個零維數(shù)組)
# 相當(dāng)于將數(shù)值 5 擴(kuò)展或重復(fù)至數(shù)組 [5, 5, 5],然后執(zhí)行加法
print(a + 5) # array([5, 6, 7])

M = np.ones((3, 3))
# 這個一維數(shù)組就被擴(kuò)展或者廣播了,沿著第二個維度擴(kuò)展,擴(kuò)展到匹配 M 數(shù)組的形狀
# M = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
# a = [[0, 1, 2], [0, 1, 2], [0, 1, 2]]
print(M + a)

# 兩個數(shù)組同時廣播,最終結(jié)果為一個 3*3 數(shù)組
a = np.arange(3) # [0 1 2]
b = np.arange(3)[:, np.newaxis] # [[0], [1], [2]]
print(a + b)
2-4

廣播遵循以下規(guī)則:

規(guī)則 1:如果兩個數(shù)組的維度數(shù)不相同,那么小維度數(shù)組的形狀將會在最左邊補(bǔ) 1。
規(guī)則 2:如果兩個數(shù)組的形狀在任何一個維度上都不匹配,那么數(shù)組的形狀會沿著維度為 1 的維度擴(kuò)展以匹配另外一個數(shù)組的形狀。
規(guī)則 3:如果兩個數(shù)組的形狀在任何一個維度上都不匹配并且沒有任何一個維度等于 1,那么會引發(fā)異常。

# 廣播示例1
# 根據(jù)規(guī)則1,數(shù)組 a 的維度數(shù)更小,所以在其左邊補(bǔ)1;根據(jù)規(guī)則2,第一個維度不匹配,因此擴(kuò)展這個維度以匹配數(shù)組
M = np.ones((2, 3)) # M.shape = (2, 3) -> (2, 3) -> (2, 3)
a = np.arange(3)    # a.shape = (3,  ) -> (1, 3) -> (2, 3)

# 廣播示例2
# 根據(jù)規(guī)則1用 1 將 b 的形狀補(bǔ)全,根據(jù)規(guī)則2更新這兩個數(shù)組的維度來相互匹配
a = np.arange(3).reshape((3, 1)) # a.shape = (3, 1) -> (3, 1) -> (3, 3)
b = np.arange(3)                 # b.shape = (3,  ) -> (1, 3) -> (3, 3)

# 廣播示例3:兩個數(shù)組不兼容
M = np.ones((3, 2)) # M.shape = (3, 2) -> (3, 2) -> (3, 2)
a = np.arange(3)    # a.shape = (3,  ) -> (1, 3) -> (3, 3)
M + a[:, np.newaxis] # 通過變形數(shù)組來實現(xiàn)
廣播的實際應(yīng)用
# 1、數(shù)組的歸一化
np.random.random((10, 3))
Xmean = X.mean(0) # 沿著第一個維度聚合,計算第一個特征的均值
X_centered = X - Xmean # 從 X 數(shù)組的元素中減去這個均值實現(xiàn)歸一化

# 2、基于二維函數(shù)顯示圖像
x = np.linspace(0, 5, 50) 
y = np.linspace(0, 5, 50)[:, np.newaxis] # x和y表示0~5區(qū)間50個步長的序列
# 定義一個函數(shù) z = f (x, y),可以用廣播沿著數(shù)值區(qū)間計算該函數(shù)
z = np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x)

import matplotlib.pyplot as plt
plt.imshow(z, origin='lower', extent=[0, 5, 0, 5], cmap='viridis')
plt.colorbar();
比較、掩碼和布爾邏輯

當(dāng)你想基于某些準(zhǔn)則來抽取、修改、計數(shù)或?qū)σ粋€數(shù)組中的值進(jìn)行其他操作時,掩碼就可以派上用場了。例如你可能希望統(tǒng)計數(shù)組中有多少值大于某一個給定值,或者刪除所有超出某些門限值的異常點。在 NumPy 中,布爾掩碼通常是完成這類任務(wù)的最高效方式。

# 示例:統(tǒng)計下雨天數(shù)
import numpy as np
import pandas as pd
# 利用 Pandas 抽取 2014 年西雅圖市的日降水統(tǒng)計數(shù)據(jù),放入一個 NumPy 數(shù)組
# 數(shù)組包含 365 個值,降水量的單位是英寸
rainfall = pd.read_csv('data/Seattle2014.csv')['PRCP'].values
inches = rainfall / 254 # 1/10mm -> inches
inches.shape

%matplotlib inline
import matplotlib.pyplot as plt
import seaborn; seaborn.set() # 設(shè)置繪圖風(fēng)格

# 上面的直方圖無法回答西雅圖一年中下雨天數(shù),平均降水量,有多少天的降水量超過了半英寸
# 可以對全部數(shù)據(jù)使用循環(huán),當(dāng)數(shù)據(jù)落在區(qū)間時計數(shù)器便加 1,但浪費時間且非常低效
# NumPy 的通用函數(shù)可以用來替代循環(huán),以快速實現(xiàn)數(shù)組的逐元素(element-wise)運(yùn)算
# 同樣,我們也可以用其他通用函數(shù)實現(xiàn)數(shù)組的逐元素比較

print("Number days without rain: ", np.sum(inches == 0))
print("Number days with rain: ", np.sum(inches != 0))
print("Days with more than 0.5 inches:", np.sum(inches > 0.5))
print("Rainy days with < 0.1 inches :", np.sum((inches > 0) & (inches < 0.2)))

# 為所有下雨天創(chuàng)建一個掩碼
rainy = (inches > 0)
# 構(gòu)建一個包含整個夏季日期的掩碼(6月21日是第172天)
summer = (np.arange(365) - 172 < 90) & (np.arange(365) - 172 > 0)
print("Median precip on rainy days in 2014 (inches): ", np.median(inches[rainy]))
print("Median precip on summer days in 2014 (inches): ", np.median(inches[summer]))
print("Maximum precip on summer days in 2014 (inches): ", np.max(inches[summer]))
print("Median precip on non-summer rainy days (inches):", np.median(inches[rainy & ~summer]))

以下運(yùn)算符是借助對應(yīng)的通用函數(shù)實現(xiàn)的,直接使用運(yùn)算符即可。

運(yùn)算符 對應(yīng)的通用函數(shù)
== np.equal
!= np.not_equal
< np.less
<= np.less_equal
> np.greater
>= np.greater_equal
& np.bitwise_and
np.bitwise_or
^ np.bitwise_xor
~ np.bitwise_not
# 1、比較操作:和通用函數(shù)類似,可以用于任意形狀、大小的數(shù)組
x = np.array([1, 2, 3, 4, 5])
x <= 3 # array([True, True, True, False, False], dtype=bool)
x >= 3 # array([False, False, True, True, True], dtype=bool)
x != 3 # array([ True, True, False, True, True], dtype=bool)
x == 3 # array([False, False, True, False, False], dtype=bool)
(2 * x) == (x ** 2) # array([False, True, False, False, False], dtype=bool)

# 2、布爾數(shù)組操作
x = np.array([[5, 0, 3, 3],
              [7, 9, 3, 5],
              [2, 4, 7, 6]])
np.count_nonzero(x < 6) # 統(tǒng)計布爾數(shù)組中 True 記錄的個數(shù)
np.sum(x < 6, axis=1) # 另一種實現(xiàn)方式,好處是這個求和也可以沿著行或列進(jìn)行
np.all(x < 8, axis=1) # 是否每行的所有值都小于8?array([ True, False, True], dtype=bool)
np.all(x == 6) # 是否所有值都等于6?

# 3、布爾運(yùn)算符
np.sum((inches > 0.5) & (inches < 1)) # 降水量在 0.5 英寸~1 英寸間

# 4、將布爾數(shù)組作為掩碼,通過該掩碼選擇數(shù)據(jù)的子數(shù)據(jù)集
x[x < 5] # 這些值是掩碼數(shù)組對應(yīng)位置為 True 的值,array([0, 3, 3, 3, 2, 4])

# 5、使用關(guān)鍵字 and/or 與使用邏輯操作運(yùn)算符 &/|
# and 和 or 對整個對象執(zhí)行單個布爾運(yùn)算,所有非零的整數(shù)都會被當(dāng)作是 True
bool(42), bool(0) # (True, False)
# & 和 | 對一個對象的內(nèi)容(單個比特或字節(jié))執(zhí)行多個布爾運(yùn)算(and/or)
bin(42) # '0b101010'
bin(59) # '0b111011'
bin(42 & 59) # '0b101010'
bin(42 | 59) # '0b111011'

# 布爾數(shù)組被當(dāng)作是由比特字符組成的,可以逐個比較
A = np.array([1, 0, 1, 0, 1, 0], dtype=bool)
B = np.array([1, 1, 1, 0, 1, 1], dtype=bool)
A | B # array([ True, True, True, False, True, True], dtype=bool)
A or B # 計算整個數(shù)組的真或假,報錯

# 對給定數(shù)組進(jìn)行邏輯運(yùn)算時,也應(yīng)該使用 | 或 &,而不是 or 或 and
x = np.arange(10)
(x > 4) & (x < 8) # array([False, False, ..., True, True, False, False], dtype=bool)
(x > 4) and (x < 8) # 報錯
索引
rand = np.random.RandomState(42)
x = rand.randint(100, size=10)
print(x) # [51 92 14 71 60 20 82 86 74 74]

# 1、通過簡單索引(數(shù)組下標(biāo))獲得3個元素
[x[3], x[7], x[2]] 

# 2、通過列表索引獲得3個元素:結(jié)果的形狀與列表索引的形狀一致,而不是與被列表索引的形狀一致
ind = [3, 7, 4]
x[ind] 

X = np.arange(12).reshape((3, 4))
# array([[ 0, 1, 2, 3],
#        [ 4, 5, 6, 7],
#        [ 8, 9, 10, 11]])

row = np.array([0, 1, 2])
col = np.array([2, 1, 3])
X[row, col]
# X[0, 2],X[1, 1],X[2, 3]:array([ 2, 5, 11])

# 索引值的配對遵循廣播的規(guī)則,因此會得到一個二維的結(jié)果
X[row[:, np.newaxis], col]
# array([[ 2, 1, 3],
#        [ 6, 5, 7],
#        [10, 9, 11]])

# 每一行的值都與每一列的向量配對,就像廣播的運(yùn)算
X[row[:, np.newaxis] * col]
# array([[0, 0, 0],
#        [2, 1, 3],
#        [4, 2, 6]])

# 3、組合索引
# 列表索引+簡單索引
X[2, [2, 0, 1]] 
# array([10, 8, 9])

# 列表索引+切片
X[1:, [2, 0, 1]] 
# array([[ 6, 4, 5],
#        [10, 8, 9]])

# 列表索引+掩碼
mask = np.array([1, 0, 1, 0], dtype=bool)
X[row[:, np.newaxis], mask] 
# array([[ 0, 2],
#        [ 4, 6],
#        [ 8, 10]])

# 4、使用列表索引修改值
x = np.arange(10)
i = np.array([2, 1, 8, 4])
x[i] = 99

# 實現(xiàn)累加
i = [2, 3, 3, 4, 4, 4]
x[i] += 1 # 不能實現(xiàn),發(fā)生賦值

x = np.zeros(10)
np.add.at(x, i, 1) # 執(zhí)行就地操作,對數(shù)組x的索引i的值加1
# [ 0. 0. 1. 2. 3. 0. 0. 0. 0. 0.]

# 示例:選擇隨機(jī)點
# 通常用于快速分割數(shù)據(jù),即需要分割訓(xùn)練 / 測試數(shù)據(jù)集以驗證統(tǒng)計模型,以及在解答統(tǒng)計問題時的抽樣方法
中使用
mean = [0, 0]
cov = [[1, 2],
       [2, 5]]
X = rand.multivariate_normal(mean, cov, 100) # 二維正態(tài)分布的點組成的數(shù)組
X.shape # (100, 2)

# 散點圖
%matplotlib inline
import matplotlib.pyplot as plt
import seaborn; seaborn.set() # 設(shè)置繪圖風(fēng)格
plt.scatter(X[:, 0], X[:, 1]);

indices = np.random.choice(X.shape[0], 20, replace=False) # 隨機(jī)選取 20 個值,作為列表索引
selection = X[indices] # 利用列表索引取出數(shù)組值
plt.scatter(X[:, 0], X[:, 1], alpha=0.3)
# 將選中的點在圖上用大圓圈標(biāo)示出來
plt.scatter(selection[:, 0], selection[:, 1], facecolor='none', edgecolor='b', s=200);

# 示例:數(shù)據(jù)區(qū)間劃分
np.random.seed(42)
x = np.random.randn(100)
bins = np.linspace(-5, 5, 20) # 手動計算直方圖
counts = np.zeros_like(bins)
i = np.searchsorted(bins, x) 
np.add.at(counts, i, 1) # 為每個區(qū)間加上1
plt.plot(bins, counts, linestyle='steps'); # 畫出結(jié)果

plt.hist(x, bins, histtype='step'); # 等價于以上代碼,且數(shù)據(jù)量大時執(zhí)行效率更高
np.histogram(x, bins)
np.searchsorted(bins, x), 1)

算法效率并不是一個簡單的問題。一個對大數(shù)據(jù)集非常有效的算法并不總是小數(shù)據(jù)集的最佳選擇,反之同理。但是自己編寫這個算法的好處是可以理解這些基本方法。你可以利用這些編寫好的模塊去擴(kuò)展,以實現(xiàn)一些有意思的自定義操作。將 Python 有效地用于數(shù)據(jù)密集型應(yīng)用中的關(guān)鍵是,當(dāng)應(yīng)用場景合適時知道使用現(xiàn)成函數(shù),當(dāng)需要執(zhí)行更多指定的操作時也知道如何利用更低級的功能來實現(xiàn)。

數(shù)組的排序
# 1、選擇排序:簡潔,但是對于大數(shù)組來說太慢了
# 對于一個包含 N 個值的數(shù)組來說,它需要做 N 個循環(huán),每個循環(huán)中執(zhí)行~ N 次比較,以找到交換值,O(n2)
import numpy as np
def selection_sort(x):
    for i in range(len(x)):
        swap = i + np.argmin(x[i:])
        (x[i], x[swap]) = (x[swap], x[i])
    return x

x = np.array([2, 1, 4, 3, 5])
selection_sort(x)

# 2、全部排序
# np.sort:默認(rèn)為快速排序,O(NlogN),也可以選擇歸并排序和堆排序
# Python 有內(nèi)置的 sort 和 sorted 函數(shù)可以對列表進(jìn)行排序,但是 NumPy 的 np.sort 函數(shù)實際上效率更高
x = np.array([2, 1, 4, 3, 5])
np.sort(x) # 返回排好序的數(shù)組,不修改原始輸入數(shù)組
x.sort() # 用排好序的數(shù)組替代原始數(shù)組
i = np.argsort(x) # 返回原始數(shù)組排好序的索引值![1 0 3 2 4]
x[i] # 排好序的有序數(shù)組!array([1, 2, 3, 4, 5])

# 沿著行或列排序
# 將行或列當(dāng)作獨立的數(shù)組,任何行或列的值之間的關(guān)系將會丟失
rand = np.random.RandomState(42)
X = rand.randint(0, 10, (4, 6))
print(X)
# array([[6 3 7 4 6 9]
#        [2 6 7 4 3 7]
#        [7 2 5 4 1 7]
#        [5 1 4 0 9 5]])

np.sort(X, axis=0) # 對X的每一列排序
# array([[2, 1, 4, 0, 1, 5],
#        [5, 2, 5, 4, 3, 7],
#        [6, 3, 7, 4, 6, 7],
#        [7, 6, 7, 4, 9, 9]])

np.sort(X, axis=1) # 對X每一行排序
# array([[3, 4, 6, 6, 7, 9],
#        [2, 3, 4, 6, 7, 7],
#        [1, 2, 4, 5, 7, 7],
#        [0, 1, 4, 5, 5, 9]])

# 3、部分排序:分隔
# np.partition:找到數(shù)組中第 K 小的值,用該值進(jìn)行分隔,在這兩個分隔區(qū)間中,元素都是任意排列的
x = np.array([7, 2, 3, 1, 6, 5, 4])
np.partition(x, 3) # 前三個值是數(shù)組中最小的三個值,剩下的位置是原始數(shù)組剩下的值
# array([2, 1, 3, 4, 6, 5, 7]) 

np.partition(X, 2, axis=1) # 沿著多維數(shù)組任意的軸進(jìn)行分隔
# 該數(shù)組每一行的前兩個元素是該行最小的兩個值,每行的其他值分布在剩下的位置
# array([[3, 4, 6, 7, 6, 9],
#        [2, 3, 4, 7, 6, 7],
#        [1, 2, 4, 5, 7, 7],
#        [0, 1, 4, 5, 9, 5]])

i = np.argpartition(X) # 分隔的索引值

# 示例:K個最近鄰
X = rand.rand(10, 2) # 有 10 個隨機(jī)點的集合

%matplotlib inline
import matplotlib.pyplot as plt
import seaborn; seaborn.set() # 設(shè)置畫圖風(fēng)格
plt.scatter(X[:, 0], X[:, 1], s=100); # 散點圖

# 計算兩兩數(shù)據(jù)點對間的距離的平方,(X1-Y1)2+(X2-Y2)2
dist_sq = np.sum((X[:,np.newaxis,:] - X[np.newaxis,:,:]) ** 2, axis=-1) 

# 計算每個點的最近鄰,因為要排除本身,所以axis為1
nearest = np.argsort(dist_sq, axis=1)

# 計算 k 個最近鄰,分隔每一行,最小的 k + 1 的平方距離將排在最前面,其他更長的距離排在其他位置
K = 2
nearest_partition = np.argpartition(dist_sq, K + 1, axis=1)

# 將鄰節(jié)點網(wǎng)絡(luò)可視化,每個點和其最近的兩個最近鄰連接
plt.scatter(X[:, 0], X[:, 1], s=100)
K = 2 # 將每個點與它的兩個最近鄰連接
for i in range(X.shape[0]):
    for j in nearest_partition[i, :K+1]: # 畫一條從X[i]到X[j]的線段
        plt.plot(*zip(X[j], X[i]), color='black') # 用zip方法實現(xiàn)
2-11
結(jié)構(gòu)化數(shù)據(jù):NumPy的結(jié)構(gòu)化數(shù)組

NumPy 的結(jié)構(gòu)化數(shù)組和記錄數(shù)組,它們?yōu)閺?fù)合的、異構(gòu)的數(shù)據(jù)提供了非常有效的存儲,通常也可以用 Pandas 的 DataFrame 來實現(xiàn),并且 Pandas 有時更好用。

name = ['Alice', 'Bob', 'Cathy', 'Doug']
age = [25, 45, 37, 19]
weight = [55.0, 85.5, 68.0, 61.5]

# 1、使用復(fù)合數(shù)據(jù)結(jié)構(gòu)的結(jié)構(gòu)化數(shù)組
data = np.zeros(4, dtype={'names':('name', 'age', 'weight'), 'formats':('U10', 'i4', 'f8')})
print(data.dtype) 
# [('name', '<U10'), ('age', '<i4'), ('weight', '<f8')]

# 2、將列表數(shù)據(jù)放入數(shù)組中,按順序匹配
data['name'] = name
data['age'] = age
data['weight'] = weight
print(data)
# [('Alice', 25, 55.0) ('Bob', 45, 85.5) ('Cathy', 37, 68.0) ('Doug', 19, 61.5)]

# 獲取所有名字
data['name']
# array(['Alice', 'Bob', 'Cathy', 'Doug'], dtype='<U10')

# 獲取數(shù)據(jù)第一行
data[0]
# ('Alice', 25, 55.0)

# 獲取最后一行的名字
data[-1]['name']
# 'Doug'

# 獲取年齡小于30歲的人的名字(布爾掩碼)
data[data['age'] < 30]['name']
# array(['Alice', 'Doug'], dtype='<U10')

dtype格式每一位的含義:

  • <(低字節(jié)序 / >(高字節(jié)序):字節(jié)(bytes)類型的數(shù)據(jù)在內(nèi)存中存放順序的習(xí)慣用法
  • 數(shù)據(jù)的類型:字符、字節(jié)、整型、浮點型,等等
  • 該對象的字節(jié)大小

NumPy的數(shù)據(jù)類型:

NumPy數(shù)據(jù)類型符號 描述 示例
'b' 字節(jié)型 np.dtype('b')
'i' 有符號整型 np.dtype('i4') == np.int32
'u' 無符號整型 np.dtype('u1') == np.uint8
'f' 浮點型 np.dtype('f8') == np.int64
'c' 復(fù)數(shù)浮點型 np.dtype('c16') == np.complex128
'S'、'a' 字符串 np.dtype('S5')
'U' Unicode 編碼字符串 np.dtype('U') == np.str_
'V' 原生數(shù)據(jù),raw data(空,void) np.dtype('V') == np.void
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • NumPy(Numerical Python 的簡稱)提供了高效存儲和操作密集數(shù)據(jù)緩存的接口,可以理解是一個數(shù)組,...
    奉先閱讀 808評論 0 0
  • To be continue NumPy(Numerical Python) 是 Python 語言的一個擴(kuò)展程序...
    z761943閱讀 1,722評論 1 7
  • Numpy的組成與功能 Numpy(Numeric Python)可以被理解為一個用python實現(xiàn)的科學(xué)計算包,...
    不做大哥好多年閱讀 4,562評論 0 10
  • 介紹 NumPy是Python數(shù)值計算最重要的基礎(chǔ)包,大多數(shù)提供科學(xué)計算的包都是用NumPy的數(shù)組作為構(gòu)建基礎(chǔ)。N...
    無味之味閱讀 8,007評論 0 3
  • 終于完成周末作業(yè):采買生活用品,洗衣服,打掃衛(wèi)生,整理物品,每周必做。和老公出去透透氣,順便買兩盆小花換換心情。沒...
    梅云木子1閱讀 429評論 0 7

友情鏈接更多精彩內(nèi)容