20個不常見但是卻非常有用的Numpy函數(shù)
Numpy是每個數(shù)據(jù)科學(xué)家都應(yīng)該掌握的Python包,它提供了許多創(chuàng)建和操作數(shù)字?jǐn)?shù)組的方法。它構(gòu)成了許多與數(shù)據(jù)科學(xué)相關(guān)的廣泛使用的Python庫的基礎(chǔ),比如panda和Matplotlib。
以下這些函數(shù)并不常見,甚至你可能都沒聽說過,但是在有些時候它們真的很有用。
np.full_like
我敢打賭,你肯定使用過像ones_like 或 zeros_like 這樣的常見 NumPy 函數(shù)。 full_like 和這兩個完全一樣,除了你可以創(chuàng)建一個與另一個矩陣具有相同形狀的矩陣但是這些矩陣是使用自定義值填充的。
- array = np.array([[1, 4, 6, 8], [9, 4, 4, 4], [2, 7, 2, 3]])
- array_w_inf = np.full_like(array, fill_value=np.pi, dtype=np.float32)
- >>> array_w_inf
- array([[3.1415927, 3.1415927, 3.1415927, 3.1415927],
- [3.1415927, 3.1415927, 3.1415927, 3.1415927],
- [3.1415927, 3.1415927, 3.1415927, 3.1415927]], dtype=float32)
在這里,我們正在創(chuàng)建一個數(shù)組值都是pi 矩陣。
np.logspace
我相信你經(jīng)常使用linspace。它可以在一個區(qū)間內(nèi)創(chuàng)建自定義的線性間隔數(shù)據(jù)點數(shù)量。它的同類logspace在這方面做得更深入一些。它可以在對數(shù)尺度上生成均勻間隔的自定義點數(shù)。你可以選擇任何一個數(shù)作為基數(shù),只要它是非零的:
- log_array = np.logspace(start=1, stop=100, num=15, base=np.e)
- >>> log_array
- array([2.71828183e+00, 3.20167238e+03, 3.77102401e+06, 4.44162312e+09,
- 5.23147450e+12, 6.16178472e+15, 7.25753148e+18, 8.54813429e+21,
- 1.00682443e+25, 1.18586746e+28, 1.39674961e+31, 1.64513282e+34,
- 1.93768588e+37, 2.28226349e+40, 2.68811714e+43])
np.meshgrid
這是只有在文檔中才能看到的函數(shù)之一。因為大部分人難理解它。可以使用meshgrid從給定的X和Y數(shù)組創(chuàng)建每個可能的坐標(biāo)對。這里有一個簡單的例子:
- x = [1, 2, 3, 4]
- y = [3, 5, 6, 8]
- xx, yy = np.meshgrid(x, y)
- >>> xx
- array([[1, 2, 3, 4],
- [1, 2, 3, 4],
- [1, 2, 3, 4],
- [1, 2, 3, 4]])
- >>> yy
- array([[3, 3, 3, 3],
- [5, 5, 5, 5],
- [6, 6, 6, 6],
- [8, 8, 8, 8]])
得到 16 個唯一坐標(biāo)對,結(jié)果數(shù)組中的每個索引到索引元素對對應(yīng)一個。可視化一下就很好理解了
- >>> plt.plot(xx, yy, linestyle="none", marker="o", color="red");

meshgrid通常用于使用循環(huán)需要很長時間的復(fù)雜任務(wù)。如繪制三維正弦函數(shù)等高線圖就是一個例子:
- def sinus2d(x, y):
- return np.sin(x) + np.sin(y)
- xx, yy = np.meshgrid(np.linspace(0, 2 * np.pi, 100), np.linspace(0, 2 * np.pi, 100))
- z = sinus2d(xx, yy) # Create the image on this grid
- import matplotlib.pyplot as plt
- plt.imshow(z, origin="lower", interpolation="none")
- plt.show()

