【问题标题】:How to efficiently replace values in a large dataframe (100k+ rows) from another based on closest match?如何根据最接近的匹配有效地替换大型数据帧(100k+ 行)中的值?
【发布时间】:2021-03-04 04:57:42
【问题描述】:

所以我使用 levenshire 距离来查找最接近的匹配项,并使用 this answer 作为基础替换大型数据框中的许多值:

import operator

def levenshteinDistance(s1, s2):
    if len(s1) > len(s2):
        s1, s2 = s2, s1

    distances = range(len(s1) + 1)
    for i2, c2 in enumerate(s2):
        distances_ = [i2+1]
        for i1, c1 in enumerate(s1):
            if c1 == c2:
                distances_.append(distances[i1])
            else:
                distances_.append(1 + min((distances[i1], distances[i1 + 1], distances_[-1])))
        distances = distances_
    return distances[-1]

def closest_match(string, matchings):
    scores = {}
    for m in matchings:
        scores[m] = 1 - levenshteinDistance(string,m)
    
    return max(scores.items(), key=operator.itemgetter(1))[0]

因此,当从另一个类似大小的数据帧(100k+ 行)中替换许多值时,需要永远运行:(从最后半小时开始运行!)

results2.products = [closest_match(string, results2.products) 
                    if string not in results2.products else string 
                    for string in results.products]

那么有没有办法更有效地做到这一点?我出于同样的目的添加了 if-else 条件,这样如果有直接匹配,就不会涉及任何也会产生相同结果的计算。

样本数据

results:

   products
0, pizza
1, ketchup
2, salami
3, anchovy
4, pepperoni
5, marinara
6, olive
7, sausage
8, cheese
9, bbq sauce
10, stuffed crust

results2:

   products
0, salaaaami
1, kechap
2, lives
3, ppprn
4, pizzas
5, marinara
6, sauce de bbq
7, marinara sauce
8, chease
9, sausages
10, crust should be stuffed

我希望将 results2 中的值替换为 results 中最接近的匹配项

【问题讨论】:

  • 你能粘贴一些示例数据和预期的输出吗?
  • @VivekKalyanarangan 已添加!

标签: python python-3.x pandas


【解决方案1】:

所以我采取了一些措施来提高速度,并获得了近 4800 倍的加速。

在此发帖以帮助任何处理 Pandas 上任何 CPU 密集型任务性能缓慢的人:

  1. 我没有像问题中那样一次性替换所有内容,而是制作了一个替换字典,用每个数据帧中的唯一值进行替换,这使得它从永远需要(我在 2 小时后停止)到 2 分钟,因为有许多许多重复的值。那是 60 倍的加速:

    replacements = {string: closest_match(string, results2.products.unique())
                  if string not in results2.products.unique() else string 
                    for string in results.products.unique()}
    results.replace({'products':replacements}, inplace = True)
    
  2. 我使用了一个基于 c 的实现来计算 levenshtein 距离,它使用了:editdistance 库。在研究中,我发现许多此类任务都具有基于 C 的实现,例如矩阵乘法和搜索算法等。很容易获得。此外,您始终可以用 C 编写模块并在 python 中使用它。 editdistance.eval('banana', 'bahama') 只使用了 1.71 µs ± 289 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each),而我定义的函数 levenshteinDistance('banana', 'bahama') 使用了 34.4 µs ± 4.2 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each),这是一个 20 倍的加速。结果closest_match() function

    import operator
    import editdistance
    def closest_match(string, matchings):
        scores = {}
        for m in matchings:
            scores[m] = 1 - editdistance.eval(string,m)
    
        return max(scores.items(), key=operator.itemgetter(1))[0]
    
  3. 然后我通过并行性一次性使用了我所有的内核。为此,我经历了各种替代方案,例如多处理和线程,但它们都没有像modin.pandas 那样快速进行比较。它的改动很小(只需一行导入modin.pands as pd 代替import pandas as pd)并且工作优雅。它使之前的运行速度提高了大约 4 倍。

因此总共有 4800 倍的加速,这是巨大的,整个事情都在瞬间运行。

更新

我获得了大量产品,因此正在寻找进一步的优化。原来矢量化是答案。现在我更新后的最接近匹配函数如下所示:

results.index = results.products
def closest_match_results_product(string):
    return results.products.apply(lambda x: editdistance.eval(string,x)).idxmin()

应用这个函数看起来像:

results2.products = results2.products.apply(closest_match_results_product)

砰!比以往更优化!如果还有进一步改进的余地,希望看到任何建议!

【讨论】:

  • 很好的答案绝对是书签
【解决方案2】:

使用已编译的 Python。

使用 Cython / CPython

使用 PyPy 又名 Stackless Python

将 Numba 用于您的两个功能,如下所示:

from numba import jit
@jit
def levenshteinDistance(s1, s2):
...

【讨论】:

  • psyco 自 2012 年以来就死了。即便如此,它也仅适用于 32 位 python,显然在 2020 年我正在运行 64 位版本:superuser.com/questions/195047/…
  • @jit 支持 (parallel = True, no_python = True) 实现以加快执行速度,但对于我正在使用的数据类型,这两个标志都不可用。没有这些标志,我让它运行,但警告显示它回退到我正在使用的字符串/其他数据类型,并且没有明显的速度优势。即使这样,我也让它运行,但在 2 小时后它没有到达任何地方。它仍在运行,我停止了它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-26
  • 2015-08-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多