【问题标题】:Merge data frames with partial id合并具有部分 id 的数据帧
【发布时间】:2014-08-16 09:20:01
【问题描述】:

假设我有这两个数据框:

> df1 <- data.frame(name = c('John Doe',
                             'Jane F. Doe',
                             'Mark Smith Simpson',
                             'Sam Lee'))
> df1
                name
1           John Doe
2        Jane F. Doe
3 Mark Smith Simpson
4            Sam Lee

> df2 <- data.frame(family = c('Doe', 'Smith'), size = c(2, 6))
> df2
  family size
1    Doe    2
2  Smith    6

我想合并两个数据框以获得这个:

                name family size
1           John Doe    Doe    2
2        Jane F. Doe    Doe    2
3 Mark Smith Simpson  Smith    6
4            Sam Lee   <NA>   NA

但除了以下非常复杂的解决方案之外,我无法找到一种方法来做到这一点,这与我的真实数据变得非常混乱,其中有超过 100 个“姓氏”:

> df3 <- within(df1, {
    family <- ifelse(test = grepl('Doe', name),
                     yes  = 'Doe',
                     no   = ifelse(test = grepl('Smith', name),
                                   yes  = 'Smith',
                                   no   = NA))
  })
> merge(df3, df2, all.x = TRUE)
  family               name size
1    Doe           John Doe    2
2    Doe        Jane F. Doe    2
3  Smith Mark Smith Simpson    6
4   <NA>            Sam Lee   NA

我已尝试查看 pmatch 以及 R partial match in data frame 提供的解决方案,但仍然没有找到我想要的。

【问题讨论】:

    标签: r merge dataframe match partial


    【解决方案1】:

    您可以将姓名拆分为查找表格式,而不是尝试使用正则表达式和部分匹配,在该格式中,人名的每个组成部分都保持在一行中,并与他们的全名匹配:

    df1 <- data.frame(name = c('John Doe',
                               'Jane F. Doe',
                               'Mark Smith Simpson',
                               'Sam Lee'),
                      stringsAsFactors = FALSE)
    df2 <- data.frame(family = c('Doe', 'Smith'), size = c(2, 6),
                      stringsAsFactors = FALSE)
    
    
    library(tidyr)
    library(dplyr)
    
    str_df <- function(x) {
      ss <- strsplit(unlist(x)," ")
      data.frame(family = unlist(ss),stringsAsFactors = FALSE)
      }
    
    splitnames <- df1 %>%
      group_by(name) %>%
      do(str_df(.))
    
    splitnames 
    
                     name  family
    1         Jane F. Doe    Jane
    2         Jane F. Doe      F.
    3         Jane F. Doe     Doe
    4            John Doe    John
    5            John Doe     Doe
    6  Mark Smith Simpson    Mark
    7  Mark Smith Simpson   Smith
    8  Mark Smith Simpson Simpson
    9             Sam Lee     Sam
    10            Sam Lee     Lee
    

    现在您只需将其与df2 合并或加入即可获得答案:

    left_join(df2,splitnames)
    
    Joining by: "family"
      family size               name
    1    Doe    2        Jane F. Doe
    2    Doe    2           John Doe
    3  Smith    6 Mark Smith Simpson
    

    潜在问题:如果一个人的名字与其他人的姓氏相同,您将得到一些不正确的匹配!

    【讨论】:

      【解决方案2】:

      这是一种策略,您可以使用 lapplygrep 匹配所有姓氏。这将在任何位置找到它们。首先让我定义一个辅助函数

      transindex<-function(start=1) {
          function(x) {
              start<<-start+1
              ifelse(x, start-1, NA)
          }
      }
      

      我还将使用函数coalesce.R 让事情变得更简单。在这里,我将运行代码以匹配 df2df1

      idx<-do.call(coalesce, lapply(lapply(as.character(df2$family), 
           function(x) grepl(paste0("\\b", x, "\\b"), as.character(df1$name))),
           transindex()))
      

      从内部开始,我循环遍历df2grep 中的所有姓氏以获得这些值(在模式中添加“\b”,以便匹配整个单词)。 grepl 将返回一个逻辑向量 (TRUE/FALSE)。然后我应用上面的辅助函数transindex() 将这些向量更改为df2 中匹配的行的索引,或者NA。由于一行可能匹配多个家族,我只是使用coalesce 辅助函数选择第一个。

      不是我可以将df1 中的行匹配到df2,我可以将它们放在一起

      cbind(df1, size=df2[idx,])
      
                          name family size
      # 1             John Doe    Doe    2
      # 1.1        Jane F. Doe    Doe    2
      # 2   Mark Smith Simpson  Smith    6
      # NA             Sam Lee   <NA>   NA
      

      【讨论】:

      • 对于不正确的答案,我深表歉意。我应该向提出问题的用户澄清,而不是假设他们想要姓氏的最后一部分。答案已被删除。
      • @ccapizzano 不用担心。您实际上指出了我能够纠正的结果中的错误。我只是想确保您知道您的样本与测试数据不匹配,以防您想更新它。如果它以这种方式出现,我并不打算苛刻。
      【解决方案3】:

      另一种看起来有效的方法,至少对于示例数据:

      df1name = as.character(df1$name)
      df1name
      #[1] "John Doe"           "Jane F. Doe"        "Mark Smith Simpson" "Sam Lee"           
      regmatches(df1name, regexpr(paste(df2$family, collapse = "|"), df1name), invert = T) <- ""
      df1name
      #[1] "Doe"   "Doe"   "Smith" ""     
      cbind(df1, df2[match(df1name, df2$family), ])
      #                  name family size
      #1             John Doe    Doe    2
      #1.1        Jane F. Doe    Doe    2
      #2   Mark Smith Simpson  Smith    6
      #NA             Sam Lee   <NA>   NA
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-03-28
        • 1970-01-01
        • 2020-08-26
        • 2015-02-10
        • 2016-07-31
        • 2021-01-29
        • 1970-01-01
        相关资源
        最近更新 更多