Слияние разнородных фреймов данных. - PullRequest
0 голосов
/ 24 сентября 2018

Я пытаюсь объединить два data.frames в 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))
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 и оставить только Id, Name, Age,Sex и Grade столбцы в последнем data.frame.

Я пришел с длинным кодом, который делает эту работу, но есть ли лучший способ?

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)]
}

Ответы [ 5 ]

0 голосов
/ 26 февраля 2019

Вы можете использовать мой пакет 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
0 голосов
/ 27 сентября 2018

Это может быть не идеальный ответ, но здесь есть опция без слияния, без объединения с использованием 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)
0 голосов
/ 24 сентября 2018

Лично я большой поклонник 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.

EDIT: Использовал рабочую среду 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
0 голосов
/ 27 сентября 2018

В базе 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, вам необходимо преобразовать коэффициенты в символы перед применением этого решения.

0 голосов
/ 24 сентября 2018

Вот идея через с использованием функции coalesce.Эта функция в основном заменяет значения NA значениями другого (указанного) столбца.- Вы можете найти больше информации и реализации функции 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  
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...