【问题标题】:How to speed up multiple str.contains searches for millions of rows?如何加速多个 str.contains 搜索数百万行?
【发布时间】:2020-05-17 19:43:30
【问题描述】:

我有一个正在尝试标准化的商店名称数据框。小样本在这里测试:

import pandas as pd

df = pd.DataFrame({'store': pd.Series(['McDonalds', 'Lidls', 'Lidl New York 123', 'KFC', 'Lidi Berlin', 'Wallmart LA 90210', 'Aldi', 'London Lidl', 'Aldi627', 'mcdonaldsabc123', 'Mcdonald_s', 'McDonalds12345', 'McDonalds5555', 'McDonalds888', 'Aldi123', 'KFC-786', 'KFC-908', 'McDonalds511', 'GerALDInes Shop'],dtype='object',index=pd.RangeIndex(start=0, stop=19, step=1)), 'standard': pd.Series([pd.np.nan, pd.np.nan, pd.np.nan, pd.np.nan, pd.np.nan, pd.np.nan, pd.np.nan, pd.np.nan, pd.np.nan, pd.np.nan, pd.np.nan, pd.np.nan, pd.np.nan, pd.np.nan, pd.np.nan, pd.np.nan, pd.np.nan, pd.np.nan, pd.np.nan],dtype='float64',index=pd.RangeIndex(start=0, stop=19, step=1))}, index=pd.RangeIndex(start=0, stop=19, step=1))

                store  standard
0           McDonalds       NaN
1               Lidls       NaN
2   Lidl New York 123       NaN
3                 KFC       NaN
4         Lidi Berlin       NaN
5   Wallmart LA 90210       NaN
6                Aldi       NaN
7         London Lidl       NaN
8             Aldi627       NaN
9     mcdonaldsabc123       NaN
10         Mcdonald_s       NaN
11     McDonalds12345       NaN
12      McDonalds5555       NaN
13       McDonalds888       NaN
14            Aldi123       NaN
15            KFC-786       NaN
16            KFC-908       NaN
17       McDonalds511       NaN
18    GerALDInes Shop       NaN

我设置了一个正则表达式字典来搜索字符串,并将商店名称的标准化版本插入standard 列。这适用于这个小数据框:

# set up the dictionary
regex_dict = {
 "McDonalds": r'(mcdonalds|mcdonald_s)',
 "Lidl" : r'(lidl|lidi)',
 "Wallmart":r'wallmart',
 "KFC": r'KFC',
 "Aldi":r'(\baldi\b|\baldi\d+)'
}

# loop through dictionary, using str.replace 
for regname, regex_formula in regex_dict.items(): 

    df.loc[df['store'].str.contains(regex_formula,na=False,flags=re.I), 'standard'] = regname

print(df)

                store   standard
0           McDonalds  McDonalds
1               Lidls       Lidl
2   Lidl New York 123       Lidl
3                 KFC        KFC
4         Lidi Berlin       Lidl
5   Wallmart LA 90210   Wallmart
6                Aldi       Aldi
7         London Lidl       Lidl
8             Aldi627       Aldi
9     mcdonaldsabc123  McDonalds
10         Mcdonald_s  McDonalds
11     McDonalds12345  McDonalds
12      McDonalds5555  McDonalds
13       McDonalds888  McDonalds
14            Aldi123       Aldi
15            KFC-786        KFC
16            KFC-908        KFC
17       McDonalds511  McDonalds
18    GerALDInes Shop        NaN

问题是我有大约六百万行要标准化,正则表达式字典比这里显示的字典大得多。 (许多不同的商店名称,有一些拼写错误等)

我想做的是在每个循环中,仅对标准化的行使用str.contains,而忽略已标准化的行。这个想法是减少每个循环的搜索空间,从而减少整体处理时间。

我已经测试了standard 列的索引,只在standardNan 的行上执行str.contains,但它不会导致任何真正的加速。在应用str.contains 之前,仍然需要时间来确定哪些行是Nan

这是我试图减少每个循环的处理时间的方法:

for regname, regex_formula in regex_dict.items(): 

    # only apply str.contains to rows where standard == NAN
    df.loc[df['standard'].isnull() & df['store'].str.contains(regex_formula,na=False,flags=re.I), 'standard'] = regname

这行得通.. 但是在我的全部 600 万行上使用它并没有真正的速度差异。

甚至有可能在 600 万行的数据帧上加快速度吗?

【问题讨论】:

  • .str 访问器非常循环。你最好使用列表理解。
  • 我认为str.contains 是一种快速且矢量化的字符串搜索方法?不过,我绝对不是 pandas 或 python 专家。你有一个列表理解版本的例子吗?

标签: python regex pandas


【解决方案1】:

使用它,我设法将所需时间减少了 40%。我能做的最好的

我创建了一个名为 fixed_df 的空数据框来追加新的标准化行,然后在每个循环结束时删除原始数据框中的相同行。随着每个商店的标准化,每个循环的搜索空间都减少了,fixed_df 的大小随着每个循环而增加。最后,fixed_df 应该有所有原始行,现在标准化,原始 df 应该是空的。

# create empty df to store new results
fixed_df = pd.DataFrame()

# loop through dictionary
for regname, regex_formula in regex_dict.items(): 

    # search for regex formula, add standardized name into standard column
    df.loc[df['term_location'].str.contains(regex_formula,na=False,flags=re.I), 'standard'] = regname

    # get index of where names were fixed
    ind = df[df['standard']==regname].index

    # append fixed data to new df
    fixed_df.append(df[df.index.isin(ind)].copy())

    # remove processed stuff from original df
    df = df[~df.index.isin(ind)].copy()

【讨论】:

    【解决方案2】:

    另一种方法是先提取组,然后像下面这样替换,您的循环方法仍然更好。

    我们需要稍微修改一下 regex_dict,

    regex_dict = {
     r'mcdonalds|mcdonald_s':"McDonalds",
     r'lidl|lidi':"Lidl",
     r'wallmart': "Wallmart",
     r'kfc':"KFC" ,
     r'aldi|aldi':"Aldi"
    }
    
    df.str.extract(r'('+ '|'.join(regex_dict.keys())+')',expand=False).replace(regex_dict,regex=True)
    0    McDonalds
    1         Lidl
    2         Lidl
    3          KFC
    4         Lidl
    

    【讨论】:

    • 如果有成百上千个搜索词,r'('+ '|'.join(regex_dict.keys())+')',其中很多在同一个位置匹配,会大大减慢代码执行速度。您应该始终避免使用 (abcd|abce|abc|ab|...) 之类的替代品。
    猜你喜欢
    • 1970-01-01
    • 2012-05-15
    • 1970-01-01
    • 2018-10-31
    • 1970-01-01
    • 1970-01-01
    • 2014-09-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多