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 所示。

在實現(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)

廣播遵循以下規(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)

結(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 |