1

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

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))

提前致谢。

4

3 回答 3

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
于 2021-11-02T13:49:49.073 回答
0

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

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).

于 2021-11-02T13:33:48.160 回答
0

以下是我的一些发现。分享它们,以便帮助其他人。以下是我能够实现的优化。我尝试扩展@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提供了加速(对于较少的行,我认为生成过程和组合结果的开销远大于计算本身。

这也是一很好的读物。

于 2021-11-10T16:36:20.123 回答