【问题标题】:Merge two tables on LIKE but for whole string not parts of strings在 LIKE 上合并两个表,但对于整个字符串而不是字符串的一部分
【发布时间】:2017-06-23 19:42:48
【问题描述】:

这是我的第一篇文章/问题,请善待。 我有一个这样的数据框:

        id                             product
1 00109290                    Wax Salt; Pepper
2 23243242                          Wood Stuff
3 23242433   Magic Unicorn Powder and My Tears
4 23778899                             gelatin
5 25887766                                tin;
6  7786655             fart noises, and things
7  3432422 --spearmint bacon& hydrangia leaves

我有一个这样的查找表:

        ingredients
1               wax
2              salt
3              wood
4          my tears
5    unicorn powder
6           gelatin
7               tin
8  hydrangia leaves
9         spearmint
10            bacon

我想将它们合并到整个字符串上,所以我得到了这个:

     id                             product      ingredients
1  00109290                    Wax Salt; Pepper              wax
2  00109290                    Wax Salt; Pepper             salt
3  23243242                          Wood Stuff             wood
4  23242433   Magic Unicorn Powder and My Tears         my tears
5  23242433   Magic Unicorn Powder and My Tears   unicorn powder
6  23778899                             gelatin          gelatin
7  25887766                                tin;              tin
8   3432422 --spearmint bacon& hydrangia leaves hydrangia leaves
9   3432422 --spearmint bacon& hydrangia leaves        spearmint
10  3432422 --spearmint bacon& hydrangia leaves            bacon

相反,我得到了这个(注意第 7 行不需要):

         id                             product      ingredients
1  00109290                    Wax Salt; Pepper              wax
2  00109290                    Wax Salt; Pepper             salt
3  23243242                          Wood Stuff             wood
4  23242433   Magic Unicorn Powder and My Tears         my tears
5  23242433   Magic Unicorn Powder and My Tears   unicorn powder
6  23778899                             gelatin          gelatin
7  23778899                             gelatin              tin
8  25887766                                tin;              tin
9   3432422 --spearmint bacon& hydrangia leaves hydrangia leaves
10  3432422 --spearmint bacon& hydrangia leaves        spearmint
11  3432422 --spearmint bacon& hydrangia leaves            bacon

我非常接近,但我错误地将“明胶”与“锡”匹配。我想匹配整个单词,而不是单词的一部分。我尝试了许多不同的技术,最接近的是:

library(sqldf)
id <- c('00109290', '23243242', '23242433', 
        '23778899', '25887766', '7786655', 
        '3432422')
product <- c('Wax Salt; Pepper', 'Wood Stuff', 
             'Magic Unicorn Powder and My Tears', 
             'gelatin', 'tin;', 'fart noises, and things', 
             '--spearmint bacon& hydrangia leaves')

ingredients <- c('wax', 'salt', 'wood', 'my tears', 
                 'unicorn powder', 'gelatin', 'tin', 
                 'hydrangia leaves', 
                 'spearmint', 'bacon') 

