【问题标题】:Improving pandas apply function performance提高 pandas 应用函数性能
【发布时间】:2021-11-02 13:19:24
【问题描述】:

我有一个 pandas 数据框,其列包含字典。我还有一个查询字典,我想计算公共键值的最小总和。
例如

dicta = {'a': 5, 'b': 21, 'c': 34, 'd': 56, 'r': 67}
dictb = {'a': 1, 'b': 1, 't': 34, 'g': 56, 'h': 67}
common keys = 'a', 'b'
s1 = dicta['a'] + dicta['b']
s2 = dictb['a'] + dictb['b']
result = min(s1, s2) = 2

我正在使用下面的代码来计算它。

def compute_common(dict1, dict2):

    common_keys = dict1.keys() & dict2.keys()
    im_count1 = sum((dict1[k] for k in common_keys))
    im_count2 = sum((dict2[k] for k in common_keys))
    return int(min(im_count1, im_count2))

以下是我的 i7 8 核机器上的操作时间,带有 8GB 内存。

%timeit df['a'].apply(lambda x:compute_common(dictb, x))
55.2 ms ± 702 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

我还发现,我可以使用 swifter 来提高 pandas apply 的性能(通过内部使用多处理)

%timeit df['a'].swifter.progress_bar(False).apply(lambda x:compute_common(dictb, x))
66.4 ms ± 1.73 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

使用 swifter 甚至更慢(可能是因为多处理的开销)。我想知道是否有任何方法可以从这个操作中挤出更多的性能。

您可以使用以下内容进行复制。

dicta = {'a': 5, 'b': 21, 'c': 34, 'd': 56, 'r': 67}
dictb = {'a': 1, 'b': 1, 't': 34, 'g': 56, 'h': 67}
df = pd.DataFrame({'a': [dicta] * 30000})

%timeit df['a'].apply(lambda x:compute_common(dictb, x))
%timeit df['a'].swifter.progress_bar(False).apply(lambda x:compute_common(dictb, x))

提前致谢。

【问题讨论】:

    标签: python pandas swifter


    【解决方案1】:

    使用列表推导来查找公共键的值,然后对列表结果求和,找到两个字典求和的公共键值之间的最小值。 common_keys 被附加到创建 ['a','b'] 的列表中。列表推导然后找到 a 和 b 的值并将它们相加等于 26 和 2。26 和 2 的最小值为 2。

    def find_common_keys(dicta, dictb):
         '''
         >>> find_common_keys({'a': 5, 'b': 21, 'c': 34, 'd': 56, 'r': 67}, {'a': 1, 
         'b': 1, 't': 34, 'g': 56, 'h': 67})
          2
          '''
        common_keys = [key  for key in dicta if key in dictb]
    
        s1 = sum(dicta[key] for key in common_keys)
        s2 = sum(dictb[key] for key in common_keys)
        return min(s1, s2)
    
    dicta = {'a': 5, 'b': 21, 'c': 34, 'd': 56, 'r': 67}
    dictb = {'a': 1, 'b': 1, 't': 34, 'g': 56, 'h': 67}
    
    print(find_common_keys(dicta,dictb))
    

    输出

    2
    

    【讨论】:

    • 感谢@Golden Lion,但我不明白它在应用于 pandas 数据帧时如何提高性能。
    • 运行 timeit 并将您的代码与此代码进行比较。 lambda 函数不一定更快。 lambda 适合编码的禅宗模式,但必须始终衡量性能。
    • 你是对的。您的函数将运行时间缩短了 10 毫秒。点赞。
    【解决方案2】:

    您可以将字典分解为数据框并将它们相加

    dict_data = pd.DataFrame(df['a'].tolist())
    
    common_keys = dict_data.columns.intersection(dictb.keys())
    
    dictb_sum = sum(dictb[k] for k in common_keys)
    
    dicta_sum = dict_data[common_keys].sum(1)
    
    # also     
    output = dicta_sum.clip(upper=dictb_sum)
    

    这比我系统上的apply 快两倍。请注意,如果 union(x.keys() for x in df['a']) 不太大,这将有效,因为 dict_data 的所有列都足够大,因此您可以使用矢量化的 .sum(1)

    【讨论】:

    • 感谢您的回答,但每本词典最多有 30 个键。
    【解决方案3】:

    以下是我的一些发现。分享它们,以便帮助其他人。 以下是我能够实现的优化。我尝试扩展@Golden Lions 的想法。

    1. 只需使用 cython 编译函数,性能提升 10%。
    2. 由于 python 是松散类型的,使用类型编写 cython 函数可以进一步提高性能。
    3. 此外,由于 python 中的函数调用成本很高,将 min(x1, x2) 转换为 x1 if x1 < x2 else x2 会带来性能优势。

    我使用的最后一个函数使我的性能提高了 3 倍。

    cpdef int cython_common(dict_1, dict_2):
        cdef dict dict1 = dict_1[0]
        cdef dict dict2 = dict_2[0]
        cdef list common_keys = [key  for key in dict1 if key in dict2]
        cdef int sum1 = 0
        cdef int sum2 = 0
        for i in common_keys:
            sum1 += dict1[i]
            sum2 +=dict2[i]
        return sum1 if sum1 < sum2 else sum2
    
    

    另外,通过一些实验,我发现库喜欢 当数据集有大量行时,pandarallelswifter 提供了加速(对于较少的行,我认为生成过程和组合结果的开销远大于计算本身。

    this 也是一本好书。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-11-23
      • 2017-04-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多