select 行中的性能,其中条件是与集合的匹配百分比

Performance in select rows where condition is a percentage of match with a set

鉴于此示例:

df = pd.DataFrame({'col1':['id1','id2','id3'],
                  'col2':['name1','foobar','name3'],
                  'col3':[{'am', 'e1', 'me', 'na'},{'ar', 'ba', 'fo', 'ob', 'oo'},{'am', 'e3', 'me', 'na'}]})

    col1    col2    col3
0   id1     name1   {na, e1, me, am}
1   id2     foobar  {ar, fo, ba, oo, ob}
2   id3     name3   {na, e3, me, am}

目标是用满足两个集合交集的匹配阈值的所有行对 df 进行子集化。

我的解决方案:

def subset_by_intersection_threshold(set_1, set_2, threshold):
    intersection = len(list(set_1.intersection(set_2)))
    union = (len(set_1) + len(set_2)) - intersection
    return float(intersection / union)>threshold

使用 jaccard 函数和 pandas apply 按阈值过滤所有匹配条件的行(本例中为匹配的 0.4)。

set_words=set(['na','me'])

df[df.col3.apply(lambda x: subset_by_intersection_threshold(set(x), set_words,0.4))]

因为我觉得这个解决方案有点蛮力模式,所以我打开这个问题是为了学习考虑执行时间的更有效的替代方案。

添加在我的个人笔记本电脑上执行的基准测试分数:

从慢到快:

%timeit df.col3.apply(lambda x: original(set(x), set_words, 0.4))  # 74 ms per loop
%timeit df.col3.apply(lambda x: jpp(x, set_words, 0.4))            # 32.3 ms per loop
%timeit list(map(lambda x: jpp(x, set_words, 0.4), df['col3']))    # 13.9 ms
%timeit [jpp(x, set_words, 0.4) for x in df['col3']]               # 12.2 ms

通过避免不必要的 list 创建和 float / set 转换,您可以将性能提高约 2 倍。为了获得额外的提升,通过使用列表理解构建的布尔值列表进行索引。通常情况下,pd.Series.apply 可能不如列表理解中的常规循环。

def original(set_1, set_2, threshold):
    intersection = len(list(set_1.intersection(set_2)))
    union = (len(set_1) + len(set_2)) - intersection
    return float(intersection / union)>threshold

def jpp(set_1, set_2, threshold):
    intersection = len(set_1 & set_2)
    union = (len(set_1) + len(set_2)) - intersection
    return (intersection / union) > threshold

set_words = {'na', 'me'}

df = pd.concat([df]*10000)

%timeit df.col3.apply(lambda x: original(set(x), set_words, 0.4))  # 74 ms per loop
%timeit df.col3.apply(lambda x: jpp(x, set_words, 0.4))            # 32.3 ms per loop
%timeit [jpp(x, set_words, 0.4) for x in df['col3']]               # 23.4 ms per loop

根据问题的结构以及是否要 运行 多次测试,您可以将数据重塑为布尔值,然后进行稍微向量化的 jaccard 计算:

# Create bool table
    na     me     am     e1     ar     fo     ob     oo     ba     e3
0   True   True   True   True  False  False  False  False  False  False
1  False  False  False  False   True   True   True   True   True  False
2   True   True   True  False  False  False  False  False  False   True

可能不可行(如果有太多不同的值)或太慢(设置需要很长时间),但这是它的代码:

df_bool = df.col3.apply(lambda x: pd.Series({s: True for s in x})).fillna(False)

# set_words as bool
sw = df_bool.columns.to_series().apply(lambda x: x in set_words).values

# intersection / union > 0.5
res = (df_bool & sw).sum(axis=1) / (df_bool | sw).sum(axis=1) > 0.4

# setup code (run once)
%timeit df.col3.apply(lambda x: pd.Series({s: True for s in x})).fillna(False)  # 

%timeit [jpp(x, set_words, 0.4) for x in df['col3']]                    # 14.4 ms per loop
%timeit (df_bool & sw).sum(axis=1) / (df_bool | sw).sum(axis=1) > 0.4   # 10.6 ms per loop