products <- data.frame(id, product)
ingred <- data.frame(ingredients)    
new_df <- sqldf("SELECT * from products 
                 join ingred on product LIKE '%' || ingredients || '%'")

非常感谢任何建议。也许需要一种完全不同的方法?我也欢迎就问题的质量提出建议,这是我的第一次,所以你最好马上给我安排。

【问题讨论】:

  • 对你来说一个完整的词是什么?空格之间的一大堆字符?
  • 非常好的问题。因为如果它是空格之间的字符,那么我不会将 'salt' 匹配到 'salt;',而我的数据集具有这种类型的东西。我不想在一个词中匹配。所以'salt'应该匹配'salt;'但不是 'salty' 和 'tin' 应该匹配 'tin' 或 ';tin' 但不是 'gelatin'。有了你的一个问题,我意识到......这很可能是正则表达式的事情,我认为我不能使用 sqldf。谢谢!
  • 我检查了docs,但找不到任何提及它支持正则表达式的信息。在 MySQL 中,存在REGEXP 运算符,并使用[[:&lt;:]][[:&gt;:]] 标记单词边界。在 PostgreSQL 中,使用\y。在正则表达式中,\b 标记单词边界。不幸的是,我认为 sqldf 不可行。
  • @WiktorStribiżew 我认为通过使用 LIKE 保留现有解决方案,并使用正则表达式添加一个步骤以过滤出 错误 匹配项是否可行?
  • 你能否列出一个不属于单词的非空格事物的简短列表?您的示例输入似乎只包含“;”在那个名单上。我肯定会添加“,”和“&”。

标签: r regex sql-like sqldf


【解决方案1】:

使用fuzzyjoin包的解决方案,以及来自stringr的str_detect

library(fuzzyjoin)
library(stringr)

f <- function(x, y) {
  # tests whether y is an ingredient of x
  str_detect(x, regex(paste0("\\b", y, "\\b"), ignore_case = TRUE))
}

fuzzy_join(products, 
           ingred, 
           by = c("product" = "ingredients"), 
           match_fun = f)
#         id                           product    ingredients
# 1   109290                  Wax Salt; Pepper            wax
# 2   109290                  Wax Salt; Pepper           salt
# 3 23243242                        Wood Stuff           wood
# 4 23242433 Magic Unicorn Powder and My Tears       my tears
# 5 23242433 Magic Unicorn Powder and My Tears unicorn powder
# 6 23778899                           gelatin        gelatin

数据

products <- read.table(text = "
        id                             product
1 00109290                  'Wax Salt; Pepper'
2 23243242                        'Wood Stuff'
3 23242433 'Magic Unicorn Powder and My Tears'
4 23778899                             gelatin                  
  ", stringsAsFactors = FALSE)

ingred <- read.table(text = "
       ingredients
1              wax
2             salt
3             wood
4       'my tears'
5 'unicorn powder'
6          gelatin
7              tin
  ", stringsAsFactors = FALSE)

【讨论】:

    【解决方案2】:

    考虑为关键字前后的一个空格添加OR 条件,然后完全匹配并替换任何特殊字符/标点符号。

    new_df <- sqldf("SELECT * from products 
                     join ingred on Replace(product, ';', '') LIKE '% ' || ingredients || '%'
                                 OR Replace(product, ';', '') LIKE '%' || ingredients || ' %'
                                 OR Replace(product, ';', '') = ingredients
                    ")
    

    您甚至可以UNION 获取不同的特殊字符。下面的例子替换了分号和感叹号:

    new_df <- sqldf("SELECT * from products 
                     join ingred on Replace(product, ';', '') LIKE '% ' || ingredients || '%'
                                 OR Replace(product, ';', '') LIKE '%' || ingredients || ' %'
                                 OR Replace(product, ';', '') = ingredients
                     UNION    
                     SELECT * from products 
                     join ingred on Replace(product, '!', '') LIKE '% ' || ingredients || '%'
                                 OR Replace(product, '!', '') LIKE '%' || ingredients || ' %'
                                 OR Replace(product, '!', '') = ingredients
                    ")
    

    对于许多 UNIONs,考虑让 R 连接 SQL 语句:

    sql <- paste(lapply(c("!", "#", "$", "%", "(", ")", ":", ";", ".", "?", ">", "<", "/", "\\\\", "|"), 
                 function(i)
                    paste0("SELECT * from products 
                            join ingred on Replace(product, '", i, "', '') LIKE '% ' || ingredients || '%'
                                        OR Replace(product, '", i, "', '') LIKE '%' || ingredients || ' %'
                                        OR Replace(product, '", i, "', '') = ingredients
                           ")
           ), collapse = "UNION ")
    
    cat(paste(sql))
    
    SELECT * from products 
                       join ingred on Replace(product, '!', '') LIKE '% ' || ingredients || '%'
                                 OR Replace(product, '!', '') LIKE '%' || ingredients || ' %'
                                 OR Replace(product, '!', '') = ingredients
                       UNION SELECT * from products 
                       join ingred on Replace(product, '#', '') LIKE '% ' || ingredients || '%'
                                 OR Replace(product, '#', '') LIKE '%' || ingredients || ' %'
                                 OR Replace(product, '#', '') = ingredients
                       UNION SELECT * from products 
                       join ingred on Replace(product, '$', '') LIKE '% ' || ingredients || '%'
                                 OR Replace(product, '$', '') LIKE '%' || ingredients || ' %'
                                 OR Replace(product, '$', '') = ingredients
                       UNION SELECT * from products 
                       join ingred on Replace(product, '%', '') LIKE '% ' || ingredients || '%'
                                 OR Replace(product, '%', '') LIKE '%' || ingredients || ' %'
                                 OR Replace(product, '%', '') = ingredients
                       UNION SELECT * from products 
                       join ingred on Replace(product, '(', '') LIKE '% ' || ingredients || '%'
                                 OR Replace(product, '(', '') LIKE '%' || ingredients || ' %'
                                 OR Replace(product, '(', '') = ingredients
                       UNION SELECT * from products 
                       join ingred on Replace(product, ')', '') LIKE '% ' || ingredients || '%'
                                 OR Replace(product, ')', '') LIKE '%' || ingredients || ' %'
                                 OR Replace(product, ')', '') = ingredients
                       UNION SELECT * from products 
                       join ingred on Replace(product, ':', '') LIKE '% ' || ingredients || '%'
                                 OR Replace(product, ':', '') LIKE '%' || ingredients || ' %'
                                 OR Replace(product, ':', '') = ingredients
                       UNION SELECT * from products 
                       join ingred on Replace(product, ';', '') LIKE '% ' || ingredients || '%'
                                 OR Replace(product, ';', '') LIKE '%' || ingredients || ' %'
                                 OR Replace(product, ';', '') = ingredients
                       UNION SELECT * from products 
                       join ingred on Replace(product, '.', '') LIKE '% ' || ingredients || '%'
                                 OR Replace(product, '.', '') LIKE '%' || ingredients || ' %'
                                 OR Replace(product, '.', '') = ingredients
                       UNION SELECT * from products 
                       join ingred on Replace(product, '?', '') LIKE '% ' || ingredients || '%'
                                 OR Replace(product, '?', '') LIKE '%' || ingredients || ' %'
                                 OR Replace(product, '?', '') = ingredients
                       UNION SELECT * from products 
                       join ingred on Replace(product, '>', '') LIKE '% ' || ingredients || '%'
                                 OR Replace(product, '>', '') LIKE '%' || ingredients || ' %'
                                 OR Replace(product, '>', '') = ingredients
                       UNION SELECT * from products 
                       join ingred on Replace(product, '<', '') LIKE '% ' || ingredients || '%'
                                 OR Replace(product, '<', '') LIKE '%' || ingredients || ' %'
                                 OR Replace(product, '<', '') = ingredients
                       UNION SELECT * from products 
                       join ingred on Replace(product, '/', '') LIKE '% ' || ingredients || '%'
                                 OR Replace(product, '/', '') LIKE '%' || ingredients || ' %'
                                 OR Replace(product, '/', '') = ingredients
                       UNION SELECT * from products 
                       join ingred on Replace(product, '\\', '') LIKE '% ' || ingredients || '%'
                                 OR Replace(product, '\\', '') LIKE '%' || ingredients || ' %'
                                 OR Replace(product, '\\', '') = ingredients
                       UNION SELECT * from products 
                       join ingred on Replace(product, '|', '') LIKE '% ' || ingredients || '%'
                                 OR Replace(product, '|', '') LIKE '%' || ingredients || ' %'
                                 OR Replace(product, '|', '') = ingredients
    

    【讨论】:

    • 天哪……我怎么没想到呢?我的第一个帖子/问题,现在我很尴尬。非常感谢。
    • 我认为“gelatin”就像“%tin %”(在第一个和第二个“OR”之间)。而且“明胶和盐”也会有问题。
    • @DieselBlue 我实际上在我的第一条评论中问过这个问题。如果你没有提到你需要在tin; 中匹配tin,我会发布这个答案。
    • @Wiktor Stribiżew ...你是对的,这不会匹配 'tin;'和 dziękuję za pomoc。
    • @DieselBlue ... 查看更新,您可以在其中使用 Replace 删除特殊字符,例如 ;
    猜你喜欢
    • 2015-09-06
    • 2018-01-27
    • 2021-12-08
    • 1970-01-01
    • 2017-09-11
    • 2011-01-08
    • 2021-11-27
    • 2023-03-16
    • 2016-12-08
    相关资源
    最近更新 更多