【问题标题】:Smarter way to check if a string contains an element in a list - python检查字符串是否包含列表中元素的更智能方法 - python
【发布时间】:2018-02-07 01:01:23
【问题描述】:

List top_brands 包含品牌列表,例如

top_brands = ['Coca Cola', 'Apple', 'Victoria\'s Secret', ....]

items 是pandas.DataFrame,其结构如下所示。如果缺少brand_name,我的任务是从item_title 中填写brand_name

row     item_title                 brand_name

1    |  Apple 6S                  |  Apple
2    |  New Victoria\'s Secret    |  missing  <-- need to fill with Victoria\'s Secret
3    |  Used Samsung TV           |  missing  <--need fill with Samsung
4    |  Used bike                 |  missing  <--No need to do anything because there is no brand_name in the title 
    ....

我的代码如下。问题在于 对于包含 200 万条记录的数据框来说太慢了。有什么方法可以使用 pandas 或 numpy 来处理任务?

def get_brand_name(row):
    if row['brand_name'] != 'missing':
        return row['brand_name']

    item_title = row['item_title']

    for brand in top_brands:
        brand_start = brand + ' '
        brand_in_between = ' ' + brand + ' '
        brand_end = ' ' + brand
        if ((brand_in_between in item_title) or item_title.endswith(brand_end) or item_title.startswith(brand_start)): 
            print(brand)
            return brand

    return 'missing'    ### end of get_brand_name


items['brand_name'] = items.apply(lambda x: get_brand_name(x), axis=1)

【问题讨论】:

  • 几个问题:我们可以假设没有重叠的品牌名称,例如“苹果”和“苹果公司”。在您的示例中,brand_name = VS,您是如何获得 VS 缩写的?
  • 场景中没有VS缩写。我不应该把它放在那里。并且有重叠的品牌名称,但为了性能,我们可以假设没有重叠

标签: python pandas


【解决方案1】:

试试这个:

pd.concat([df['item_title'], df['item_title'].str.extract('(?P<brand_name>{})'.format("|".join(top_brands)), expand=True).fillna('missing')], axis=1)

输出:

              item_title         brand_name
0               Apple 6S              Apple
1  New Victoria's Secret  Victoria's Secret
2        Used Samsung TV            Samsung
3              Used Bike            missing

我在我的机器上运行了一个包含 200 万个项目的随机样本:

def read_file():
    df = pd.read_csv('file1.txt')
    new_df = pd.concat([df['item_title'], df['item_title'].str.extract('(?P<brand_name>{})'.format("|".join(top_brands)), expand=True).fillna('missing')], axis=1)
    return new_df

start = time.time()
print(read_file())
end = time.time() - start
print(f'Took {end}s to process')

输出:

                                   item_title         brand_name
0                                    LG watch                 LG
1                                  Sony watch               Sony
2                                 Used Burger            missing
3                                    New Bike            missing
4                               New underwear            missing
5                                    New Sony               Sony
6                        Used Apple underwear              Apple
7                       Refurbished Panasonic          Panasonic
8                   Used Victoria's Secret TV  Victoria's Secret
9                                Disney phone             Disney
10                                Used laptop            missing
...                                       ...                ...
1999990             Refurbished Disney tablet             Disney
1999991                    Refurbished laptop            missing
1999992                       Nintendo Coffee           Nintendo
1999993                      Nintendo desktop           Nintendo
1999994         Refurbished Victoria's Secret  Victoria's Secret
1999995                           Used Burger            missing
1999996                    Nintendo underwear           Nintendo
1999997                     Refurbished Apple              Apple
1999998                      Refurbished Sony               Sony
1999999                      New Google phone             Google

[2000000 rows x 2 columns]
Took 3.2660000324249268s to process

我的机器规格:

Windows 7 专业版 64 位 英特尔 i7-4770 @ 3.40GHZ 12.0 GB 内存

3.266 秒相当快……对吧?

