Pandas进阶之高性能函数eval()和query()

一、说明

Python数据科学生态环境的强大力量在Numpy和Pandas的基础之上,并通过直观的语法将基本操作转化为c语言:在Numpy里是向量化/广播运算,在pandas里是分组型的运算。虽然这些抽象功能可以简洁高效的解决很多问题,但是他们经常需要创建临时对象,这样会占用很大的计算时间和内存。

Pandas为了解决性能问题,引入了eval()函数和query()函数,实现了直接运行C语言速度的操作,不需要费力配置中间数组,它们都依赖于Numexpr程序包。

import numpy as np
x = np.random.rand(1000000)
y = np.random.rand(1000000)

# numpy的向量化运算
%timeit x + y   # timeit模块:准确测量小段代码的执行时间

# python的列表运算
%timeit np.fromiter([x1+y1 for x1, y1 in zip(x, y)], dtype=np.float)

输出结果
1.58 ms ± 60.4 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
177 ms ± 1.75 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

对于上面的numpy向量化运算,其优点很明显:对比普通的python循环或者列表综合运行速度要快很多,但是对于下面的复合代数式问题,numpy的向量化运算效率也比较低。

mask = (x>0.5) & (x<0.5)

#上式等价于于:
tmp1 = (x>0.5)
tmp2 = (y<0.5)
mask = tmp1 & tmp2

原因是,每段中间过程都需要显式的分配内存。如果x数组和y数组很大,这么运算将会占用大量的时间和内存。Numexpr程序库可以实现不为中间过程分配全部内存的前提下,完成元素到元素的复合代数式运算。Pandas的eval函数()和query()函数就是基于Numexpr实现的。

二、pandas.eval()函数

1. 算术运算

import numpy as np
df1, df2, df3, df4, df5 = (pd.DataFrame(rng.randint(0, 1000, (100, 3))) for i in range(5))

result1 = -df1 * df2 / (df3 + df4) - df5
result2 = pd.eval('-df1 * df2 / (df3 + df4) - df5')
np.allclose(result1, result2)  # np.allclose():,比较两个array的每一元素是否相等
True

2. 比较运算

result1 = (df1 < df2) & (df2 <= df3) & (df3 != df4)
result2 = pd.eval('df1 < df2 <= df3 != df4')

np.allclose(result1, result2)
True

3. 位运算

result1 = (df1 < 0.5) & (df2 < 0.5) | (df3 < df4)
result2 = pd.eval('(df1 < 0.5) & (df2 < 0.5) | (df3 < df4)')

np.allclose(result1, result2)
True

4. 对象属性和索引

result1 = df2.T[0] + df3.iloc[1]
result2 = pd.eval('df2.T[0] + df3.iloc[1]')

np.allclose(result1, result2)
True

三、DataFrame.eval()

  • pandas.eval() 是 Pandas 的顶层函数,因此 DataFrame 也有一个 eval()方法可以做类似的运算;
  • DataFrame.eval()方法的好处:通过列名实现简洁的代数式运算。

1. 列名作为变量进行代数运算

df = pd.DataFrame(rng.rand(1000, 3), columns=['A', 'B', 'C'])
df.head()
        A           B           C
0   0.401791    0.973228    0.005811
1   0.453365    0.715901    0.635402
2   0.171049    0.175610    0.004500
3   0.254660    0.513748    0.754389
4   0.897135    0.649130    0.368049

# 三种结果相同的不同写法,第3种 最高效简洁
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)')

np.allclose(result1, result2)
True
np.allclose(result1, result3)
True

2. 新增列

df.eval('D = (A + B) / C', inplace=True)    # 新增D列

df.head()
        A           B           C           D
0   0.401791    0.973228    0.005811    236.627079
1   0.453365    0.715901    0.635402    1.840199
2   0.171049    0.175610    0.004500    77.033882
3   0.254660    0.513748    0.754389    1.018584
4   0.897135    0.649130    0.368049    4.201252

3. 局部变量

  • 通过 @ 符号可以使用 Python 的局部变量;
  • @符号表示“这是一个变量名称而不是一个列名称”。从而灵活地使用两个“命名空间”的资源计算代数式(列名称的命名空间、Python 对象的命名空间);
  • 说明:该方法不能在 pandas.eval() 函数中使用,因为 pandas.eval() 函数只能获取一个命名空间的内容。
column_mean = df.mean(1)

result1 = df['A'] + column_mean
result2 = df.eval('A + @column_mean')

np.allclose(result1, result2)
True

四、DataFrame.query()

  1. query()函数和eval()函数一样,是基于DataFrame列计算代数式。通常,过滤操作时,使用query()函数更简洁;
result1 = pd.eval('df[(df.A < 0.5) & (df.B < 0.5)]')
result2 = df.query('A < 0.5 and B < 0.5')

np.allclose(result1, result2)
True
  1. query()函数也支持局部变量,同样是通过关键符@进行识别,当存在isin()判断时,需要使用==代替isin()。特别地,因为pandas本身没有isnotin()函数,如果需要按此逻辑进行判断,仅需要把==改为!=即可。
filter_list = [2, 6, 10]    # 按列表元素筛选
df = pd.DataFrame({"A": [5, 0, 1, 2, 4, 3], "B": [2, 7, 8, 9, 6, 10]})

# isin()判断
df.query("B == @ filter_list")  
    A   B
0   5   2
4   4   6
5   3   10

# is not in 判断
df.query("B != @ filter_list")
    A   B
1   0   7
2   1   8
3   2   9

五、性能

  • 在考虑要不要用这两个函数时,需要思考两个方面:计算时间和内存消耗,而内存消耗是更重要的影响因素;
  • 每个涉及 NumPy 数组或 Pandas 的 DataFrame的复合代数式运算时,都会产生临时数组;
  • 普通方法在处理较小的数组时,反而速度更快! eval()函数和query 函数的优点主要是节省内存,语法也更加简洁。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容