让我们先设置一些示例数据:
import pandas as pd
import string
import numexpr as ne
n = 10_000_000
k = 5
df = pd.DataFrame({'col1': pd.np.random.choice(list(string.ascii_lowercase),size=n*k).view((pd.np.str_,k)),
'col2': pd.np.random.random(n)})
为了使contains 与numexpr 引擎一起工作,您必须添加.values。但是,查询仍然很慢,原因是不是.values 操作,正如您在python 引擎的使用和不使用.values 的比较中看到的那样:
%timeit df.query("col1.str.contains('a').values", engine='numexpr')
#1 loop, best of 3: 3.48 s per loop
%timeit df.query("col1.str.contains('a')", engine='python')
#1 loop, best of 3: 3.55 s per loop
%timeit df.query("col1.str.contains('a').values", engine='python')
#1 loop, best of 3: 3.52 s per loop
numexpr 的性能优势可以忽略不计,但是对于相对简单的查询(另请参阅https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#performance-of-query)。正如我们所见,.values 只增加了大约 60 毫秒:
%timeit df.query("col2.values < .5", engine='numexpr')
1 loop, best of 3: 363 ms per loop
%timeit df.query("col2 < .5", engine='numexpr')
1 loop, best of 3: 298 ms per loop
%timeit df.query("col2 < .5", engine='python')
1 loop, best of 3: 299 ms per loop
Numexpr does support contains 但它没有在 pandas 中实现。一个 - 诚然不是很优雅 - 解决方法是将值从 pandas 复制到数组中,然后使用 numexpr.evaluate 进行布尔索引:
%timeit df.query("col1.str.contains('a').values & (col2 < .5)", engine='numexpr')
#1 loop, best of 3: 3.53 s per loop
%timeit df.query("col1.str.contains('a') & (col2 < .5)", engine='python')
#1 loop, best of 3: 3.45 s per loop
%%timeit
arr1 = df.col1.to_numpy(bytes)
arr2 = df.col2.to_numpy()
df[ne.evaluate("contains(arr1, 'a') & (arr2 < .5)")]
#1 loop, best of 3: 1.18 s per loop
这比 pandas 版本快大约 3 倍,包括复制到数组和布尔索引的开销。 ne.evaluate("contains(arr1, 'a') & (arr2 < .5)") 仅需要 280 毫秒。