首页 > 解决方案 > 如何加快 Pandas 应用函数在数据框中创建新列的速度?

问题描述

在我的熊猫数据框中,我有一列包含用户位置。我创建了一个函数来从位置识别国家,我想创建一个带有国家名称的新列。功能是:

from geopy.geocoders import Nominatim
geolocator = Nominatim()
import numpy as np

def do_fuzzy_search(location):
    if type(location) == float and np.isnan(location):
        return np.nan
    else:
      try:
          result = pycountry.countries.search_fuzzy(location)
      except Exception:
          try:
              loc = geolocator.geocode(str(location))
              return loc.raw['display_name'].split(', ')[-1]
          except:
              return np.nan
      else:
          return result[0].name

在传递任何位置名称时,该函数将返回国家名称。对于前-

do_fuzzy_search("Bombay")返回'India'

我只是想使用 apply 函数创建一个新列。

df['country'] = df.user_location.apply(lambda row: do_fuzzy_search(row) if (pd.notnull(row)) else row)

但它需要永远运行。我已经尝试过在 Stackoverflow 上发布的其他问题和使用相同主题编写的博客中提到的一些技术,例如Pandas 的性能 apply vs np.vectorize优化 Pandas 代码以提高速度使用 dask 或 swift加速 pandas以及使用 cudf 加速 pandas .

使用各种技术仅执行该列的前 10 行所花费的时间如下:

%%time
attractions.User_loc[:10].apply(lambda row: do_fuzzy_search(row) if (pd.notnull(row)) else row)
CPU times: user 27 ms, sys: 1.18 ms, total: 28.2 ms
Wall time: 6.59 s
0    United States of America
1                         NaN
2                   Australia
3                       India
4                         NaN
5                   Australia
6                       India
7                       India
8              United Kingdom
9                   Singapore
Name: User_loc, dtype: object

使用Swifter 库

%%time
attractions.User_loc[:10].swifter.apply(lambda row: do_fuzzy_search(row) if (pd.notnull(row)) else row)
CPU times: user 1.03 s, sys: 17.9 ms, total: 1.04 s
Wall time: 7.94 s
0    United States of America
1                         NaN
2                   Australia
3                       India
4                         NaN
5                   Australia
6                       India
7                       India
8              United Kingdom
9                   Singapore
Name: User_loc, dtype: object

使用np.vectorize

%%time
np.vectorize(do_fuzzy_search)(attractions['User_loc'][:10])
CPU times: user 34.3 ms, sys: 3.13 ms, total: 37.4 ms
Wall time: 9.05 s
array(['United States of America', 'Italia', 'Australia', 'India',
       'Italia', 'Australia', 'India', 'India', 'United Kingdom',
       'Singapore'], dtype='<U24')

此外,使用Dask 的 map_partitions并没有比 apply 函数带来太多的性能提升。

import dask.dataframe as dd
import multiprocessing

dd.from_pandas(attractions.User_loc, npartitions=4*multiprocessing.cpu_count())\
   .map_partitions(lambda df: df.apply(lambda row: do_fuzzy_search(row) if (pd.notnull(row)) else row)).compute(scheduler='processes')

每种技术的 10 行计算时间超过 5 秒。100k 行需要永远。我也尝试实现 cudf 但这会使我的 colab 笔记本崩溃。

我可以做些什么来提高性能并在合理的时间内达到结果?

标签: pythonpandasperformancenumpyapply

解决方案


在大多数情况下, an.apply()很慢,因为它在数据帧的每行调用一次一些简单的可并行化函数,但在您的情况下,您正在调用外部 API。因此,网络访问和 API 速率限制可能是决定运行时间的主要因素。不幸的是,这意味着除了等待之外,您无能为力。

如果某些元素经常重复,您可能可以通过do_fuzzy_search使用functools.lru_cache进行装饰而受益,因为如果在缓存中找到该位置,这将允许该函数避免 API 调用。


推荐阅读