【讨论】:

    【解决方案2】:

    在我看来,这样的事情可能会奏效:

    top_brands = [r'Coca Cola', r'Apple', r'Victoria\'s Secret', r'Samsung']
    
    df = pd.DataFrame({
             'item_title': ['Apple 6S', 'New Victoria\'s Secret', 'Used Samsung TV', 'Used bike'],
             'brand_name': ['Apple', 'missing', 'missing', 'missing']
             }, columns=['item_title' ,'brand_name'])
    
    #               item_title brand_name
    # 0               Apple 6S      Apple
    # 1  New Victoria's Secret    missing
    # 2        Used Samsung TV    missing
    # 3              Used bike    missing
    
    # concatenate brand names into regex string
    # with each brand as a capture group
    top_brands = '|'.join(['(' + x + ')'  for x in top_brands])
    
    # "(Coca Cola)|(Apple)|(Victoria\\'s Secret)|(Samsung)"
    
    df.loc[:, 'brand_name'] = df['item_title'].str.extract(\ 
                              top_brands).fillna('').sum(axis=1).replace('', 'missing')
    
    #               item_title         brand_name
    # 0               Apple 6S              Apple
    # 1  New Victoria's Secret  Victoria's Secret
    # 2        Used Samsung TV            Samsung
    # 3              Used bike            missing
    

    【讨论】:

      【解决方案3】:

      由于需要识别多词品牌,这是一项 NER(命名实体识别)任务。

      您需要将 item_title 中的单词聚类成最大长度为 n 的块

      ['New','New Victoria\'s', 'New Victoria\'s Secret', 'Victoria\'s', 'Victoria\'s Secret', 'Secret']
      

      ,然后根据您的品牌列表检查集群。

      如果您预计会出现拼写错误,请为您的品牌列表编制三元组索引,然后将 item_title 块的值分解为三元组,并根据三元组索引对它们进行评分。或者,您可以在具有一定 n 步容差的块上使用 levenshtein 距离,以防止非常糟糕的匹配。

      【讨论】:

        【解决方案4】:

        用 2M 数据点构建数据集:

        import pandas as pd
        import time
        top_brands = ['Coca Cola', 'Apple', 'Victoria\'s Secret', 'Samsung']
        items = pd.DataFrame(
                [['Apple 6S', 'Apple'],
                 ['New Victoria\'s Secret', 'missing'],
                 ['Used Samsung TV', 'missing'],
                 ['Used bike', 'missing']],
                 columns=['item_title', 'brand_name'])
        items = pd.concat([items]*500000, ignore_index=True)
        

        为您的原始代码计时以用作比较的参考:

        ''' Code Block 1 '''
        items1 = items.copy()
        t = time.time()
        def get_brand_name_v1(row):
            if row['brand_name'] != 'missing':
                return row['brand_name']
            item_title = row['item_title']
            for brand in top_brands:
                brand_start = brand + ' '
                brand_in_between = ' ' + brand + ' '
                brand_end = ' ' + brand
                if ((brand_in_between in item_title) or \
                    item_title.endswith(brand_end) or  \
                    item_title.startswith(brand_start)): 
                    return brand
            return 'missing'
        items1['brand_name'] = items1.apply(lambda x: get_brand_name_v1(x), axis=1)
        print('Code Block 1 time: {:f}'.format(time.time()-t))
        
        # Code Block 1 time: 53.718933
        

        您的代码的修改版本: 使用NAN 值通常比使用'missing' 进行字符串比较要快。此外,根据我的经验,与调用整个数据帧相比,为直接调用数据帧中的值创建临时“指针”要快一些(例如,使用 brand_name 作为指针而不是调用 row['brand_name' ] 多次)

        ''' Code Block 2 '''
        items2 = items.copy()
        t = time.time()
        items2.loc[:,'brand_name'].replace(['missing'], [None], inplace=True)
        def get_brand_name_v2(row):
            brand_name = row['brand_name']
            if brand_name is not None: return brand_name
            item_title = row['item_title']
            for brand in top_brands:
                if brand in item_title: return brand
        items2['brand_name'] = items2.apply(lambda x: get_brand_name_v2(x), axis=1)
        items2.loc[:,'brand_name'].fillna('missing', inplace=True)
        print('Code Block 2 time: {:f}'.format(time.time()-t))
        
        # Code Block 2 time: 47.940444
        

        受Idlehands 的回答启发: 此版本不忽略原始数据集brand_name 列中的信息,而是仅填充missing 值。这样可以提高速度,但会占用更多内存。

        ''' Code Block 3 '''
        items3 = items.copy()
        items3.loc[:,'brand_name'].replace(['missing'], [None], inplace=True)
        t = time.time()
        brands = (items3['item_title'].str.extract(
                '(?P<brand_name>{})'.format("|".join(top_brands)), expand=True))
        brands.loc[:,'brand_name'].fillna('missing', inplace=True)
        items3.loc[:,'brand_name'].fillna(brands.loc[:,'brand_name'], inplace=True)
        print('Code Block 3 time: {:f}'.format(time.time()-t))
        
        # Code Block 3 time: 3.388266
        

        如果您能够负担得起在数据集中使用NAN 而不是'missing' 并删除所有将NAN 替换为'missing' 的操作,那么您可以使这些更快。

        【讨论】:

          猜你喜欢
          • 2017-11-20
          • 2010-10-04
          • 1970-01-01
          • 2013-09-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-03-16
          • 1970-01-01
          相关资源
          最近更新 更多