np.triu / np.tril
與ones_like或zeros_like類似,這兩個函數(shù)在矩陣的某個對角線上方或下方返回0。例如,我們可以使用triu函數(shù)在主對角線上創(chuàng)建一個值為True的布爾掩碼,并在繪制相關(guān)熱圖時使用這個掩碼。
- import seaborn as sns
- diamonds = sns.load_dataset("diamonds")
- matrix = diamonds.corr()
- mask = np.triu(np.ones_like(matrix, dtype=bool))
- sns.heatmap(matrix, square=True, mask=mask, annot=True, fmt=".2f", center=0);

如你所見,用triu創(chuàng)建的掩碼可以用在相關(guān)矩陣上,去掉不必要的上三角形和對角線。這使得熱圖更加緊湊,可讀性更強(qiáng)。
np.ravel / np.flatten
NumPy是關(guān)于高維矩陣和ndarrays的。但是有時候你只是想把這些數(shù)組壓縮成一維。這就是你使用ravel或flatten的地方:
- array = np.random.randint(0, 10, size=(4, 5))
- >>> array
- array([[6, 4, 8, 9, 6],
- [5, 0, 4, 8, 5],
- [1, 3, 1, 0, 3],
- [2, 3, 3, 6, 5]])
- >>> array.ravel()
- array([6, 4, 8, 9, 6, 5, 0, 4, 8, 5, 1, 3, 1, 0, 3, 2, 3, 3, 6, 5])
- >>> array.flatten()
- array([6, 4, 8, 9, 6, 5, 0, 4, 8, 5, 1, 3, 1, 0, 3, 2, 3, 3, 6, 5])
它們看起來一樣嗎?不完全是。flatten總是返回一個1D副本,而ravel則試圖生成原始數(shù)組的1D視圖。也就是說如果修改從ravel返回的數(shù)組可能會改變原來的數(shù)組。
np.vstack / np.hstack
在Kaggle上這兩個函數(shù)經(jīng)常被使用。通常人們從不同的模型對測試集有多個預(yù)測,他們希望以某種方式集成這些預(yù)測。為了使它們易于處理,必須將它們組合成一個矩陣。
- array1 = np.arange(1, 11).reshape(-1, 1)
- array2 = np.random.randint(1, 10, size=10).reshape(-1, 1)
- hstacked = np.hstack((array1, array2))
- >>> hstacked
- array([[ 1, 2],
- [ 2, 6],
- [ 3, 6],
- [ 4, 7],
- [ 5, 4],
- [ 6, 6],
- [ 7, 6],
- [ 8, 8],
- [ 9, 2],
- [10, 8]])
- array1 = np.arange(20, 31).reshape(1, -1)
- array2 = np.random.randint(20, 31, size=11).reshape(1, -1)
- vstacked = np.vstack((array1, array2))
- >>> vstacked
- array([[20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30],
- [21, 23, 23, 26, 29, 26, 27, 27, 28, 25, 25]])
在將每個數(shù)組與這些數(shù)組堆疊之前,要對數(shù)組進(jìn)行重塑,因為默認(rèn)情況下它們需要2D數(shù)組。這就是我們使用重塑函數(shù)的原因。這里,reshape(-1,1)表示將數(shù)組轉(zhuǎn)換為具有盡可能多行的單列。
類似地,reshape(1,-1)將數(shù)組轉(zhuǎn)換為具有盡可能多列的單行向量。
np.r_ / np.c_
如果你像我一樣懶惰,不想對所有數(shù)組調(diào)用重塑,那么有一個更優(yōu)雅的解決方案。np.r_ / np.c_操作符(不是函數(shù)!)允許將數(shù)組分別堆疊為行和列。
下面,我們模擬一個有100個可能性的預(yù)測數(shù)組。為了將它們堆疊在一起,我們調(diào)用np.r_用括號表示(如pandas.DataFrame.loc)。
- preds1 = np.random.rand(100)
- preds2 = np.random.rand(100)
- as_rows = np.r_[preds1, preds2]
- as_cols = np.c_[preds1, preds2]
- >>> as_rows.shape
- (200,)
- >>> as_cols.shape
- (100, 2)
類似地,np.c_將數(shù)組堆疊在一起創(chuàng)建一個矩陣。其實它們的功能并不局限于簡單的水平和垂直堆棧。要了解更多的功能,我建議你閱讀文檔。
np.info
NumPy的函數(shù)非常的多。你可能沒有時間和耐心學(xué)習(xí)每個函數(shù)和類。如果你面對一個未知的函數(shù)呢?你不用去看文檔了因為有更好的選擇。
info函數(shù)可以打印NumPy API中任何名稱的docstring。這里是info使用的信息:
- >>> np.info(np.info)
- info(object=None, maxwidth=76,
- output=<ipykernel.iostream.OutStream object at 0x0000021B875A8820>,
- toplevel='numpy')
- Get help information for a function, class, or module.
- Parameters
- ----------
- object : object or str, optional
- Input object or name to get information about. If `object` is a
- numpy object, its docstring is given. If it is a string, available
- modules are searched for matching objects. If None, information
- about `info` itself is returned.
- maxwidth : int, optional
- Printing width.
還記得我們在vscode的文章中說過lint要求強(qiáng)制編寫docstring嗎,這就是原因了。
np.where
顧名思義,這個函數(shù)返回一個條件為True的數(shù)組的所有下標(biāo):
- probs = np.random.rand(100)
- idx = np.where(probs > 0.8)
- >>> probs[idx]
- array([0.80444302, 0.80623093, 0.98833642, 0.96856382, 0.89329919,
- 0.88664223, 0.90515148, 0.96363973, 0.81847588, 0.88250337,
- 0.98737432, 0.92104315])
它在搜索稀疏數(shù)組中的非零元素時特別有用,甚至可以在Pandas DataFrames上使用它來基于條件進(jìn)行更快的索引檢索。
np.all / np.any
當(dāng)與assert語句一起使用時,這兩個函數(shù)將在數(shù)據(jù)清理期間非常方便。np.all僅當(dāng)數(shù)組中的所有元素都符合特定條件時返回True:
- array1 = np.random.rand(100)
- array2 = np.random.rand(100)
- >>> np.all(array1 == array2)
- False
因為我們創(chuàng)建了兩個隨機(jī)數(shù)的數(shù)組,所以不可能每個元素都相等。然而,如果這些數(shù)字是整數(shù),那么它們中至少有兩個相等的可能性要大得多:
- a1 = np.random.randint(1, 100, size=100)
- a2 = np.random.randint(1, 100, size=100)
- >>> np.any(a1 == a2)
- True
any返回True是因為數(shù)組中至少有一個元素滿足特定條件,
np.allclose
如果想要檢查兩個長度相等的數(shù)組是否互為副本,簡單的==操作符不會將其截斷。但是你可能想要比較浮點數(shù)數(shù)組,但是它們的小數(shù)點長度使得比較困難。在這種情況下可以使用allclose,如果一個數(shù)組的所有元素彼此之間距離很近,給定一定的容忍度,它將返回True。
- a1 = np.arange(1, 10, step=0.5)
- a2 = np.arange(0.8, 9.8, step=0.5)
- >>> np.all(a1 == a2)
- False
- >>> a1
- array([1. , 1.5, 2. , 2.5, 3. , 3.5, 4. , 4.5, 5. , 5.5, 6. , 6.5, 7. ,
- 7.5, 8. , 8.5, 9. , 9.5])
- >>> a2
- array([0.8, 1.3, 1.8, 2.3, 2.8, 3.3, 3.8, 4.3, 4.8, 5.3, 5.8, 6.3, 6.8,
- 7.3, 7.8, 8.3, 8.8, 9.3])
- >>> np.allclose(a1, a2, rtol=0.2)
- False
- >>> np.allclose(a1, a2, rtol=0.3)
- True
只有當(dāng)差異(<)小于rtol時,函數(shù)才返回True,而不是<=!
np.argsort
np.sort返回一個已排序的數(shù)組副本。有時需要對數(shù)組進(jìn)行排序的索引,以便為不同的目的多次使用相同的索引。 這就是 argsort 派上用場的地方:
- random_ints = np.random.randint(1, 100, size=20)
- idx = np.argsort(random_ints)
- >>> random_ints[idx]
- array([ 6, 19, 22, 23, 35, 36, 37, 45, 46, 57, 61, 62, 64, 66, 66, 68, 72,
- 74, 87, 89])
它來自以 arg 開頭的一系列函數(shù),這些函數(shù)總是從某個函數(shù)的結(jié)果返回一個或多個索引。 例如,argmax 查找數(shù)組中的最大值并返回其索引(分類的TOP N就可以用這種方法)。
np.isneginf / np.isposinf
這兩個布爾函數(shù)檢查數(shù)組中的元素是負(fù)無窮大還是正無窮大。 但是計算機(jī)和 NumPy 不理解無窮大的概念(好吧,我也不知道是為什么)。 它們只能將無窮大表示為一個非常大或非常小的數(shù)字,這樣才可以放入一個變量中(我希望我說得對)。
這就是為什么當(dāng)你打印 np.inf 的類型時,它返回浮點數(shù):
- >>> type(np.inf) # type of the infinity
- float
- >>> type(-np.inf)
- float
這意味著無窮大值可以很容易地被當(dāng)作數(shù)組的正常值。 所以你需要一個特殊的功能來找到這些異常的值:
- a = np.array([-9999, 99999, 97897, -79897, -np.inf])
- >>> np.all(a.dtype == "float64")
- True
- >>> np.any(np.isneginf(a))
- True
np.polyfit
如果要執(zhí)行傳統(tǒng)的線性回歸,則不一定需要 Sklearn。 NumPy 也可以的:
- X = diamonds["carat"].values.flatten()
- y = diamonds["price"].values.flatten()
- slope, intercept = np.polyfit(X, y, deg=1)
- >>> slope, intercept
- (7756.425617968436, -2256.3605800454034)
polyfit 獲取兩個向量,對它們應(yīng)用線性回歸并返回斜率和截距。 你只需要使用 deg 指定次數(shù),因為此函數(shù)可用于逼近任何次數(shù)多項式的根。
檢查發(fā)現(xiàn)用 polyfit 找到的斜率和截距與 Sklearn 的 LinearRegression 模型相同:
- from sklearn.linear_model import LinearRegression
- lr = LinearRegression().fit(X.reshape(-1, 1), y)
- >>> lr.coef_, lr.intercept_
- (array([7756.42561797]), -2256.360580045441)
概率分布
NumPy 的 random 模塊有多種偽隨機(jī)數(shù)生成器可供選擇。 除了我最喜歡的樣本和選擇之外,還有模擬偽完美概率分布的函數(shù)。
例如,二項式、伽馬、正態(tài)和 tweedie 函數(shù)從它們各自的分布中繪制自定義數(shù)量的數(shù)據(jù)點。
當(dāng)你必須近似數(shù)據(jù)中特征的分布時,你可能會發(fā)現(xiàn)它們非常有用。 例如,下面我們檢查鉆石價格是否服從正態(tài)分布。
- fig, ax = plt.subplots(figsize=(6, 8))
- price_mean = diamonds["price"].mean()
- price_std = diamonds["price"].std()
- # Draw from a perfect normal distribution
- perfect_norm = np.random.normal(price_mean, price_std, size=1000000)
- sns.kdeplot(diamonds["price"], ax=ax)
- sns.kdeplot(perfect_norm, ax=ax)
- plt.legend(["Price", "Perfect Normal Distribution"]);

