【问题标题】:Merging heterogeneous data.frames合并异构data.frames
【发布时间】:2018-09-24 09:34:32
【问题描述】:

我正在尝试在 R 中合并两个 data.frames

d1 <- data.frame(Id=1:3,Name=c("Yann","Anne","Sabri"),Age=c(21,19,31),Height=c(178,169,192),Grade=c(15,12,18))
d2 <- data.frame(Id=c(1,3,4),Name=c("Yann","Sabri","Jui"),Age=c(28,21,15),Sex=c("M","M","F"),City=c("Paris","Paris","Toulouse"))

我想通过Id 合并,并在最后的data.frame 中仅保留IdNameAgeSexGrade 列。

我想出了一个冗长的代码来完成这项工作,但有没有更好的方法?

dm <- data.frame(Id=unique(c(d1$Id,d2$Id)))
dm.d1.rows <- sapply(dm$Id, match, table = d1$Id)
dm.d2.rows <- sapply(dm$Id, match, table = d2$Id)
for(i in c("Name", "Age","Sex","Grade")) {
    if(i %in% colnames(d1) && is.factor(d1[[i]]) || i %in% colnames(d2) && is.factor(d2[[i]])) dm[[i]]<- factor(rep(NA,nrow(dm)),
            levels=unique(c(levels(d1[[i]]),levels(d2[[i]]))))
    else dm[[i]]<- rep(NA,nrow(dm))
    if(i %in% colnames(d1)) dm[[i]][!is.na(dm.d1.rows)] <- d1[[i]][na.exclude(dm.d1.rows)]
    if(i %in% colnames(d2)) dm[[i]][!is.na(dm.d2.rows)] <- d2[[i]][na.exclude(dm.d2.rows)]
}

【问题讨论】:

  • dm2 &lt;- merge(d1, d2, by=c("Id", "Name"), all=TRUE); dm2$Age &lt;- with(dm2, ifelse(is.na(Age.x), Age.y, Age.x)); dm2[c("Id", "Name", "Age", "Sex", "Grade")]

标签: r dataframe


【解决方案1】:

这是来自 的一个想法,使用函数coalesce。此函数基本上将NA 值替换为另一个(指定)列的值。 - 你可以找到更多信息和函数的实现coalescehere

coalesce 的官方文档: 给定一组向量,coalesce() 会在每个位置找到第一个非缺失值。这受到 SQL COALESCE 函数的启发,该函数对 NULL 执行相同的操作。


library(tidyverse)

d1 %>% 
 full_join(d2, by = c('Id', 'Name')) %>% 
 mutate(Age = coalesce(Age.x, Age.y)) %>% 
 select(Id, Name, Age, Sex, Grade)

给出,

  Id  Name Age  Sex Grade
1  1  Yann  21    M    15
2  2  Anne  19 <NA>    12
3  3 Sabri  31    M    18
4  4   Jui  15    F    NA

同样,在 语法中,

library(data.table)

#Convert to data.tables
d1_t <- setDT(d1)
d2_t <- setDT(d2)

merge(d1_t, d2_t, by = c('Id', 'Name'), all = TRUE)[,
            Age := ifelse(is.na(Age.x), Age.y, Age.x)][, 
              c('Age.x', 'Age.y', 'City', 'Height') := NULL][]

给出,

   Id  Name Grade  Sex Age
1:  1  Yann    15    M  21
2:  2  Anne    12 <NA>  19
3:  3 Sabri    18    M  31
4:  4   Jui    NA    F  15  

