为什么Pandas理解列表比应用快

flseospp  于 2023-01-07  发布在  其他
关注(0)|答案(1)|浏览(134)

使用列表解析比普通的for循环要快得多。这里给出的原因是列表解析中不需要append,这是可以理解的。但是我在很多地方发现列表比较比apply快。我也经历过这种情况。但是我不明白是什么内部工作使得它比apply快得多?
我知道这与numpy中的向量化有关,numpy是panda Dataframe 的基本实现,但是为什么列表解析比apply更好,这不是很容易理解的,因为在列表解析中,我们在列表中给予for循环,而在apply中,我们甚至没有给出任何for循环(我假设向量化也在那里发生)
编辑:添加代码:这是在titanic数据集上工作,其中标题从名称中提取:https://www.kaggle.com/c/titanic/data

%timeit train['NameTitle'] = train['Name'].apply(lambda x: 'Mrs.' if 'Mrs' in x else \
                                         ('Mr' if 'Mr' in x else ('Miss' if 'Miss' in x else\
                                                ('Master' if 'Master' in x else 'None'))))

%timeit train['NameTitle'] = ['Mrs.' if 'Mrs' in x else 'Mr' if 'Mr' in x else ('Miss' if 'Miss' in x else ('Master' if 'Master' in x else 'None')) for x in train['Name']]

结果:782 µs ± 6.36 µs/循环(7次运行的平均值±标准差,每次运行1000个循环)
499 µs ± 5.76 µs/循环(7次运行的平均值±标准差,每次运行1000个循环)
Edit 2:要为SO添加代码,创建了一个简单的代码,令人惊讶的是,对于下面的代码,结果相反:

import pandas as pd
import timeit
df_test = pd.DataFrame()
tlist = []
tlist2 = []
for i in range (0,5000000):
  tlist.append(i)
  tlist2.append(i+5)
df_test['A'] = tlist
df_test['B'] = tlist2

display(df_test.head(5))

%timeit df_test['C'] = df_test['B'].apply(lambda x: x*2 if x%5==0 else x)
display(df_test.head(5))
%timeit df_test['C'] = [ x*2 if x%5==0 else x for x in df_test['B']]

display(df_test.head(5))

1圈,3局两胜:2.14 s/循环
1圈,3局两胜:2.24 s/循环
Edit 3:正如一些人所建议的那样,apply本质上是一个for循环,这与我用for循环运行这段代码的情况不同,它几乎永远不会结束,我不得不在3-4分钟后手动停止它,它在这段时间内从未完成。

for row in df_test.itertuples():
  x = row.B
  if x%5==0:
    df_test.at[row.Index,'B'] = x*2

运行上面的代码大约需要23秒,而apply只需要1.8秒,那么,迭代元组中的这些物理循环和apply有什么区别呢?

x6yk4ghg

x6yk4ghg1#

apply和列表解析之间的性能差异有几个原因。
首先,代码中的列表解析不会在每次迭代时都调用函数,而apply会,这是一个巨大的差异:

map_function = lambda x: 'Mrs.' if 'Mrs' in x else \
                 ('Mr' if 'Mr' in x else ('Miss' if 'Miss' in x else \
                 ('Master' if 'Master' in x else 'None')))

%timeit train['NameTitle'] = [map_function(x) for x in train['Name']]
# 581 µs ± 21.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit train['NameTitle'] = ['Mrs.' if 'Mrs' in x else \
                 ('Mr' if 'Mr' in x else ('Miss' if 'Miss' in x else \
                 ('Master' if 'Master' in x else 'None'))) for x in train['Name']]
# 482 µs ± 14.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

其次,apply的作用远不止列表解析,例如,它试图为结果找到合适的数据类型,通过禁用该行为,你可以看到它的影响:

%timeit train['NameTitle'] = train['Name'].apply(map_function)
# 660 µs ± 2.57 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit train['NameTitle'] = train['Name'].apply(map_function, convert_dtype=False)
# 626 µs ± 4.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

apply中还发生了许多其他的事情,所以在本例中,您可能希望使用map

%timeit train['NameTitle'] = train['Name'].map(map_function)
# 545 µs ± 4.02 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

它比包含函数调用的列表解析性能更好。
那么为什么要使用apply呢?我至少知道一个例子,它的性能优于其他任何东西--当你想要应用的操作是一个矢量化的universal function时。这是因为apply不像map,列表解析允许函数在整个Series上运行,而不是在其中的单个对象上运行。让我们来看一个例子:

%timeit train['AgeExp'] = train['Age'].apply(lambda x: np.exp(x))
# 1.44 ms ± 41.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit train['AgeExp'] = train['Age'].apply(np.exp)
# 256 µs ± 12.3 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit train['AgeExp'] = train['Age'].map(np.exp)
# 1.01 ms ± 8.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit train['AgeExp'] = [np.exp(x) for x in train['Age']]
# 1.21 ms ± 28.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

相关问题