作者|Louis Chan
編譯|VK
來源|Towards Data Science

Python可以說是當今最酷的編程語言(多虧了機器學習和資料科學),但與最好的編程語言之一C相比,它的效率并不是很高,
在開發機器學習模型時,很常見的情況是,我們需要根據從統計分析或上一次迭代的結果匯出的硬編碼規則,然后以編程方式更新,承認這一點并不羞恥:我一直在用Pandas apply撰寫代碼,直到有一天我對嵌套非常厭煩,于是決定研究(又稱Google)其他更可維護、更高效的方法
演示資料集
我們將要使用的資料集是iris資料集,你可以通過pandas或seaborn免費獲得它,
import pandas as pd
iris = pd.read_csv('https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv')
# import seaborn as sns
# iris = sns.load_dataset("iris")
iris資料集的前5行

資料統計資訊

假設在初始分析之后,我們希望用以下邏輯標記資料集:
-
如果萼片長度(sepal length)< 5.1,則標簽為0;
-
否則,如果萼片寬度(sepal width)> 3.3和萼片長度< 5.8,則標簽為1;
-
否則,如果萼片寬度> 3.3,花瓣長度(petal length)> 5.1,則標簽為2;
-
否則,如果萼片寬度> 3.3,花瓣長度< 1.6且萼片長度< 6.4或花瓣寬度< 1.3,則標簽3;
-
否則,如果萼片寬度>3.3且萼片長度< 6.4或花瓣寬度< 1.3,則標簽為4;
-
否則,如果萼片寬度> 3.3,則標簽為5;
-
否則標簽6
在深入研究代碼之前,讓我們快速地將一個新的label列設定為None:
iris['label'] = None
Pandas.iterrows+嵌套If Else塊
如果你還在用這個,這篇博文絕對是適合你的地方!
%%timeit
for idx, row in iris.iterrows():
if row['sepal_length'] < 5.1:
iris.loc[idx, 'label'] = 0
elif row['sepal_width'] > 3.3:
if row['sepal_length'] < 5.8:
iris.loc[idx, 'label'] = 1
elif row['petal_length'] > 5.1:
iris.loc[idx, 'label'] = 2
elif (row['sepal_length'] < 6.4) or (row['petal_width'] < 1.3):
if row['petal_length'] < 1.6:
iris.loc[idx, 'label'] = 3
else:
iris.loc[idx, 'label'] = 4
else:
iris.loc[idx, 'label'] = 5
else:
iris.loc[idx, 'label'] = 6
1min 29s ± 8.91 s per loop (mean ± std. dev. of 7 runs, 1 loop each)
時間挺長…好吧,我們繼續…
Pandas .apply
Pandas.apply直接用于沿資料幀的軸或Series來應用函式,例如,如果我們有一個函式f,它可以是一個數列的和(例如,可以是一個list, np.array, tuple等),并將其傳遞給如下資料幀,我們將跨行求和:
def f(numbers):
return sum(numbers)
df['Row Subtotal'] = df.apply(f, axis=1)
在axis=1上應用函式,默認情況下,apply引數axis=0,即逐行應用函式;而axis=1將逐列應用函式,
現在我們已經對pandas.apply有了基本的了解,現在讓我們撰寫分配標簽的邏輯代碼,看看它運行多長時間:
%%timeit
def rules(row):
if row['sepal_length'] < 5.1:
return 0
elif row['sepal_width'] > 3.3:
if row['sepal_length'] < 5.8:
return 1
elif row['petal_length'] > 5.1:
return 2
elif (row['sepal_length'] < 6.4) or (row['petal_width'] < 1.3):
if row['petal_length'] < 1.6:
return 3
return 4
return 5
return 6
iris['label'] = iris.apply(rules, 1)
1.43 s ± 115 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
15萬行只需要1.43s比之前的水平有了很大的提高,但仍然非常緩慢,
想象一下,如果你需要處理一個由數百萬個交易資料或信貸批準組成的資料集,那么每次我們要應用一組規則并將函式應用在一個列時,它將占用14秒以上,運行足夠多的列,你一個下午可能就沒了,
Pandas.loc[]
如果你熟悉SQL,那么使用.loc[]為新列賦值實際上只是一個帶有WHERE條件的UPDATE陳述句,因此,這應該比將函式應用于每個行或列要好得多,
%%timeit
iris['label'] = 6
iris.loc[iris['sepal_width'] > 3.3, 'label'] = 5
iris.loc[
(iris['sepal_width'] > 3.3) &
((iris['sepal_length'] < 6.4) | (iris['petal_width'] < 1.3)),
'label'] = 4
iris.loc[
(iris['sepal_width'] > 3.3) &
((iris['sepal_length'] < 6.4) | (iris['petal_width'] < 1.3)) &
(iris['petal_length'] < 1.6),
'label'] = 3
iris.loc[
(iris['sepal_width'] > 3.3) &
(iris['petal_length'] > 5.1),
'label'] = 2
iris.loc[
(iris['sepal_width'] > 3.3) &
(iris['sepal_length'] < 5.8),
'label'] = 1
iris.loc[
(iris['sepal_length'] < 5.1),
'label'] = 0
13.3 ms ± 837 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)
現在我們只花了前一次的十分之一的時間,這意味著當你在家作業的時候,你沒有更多的借口離開辦公桌,不過,我們目前只使用pandas內置的函式,盡管pandas為我們提供了一個非常方便的高級介面來與資料表互動,但是通過層層抽象,效率可能會降低,
Numpy.where
Numpy有一個較低級別的介面,允許與n維iterables(即向量、矩陣、張量等)進行更有效的互動,它的方法通常是基于C語言的,當涉及到更復雜的計算時,它使用了優化的演算法,使得它比我們重新發明的輪子更快,
根據numpy的官方檔案,np.where()接受以下語法:
np.where(condition, return value if True, return value if False)
本質上,這是一種二分,其中條件將被計算為布林值并相應地回傳值,這里的技巧是條件實際上可以是iterable(即布爾ndarray型別),這意味著我們可以將df['feature']==1作為條件,并將where邏輯編碼為:
np.where(
df['feature'] == 1,
'It is one',
'It is not one'
)
所以你可能會問,我們如何用一個像np.where()這樣的二分函式來實作上述邏輯呢?答案很簡單,但卻令人不安,嵌套np.where()
%%timeit
iris['label'] = np.where(
iris['sepal_length'] < 5.1,
0,
np.where(
iris['sepal_width'] > 3.3,
np.where(
iris['sepal_length'] < 5.8,
1,
np.where(
iris['petal_length'] > 5.1,
2,
np.where(
(iris['sepal_length'] < 6.4) | (iris['petal_width'] < 1.3),
np.where(
iris['petal_length'] < 1.6,
3,
4
),
5
)
)
),
6
)
)
3.6 ms ± 149 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)
恭喜你,你挺過來了,我不能告訴你我花了多少次來計算右括號,但是嘿,這就完成了!我們又從pandas身上砍下了10毫秒,loc[],然而,這個代碼片段是不可維護的,這意味著,它是不可接受的,
Numpy.select
Numpy.select,它與.where不同,它是用來實作多執行緒邏輯的函式,
np.select(condlist, choicelist, default=0)
它的語法近似于np.where,但第一個引數現在是一個條件串列,它的長度應該與選項的長度相同,使用時要記住一件事np.select是在滿足第一個條件后立即選擇一個選項,
這意味著,如果超集規則出現在串列中的子集規則之前,那么子集選擇將永遠不會被選擇,具體說來:
condlist = [
df['A'] <= 1,
df['A'] < 1
]
choicelist = ['<=1', '<1']
selection = np.select(condlist, choicelist, default='>1')
因為所有命中df['A']<1的行也將被df['A']<=1捕獲,因此沒有行最終被標記為'<1',為了避免這種情況發生,請務必在更具體的規則之前先制定一個不太具體的規則:
condlist = [
df['A'] < 1, # < ───┬ 交換
df['A'] <= 1 # < ───┘
]
choicelist = ['<1', '<=1'] # 記住也要更新這個!
selection = np.select(condlist, choicelist, default='>1')
從上面可以看到,你需要同時更新condlist和choicelsit,以確保代碼順利運行,但說真的,這一步也耗我們自己的時間,通過將其更改為字典,我們將達到大致相同的時間和記憶體復雜性,但使用更易于維護的代碼片段:
%%timeit
rules = {
0: (iris['sepal_length'] < 5.1),
1: (iris['sepal_width'] > 3.3) & (iris['sepal_length'] < 5.8),
2: (iris['sepal_width'] > 3.3) & (iris['petal_length'] > 5.1),
3: (
(iris['sepal_width'] > 3.3) & \
((iris['sepal_length'] < 6.4) | (iris['petal_width'] < 1.3)) & \
(iris['petal_length'] < 1.6)
),
4: (
(iris['sepal_width'] > 3.3) & \
((iris['sepal_length'] < 6.4) | (iris['petal_width'] < 1.3))
),
5: (iris['sepal_width'] > 3.3),
}
iris['label'] = np.select(rules.values(), rules.keys(), default=6)
6.29 ms ± 475 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)
大約是np.where的一半,但這不僅使你免于對各種嵌套的除錯,而且使choicelist發生了變化,之前我已經忘記更新choicelist太多次了,以至于我花了四倍多的時間來除錯我的機器學習模型,相信我,np.select和dict,這是非常好的選擇
優秀函式
-
Numpy的向量化操作:如果你的代碼涉及回圈和計算一元函式、二進制函式或對數字序列進行操作的函式,你應該通過將資料轉換為numpy-ndarray來重構代碼,并充分利用numpy的向量化操作來極大地提高腳本的速度,在Numpy的官方檔案中查看一元函式、二元函式或對數字序列進行操作的函式的示例:https://www.pythonlikeyoumeanit.com/Module3_IntroducingNumpy/VectorizedOperations.html#NumPy’s-Mathematical-Functions
-
np.vectorize:不要被這個函式的名字愚弄,這只是一個方便的函式,并不會使代碼運行得更快,要使用此函式,首先需要將邏輯編碼為可呼叫函式,然后運行np.vectorize(你的函式)(你的資料系列),另一個大的缺點是需要將資料幀轉換為一維的iterable,以便傳遞到“矢量化”函式中,結論:如果不方便使用np.vectorize,別使用,
-
numba.njit:現在這是真正的向量化,它試圖將任何numpy值移動到盡可能接近C語言,以提高其效率,雖然它可以加速數值計算,但它也將自己限制為數值計算,這意味著沒有pandas系列,沒有字串索引,只有具有int、float、datetime、bool和category型別的numpy的ndarray,結論:如果你能夠輕松地使用Numpy的ndarray并將邏輯轉換為數值計算或僅轉換為數值計算,那么它將是一個非常優秀的選擇,從這里了解更多:https://numba.pydata.org/numba-doc/dev/user/5minguide.html
結尾
如果可能的話,去爭取numba.njit;否則,使用np.select和dict就可以幫助你遠航了,記住,每一點改進都會有幫助!
原文鏈接:https://towardsdatascience.com/efficient-implementation-of-conditional-logic-on-pandas-dataframes-4afa61eb7fce
歡迎關注磐創AI博客站:
http://panchuang.net/
sklearn機器學習中文官方檔案:
http://sklearn123.com/
歡迎關注磐創博客資源匯總站:
http://docs.panchuang.net/
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/211577.html
標籤:其他
上一篇:Ubuntu 20.04安裝CUDA 11.0、cuDNN 8.0.5、PyTorch 1.7.0
下一篇:使用NLP創建摘要