【讨论】:

    【解决方案2】:

    在基础 R 中:

    d1 <- data.frame(Id=1:3,Name=c("Yann","Anne","Sabri"),Age=c(21,19,31),Height=c(178,169,192),Grade=c(15,12,18),stringsAsFactors = F)
    d2 <- data.frame(Id=c(1,3,4),Name=c("Yann","Sabri","Jui"),Age=c(28,21,15),Sex=c("M","M","F"),City=c("Paris","Paris","Toulouse"),stringsAsFactors = F)
    nms <- c("Id","Name", "Age", "Sex", "Grade")
    
    . <- merge(d2,d1,all=TRUE,sort=FALSE)[nms]
    aggregate(.,list(.$Id), function(x) c(na.omit(x),NA)[1])[-1]
    #   Id  Name Age  Sex Grade
    # 1  1  Yann  28    M    15
    # 2  2  Anne  19 <NA>    12
    # 3  3 Sabri  21    M    18
    # 4  4   Jui  15    F    NA
    

    注意stringsAsFactors = F,在应用此解决方案之前,您需要将因子转换为字符。

    【讨论】:

    • 在第一行反转 d1d2 以获得与其他答案相同的输出,但这会从 OP 复制 dm
    【解决方案3】:

    我个人是sqldf 的忠实粉丝,它允许您使用 SQL 查询来创建/操作数据帧。在您的情况下,下面的语句应该可以解决问题。

    d1 <- data.frame(Id=1:3,Name=c("Yann","Anne","Sabri"),Age=c(21,19,31),
        Height=c(178,169,192),Grade=c(15,12,18))
    d2 <- data.frame(Id=c(1,3,4),Name=c("Yann","Sabri","Jui"),Age=c(28,21,15),
        Sex=c("M","M","F"),City=c("Paris","Paris","Toulouse"))
    
    d3 = sqldf("SELECT d1.Id, d1.Name, d1.Age, d2.Sex , d1.Grade
                FROM d1
                LEFT JOIN d2 ON d1.Id = d2.Id
                UNION
                SELECT d2.Id, d2.Name, coalesce(d1.Age, d2.Age) , d2.Sex, coalesce(d1.Grade, NULL)
                FROM d2 
                LEFT JOIN d1 ON d2.Id = d1.Id")
    

    特别是对于更复杂的数据框合并/操作,sqldf/SQL 的使用会很有用。

    编辑:使用工作sqldf /R环境修复SQL语句,结果如下表:

    Id  Name Age  Sex Grade
    1  Yann  21    M    15
    2  Anne  19 <NA>    12
    3 Sabri  31    M    18
    4   Jui  15    F    NA
    

    【讨论】:

      【解决方案4】:

      这可能不是一个理想的答案,但这里是一个使用 sapply 的非合并、非连接选项,因为我们希望仅使用一列组合两个数据框

      #Name the cols which you want in the final data frame
      cols <- c("Id", "Name", "Age", "Sex","Grade")
      #Get all unique id's 
      ids <- union(d1$Id, d2$Id)
      
      #Loop over each ID
      data.frame(t(sapply(ids, function(x) {
         #Get indices in d1 where Id is present
         d1inds <- d1$Id == x
         #Get indices in d2 where Id is present
         d2inds <- d2$Id == x
      
         #If the Id is present in both d1 AND d2
         if (any(d1inds) & any(d2inds))
      
           #Combine d2 and d1 and select only cols column
           #This is based on your expected output that in case if the ID is same 
           #we want to prefer Name and Age column from d2 rather than d1 
           return(cbind(d2[d2inds, ], d1[d1inds, ])[cols])
           #If you want to prefer d1 over d2, we can do
           #return(cbind(d1[d1inds, ], d2[d2inds, ])[cols])
      
         #If the Id is present only in d1, add a "Sex" column with NA
         if (any(d1inds))
            return(cbind(d1[d1inds, ], "Sex" = NA)[cols])
      
         #If the Id is present only in d2, add a "Grade" column with NA
         else     
            return(cbind(d2[d2inds, ], "Grade" = NA)[cols])
      })))
      
      #  Id  Name Age Sex Grade
      #1  1  Yann  28   M    15
      #2  2  Anne  19  NA    12
      #3  3 Sabri  21   M    18
      #4  4   Jui  15   F    NA
      

      数据

      d1 <- data.frame(Id=1:3,Name=c("Yann","Anne","Sabri"),Age=c(21,19,31),
          Height=c(178,169,192),Grade=c(15,12,18), stringsAsFactors = FALSE)
      d2 <- data.frame(Id=c(1,3,4),Name=c("Yann","Sabri","Jui"),Age=c(28,21,15),
         Sex=c("M","M","F"),City=c("Paris","Paris","Toulouse"), stringsAsFactors = FALSE)
      

      【讨论】:

        【解决方案5】:

        您可以使用我的包safejoin,进行完全连接并使用dplyr::coalesce 处理冲突。我们还使用dplyr::one_of,因此我们不必手动并排选择列。

        # devtools::install_github("moodymudskipper/safejoin")
        library(safejoin)
        
        keep <- c("Id", "Name", "Age", "Sex", "Grade")
        safe_full_join(select(d1,one_of(keep)), select(d2,one_of(keep)),  
          by = c("Id","Name"), conflict = coalesce, check="")
        #   Id  Name Age Grade  Sex
        # 1  1  Yann  21    15    M
        # 2  2  Anne  19    12 <NA>
        # 3  3 Sabri  31    18    M
        # 4  4   Jui  15    NA    F
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-01-06
          • 2012-12-15
          • 1970-01-01
          相关资源
          最近更新 更多