Pandas:如何更快地应用数据框?

Posted

技术标签:

【中文标题】Pandas:如何更快地应用数据框?【英文标题】:Pandas: How to make apply on dataframe faster? 【发布时间】:2017-05-26 01:49:41 【问题描述】:

考虑这个 pandas 示例,其中我通过将 ABfloat 相乘来计算列 C,如果使用 applylambda 函数满足某个条件:

import pandas as pd
df = pd.DataFrame('A':[1,2,3,4,5,6,7,8,9],'B':[9,8,7,6,5,4,3,2,1])

df['C'] = df.apply(lambda x: x.A if x.B > 5 else 0.1*x.A*x.B, axis=1)

预期的结果是:

   A  B    C
0  1  9  1.0
1  2  8  2.0
2  3  7  3.0
3  4  6  4.0
4  5  5  2.5
5  6  4  2.4
6  7  3  2.1
7  8  2  1.6
8  9  1  0.9

问题是这段代码很慢,我需要对大约 5600 万行的数据帧执行此操作。

上述lambda操作的%timeit-结果为:

1000 loops, best of 3: 1.63 ms per loop

从计算时间以及在我的大型数据帧上执行此操作时的内存使用情况来看,我假设此操作在进行计算时使用中间序列。

我尝试以不同的方式制定它,包括使用临时列,但我想出的每个替代解决方案都更慢。

有没有办法以不同且更快的方式获得我需要的结果,例如通过使用numpy?

【问题讨论】:

你应该看看numpy.where 【参考方案1】:

为了提高性能,您最好使用 NumPy 数组并使用 np.where -

a = df.values # Assuming you have two columns A and B
df['C'] = np.where(a[:,1]>5,a[:,0],0.1*a[:,0]*a[:,1])

运行时测试

def numpy_based(df):
    a = df.values # Assuming you have two columns A and B
    df['C'] = np.where(a[:,1]>5,a[:,0],0.1*a[:,0]*a[:,1])

时间安排 -

In [271]: df = pd.DataFrame(np.random.randint(0,9,(10000,2)),columns=[['A','B']])

In [272]: %timeit numpy_based(df)
1000 loops, best of 3: 380 µs per loop

In [273]: df = pd.DataFrame(np.random.randint(0,9,(10000,2)),columns=[['A','B']])

In [274]: %timeit df['C'] = df.A.where(df.B.gt(5), df[['A', 'B']].prod(1).mul(.1))
100 loops, best of 3: 3.39 ms per loop

In [275]: df = pd.DataFrame(np.random.randint(0,9,(10000,2)),columns=[['A','B']])

In [276]: %timeit df['C'] = np.where(df['B'] > 5, df['A'], 0.1 * df['A'] * df['B'])
1000 loops, best of 3: 1.12 ms per loop

In [277]: df = pd.DataFrame(np.random.randint(0,9,(10000,2)),columns=[['A','B']])

In [278]: %timeit df['C'] = np.where(df.B > 5, df.A, df.A.mul(df.B).mul(.1))
1000 loops, best of 3: 1.19 ms per loop

仔细观察

让我们仔细看看 NumPy 的数字运算能力,并与 pandas 进行比较 -

# Extract out as array (its a view, so not really expensive
#   .. as compared to the later computations themselves)

In [291]: a = df.values 

In [296]: %timeit df.values
10000 loops, best of 3: 107 µs per loop

案例#1:使用 NumPy 数组并使用 numpy.where :

In [292]: %timeit np.where(a[:,1]>5,a[:,0],0.1*a[:,0]*a[:,1])
10000 loops, best of 3: 86.5 µs per loop

再次,分配到一个新列:df['C'] 也不会很昂贵 -

In [300]: %timeit df['C'] = np.where(a[:,1]>5,a[:,0],0.1*a[:,0]*a[:,1])
1000 loops, best of 3: 323 µs per loop

案例 #2:使用 pandas 数据框并使用其 .where 方法(无 NumPy)

In [293]: %timeit df.A.where(df.B.gt(5), df[['A', 'B']].prod(1).mul(.1))
100 loops, best of 3: 3.4 ms per loop

案例 #3:使用 pandas 数据帧(无 NumPy 数组),但使用 numpy.where -

In [294]: %timeit np.where(df['B'] > 5, df['A'], 0.1 * df['A'] * df['B'])
1000 loops, best of 3: 764 µs per loop

案例 #4:再次使用 pandas 数据帧(没有 NumPy 数组),但使用 numpy.where -

In [295]: %timeit np.where(df.B > 5, df.A, df.A.mul(df.B).mul(.1))
1000 loops, best of 3: 830 µs per loop

【讨论】:

你打败了我,但numpy.where 与熊猫系列配合得很好,我相信我的版本更具可读性;) @IanS OP 要求更快。这会将所有内容都带入 numpy,从而提高效率。 @IanS NumPy 在数字运算和数据帧方面表现出色,至少我知道! ;) 我从这个解决方案中得到了1000 loops, best of 3: 255 µs per loop,非常感谢。 我简直不敢相信,我的 5600 万行数据帧上的操作花了大约 1 秒。【参考方案2】:

使用numpy.where:

df['C'] = numpy.where(df['B'] > 5, df['A'], 0.1 * df['A'] * df['B'])

【讨论】:

【参考方案3】:

用途:

df['C'] = np.where(df.B > 5, df.A, df.A.mul(df.B).mul(.1))
print (df)
   A  B    C
0  1  9  1.0
1  2  8  2.0
2  3  7  3.0
3  4  6  4.0
4  5  5  2.5
5  6  4  2.4
6  7  3  2.1
7  8  2  1.6
8  9  1  0.9

【讨论】:

mul* 差别不大,是吗? ;) 我做了一些研究,似乎如果使用df.A*df.Bdf.A.mul(df.B) 然后mul 更快。但如果乘以常数,则相同。 @IanS 也可以,方便上链 嗯,这里更快*,可能原因是np.where 使用numpy 数组。 这也是对另一次 jezrael 击败我的答案的引用,我们之间唯一的区别是我使用了/,而他使用了div,他指出了这一点差别不大。【参考方案4】:

pandas 使用pd.Series.where

df['C'] = df.A.where(df.B.gt(5), df[['A', 'B']].prod(1).mul(.1))

   A  B    C
0  1  9  1.0
1  2  8  2.0
2  3  7  3.0
3  4  6  4.0
4  5  5  2.5
5  6  4  2.4
6  7  3  2.1
7  8  2  1.6
8  9  1  0.9

【讨论】:

不错,prod 和 mul 一样快? 也许……勉强 @IanS 因为它是纯熊猫....慢。这就是为什么我们都为 numpy 比赛。但 Divakar 也击败了我们。我提供了这个答案,因为它不同。从熊猫的角度来看,几乎没有人使用where。这很有趣,因为它在条件为True 时假定现有值,或者它采用替代方案。【参考方案5】:

Pandas 是一款出色的数据处理工具,但默认运行在单个 CPU 内核上。此外,Pandas 旨在一次性在整个列或数据集上运行矢量化 API 函数,但 apply 运行自定义用户代码。其他答案避免将apply 与自定义代码一起使用,但这通常可能/不切实际。如果使用apply 处理大型数据集是您的痛点,您应该考虑使用加速和扩展解决方案,例如 Bodo。 Bodo 直接编译你的apply 代码以优化它,这是 Pandas 无法做到的。除了矢量化代码之外,Bodo 还提供自动并行化。您可以使用 Bodo 社区版(免费使用)运行最多 4 个内核的代码。这里是Bodo安装说明的链接:https://docs.bodo.ai/latest/source/installation_and_setup/install.html

我生成了一个与您类似的数据集,但有 2000 万行,并在一个核心上使用常规 Pandas 和在 4 个核心上使用 Bodo 运行代码。使用普通 Pandas,运行代码大约需要 6.5 分钟,而使用 Bodo 的社区版大约需要半秒。

#data generation
import numpy as np
import pandas as pd

df = pd.DataFrame(np.random.randint(1,10,size=(20000000, 2)), columns=list('AB'))
df.to_parquet("data.pq")

普通熊猫:

import pandas as pd
import time

start = time.time()

df = pd.read_parquet("data.pq")
df['C'] = df.apply(lambda x: x.A if x.B > 5 else 0.1*x.A*x.B, axis=1)

end = time.time()
print("computation time: ", end - start)

print(df.head())

output:
computation time:  378.3832001686096
   A  B    C
0  3  5  1.5
1  8  6  8.0
2  1  7  1.0
3  8  1  0.8
4  4  8  4.0

与博多:

%%px

import pandas as pd
import time
import bodo

@bodo.jit(distributed = ['df'])
def apply():
    start = time.time()
    df = pd.read_parquet("data.pq")
    df['C'] = df.apply(lambda x: x.A if x.B > 5 else 0.1*x.A*x.B, axis=1)
    end = time.time()
    print("computation time: ", end - start)
    print(df.head())
    return df
df = apply()

output:
[stdout:0] 
computation time:  0.3610380489999443
   A  B    C
0  3  5  1.5
1  8  6  8.0
2  1  7  1.0
3  8  1  0.8
4  4  8  4.0

免责声明:我在 Bodo.ai 担任数据科学家倡导者。

【讨论】:

以上是关于Pandas:如何更快地应用数据框?的主要内容,如果未能解决你的问题,请参考以下文章

如何基于多个条件更快地合并 2 个 pandas 数据帧

Pandas 更快地将 pd.pct_change 应用于多个列和多个间隔

在某些条件下更快地复制 pandas 数据

更快地在新列 pandas 中添加未来间隔

更快地计算pandas中列表列中值的总出现次数?

更快的 For 循环在 Pandas 中处理数据