這可以通過在完美正態(tài)分布之上繪制鉆石價格的 KDE 來實現(xiàn),以使差異可見。
np.rint
如果你想將數(shù)組的每個元素四舍五入到最接近的整數(shù), rint 是一個漂亮的小函數(shù)。 當(dāng)你想將類概率轉(zhuǎn)換為二進(jìn)制分類中的類標(biāo)簽時,可以不必調(diào)用模型的 predict 方法改成直接使用它:
- preds = np.random.rand(100)
- >>> np.rint(preds[:50])
- array([1., 1., 0., 1., 0., 1., 1., 0., 0., 0., 0., 1., 0., 1., 0., 1., 0.,
- 1., 0., 1., 1., 1., 1., 1., 0., 0., 1., 0., 0., 0., 0., 1., 0., 0.,
- 0., 0., 0., 0., 0., 0., 1., 0., 1., 0., 1., 1., 0., 0., 1., 0.])
np.nanmean / np.nan*
是否知道如果至少有一個元素是 NaN,則純 NumPy 數(shù)組上的算術(shù)運(yùn)算會失敗?
- a = np.array([12, 45, np.nan, 9, np.nan, 22])
- >>> np.mean(a)
- nan
要在不修改原始數(shù)組的情況下解決此問題,你可以使用一系列 nan 函數(shù):
- >>> np.nanmean(a)
- 22.0
以上是忽略缺失值的算術(shù)平均函數(shù)的示例。 許多其他函數(shù)以同樣的方式工作:
- >>> [func for func in dir(np) if func.startswith("nan")]
- ['nan',
- 'nan_to_num',
- 'nanargmax',
- 'nanargmin',
- 'nancumprod',
- 'nancumsum',
- 'nanmax',
- 'nanmean',
- 'nanmedian',
- 'nanmin',
- 'nanpercentile',
- 'nanprod',
- 'nanquantile',
- 'nanstd',
- 'nansum',
- 'nanvar']
但是,如果只使用 Pandas DataFrames 或 Series,可能會有些不同,因為它們默認(rèn)會忽略 NaN。
np.clip
當(dāng)想對數(shù)組的值施加嚴(yán)格限制時,clip 很有用。 下面,我們將裁剪任何超出 10 和 70 硬限制的值:
- ages = np.random.randint(1, 110, size=100)
- limited_ages = np.clip(ages, 10, 70)
- >>> limited_ages
- array([13, 70, 10, 70, 70, 10, 63, 70, 70, 69, 45, 70, 70, 56, 60, 70, 70,
- 10, 52, 70, 32, 62, 21, 70, 13, 13, 10, 50, 38, 32, 70, 20, 27, 64,
- 34, 10, 70, 70, 53, 70, 53, 54, 26, 70, 57, 70, 46, 70, 17, 48, 70,
- 15, 49, 70, 10, 70, 19, 23, 70, 70, 70, 45, 47, 70, 70, 34, 25, 70,
- 10, 70, 42, 62, 70, 10, 70, 23, 25, 49, 70, 70, 62, 70, 70, 11, 10,
- 70, 30, 44, 70, 49, 10, 35, 52, 21, 70, 70, 25, 10, 55, 59])
np.count_nonzero
使用稀疏數(shù)組是很常見的。 通常,它們是對具有高基數(shù)(High-Cardinality)或只有許多二進(jìn)制列的分類特征進(jìn)行獨熱編碼的結(jié)果。
你可以使用count_nonzero來檢查任意數(shù)組中非零元素的數(shù)量:
- a = np.random.randint(-50, 50, size=100000)
- >>> np.count_nonzero(a)
- 98993
100k隨機(jī)整數(shù)中,~1000個為零。
np.array_split
它可以用來將ndarray或dataframe分成N個bucket。此外,當(dāng)你想要將數(shù)組分割成大小不相等的塊(如vsplit)時,它不會引發(fā)錯誤:
- import datatable as dt
- df = dt.fread("data/train.csv").to_pandas()
- splitted_dfs = np.array_split(df, 100)
- >>> len(splitted_dfs)
- 100