合并具有部分ID的数据帧

时间:2014-06-25 17:26:46

标签: r merge dataframe match partial

说我有这两个数据框:

> 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个&#34;姓氏&#34; :

> 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提供的解决方案,但仍无法找到我正在寻找的内容。

3 个答案:

答案 0 :(得分:1)

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

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

答案 1 :(得分: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 :(得分:0)

另一种看起来有效的方法,至少是样本数据:

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