Pandas的eval()與query()函數
0.13版本之後,Pandas增加了一些工具,能夠在沒有任何中間內存開銷的情況下直接獲得C語言級別的運算速度。這就是我們接下來要介紹的兩個函數eval()和query()
先來看一個例子:
import numpy as np
x = rng.rand(1E6)
y = rng.rand(1E6)
?
mask = (x > 0.5) & (y < 0.5)
Numpy會逐個表達式進行運算,因此上面的代碼相當於(頂多中間變數沒有暴露給用戶):
tmp1 = (x > 0.5)
tmp2 = (y < 0.5)
mask = tmp1 & tmp2
每一步的中間計算,都需要分配(allocate)額外的內存,當數據量很大時就會佔有非常大的內存。
Numexpr庫
Numexpr
庫,通過在CPU的cache中逐元素的計算,實現無中間內存開銷。只需要傳入一個 Numpy-Style的表達式字元串
import numexpr
?
mask_numexpr = numexpr.evaluate((x > 0.5) & (y < 0.5))
Pandas的eval()與query()
Pandas的eval()和query()與其很類似,其實他們底層都調用了Numexpr包
為了更好的演示,我們先隨機生成4個DataFrame數據
import pandas as pd
?
nrows, ncols = 100000, 100
rng = np.random.RandomState(42)
df1, df2, df3, df4 = (pd.DataFrame(rng.rand(nrows, ncols)) for i in range(4))
eval()
符號說明:後續的代碼中我們用
result1代表傳統的操作方法及其結果
result2代表eval/query操作方式及其結果
如果有result3,表示另一種寫法 也屬於eval/query
先演示一個最基本的,我們將四個df對應位置元素相加
result1 = df1 + df2 + df3 + df4 # 標準做法:87.1 ms
result2 = pd.eval(df1 + df2 + df3 + df4) # 42.2 ms
下面我們一一舉例,有哪些操作可以使用eval:
算術運算:
result1 = -df1 * df2 / (df3 + df4) - df5
result2 = pd.eval(-df1 * df2 / (df3 + df4) - df5)
比較運算:
result1 = (df1 < df2) & (df2 <= df3) & (df3 != df4)
result2 = pd.eval(df1 < df2 <= df3 != df4)
Bitwise運算:
result1 = (df1 < 0.5) & (df2 < 0.5) | (df3 < df4)
result2 = pd.eval((df1 < 0.5) & (df2 < 0.5) | (df3 < df4))
result3 = pd.eval((df1 < 0.5) and (df2 < 0.5) or (df3 < df4))
對象屬性和索引:
還可以對DataFrame的某一列進行操作
result1 = df2.T[0] + df3.iloc[1]
result2 = pd.eval(df2.T[0] + df3.iloc[1])
Column-Wise操作
除了有top-level的np.eval(),DataFramse有eval()方法。這點與Pandas的整體範式保持一致
result1 = (df[A] + df[B]) / (df[C] - 1)
result2 = pd.eval("(df.A + df.B) / (df.C - 1)")
result3 = df.eval((A + B) / (C - 1))
通過eval賦值與修改
我們可以通過eval()新建一列數據D
df.eval(D = (A + B) / C, inplace=True)
之後還可以修改D列:
df.eval(D = (A - B) / C, inplace=True)
在eval()中引用 local變數
使用@
符號後面跟變數名,就可以引用該變數進行計算
column_mean = df.mean(1)
result1 = df[A] + column_mean
result2 = df.eval(A + @column_mean)
Query
當我們要做的不是修改數據而是篩選數據時,使用query()函數
result1 = df[(df.A < 0.5) & (df.B < 0.5)]
result2 = df.query(A < 0.5 and B < 0.5)
通用,query也可以引用local變數:
Cmean = df[C].mean()
result1 = df[(df.A < Cmean) & (df.B < Cmean)]
result2 = df.query(A < @Cmean and B < @Cmean)
性能:什麼時候考慮使用eval()和query()?
當我們選擇eval()和query()時一般出於兩點考慮:
- 計算時間
- 內存消耗
內存消耗
內存消耗是非常可預測的,使用以下語句估計數據量的大小(單位為位元組)
df.values.nbytes
計算時間
從運算速度角度,取決於臨時變數的大小有沒有超出CPU的L1或L2 Cache,如果超出了,會有一些緩存之間交換數據的開銷。
結論
實際使用中,傳統方法和eval/query相差不是很明顯,傳統方法在較小的array上更快一些
使用eval/query的好處,主要是節省內存以及有時他們具有更簡潔的語法形式
參考閱讀:
Numexpr documentation
Enhancing Performance
推薦閱讀: